diff --git a/package.json b/package.json index cefee6a4..dbbe176b 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/renderer/package.json b/src/renderer/package.json index a386e587..37be079c 100644 --- a/src/renderer/package.json +++ b/src/renderer/package.json @@ -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", diff --git a/src/renderer/src/components/dialogs/SimulationExperimentViewSettingsDialog.vue b/src/renderer/src/components/dialogs/SimulationExperimentViewSettingsDialog.vue index c4804d66..520725db 100644 --- a/src/renderer/src/components/dialogs/SimulationExperimentViewSettingsDialog.vue +++ b/src/renderer/src/components/dialogs/SimulationExperimentViewSettingsDialog.vue @@ -102,8 +102,8 @@
- - Model inputs + + Simulation inputs {{ localSettings.interactive.uiJson.input.length }} @@ -122,21 +122,21 @@ - +
-

Model inputs

+

Simulation inputs

- 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.

- +
@@ -287,7 +287,7 @@

Model parameters

- Configure the model parameters using the value of the model inputs. + Configure the model parameters using the value of the simulation inputs.

@@ -645,7 +645,7 @@ Live Updates
-

Automatically re-run the simulation when model inputs change

+

Automatically re-run the simulation when simulation inputs change.

@@ -771,6 +771,7 @@ export interface ISimulationExperimentViewSettings { const props = defineProps<{ settings: ISimulationExperimentViewSettings; + voiId: string; voiName: string; voiUnit: string; allModelParameters: string[]; @@ -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); @@ -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); @@ -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' }); } @@ -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: [] @@ -1033,7 +1033,7 @@ function addTrace(plotIndex: number) { } plot.additionalTraces.push({ - xValue: voiNameId, + xValue: props.voiId, yValue: 'y_id' }); } @@ -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'; } diff --git a/src/renderer/src/components/views/SimulationExperimentView.vue b/src/renderer/src/components/views/SimulationExperimentView.vue index 906bb31c..cf1c5750 100644 --- a/src/renderer/src/components/views/SimulationExperimentView.vue +++ b/src/renderer/src/components/views/SimulationExperimentView.vue @@ -227,7 +227,8 @@ (!!props.uiJson); const interactiveLiveUpdatesEnabled = vue.ref(true); const interactiveSettingsVisible = vue.ref(false); -const interactiveUiJson = vue.ref(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; @@ -500,6 +492,28 @@ let interactiveInstance = interactiveDocument.instantiate(); let interactiveInstanceTask = interactiveInstance.task(0); const interactiveAllModelParameters = vue.ref([]); const interactiveEditableModelParameters = vue.ref([]); +const interactiveVoiName = vue.ref(interactiveInstanceTask.voiName()); +const interactiveVoiId = vue.ref(interactiveVoiName.value.split('/')[1]); +const interactiveUiJson = vue.ref(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([]); @@ -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: [] diff --git a/src/renderer/src/components/widgets/GraphPanelWidget.vue b/src/renderer/src/components/widgets/GraphPanelWidget.vue index 40fcbefb..a9752b34 100644 --- a/src/renderer/src/components/widgets/GraphPanelWidget.vue +++ b/src/renderer/src/components/widgets/GraphPanelWidget.vue @@ -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 } } }; diff --git a/src/renderer/src/components/widgets/InputWidget.vue b/src/renderer/src/components/widgets/InputWidget.vue index 56625b84..92caabcb 100644 --- a/src/renderer/src/components/widgets/InputWidget.vue +++ b/src/renderer/src/components/widgets/InputWidget.vue @@ -19,7 +19,7 @@ @update:model-value="inputTextValueUpdated" /> @@ -45,6 +45,17 @@ let oldValue = value.value; const discreteValue = vue.ref( 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,