@abi-software/simulationvuer 0.4.5 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,35 +1,15 @@
1
1
  <template>
2
- <div class="simulation-vuer" v-loading="simulationBeingComputed" :element-loading-text="simulationBeingComputedLabel">
3
- <p v-show="mode === -1" class="default error"><span class="error">Error:</span> an unknown model was provided.</p>
4
- <div class="main" v-show="mode !== -1">
2
+ <div class="simulation-vuer" v-loading="showUserMessage" :element-loading-text="userMessage">
3
+ <p v-if="!hasValidSimulationUiInformation && !showUserMessage" class="default error"><span class="error">Error:</span> an unknown or invalid model was provided.</p>
4
+ <div class="main" v-if="hasValidSimulationUiInformation">
5
5
  <div class="main-left">
6
6
  <p class="default title">{{title}}</p>
7
7
  <el-divider></el-divider>
8
8
  <p class="default input-parameters">Input parameters</p>
9
- <div v-show="mode === 0">
10
- <p class="default simulation-mode">Simulation mode</p>
11
- <el-select class="simulation-mode" popper-class="simulation-mode-popper" :popper-append-to-body="false" v-model="simulationMode" size="mini" @change="simulationModeChanged()">
12
- <el-option v-for="simulationMode in simulationModes" :key="simulationMode.value" :label="simulationMode.label" :value="simulationMode.value" />
13
- </el-select>
14
- <div class="sliders-and-fields">
15
- <p class="default first-slider-and-field">Stimulation level</p>
16
- <el-slider v-model="stimulationLevel" :max="10" :show-tooltip="false" :show-input="false" :disabled="simulationMode == 0" />
17
- <el-input-number class="slider-and-field" v-model="stimulationLevel" size="mini" :controls="false" :min="0" :max="10" :disabled="simulationMode == 0" />
18
- </div>
19
- </div>
20
- <div v-show="mode === 1">
21
- <div class="sliders-and-fields">
22
- <p class="default first-slider-and-field">Spike frequency</p>
23
- <el-slider v-model="simulationSpikeFrequency" :max="1000" :show-tooltip="false" :show-input="false" />
24
- <el-input-number class="slider-and-field" v-model="simulationSpikeFrequency" size="mini" :controls="false" :min="0" :max="1000" />
25
- <p class="default slider-and-field">Spike number</p>
26
- <el-slider v-model="simulationSpikeNumber" :max="30" :show-tooltip="false" :show-input="false" />
27
- <el-input-number class="slider-and-field" v-model="simulationSpikeNumber" size="mini" :controls="false" :min="0" :max="30" />
28
- <p class="default slider-and-field">Spike amplitude</p>
29
- <el-slider v-model="simulationSpikeAmplitude" :max="30" :show-tooltip="false" :show-input="false" />
30
- <el-input-number class="slider-and-field" v-model="simulationSpikeAmplitude" size="mini" :controls="false" :min="0" :max="30" />
31
- </div>
9
+ <div>
10
+ <SimulationVuerInput v-for="(input, index) in simulationUiInformation.input" :defaultValue="input.defaultValue" :firstScalarInput="firstScalarInput[index]" :key="`input-${index}`" :name="input.name" :maximumValue="input.maximumValue" :minimumValue="input.minimumValue" :possibleValues="input.possibleValues" />
32
11
  </div>
12
+ <div ref="input" />
33
13
  <div class="primary-button">
34
14
  <el-button type="primary" size="mini" @click="runSimulation()">Run simulation</el-button>
35
15
  </div>
@@ -39,16 +19,12 @@
39
19
  <div class="secondary-button">
40
20
  <el-button size="mini" @click="viewDataset()">View dataset</el-button>
41
21
  </div>
42
- <p class="default note">{{note}}</p>
43
- </div>
44
- <div class="main-right" v-if="mode === 0" v-show="simulationValid">
45
- <PlotVuer :layout-input="simulationPotentialLayout" :dataInput="simulationPotentialData" :plotType="'plotly-only'" />
22
+ <p class="default note">Additional parameters are available on oSPARC</p>
46
23
  </div>
