@abi-software/simulationvuer 0.5.0 → 0.5.3

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,11 +1,14 @@
1
1
  <template>
2
- <div class="simulation-vuer" v-loading="simulationBeingComputed" element-loading-text="Loading simulation results...">
3
- <p v-if="!hasValidJson" class="default error"><span class="error">Error:</span> an unknown or invalid model was provided.</p>
4
- <div class="main" v-if="hasValidJson">
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>
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" />
11
+ </div>
9
12
  <div ref="input" />
10
13
  <div class="primary-button">
11
14
  <el-button type="primary" size="mini" @click="runSimulation()">Run simulation</el-button>
@@ -18,10 +21,10 @@
18
21
  </div>
19
22
  <p class="default note">Additional parameters are available on oSPARC</p>
20
23
  </div>
21
- <div class="main-right" ref="output" v-show="simulationValid">
22
- <PlotVuer v-for="(outputPlot, index) in json.output.plots" :key="`output-${index}`" :layout-input="layout[index]" :dataInput="simulationData[index]" :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'" />
23
26
  </div>
24
- <div class="main-right" v-show="!simulationValid">
27
+ <div class="main-right" v-show="!isSimulationValid">
25
28
  <p class="default error"><span class="error">Error:</span> <span v-html="errorMessage"></span>.</p>
26
29
  </div>
27
30
  </div>
@@ -32,28 +35,21 @@
32
35
  import Vue from "vue";
33
36
  import { PlotVuer } from "@abi-software/plotvuer";
34
37
  import "@abi-software/plotvuer/dist/plotvuer.css";
35
- import { Aside, Button, Container, Divider, InputNumber, Loading, Main, Option, Select, Slider } from "element-ui";
36
- import { jsonForNormalModel, jsonForCompositeModel } from "./configuration.js";
38
+ import SimulationVuerInput from "./SimulationVuerInput.vue";
39
+ import { Button, Divider, Loading } from "element-ui";
37
40
  import { evaluateValue, evaluateSimulationValue } from "./common.js";
38
41
  import { validJson } from "./json.js";
39
- import Ui from "./ui.js";
40
- import { prepareForUi } from "./ui.js";
42
+ import { initialiseUi, finaliseUi } from "./ui.js";
41
43
 
42
- Vue.use(Aside);
43
44
  Vue.use(Button);
44
- Vue.use(Container);
45
45
  Vue.use(Divider);
46
- Vue.use(InputNumber);
47
46
  Vue.use(Loading);
48
- Vue.use(Main);
49
- Vue.use(Option);
50
- Vue.use(Select);
51
- Vue.use(Slider);
52
47
 
