Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"url": "git+https://github.com/opencor/webapp.git"
},
"type": "module",
"version": "0.20260207.0",
"version": "0.20260207.1",
"scripts": {
"archive:web": "bun src/renderer/scripts/archive.web.js",
"build": "electron-vite build",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
},
"./style.css": "./dist/opencor.css"
},
"version": "0.20260207.0",
"version": "0.20260207.1",
"scripts": {
"build": "vite build",
"build:lib": "vite build --config vite.lib.config.ts && cp index.d.ts dist/index.d.ts",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@
<div class="h-full flex flex-col">
<Tabs v-model:value="activeInteractiveTab" class="min-h-0">
<TabList class="mb-2">
<Tab value="modelInputs">
<i class="pi pi-sign-in mr-2"></i>Model inputs
<Tab value="simulationInputs">
<i class="pi pi-sign-in mr-2"></i>Simulation inputs
<span class="ml-2 badge">{{ localSettings.interactive.uiJson.input.length }}</span>
</Tab>
<Tab value="modelParameters">
Expand All @@ -122,21 +122,21 @@
<TabPanels>
<!-- Inputs -->

<TabPanel value="modelInputs" class="h-full">
<TabPanel value="simulationInputs" class="h-full">
<div class="h-full flex flex-col">
<div class="section-header section-header-interactive">
<i class="pi pi-sliders-h text-primary"></i>
<div>
<h3 class="section-title">Model inputs</h3>
<h3 class="section-title">Simulation inputs</h3>
<p class="section-description">
Configure the model inputs that a user can modify and that will be available to set the model parameters.
Configure the simulation inputs that a user can modify and that will be available to set the model parameters.
</p>
</div>
<div class="flex-1"></div>
<div class="flex-none">
<Button
icon="pi pi-plus"
label="Add model input"
label="Add simulation input"
outlined
size="small"
@click="addInput"
Expand All @@ -149,10 +149,10 @@

<div v-if="!localSettings.interactive.uiJson.input.length" class="empty-state">
<i class="pi pi-info-circle empty-state-icon"></i>
<p class="text-muted-color mb-2">No model inputs are configured.</p>
<p class="text-muted-color mb-2">No simulation inputs are configured.</p>
</div>

<!-- Model input cards -->
<!-- Simulation input cards -->

<div v-for="(input, inputIndex) in localSettings.interactive.uiJson.input" :key="`input_${inputIndex}`">
<div class="card-item">
Expand Down Expand Up @@ -287,7 +287,7 @@
<div>
<h3 class="section-title">Model parameters</h3>
<p class="section-description">
Configure the model parameters using the value of the model inputs.
Configure the model parameters using the value of the simulation inputs.
</p>
</div>
<div class="flex-1"></div>
Expand Down Expand Up @@ -645,7 +645,7 @@
<i class="pi pi-sync"></i>
<span class="font-medium">Live Updates</span>
</div>
<p class="text-muted-color text-sm mt-1">Automatically re-run the simulation when model inputs change</p>
<p class="text-muted-color text-sm mt-1">Automatically re-run the simulation when simulation inputs change.</p>
</div>
<ToggleSwitch v-model="localSettings.miscellaneous.liveUpdates" />
</div>
Expand Down Expand Up @@ -771,6 +771,7 @@ export interface ISimulationExperimentViewSettings {

const props = defineProps<{
settings: ISimulationExperimentViewSettings;
voiId: string;
voiName: string;
voiUnit: string;
allModelParameters: string[];
Expand All @@ -783,7 +784,7 @@ const emit = defineEmits<{
}>();

const DEFAULT_TAB = 'interactive';
const DEFAULT_INTERACTIVE_TAB = 'modelInputs';
const DEFAULT_INTERACTIVE_TAB = 'simulationInputs';

const simulationSettingsIssuesPopup = vue.ref<{ toggle: (event: Event) => void } | null>(null);
const solversSettingsIssuesPopup = vue.ref<{ toggle: (event: Event) => void } | null>(null);
Expand Down Expand Up @@ -884,7 +885,6 @@ const uiJsonIssues = vue.computed(() => {

return validateUiJson(localSettings.value.interactive.uiJson);
});
const voiNameId = props.voiName.split('/').slice(-1)[0];

function plotTraceCount(plot: locApi.IUiJsonOutputPlot): number {
return 1 + (plot.additionalTraces?.length ?? 0);
Expand Down Expand Up @@ -998,8 +998,8 @@ function removePossibleValue(inputIndex: number, possibleValueIndex: number) {

function addSimulationData() {
localSettings.value.interactive.uiJson.output.data.push({
id: voiNameId,
name: props.voiName
id: `simulation_data`,
name: 'component/variable'
});
}

Expand All @@ -1012,7 +1012,7 @@ function addPlot() {
localSettings.value.interactive.uiJson.output.plots.push({
name: '',
xAxisTitle: '',
xValue: voiNameId,
xValue: props.voiId,
yAxisTitle: '',
yValue: 'y_id',
additionalTraces: []
Expand All @@ -1033,7 +1033,7 @@ function addTrace(plotIndex: number) {
}

plot.additionalTraces.push({
xValue: voiNameId,
xValue: props.voiId,
yValue: 'y_id'
});
}
Expand All @@ -1058,7 +1058,7 @@ function removeTrace(plotIndex: number, traceIndex: number) {
plot.yValue = firstAdditionalTrace.yValue;
} else {
plot.name = undefined;
plot.xValue = voiNameId;
plot.xValue = props.voiId;
plot.yValue = 'y_id';
}

Expand Down
43 changes: 32 additions & 11 deletions src/renderer/src/components/views/SimulationExperimentView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,8 @@
<SimulationExperimentViewSettingsDialog
v-model:visible="interactiveSettingsVisible"
:settings="interactiveSettings"
:voiName="interactiveInstanceTask.voiName()"
:voiId="interactiveVoiId"
:voiName="interactiveVoiName"
:voiUnit="interactiveInstanceTask.voiUnit()"
:allModelParameters="interactiveAllModelParameters"
:editableModelParameters="interactiveEditableModelParameters"
Expand Down Expand Up @@ -483,15 +484,6 @@ interface IPopover {
const interactiveModeEnabled = vue.ref<boolean>(!!props.uiJson);
const interactiveLiveUpdatesEnabled = vue.ref<boolean>(true);
const interactiveSettingsVisible = vue.ref<boolean>(false);
const interactiveUiJson = vue.ref<locApi.IUiJson>(initialUiJson());
const interactiveUiJsonEmpty = vue.computed(() => {
return (
interactiveUiJson.value.input.length === 0 &&
interactiveUiJson.value.output.data.length === 0 &&
interactiveUiJson.value.output.plots.length === 0 &&
interactiveUiJson.value.parameters.length === 0
);
});
const interactiveFile = props.file;
const interactiveDocument = interactiveFile.document();
const interactiveUniformTimeCourse = interactiveDocument.simulation(0) as locApi.SedUniformTimeCourse;
Expand All @@ -500,6 +492,28 @@ let interactiveInstance = interactiveDocument.instantiate();
let interactiveInstanceTask = interactiveInstance.task(0);
const interactiveAllModelParameters = vue.ref<string[]>([]);
const interactiveEditableModelParameters = vue.ref<string[]>([]);
const interactiveVoiName = vue.ref(interactiveInstanceTask.voiName());
const interactiveVoiId = vue.ref(interactiveVoiName.value.split('/')[1]);
const interactiveUiJson = vue.ref<locApi.IUiJson>(initialUiJson());
const interactiveUiJsonEmpty = vue.computed(() => {
if (
interactiveUiJson.value.input.length === 0 &&
interactiveUiJson.value.output.plots.length === 0 &&
interactiveUiJson.value.parameters.length === 0
) {
if (interactiveUiJson.value.output.data.length === 0) {
return true;
}

if (interactiveUiJson.value.output.data.length === 1) {
const data = interactiveUiJson.value.output.data[0];

return data.id === interactiveVoiId.value && data.name === interactiveVoiName.value;
}
}

return false;
});
const interactiveMath = mathjs.create(mathjs.all ?? {}, {});
const interactiveModel = interactiveDocument.model(0);
const interactiveData = vue.ref<IGraphPanelData[]>([]);
Expand Down Expand Up @@ -596,10 +610,17 @@ function initialUiJson(): locApi.IUiJson {
return JSON.parse(JSON.stringify(props.uiJson));
}

// No UI JSON provided, so we create a default one with the VOI as a default simulation data.

return {
input: [],
output: {
data: [],
data: [
{
id: interactiveVoiId.value,
name: interactiveVoiName.value
}
],
plots: []
},
parameters: []
Expand Down
60 changes: 26 additions & 34 deletions src/renderer/src/components/widgets/GraphPanelWidget.vue
Original file line number Diff line number Diff line change
Expand Up @@ -415,55 +415,47 @@ function themeData(): IThemeData {
};
}

interface IAxesData {
xaxis: {
tickangle: number;
automargin: boolean;
title: {
font: {
size: number;
};
text?: string;
standoff: number;
};
interface IAxesDataAxis {
automargin: boolean;
tickangle: number;
tickfont: {
size: number;
};
yaxis: {
tickangle: number;
automargin: boolean;
title: {
font: {
size: number;
};
text?: string;
standoff: number;
};
title: {
standoff: number;
text?: string;
};
}

interface IAxesData {
xaxis: IAxesDataAxis;
yaxis: IAxesDataAxis;
}

function axesData(): IAxesData {
const axisTitleFontSize = 10;
const axisTickFontSize = 10;

return {
xaxis: {
tickangle: 0,
automargin: true,
tickangle: 0,
tickfont: {
size: axisTickFontSize
},
title: {
font: {
size: axisTitleFontSize
},
text: props.data.xAxisTitle,
standoff: 8
standoff: 8,
text: props.data.xAxisTitle
}
},
yaxis: {
tickangle: 0,
automargin: true,
tickangle: 0,
tickfont: {
size: axisTickFontSize
},
title: {
font: {
size: axisTitleFontSize
},
text: props.data.yAxisTitle,
standoff: 8
standoff: 8,
text: props.data.yAxisTitle
}
}
};
Expand Down
13 changes: 12 additions & 1 deletion src/renderer/src/components/widgets/InputWidget.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
@update:model-value="inputTextValueUpdated"
/>
<Slider v-model="value" class="w-full mt-3"
:min="minimumValue" :max="maximumValue" :step="stepValue"
:min="minimumValue" :max="maximumValue" :step="compStepValue"
size="small"
@change="sliderChange"
/>
Expand All @@ -45,6 +45,17 @@ let oldValue = value.value;
const discreteValue = vue.ref<locApi.IUiJsonDiscreteInputPossibleValue | undefined>(
props.possibleValues?.find((possibleValue) => possibleValue.value === value.value)
);
const compStepValue = vue.computed(() => {
if (props.stepValue !== undefined) {
return props.stepValue;
}

if (props.maximumValue !== undefined && props.minimumValue !== undefined) {
return 0.01 * (props.maximumValue - props.minimumValue);
}

return 1;
});

vue.watch(
() => value.value,
Expand Down