47
- <div class="main-right composite" v-if="mode === 1" v-show="simulationValid">
48
- <PlotVuer :layout-input="simulationSpikeLayout" :dataInput="simulationSpikeData" :plotType="'plotly-only'" />
49
- <PlotVuer :layout-input="simulationPotentialLayout" :dataInput="simulationPotentialData" :plotType="'plotly-only'" />
24
+ <div class="main-right" ref="output" v-show="isSimulationValid">
25
+ <PlotVuer v-for="(outputPlot, index) in simulationUiInformation.output.plots" :key="`output-${index}`" :layout-input="layout[index]" :dataInput="simulationData[index]" :plotType="'plotly-only'" />
50
26
  </div>
51
- <div class="main-right" v-show="!simulationValid">
27
+ <div class="main-right" v-show="!isSimulationValid">
52
28
  <p class="default error"><span class="error">Error:</span> <span v-html="errorMessage"></span>.</p>
53
29
  </div>
54
30
  </div>
@@ -59,106 +35,48 @@
59
35
  import Vue from "vue";
60
36
  import { PlotVuer } from "@abi-software/plotvuer";
61
37
  import "@abi-software/plotvuer/dist/plotvuer.css";
62
- import { Aside, Button, Container, Divider, InputNumber, Loading, Main, Option, Select, Slider } from "element-ui";
63
-
64
- var NoSimulationData = [{}];
38
+ import SimulationVuerInput from "./SimulationVuerInput.vue";
39
+ import { Button, Divider, Loading } from "element-ui";
40
+ import { evaluateValue, evaluateSimulationValue } from "./common.js";
41
+ import { validJson } from "./json.js";
42
+ import { initialiseUi, finaliseUi } from "./ui.js";
65
43
 
66
- Vue.use(Aside);
67
44
  Vue.use(Button);
68
- Vue.use(Container);
69
45
  Vue.use(Divider);
70
- Vue.use(InputNumber);
71
46
  Vue.use(Loading);
72
- Vue.use(Main);
73
- Vue.use(Option);
74
- Vue.use(Select);
75
- Vue.use(Slider);
76
47
 