53
48
  export default {
54
49
  name: "SimulationVuer",
55
50
  components: {
56
51
  PlotVuer,
52
+ SimulationVuerInput,
57
53
  },
58
54
  props: {
59
55
  apiLocation: {
@@ -67,15 +63,18 @@ export default {
67
63
  },
68
64
  data: function() {
69
65
  return {
70
- mode: 0, //---GRY--- TO BE DELETED!
71
- json: {},
72
- hasValidJson: true,
73
66
  errorMessage: "",
67
+ firstScalarInput: [],
68
+ hasFinalisedUi: false,
69
+ hasValidSimulationUiInformation: false,
70
+ isMounted: false,
71
+ isSimulationValid: true,
74
72
  layout: [],
73
+ showUserMessage: false,
75
74
  simulationData: [],
76
75
  simulationDataId: {},
77
- simulationBeingComputed: false,
78
- simulationValid: true,
76
+ simulationUiInformation: {},
77
+ userMessage: "",
79
78
  title: (this.entry !== undefined)?this.entry.name:"",
80
79
  ui: null,
81
80
  };
@@ -88,41 +87,42 @@ export default {
88
87
  window.open(this.entry.dataset, "_blank");
89
88
  },
90
89
  runSimulation() {
91
- this.simulationBeingComputed = true;
90
+ this.userMessage = "Loading simulation results...";
91
+ this.showUserMessage = true;
92
92
 
93
93
  let request = {
94
94
  model_url: this.entry.resource,
95
95
  json_config: {},
96
96
  };
97
97
 
98
- // Specify the ending point and point interval for the normal mode (since
99
- // our resource is a CellML file).
98
+ // Specify the ending point and point interval, if we have some simulation
99
+ // data.
100
100
 
101
- if (this.json.simulation !== undefined) {
101
+ if (this.simulationUiInformation.simulation !== undefined) {
102
102
  request.json_config.simulation = {
103
- "Ending point": this.json.simulation.endingPoint,
104
- "Point interval": this.json.simulation.pointInterval,
103
+ "Ending point": this.simulationUiInformation.simulation.endingPoint,
104
+ "Point interval": this.simulationUiInformation.simulation.pointInterval,
105
105
  };
106
106
  }
107
107
 
108
108
  // Specify the simulation parameters.
109
109
 
110
- if (this.json.parameters != undefined) {
110
+ if (this.simulationUiInformation.parameters != undefined) {
111
111
  request.json_config.parameters = {};
112
112
 
113
- this.json.parameters.forEach((parameter) => {
114
- request.json_config.parameters[parameter.name] = evaluateValue(this.ui, parameter.value);
113
+ this.simulationUiInformation.parameters.forEach((parameter) => {
114
+ request.json_config.parameters[parameter.name] = evaluateValue(this, parameter.value);
115
115
  });
116
116
  }
117
117
 
118
118
  // Specify what we want to retrieve.
119
119
 
120
- if (this.json.output.data !== undefined) {
120
+ if (this.simulationUiInformation.output.data !== undefined) {
121
121
  let index = -1;
122
122
 
123
123
  request.json_config.output = [];
124
124
 
125
- this.json.output.data.forEach((outputData) => {
125
+ this.simulationUiInformation.output.data.forEach((outputData) => {
126
126
  request.json_config.output[++index] = outputData.name;
127
127
  });
128
128
  }
@@ -135,20 +135,20 @@ export default {
135
135
  xmlhttp.setRequestHeader("Content-type", "application/json");
136
136
  xmlhttp.onreadystatechange = () => {
137
137
  if (xmlhttp.readyState === 4) {
138
- this.simulationBeingComputed = false;
138
+ this.showUserMessage = false;
139
139
 
140
140
  if (xmlhttp.status === 200) {
141
141
  let response = JSON.parse(xmlhttp.responseText);
142
142
 
143
- this.simulationValid = response.status === "ok";
143
+ this.isSimulationValid = response.status === "ok";
144
144
 
145
- if (this.simulationValid) {
145
+ if (this.isSimulationValid) {
146
146
  // Retrieve and post-process the simulation data.
147
147
 
148
148
  let index = -1;
149
149
  let iMax = response.results[this.simulationDataId[Object.keys(this.simulationDataId)[0]]].length;
150
150
 
151
- this.json.output.plots.forEach((outputPlot) => {
151
+ this.simulationUiInformation.output.plots.forEach((outputPlot) => {
152
152
  let xValue = [];
153
153
  let yValue = [];
154
154
 
@@ -168,7 +168,7 @@ export default {
168
168
  this.errorMessage = response.description;
169
169
  }
170
170
  } else {
171
- this.simulationValid = false;
171
+ this.isSimulationValid = false;
172
172
 
173
173
  this.errorMessage = xmlhttp.statusText.toLowerCase() + " (<a href='https://httpstatuses.com/" + xmlhttp.status + "/' target='_blank'>" + xmlhttp.status + "</a>)";
174
174
  }
@@ -178,39 +178,58 @@ export default {
178
178
  },
179
179
  },
180
180
  created: function() {
181
- // Manually (for now) specify the JSON configuration to be used by either
182
- // the normal model or the composite model.
183
-
184
- this.mode = -1;
185
-
186
- if (this.entry !== undefined) {
187
- if (this.entry.resource === "https://models.physiomeproject.org/workspace/486/rawfile/55879cbc485e2d4c41f3dc6d60424b849f94c4ee/HumanSAN_Fabbri_Fantini_Wilders_Severi_2017.cellml") {
188
- this.mode = 0;
189
- this.json = jsonForNormalModel();
190
- } else if (this.entry.resource === "https://models.physiomeproject.org/workspace/698/rawfile/f3fc911063ac72ed44e84c0c5af28df41c25d452/fabbri_et_al_based_composite_SAN_model.sedml") {
191
- this.mode = 1;
192
- this.json = jsonForCompositeModel();
193
- }
194
- }
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();
195
187
 
196
- // Make sure that the JSON configuration is valid.
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.
197
193
 
198
- this.hasValidJson = validJson(this.json);
194
+ this.simulationUiInformation = JSON.parse(xmlhttp.responseText);
199
195
 
200
- if (!this.hasValidJson) {
201
- return;
202
- }
196
+ // Make sure that the simulation UI information is valid.
203
197
 
204
- // Prepare ourselves for the UI by initialising some data.
198
+ this.hasValidSimulationUiInformation = validJson(this.simulationUiInformation);
199
+
200
+ if (!this.hasValidSimulationUiInformation) {
201
+ this.showUserMessage = false;
202
+
203
+ return;
204
+ }
205
205
 
206
- prepareForUi(this);
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
+ });
220
+ }
221
+ };
222
+ xmlhttp.send();
207
223
  },
208
224
  mounted: function() {
209
- // Finish mounting by creating the UI for the given simulation dataset.
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;
210
231
 
211
- if (this.hasValidJson) {
212
- this.ui = new Ui(this);
213
- }
232
+ finaliseUi(this);
214
233
  },
215
234
  };
216
235
  </script>
@@ -222,51 +241,9 @@ export default {
222
241
  box-shadow: -3px 2px 4px #00000040;
223
242
  }
224
243
  >>> .el-divider {
225
- margin: -8px 0 8px 0;
244
+ margin: -8px 0 8px 0 !important;
226
245
  width: 191px;
227
246
  }
228
- >>> .el-input-number.scalar {
229
- margin-top: -8px;
230
- width: 0;
231
- height: 0;
232
- }
233
- >>> .el-input-number.scalar .el-input {
234
- width: 60px;
235
- }
236
- >>> .el-input-number.scalar .el-input__inner:focus {
237
- border-color: #8300bf;
238
- }
239
- >>> .el-select.discrete {
240
- margin-bottom: 16px;
241
- }
242
- >>> .el-slider {
243
- width: 108px;
244
- margin-top: -12px;
245
- margin-left: 8px;
246
- }
247
- >>> .el-slider__bar {
248
- background-color: #8300bf;
249
- }
250
- >>> .el-slider__button {
251
- border-color: #8300bf;
252
- }
253
- .discrete {
254
- margin-left: 8px;
255
- }
256
- .discrete >>> .el-input__inner {
257
- font-family: Asap, sans-serif;
258
- }
259
- .discrete >>> .el-input__inner:focus,
260
- .discrete >>> .el-input.is-focus .el-input__inner {
261
- border-color: #8300bf;
262
- }
263
- .discrete-popper .el-select-dropdown__item {
264
- font-family: Asap, sans-serif;
265
- }
266
- .discrete-popper .el-select-dropdown__item.selected {
267
- font-weight: normal;
268
- color: #8300bf;
269
- }
270
247
  div.main {
271
248
  display: grid;
272
249
  --mainLeftWidth: 224px;
@@ -309,10 +286,6 @@ div.main-right.x9 {
309
286
  >>> div.main-right div.controls {
310
287
  height: 0;
311
288
  }
312
- div.plot-vuer {
313
- display: grid;
314
- width: 100%;
315
- }
316
289
  div.primary-button,
317
290
  div.secondary-button {
318
291
  display: flex;
@@ -344,11 +317,6 @@ div.secondary-button .el-button:hover {
344
317
  div.simulation-vuer {
345
318
  height: 100%;
346
319
  }
347
- div.sliders-and-fields {
348
- display: grid;
349
- grid-template-columns: 132px auto;
350
- width: 191px;
351
- }
352
320
  p.default {
353
321
  font-family: Asap, sans-serif;
354
322
  letter-spacing: 0;
@@ -358,12 +326,6 @@ p.default {
358
326
  p.error {
359
327
  margin-left: 16px;
360
328
  }
361
- p.first-scalar,
362
- p.scalar {
363
- grid-column-start: 1;
364
- grid-column-end: 3;
365
- margin-bottom: 8px;
366
- }
367
329
  p.note {
368
330
  font-size: 12px;
369
331
  line-height: 16px;
@@ -376,31 +338,13 @@ p.input-parameters {
376
338
  p.title {
377
339
  line-height: 20px;
378
340
  }
379
- p.discrete {
380
- margin-top: 0;
381
- margin-bottom: 4px;
382
- }
383
- p.first-scalar {
384
- margin-top: 0;
385
- }
386
- p.scalar {
387
- margin-top: 6px;
388
- }
389
341
  span.error {
390
342
  font-weight: 500 /* Medium */;
391
343
  }
392
344
  </style>
393
- <style scoped src="../styles/purple/aside.css">
394
- </style>
395
345
  <style scoped src="../styles/purple/button.css">
396
346
  </style>
397
- <style scoped src="../styles/purple/container.css">
398
- </style>
399
- <style scoped src="../styles/purple/input-number.css">
400
- </style>
401
- <style scoped src="../styles/purple/option.css">
402
- </style>
403
- <style scoped src="../styles/purple/select.css">
347
+ <style scoped src="../styles/purple/divider.css">
404
348
  </style>
405
- <style scoped src="../styles/purple/slider.css">
349
+ <style scoped src="../styles/purple/loading.css">
406
350
  </style>
@@ -0,0 +1,150 @@
1
+ <template>
2
+ <div>
3
+ <p :class="labelClasses">{{ name }}</p>
4
+ <el-select class="discrete" popper-class="discrete-popper" size="mini" v-if="isDiscrete" v-model="vModel" :disabled="!enabled" :popper-append-to-body="false" @change="updateUi()">
5
+ <el-option v-for="possibleValue in possibleValues" :key="possibleValue.value" :label="possibleValue.name" :value="possibleValue.value" />
6
+ </el-select>
7
+ <div class="sliders-and-fields" v-if="!isDiscrete">
8
+ <el-slider v-model="vModel" :disabled="!enabled" :max="maximumValue" :min="minimumValue" :show-input="false" :show-tooltip="false" @change="updateUi()" />
9
+ <el-input-number class="scalar" size="mini" v-model="vModel" :controls="false" :disabled="!enabled" :max="maximumValue" :min="minimumValue" @input="updateUi()" />
10
+ </div>
11
+ </div>
12
+ </template>
13
+
14
+ <script>
15
+ import Vue from "vue";
16
+ import { InputNumber, Option, Select, Slider } from "element-ui";
17
+ import { updateUi } from "./ui.js";
18
+
19
+ Vue.use(InputNumber);
20
+ Vue.use(Option);
21
+ Vue.use(Select);
22
+ Vue.use(Slider);
23
+
24
+ export default {
25
+ name: "SimulationVuerInput",
26
+ props: {
27
+ defaultValue: {
28
+ required: true,
29
+ type: Number,
30
+ },
31
+ firstScalarInput: {
32
+ type: Boolean,
33
+ },
34
+ maximumValue: {
35
+ type: Number,
36
+ },
37
+ minimumValue: {
38
+ type: Number,
39
+ },
40
+ name: {
41
+ required: true,
42
+ type: String,
43
+ },
44
+ possibleValues: {
45
+ type: Array,
46
+ },
47
+ },
48
+ data: function() {
49
+ return {
50
+ enabled: true,
51
+ isDiscrete: this.possibleValues !== undefined,
52
+ labelClasses: "default " + ((this.possibleValues !== undefined)?"discrete":this.firstScalarInput?"first-scalar":"scalar"),
53
+ vModel: this.defaultValue,
54
+ };
55
+ },
56
+ methods: {
57
+ updateUi: function() {
58
+ updateUi(this.$parent);
59
+ },
60
+ },
61
+ };
62
+ </script>
63
+
64
+ <!-- Add "scoped" attribute to limit CSS to this component only -->
65
+ <style scoped>
66
+ @import url("//unpkg.com/element-ui@2.14.1/lib/theme-chalk/index.css");
67
+ >>> .el-input-number.scalar {
68
+ margin-top: -8px;
69
+ width: 0;
70
+ }
71
+ >>> .el-input-number.scalar .el-input {
72
+ width: 60px;
73
+ }
74
+ >>> .el-input-number.scalar .el-input__inner:focus {
75
+ border-color: #8300bf;
76
+ }
77
+ >>> .el-select.discrete {
78
+ margin-bottom: 16px;
79
+ }
80
+ >>> .el-slider {
81
+ width: 108px;
82
+ margin-top: -12px;
83
+ margin-left: 8px;
84
+ }
85
+ >>> .el-slider__bar {
86
+ background-color: #8300bf;
87
+ }
88
+ >>> .el-slider__button {
89
+ border-color: #8300bf;
90
+ }
91
+ .discrete {
92
+ margin-left: 8px;
93
+ }
94
+ .discrete >>> .el-input__inner {
95
+ font-family: Asap, sans-serif;
96
+ }
97
+ .discrete >>> .el-input__inner:focus,
98
+ .discrete >>> .el-input.is-focus .el-input__inner {
99
+ border-color: #8300bf;
100
+ }
101
+ .discrete-popper .el-select-dropdown__item {
102
+ font-family: Asap, sans-serif;
103
+ }
104
+ .discrete-popper .el-select-dropdown__item.selected {
105
+ font-weight: normal;
106
+ color: #8300bf;
107
+ }
108
+ .scalar >>> .el-input__inner {
109
+ text-align: center;
110
+ }
111
+ div.simulation-vuer {
112
+ height: 100%;
113
+ }
114
+ div.sliders-and-fields {
115
+ display: grid;
116
+ grid-template-columns: 132px auto;
117
+ width: 191px;
118
+ height: 26px;
119
+ }
120
+ p.default {
121
+ font-family: Asap, sans-serif;
122
+ letter-spacing: 0;
123
+ margin: 16px 0;
124
+ text-align: start;
125
+ }
126
+ p.first-scalar,
127
+ p.scalar {
128
+ grid-column-start: 1;
129
+ grid-column-end: 3;
130
+ margin-bottom: 8px;
131
+ }
132
+ p.discrete {
133
+ margin-top: 0;
134
+ margin-bottom: 4px;
135
+ }
136
+ p.first-scalar {
137
+ margin-top: 0;
138
+ }
139
+ p.scalar {
140
+ margin-top: 6px;
141
+ }
142
+ </style>
143
+ <style scoped src="../styles/purple/input-number.css">
144
+ </style>
145
+ <style scoped src="../styles/purple/option.css">
146
+ </style>
147
+ <style scoped src="../styles/purple/select.css">
148
+ </style>
149
+ <style scoped src="../styles/purple/slider.css">
150
+ </style>
@@ -8,20 +8,20 @@ function doEvaluateValue(value, from, to) {
8
8
  return value;
9
9
  }
10
10
 
11
- export function evaluateValue(ui, value) {
12
- ui.discreteElements.forEach((discreteElement) => {
13
- value = doEvaluateValue(value, discreteElement.id, discreteElement.select.vModel);
14
- });
11
+ export function evaluateValue(parent, value) {
12
+ let index = -1;
13
+
14
+ parent.simulationUiInformation.input.forEach((input) => {
15
+ ++index;
15
16
 
16
- ui.scalarElements.forEach((scalarElement) => {
17
- value = doEvaluateValue(value, scalarElement.id, scalarElement.input_number.vModel);
17
+ value = doEvaluateValue(value, input.id, parent.$children[index].vModel);
18
18
  });
19
19
 
20
20
  return Function("return " + value + ";")();
21
21
  }
22
22
 
23
23
  export function evaluateSimulationValue(parent, results, value, i) {
24
- parent.json.output.data.forEach((data) => {
24
+ parent.simulationUiInformation.output.data.forEach((data) => {
25
25
  value = doEvaluateValue(value, data.id, results[parent.simulationDataId[data.id]][i]);
26
26
  });
27
27
 
@@ -17,6 +17,9 @@ export function validJson(json) {
17
17
  required: true,
18
18
  type: "number",
19
19
  },
20
+ enabled: {
21
+ type: "string",
22
+ },
20
23
  id: {
21
24
  type: "string",
22
25
  },
@@ -76,16 +79,19 @@ export function validJson(json) {
76
79
  type: "object",
77
80
  },
78
81
  minItems: 1,
82
+ required: true,
79
83
  type: "array",
80
84
  },
81
85
  output: {
82
86
  additionalProperties: false,
87
+ minItems: 1,
83
88
  properties: {
84
89
  data: {
85
90
  items: {
86
91
  additionalProperties: false,
87
92
  properties: {
88
93
  id: {
94
+ required: true,
89
95
  type: "string",
90
96
  },
91
97
  name: {
@@ -95,6 +101,8 @@ export function validJson(json) {
95
101
  },
96
102
  type: "object",
97
103
  },
104
+ minItems: 1,
105
+ required: true,
98
106
  type: "array",
99
107
  },
100
108
  plots: {