77
48
  export default {
78
49
  name: "SimulationVuer",
79
50
  components: {
80
51
  PlotVuer,
52
+ SimulationVuerInput,
81
53
  },
82
54
  props: {
83
55
  apiLocation: {
56
+ required: true,
84
57
  type: String,
85
- default: "",
86
58
  },
87
59
  entry: {
88
- /**
89
- * Object containing information for the current simulation.
90
- */
91
- entry: Object,
60
+ required: true,
61
+ type: Object,
92
62
  },
93
63
  },
94
- data: function () {
95
- let title = this.entry?this.entry.name:"";
96
-
64
+ data: function() {
97
65
  return {
98
- mode: 0,
99
66
  errorMessage: "",
100
- note: "Additional parameters are available on oSPARC",
101
- stimulationLevel: 0,
102
- simulationMode: 0,
103
- simulationModes: [
104
- {
105
- label: "Normal sinus rhythm",
106
- value: 0,
107
- },
108
- {
109
- label: "Stellate stimulation",
110
- value: 1,
111
- },
112
- {
113
- label: "Vagal stimulation",
114
- value: 2,
115
- },
116
- ],
117
- simulationSpikeLayout: {
118
- xaxis: {
119
- title: {
120
- text: "Time (s)",
121
- font: {
122
- size: 10,
123
- },
124
- },
125
- },
126
- yaxis: {
127
- title: {
128
- text: "Spike amplitude",
129
- font: {
130
- size: 10,
131
- },
132
- }
133
- },
134
- },
135
- simulationSpikeFrequency: 300,
136
- simulationSpikeNumber: 10,
137
- simulationSpikeAmplitude: 10, // The real value is 100 times smaller.
138
- simulationSpikeData: NoSimulationData,
139
- simulationPotentialLayout: {
140
- xaxis: {
141
- title: {
142
- text: "Time (s)",
143
- font: {
144
- size: 10,
145
- },
146
- },
147
- },
148
- yaxis: {
149
- title: {
150
- text: "Membrane potential (mV)",
151
- font: {
152
- size: 10,
153
- },
154
- }
155
- },
156
- },
157
- simulationPotentialData: NoSimulationData,
158
- simulationBeingComputed: false,
159
- simulationBeingComputedLabel: "Loading simulation results...",
160
- simulationValid: true,
161
- title: title,
67
+ firstScalarInput: [],
68
+ hasFinalisedUi: false,
69
+ hasValidSimulationUiInformation: false,
70
+ isMounted: false,
71
+ isSimulationValid: true,
72
+ layout: [],
73
+ showUserMessage: false,
74
+ simulationData: [],
75
+ simulationDataId: {},
76
+ simulationUiInformation: {},
77
+ userMessage: "",
78
+ title: (this.entry !== undefined)?this.entry.name:"",
79
+ ui: null,
162
80
  };
163
81
  },
164
82
  methods: {
@@ -168,105 +86,89 @@ export default {
168
86
  viewDataset() {
169
87
  window.open(this.entry.dataset, "_blank");
170
88
  },
171
- simulationModeChanged() {
172
- this.simulationPotentialData = NoSimulationData;
173
- this.simulationValid = true;
174
- },
175
89
  runSimulation() {
176
- this.simulationBeingComputed = true;
90
+ this.userMessage = "Loading simulation results...";
91
+ this.showUserMessage = true;
177
92
 
178
- var request = {
93
+ let request = {
179
94
  model_url: this.entry.resource,
180
- json_config: {
181
- output: ["Membrane/V"],
182
- },
95
+ json_config: {},
183
96
  };
184
97
 
185
- // Specify the ending point and point interval for the normal mode (since
186
- // our resource is a CellML file).
98
+ // Specify the ending point and point interval, if we have some simulation
99
+ // data.
187
100
 
188
- if (this.mode === 0) {
189
- request.json_config["simulation"] = {
190
- "Ending point": 3,
191
- "Point interval": 0.001,
101
+ if (this.simulationUiInformation.simulation !== undefined) {
102
+ request.json_config.simulation = {
103
+ "Ending point": this.simulationUiInformation.simulation.endingPoint,
104
+ "Point interval": this.simulationUiInformation.simulation.pointInterval,
192
105
  };
193
106
  }
194
107
 
195
- // Apply a stellate/vagal stimulation, if needed.
196
- // Notes:
197
- // - this.simulationMode:
198
- // - 0: normal sinus rhythm;
199
- // - 1: stellate stimulation; and
200
- // - 2: vagal stimulation.
201
- // - this.stimulationLevel has a value in [0; 10], but to compute ACh, we
202
- // need a value in [0; 1].
203
-
204
- if (this.simulationMode !== 0) {
205
- request.json_config["parameters"] = {
206
- "Rate_modulation_experiments/Iso_1_uM": 1.0,
207
- "Rate_modulation_experiments/ACh": this.simulationMode === 1 ? (1.0 - 0.1 * this.stimulationLevel) * 22.0e-6 : 22.0e-6 + 0.1 * this.stimulationLevel * (38.0e-6 - 22.0e-6),
208
- };
209
- }
108
+ // Specify the simulation parameters.
210
109
 
211
- // Apply the spike settings, if needed.
110
+ if (this.simulationUiInformation.parameters != undefined) {
111
+ request.json_config.parameters = {};
212
112
 
213
- if (this.mode === 1) {
214
- request.json_config["parameters"] = {
215
- "Brain_stem/t_period": this.simulationSpikeFrequency,
216
- "Brain_stem/w_n": this.simulationSpikeNumber,
217
- "Brain_stem/w_value": 0.01 * this.simulationSpikeAmplitude,
218
- };
113
+ this.simulationUiInformation.parameters.forEach((parameter) => {
114
+ request.json_config.parameters[parameter.name] = evaluateValue(this, parameter.value);
115
+ });
219
116
  }
220
117
 
221
- // Request the spikes if we are in composite mode.
118
+ // Specify what we want to retrieve.
222
119
 
223
- if (this.mode === 1) {
224
- request.json_config["output"].push("Brain_stem/w");
120
+ if (this.simulationUiInformation.output.data !== undefined) {
121
+ let index = -1;
122
+
123
+ request.json_config.output = [];
124
+
125
+ this.simulationUiInformation.output.data.forEach((outputData) => {
126
+ request.json_config.output[++index] = outputData.name;
127
+ });
225
128
  }
226
129
 
227
130
  // Run the simulation.
228
131
 
229
- var xmlhttp = new XMLHttpRequest();
132
+ let xmlhttp = new XMLHttpRequest();
230
133
 
231
134
  xmlhttp.open("POST", this.apiLocation + "/simulation", true);
232
135
  xmlhttp.setRequestHeader("Content-type", "application/json");
233
136
  xmlhttp.onreadystatechange = () => {
234
137
  if (xmlhttp.readyState === 4) {
235
- this.simulationBeingComputed = false;
138
+ this.showUserMessage = false;
236
139
 
237
140
  if (xmlhttp.status === 200) {
238
- var response = JSON.parse(xmlhttp.responseText);
141
+ let response = JSON.parse(xmlhttp.responseText);
142
+
143
+ this.isSimulationValid = response.status === "ok";
144
+
145
+ if (this.isSimulationValid) {
146
+ // Retrieve and post-process the simulation data.
147
+
148
+ let index = -1;
149
+ let iMax = response.results[this.simulationDataId[Object.keys(this.simulationDataId)[0]]].length;
239
150
 
240
- this.simulationValid = response.status === "ok";
151
+ this.simulationUiInformation.output.plots.forEach((outputPlot) => {
152
+ let xValue = [];
153
+ let yValue = [];
241
154
 
242
- if (this.simulationValid) {
243
- // Retrieve the spike data, if needed. (Our default spike
244
- // amplitude is 0.1, but we normalise it to 10.)
155
+ for (let i = 0; i < iMax; ++i) {
156
+ xValue[i] = evaluateSimulationValue(this, response.results, outputPlot.xValue, i);
157
+ yValue[i] = evaluateSimulationValue(this, response.results, outputPlot.yValue, i);
158
+ }
245
159
 
246
- if (this.mode === 1) {
247
- this.simulationSpikeData = [
160
+ this.simulationData[++index] = [
248
161
  {
249
- x: response.results["environment/time"],
250
- y: response.results["Brain_stem/w"].map(function (element) {
251
- return 100 * element;
252
- }),
162
+ x: xValue,
163
+ y: yValue,
253
164
  },
254
165
  ];
255
- }
256
-
257
- // Retrieve the potential data.
258
-
259
- this.simulationPotentialData = [
260
- {
261
- x: response.results["environment/time"],
262
- y: response.results["Membrane/V"],
263
- },
264
- ];
166
+ });
265
167
  } else {
266
168
  this.errorMessage = response.description;
267
169
  }
268
170
  } else {
269
- this.simulationValid = false;
171
+ this.isSimulationValid = false;
270
172
 
271
173
  this.errorMessage = xmlhttp.statusText.toLowerCase() + " (<a href='https://httpstatuses.com/" + xmlhttp.status + "/' target='_blank'>" + xmlhttp.status + "</a>)";
272
174
  }
@@ -275,21 +177,59 @@ export default {
275
177
  xmlhttp.send(JSON.stringify(request));
276
178
  },
277
179
  },
278
- mounted: function () {
279
- // Determine the mode in which we should run:
280
- // - -1: unknown mode;
281
- // - 0: normal mode; and
282
- // - 1: composite mode.
283
-
284
- this.mode = -1;
285
-
286
- if (this.entry) {
287
- if (this.entry.resource === "https://models.physiomeproject.org/workspace/486/rawfile/55879cbc485e2d4c41f3dc6d60424b849f94c4ee/HumanSAN_Fabbri_Fantini_Wilders_Severi_2017.cellml") {
288
- this.mode = 0;
289
- } else if (this.entry.resource === "https://models.physiomeproject.org/workspace/698/rawfile/f3fc911063ac72ed44e84c0c5af28df41c25d452/fabbri_et_al_based_composite_SAN_model.sedml") {
290
- this.mode = 1;
180
+ created: function() {
181
+ this.userMessage = "Retrieving UI information...";
182
+ this.showUserMessage = true;
183
+
184
+ // Retrieve the simulation UI file for the given dataset.
185
+
186
+ let xmlhttp = new XMLHttpRequest();
187
+
188
+ xmlhttp.open("GET", this.apiLocation + "/simulation_ui_file/" + this.entry.datasetId, true);
189
+ xmlhttp.setRequestHeader("Content-type", "application/json");
190
+ xmlhttp.onreadystatechange = () => {
191
+ if (xmlhttp.readyState === 4) {
192
+ // Keep track of the simulation UI information.
193
+
194
+ this.simulationUiInformation = JSON.parse(xmlhttp.responseText);
195
+
196
+ // Make sure that the simulation UI information is valid.
197
+
198
+ this.hasValidSimulationUiInformation = validJson(this.simulationUiInformation);
199
+
200
+ if (!this.hasValidSimulationUiInformation) {
201
+ this.showUserMessage = false;
202
+
203
+ return;
204
+ }
205
+
206
+ // Initialise our UI.
207
+
208
+ initialiseUi(this);
209
+
210
+ // Finalise our UI.
211
+ // Note: we try both here and in the mounded() function since we have no
212
+ // idea how long it's going to take to retrieve the simulation UI
213
+ // information.
214
+
215
+ this.$nextTick(() => {
216
+ finaliseUi(this);
217
+
218
+ this.showUserMessage = false;
219
+ });
291
220
  }
292
- }
221
+ };
222
+ xmlhttp.send();
223
+ },
224
+ mounted: function() {
225
+ // Finalise our UI.
226
+ // Note: we try both here and in the created() function since we have no
227
+ // idea how long it's going to take to retrieve the simulation UI
228
+ // information.
229
+
230
+ this.isMounted = true;
231
+
232
+ finaliseUi(this);
293
233
  },
294
234
  };
295
235
  </script>
@@ -301,51 +241,9 @@ export default {
301
241
  box-shadow: -3px 2px 4px #00000040;
302
242
  }
303
243
  >>> .el-divider {
304
- margin: -8px 0 8px 0;
244
+ margin: -8px 0 8px 0 !important;
305
245
  width: 191px;
306
246
  }
307
- >>> .el-input-number.slider-and-field {
308
- margin-top: -8px;
309
- width: 0;
310
- height: 0;
311
- }
312
- >>> .el-input-number.slider-and-field .el-input {
313
- width: 60px;
314
- }
315
- >>> .el-input-number.slider-and-field .el-input__inner:focus {
316
- border-color: #8300bf;
317
- }
318
- >>> .el-select.simulation-mode {
319
- margin-bottom: 16px;
320
- }
321
- >>> .el-slider {
322
- width: 108px;
323
- margin-top: -12px;
324
- margin-left: 8px;
325
- }
326
- >>> .el-slider__bar {
327
- background-color: #8300bf;
328
- }
329
- >>> .el-slider__button {
330
- border-color: #8300bf;
331
- }
332
- .simulation-mode {
333
- margin-left: 8px;
334
- }
335
- .simulation-mode >>> .el-input__inner {
336
- font-family: Asap, sans-serif;
337
- }
338
- .simulation-mode >>> .el-input__inner:focus,
339
- .simulation-mode >>> .el-input.is-focus .el-input__inner {
340
- border-color: #8300bf;
341
- }
342
- .simulation-mode-popper .el-select-dropdown__item {
343
- font-family: Asap, sans-serif;
344
- }
345
- .simulation-mode-popper .el-select-dropdown__item.selected {
346
- font-weight: normal;
347
- color: #8300bf;
348
- }
349
247
  div.main {
350
248
  display: grid;
351
249
  --mainLeftWidth: 224px;
@@ -358,16 +256,36 @@ div.main-left {
358
256
  height: 100%;
359
257
  overflow: auto;
360
258
  }
361
- div.main-right.composite {
259
+ div.main-right.x1 {
260
+ height: 100%;
261
+ }
262
+ div.main-right.x2 {
362
263
  height: 50%;
363
264
  }
265
+ div.main-right.x3 {
266
+ height: 33.333%;
267
+ }
268
+ div.main-right.x4 {
269
+ height: 25%;
270
+ }
271
+ div.main-right.x5 {
272
+ height: 20%;
273
+ }
274
+ div.main-right.x6 {
275
+ height: 16.667%;
276
+ }
277
+ div.main-right.x7 {
278
+ height: 14.286%;
279
+ }
280
+ div.main-right.x8 {
281
+ height: 12.5%;
282
+ }
283
+ div.main-right.x9 {
284
+ height: 11.111%;
285
+ }
364
286
  >>> div.main-right div.controls {
365
287
  height: 0;
366
288
  }
367
- div.plot-vuer {
368
- display: grid;
369
- width: 100%;
370
- }
371
289
  div.primary-button,
372
290
  div.secondary-button {
373
291
  display: flex;
@@ -399,11 +317,6 @@ div.secondary-button .el-button:hover {
399
317
  div.simulation-vuer {
400
318
  height: 100%;
401
319
  }
402
- div.sliders-and-fields {
403
- display: grid;
404
- grid-template-columns: 132px auto;
405
- width: 191px;
406
- }
407
320
  p.default {
408
321
  font-family: Asap, sans-serif;
409
322
  letter-spacing: 0;
@@ -413,12 +326,6 @@ p.default {
413
326
  p.error {
414
327
  margin-left: 16px;
415
328
  }
416
- p.first-slider-and-field,
417
- p.slider-and-field {
418
- grid-column-start: 1;
419
- grid-column-end: 3;
420
- margin-bottom: 8px;
421
- }
422
329
  p.note {
423
330
  font-size: 12px;
424
331
  line-height: 16px;
@@ -431,30 +338,13 @@ p.input-parameters {
431
338
  p.title {
432
339
  line-height: 20px;
433
340
  }
434
- p.simulation-mode {
435
- margin-bottom: 4px;
436
- }
437
- p.first-slider-and-field {
438
- margin-top: 0;
439
- }
440
- p.slider-and-field {
441
- margin-top: 6px;
442
- }
443
341
  span.error {
444
342
  font-weight: 500 /* Medium */;
445
343
  }
446
344
  </style>
447
- <style scoped src="../styles/purple/aside.css">
448
- </style>
449
345
  <style scoped src="../styles/purple/button.css">
450
346
  </style>
451
- <style scoped src="../styles/purple/container.css">
452
- </style>
453
- <style scoped src="../styles/purple/input-number.css">
454
- </style>
455
- <style scoped src="../styles/purple/option.css">
456
- </style>
457
- <style scoped src="../styles/purple/select.css">
347
+ <style scoped src="../styles/purple/divider.css">
458
348
  </style>
459
- <style scoped src="../styles/purple/slider.css">
349
+ <style scoped src="../styles/purple/loading.css">
460
350
  </style>