@abi-software/simulationvuer 0.7.0-vue-3-alpha.4 → 1.0.0

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,534 +1,622 @@
1
- <template>
2
- <div class="simulation-vuer" v-loading="showUserMessage" :element-loading-text="userMessage">
3
- <p v-if="!hasValidSimulationUiInfo && !showUserMessage" class="default error"><span class="error">Error:</span> an unknown or invalid model was provided.</p>
4
- <div class="main" v-if="hasValidSimulationUiInfo">
5
- <div class="main-left">
6
- <p class="default name">{{name}}</p>
7
- <el-divider></el-divider>
8
- <p class="default input-parameters">Input parameters</p>
9
- <div class="input scrollbar">
10
- <SimulationVuerInput v-for="(input, index) in simulationUiInfo.input" ref="simInput" :defaultValue="input.defaultValue" :key="`input-${index}`" :name="input.name" :maximumValue="input.maximumValue" :minimumValue="input.minimumValue" :possibleValues="input.possibleValues" :stepValue="input.stepValue" />
11
- </div>
12
- <div class="primary-button">
13
- <el-button type="primary" size="small" @click="startSimulation()">Run Simulation</el-button>
14
- </div>
15
- <div class="secondary-button" v-if="uuid">
16
- <el-button size="small" @click="runOnOsparc()">Run on oSPARC</el-button>
17
- </div>
18
- <div class="secondary-button">
19
- <el-button size="small" @click="viewDataset()">View Dataset</el-button>
20
- </div>
21
- <p class="default note" v-if="uuid">Additional parameters are available on oSPARC</p>
22
- </div>
23
- <div class="main-right" ref="output" v-show="isSimulationValid">
24
- <PlotVuer v-for="(outputPlot, index) in simulationUiInfo.output.plots" :key="`output-${index}`" :metadata="plotMetadata" :layout-input="layout[index]" :data-source="{data: simulationData[index]}" :plotType="'plotly-only'" />
25
- </div>
26
- <div class="main-right" v-show="!isSimulationValid">
27
- <p class="default error"><span class="error">Error:</span> <span v-html="errorMessage"></span>.</p>
28
- </div>
29
- </div>
30
- </div>
31
- </template>
32
-
33
- <script>
34
- import { PlotVuer } from "@abi-software/plotvuer";
35
- import "@abi-software/plotvuer/dist/style.css";
36
- import SimulationVuerInput from "./SimulationVuerInput.vue";
37
- import { ElButton, ElDivider, ElLoading } from "element-plus";
38
- import { evaluateValue, evaluateSimulationValue, OPENCOR_SOLVER_NAME } from "./common.js";
39
- import { validJson } from "./json.js";
40
- import { initialiseUi, finaliseUi } from "./ui.js";
41
-
42
-
43
- export default {
44
- name: "SimulationVuer",
45
- components: {
46
- PlotVuer,
47
- SimulationVuerInput,
48
- ElButton,
49
- ElDivider,
50
- ElLoading,
51
- },
52
- props: {
53
- apiLocation: {
54
- required: true,
55
- type: String,
56
- },
57
- id: {
58
- required: true,
59
- type: Number,
60
- },
61
- },
62
- data: function() {
63
- let xmlhttp = new XMLHttpRequest();
64
- let name = undefined;
65
- let uuid = undefined;
66
- xmlhttp.open("GET", this.apiLocation + "/sim/dataset/" + this.id, false);
67
- xmlhttp.setRequestHeader("Content-type", "application/json");
68
- xmlhttp.onreadystatechange = () => {
69
- if (xmlhttp.readyState === 4) {
70
- if (xmlhttp.status === 200) {
71
- let datasetInfo = JSON.parse(xmlhttp.responseText);
72
-
73
- name = datasetInfo.name;
74
- uuid = (datasetInfo.study !== undefined)?datasetInfo.study.uuid:undefined;
75
- }
76
- }
77
- };
78
- xmlhttp.send();
79
-
80
- return {
81
- plotMetadata: {
82
- version: "1.1.0",
83
- type: "plot",
84
- attrs: {
85
- style: "timeseries"
86
- }
87
- },
88
- errorMessage: "",
89
- hasFinalisedUi: false,
90
- hasValidSimulationUiInfo: false,
91
- isMounted: false,
92
- isSimulationValid: true,
93
- layout: [],
94
- name: name,
95
- perfectScollbarOptions: {
96
- suppressScrollX: true,
97
- },
98
- showUserMessage: false,
99
- simulationData: [],
100
- simulationDataId: {},
101
- simulationUiInfo: {},
102
- userMessage: "",
103
- ui: null,
104
- uuid: uuid,
105
- };
106
- },
107
- methods: {
108
- retrieveAndBuildSimulationUi(simulationUiInfo) {
109
- // Keep track of the simulation UI information.
110
-
111
- this.simulationUiInfo = simulationUiInfo;
112
-
113
- // Make sure that the simulation UI information is valid.
114
-
115
- this.hasValidSimulationUiInfo = validJson(this.simulationUiInfo);
116
-
117
- if (!this.hasValidSimulationUiInfo) {
118
- return;
119
- }
120
-
121
- // Initialise our UI.
122
-
123
- initialiseUi(this);
124
-
125
- // Finalise our UI.
126
- // Note: we try both here and in the mounted() function since we have no
127
- // idea how long it's going to take to retrieve the simulation UI
128
- // information.
129
-
130
- this.$nextTick(() => {
131
- finaliseUi(this);
132
- });
133
- },
134
- runOnOsparc() {
135
- window.open(`https://osparc.io/study/${this.uuid}`, "_blank");
136
- },
137
- viewDataset() {
138
- window.open(`https://sparc.science/datasets/${this.id}?type=dataset`, "_blank");
139
- },
140
- retrieveRequest(request) {
141
- // Settings specific to OpenCOR/oSPARC.
142
-
143
- let isOpencorSimulation = request.solver.name === OPENCOR_SOLVER_NAME;
144
-
145
- if (isOpencorSimulation) {
146
- request.opencor = {
147
- model_url: this.simulationUiInfo.simulation.opencor.resource,
148
- json_config: {},
149
- };
150
- } else {
151
- request.osparc = {};
152
- }
153
-
154
- // Specify the ending point and point interval, if we have some.
155
-
156
- if ( isOpencorSimulation
157
- && (this.simulationUiInfo.simulation.opencor.endingPoint !== undefined)
158
- && (this.simulationUiInfo.simulation.opencor.pointInterval !== undefined)) {
159
- request.opencor.json_config.simulation = {
160
- "Ending point": this.simulationUiInfo.simulation.opencor.endingPoint,
161
- "Point interval": this.simulationUiInfo.simulation.opencor.pointInterval,
162
- };
163
- }
164
-
165
- // Specify the parameters, if any.
166
-
167
- if (this.simulationUiInfo.parameters !== undefined) {
168
- let parameters = {};
169
-
170
- this.simulationUiInfo.parameters.forEach((parameter) => {
171
- parameters[parameter.name] = evaluateValue(this, parameter.value);
172
- });
173
-
174
- if (isOpencorSimulation) {
175
- request.opencor.json_config.parameters = parameters;
176
- } else {
177
- request.osparc.job_inputs = parameters;
178
- }
179
- }
180
-
181
- // Specify what we want to retrieve, if anything.
182
-
183
- if (isOpencorSimulation && (this.simulationUiInfo.output.data !== undefined)) {
184
- let index = -1;
185
-
186
- request.opencor.json_config.output = [];
187
-
188
- this.simulationUiInfo.output.data.forEach((outputData) => {
189
- request.opencor.json_config.output[++index] = outputData.name;
190
- });
191
- }
192
-
193
- return request;
194
- },
195
- processSimulationResults(results) {
196
- // Convert, if needed, the results to a JSON format that is compatible
197
- // with our OpenCOR results.
198
-
199
- if (typeof(results) === "string") {
200
- const SPACES = /[ \t]+/g;
201
-
202
- let lines = results.trim().split("\n");
203
- let iMax = lines[0].trim().split(SPACES).length;
204
-
205
- results = {};
206
-
207
- for (let i = 0; i < iMax; ++i) {
208
- results[i] = [];
209
- }
210
-
211
- let i = -1;
212
-
213
- lines.forEach((line) => {
214
- ++i;
215
-
216
- let j = -1;
217
- let values = line.trim().split(SPACES);
218
-
219
- values.forEach((value) => {
220
- results[++j][i] = Number(value);
221
- });
222
- });
223
- }
224
-
225
- // Get the results ready for plotting.
226
-
227
- let index = -1;
228
- let iMax = results[this.simulationDataId[Object.keys(this.simulationDataId)[0]]].length;
229
-
230
- this.simulationUiInfo.output.plots.forEach((outputPlot) => {
231
- let xValue = [];
232
- let yValue = [];
233
-
234
- for (let i = 0; i < iMax; ++i) {
235
- xValue[i] = evaluateSimulationValue(this, results, outputPlot.xValue, i);
236
- yValue[i] = evaluateSimulationValue(this, results, outputPlot.yValue, i);
237
- }
238
-
239
- this.simulationData[++index] = [
240
- {
241
- x: xValue,
242
- y: yValue,
243
- type: "scatter",
244
- },
245
- ];
246
- });
247
- },
248
- checkSimulation(data) {
249
- // Check the simulation.
250
-
251
- let xmlhttp = new XMLHttpRequest();
252
-
253
- xmlhttp.open("POST", this.apiLocation + "/check_simulation", true);
254
- xmlhttp.setRequestHeader("Content-type", "application/json");
255
- xmlhttp.onreadystatechange = () => {
256
- if (xmlhttp.readyState === 4) {
257
- if (xmlhttp.status === 200) {
258
- let response = JSON.parse(xmlhttp.responseText);
259
-
260
- this.isSimulationValid = response.status === "ok";
261
-
262
- if (this.isSimulationValid) {
263
- if (response.results !== undefined) {
264
- // The simulation is finished, so process its results.
265
-
266
- this.showUserMessage = false;
267
-
268
- this.processSimulationResults(response.results);
269
- } else {
270
- // The simulation is not yet finished, so check again in a
271
- // second.
272
-
273
- let that = this;
274
-
275
- setTimeout(function() {
276
- that.checkSimulation(data);
277
- }, 1000);
278
- }
279
- } else {
280
- this.showUserMessage = false;
281
- this.errorMessage = response.description;
282
- }
283
- } else {
284
- this.isSimulationValid = false;
285
- this.showUserMessage = false;
286
- this.errorMessage = xmlhttp.statusText.toLowerCase() + " (<a href='https://httpstatuses.com/" + xmlhttp.status + "/' target='_blank'>" + xmlhttp.status + "</a>)";
287
- }
288
- }
289
- };
290
- xmlhttp.send(JSON.stringify(data));
291
- },
292
- startSimulation() {
293
- // Retrieve the solver to be used for the simulation.
294
-
295
- let solver = undefined;
296
-
297
- this.simulationUiInfo.simulation.solvers.forEach((crtSolver) => {
298
- if ((crtSolver.if === undefined) || evaluateValue(this, crtSolver.if)) {
299
- solver = crtSolver;
300
- }
301
- });
302
-
303
- if (solver === undefined) {
304
- console.warn("SIMULATION: no solver name and/or solver version specified.");
305
-
306
- return;
307
- }
308
-
309
- // Start the simulation (after resetting our previous simulation data, in
310
- // case there were sonme).
311
- // Note: we use this.$nextTick() so that the user message is shown before
312
- // we get to post our HTTP request.
313
-
314
- this.userMessage = "Loading simulation results...";
315
- this.showUserMessage = true;
316
-
317
- this.$nextTick(() => {
318
- this.simulationData = [];
319
-
320
- let xmlhttp = new XMLHttpRequest();
321
-
322
- xmlhttp.open("POST", this.apiLocation + "/start_simulation", true);
323
- xmlhttp.setRequestHeader("Content-type", "application/json");
324
- xmlhttp.onreadystatechange = () => {
325
- if (xmlhttp.readyState === 4) {
326
- if (xmlhttp.status === 200) {
327
- let response = JSON.parse(xmlhttp.responseText);
328
-
329
- this.isSimulationValid = response.status === "ok";
330
-
331
- if (this.isSimulationValid) {
332
- this.checkSimulation(response.data);
333
- } else {
334
- this.showUserMessage = false;
335
- this.errorMessage = response.description;
336
- }
337
- } else {
338
- this.isSimulationValid = false;
339
- this.showUserMessage = false;
340
- this.errorMessage = xmlhttp.statusText.toLowerCase() + " (<a href='https://httpstatuses.com/" + xmlhttp.status + "/' target='_blank'>" + xmlhttp.status + "</a>)";
341
- }
342
- }
343
- };
344
- xmlhttp.send(JSON.stringify(this.retrieveRequest({
345
- solver: solver
346
- })));
347
- });
348
- },
349
- },
350
- created: function() {
351
- // Try to retrieve the UI information, but only if we have a name.
352
-
353
- if (this.name !== undefined) {
354
- this.userMessage = "Retrieving UI information...";
355
- this.showUserMessage = true;
356
-
357
- // Retrieve and build the simulation UI.
358
- // Note: we use this.$nextTick() so that the user message is shown before
359
- // we get to post our HTTP request.
360
-
361
- this.$nextTick(() => {
362
- let xmlhttp = new XMLHttpRequest();
363
-
364
- xmlhttp.open("GET", this.apiLocation + "/simulation_ui_file/" + this.id, true);
365
- xmlhttp.setRequestHeader("Content-type", "application/json");
366
- xmlhttp.onreadystatechange = () => {
367
- if (xmlhttp.readyState === 4) {
368
- this.showUserMessage = false;
369
-
370
- if (xmlhttp.status === 200) {
371
- this.retrieveAndBuildSimulationUi(JSON.parse(xmlhttp.responseText));
372
- }
373
- }
374
- };
375
- xmlhttp.send();
376
- });
377
- }
378
- },
379
- mounted: function() {
380
- // Finalise our UI.
381
- // Note: we try both here and in the created() function since we have no
382
- // idea how long it's going to take to retrieve the simulation UI
383
- // information.
384
-
385
- this.isMounted = true;
386
-
387
- finaliseUi(this);
388
- },
389
- };
390
- </script>
391
-
392
- <!-- Add "scoped" attribute to limit CSS to this component only -->
393
- <style scoped lang="scss">
394
-
395
-
396
- :deep( .el-button:hover) {
397
- box-shadow: -3px 2px 4px #00000040;
398
- }
399
- :deep( .el-divider) {
400
- margin: -8px 0 8px 0 !important;
401
- width: 210px;
402
- }
403
- :deep( .el-loading-spinner) {
404
- .path {
405
- stroke: #8300BF;
406
- }
407
- i, .el-loading-text {
408
- color: #8300BF;
409
- }
410
- }
411
- div.input {
412
- border: 1px solid #dcdfe6;
413
- padding: 4px;
414
- height: 300px;
415
- }
416
- div.main {
417
- display: grid;
418
- --mainLeftWidth: 243px;
419
- grid-template-columns: var(--mainLeftWidth) calc(100% - var(--mainLeftWidth));
420
- height: 100%;
421
- }
422
- div.main-left {
423
- border-right: 1px solid #dcdfe6;
424
- padding: 12px 20px 12px 12px;
425
- height: 100%;
426
- overflow: auto;
427
- }
428
- div.main-right.x1 {
429
- height: 100%;
430
- }
431
- div.main-right.x2 {
432
- height: 50%;
433
- }
434
- div.main-right.x3 {
435
- height: 33.333%;
436
- }
437
- div.main-right.x4 {
438
- height: 25%;
439
- }
440
- div.main-right.x5 {
441
- height: 20%;
442
- }
443
- div.main-right.x6 {
444
- height: 16.667%;
445
- }
446
- div.main-right.x7 {
447
- height: 14.286%;
448
- }
449
- div.main-right.x8 {
450
- height: 12.5%;
451
- }
452
- div.main-right.x9 {
453
- height: 11.111%;
454
- }
455
- :deep( div.main-right div.controls) {
456
- height: 0;
457
- }
458
- div.primary-button,
459
- div.secondary-button {
460
- display: flex;
461
- justify-content: flex-end;
462
- width: 210px;
463
- }
464
- div.primary-button {
465
- margin-top: 14px;
466
- }
467
- div.secondary-button {
468
- margin-top: 8px;
469
- }
470
- div.primary-button .el-button,
471
- div.secondary-button .el-button,
472
- div.primary-button .el-button:hover,
473
- div.secondary-button .el-button:hover {
474
- width: 121px;
475
- border-color: #8300bf;
476
- }
477
- div.primary-button .el-button,
478
- div.primary-button .el-button:hover {
479
- background-color: #8300bf;
480
- }
481
- div.secondary-button .el-button,
482
- div.secondary-button .el-button:hover {
483
- background-color: #f9f2fc;
484
- color: #8300bf;
485
- }
486
- div.scrollbar {
487
- overflow-y: scroll;
488
- scrollbar-width: thin;
489
- }
490
- div.scrollbar::-webkit-scrollbar {
491
- width: 8px;
492
- right: -8px;
493
- background-color: #f5f5f5;
494
- }
495
- div.scrollbar::-webkit-scrollbar-thumb {
496
- border-radius: 4px;
497
- box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.06);
498
- background-color: #979797;
499
- }
500
- div.scrollbar::-webkit-scrollbar-track {
501
- border-radius: 10px;
502
- background-color: #f5f5f5;
503
- }
504
- div.simulation-vuer {
505
- height: 100%;
506
- }
507
- p.default {
508
- font-family: Asap, sans-serif;
509
- letter-spacing: 0;
510
- margin: 16px 0;
511
- text-align: start;
512
- }
513
- p.error {
514
- margin-left: 16px;
515
- }
516
- p.input-parameters {
517
- margin-bottom: 8px;
518
- }
519
- p.name,
520
- p.input-parameters {
521
- margin-top: 0;
522
- font-weight: 500 /* Medium */;
523
- }
524
- p.name {
525
- line-height: 20px;
526
- }
527
- p.note {
528
- font-size: 12px;
529
- line-height: 16px;
530
- }
531
- span.error {
532
- font-weight: 500 /* Medium */;
533
- }
534
- </style>
1
+ <template>
2
+ <div class="simulation-vuer" v-loading="showUserMessage" :element-loading-text="userMessage">
3
+ <p v-if="!hasValidSimulationUiInfo && !showUserMessage" class="default error"><span class="error">Error:</span> an unknown or invalid model was provided.</p>
4
+ <div class="main" v-if="hasValidSimulationUiInfo">
5
+ <div class="main-left">
6
+ <p class="default name">{{name}}</p>
7
+ <el-divider></el-divider>
8
+ <p class="default input-parameters">Input parameters</p>
9
+ <div class="input scrollbar">
10
+ <SimulationVuerInput v-for="(input, index) in simulationUiInfo.input"
11
+ ref="simInput"
12
+ :defaultValue="input.defaultValue"
13
+ :key="`input-${index}`"
14
+ :name="input.name"
15
+ :maximumValue="input.maximumValue"
16
+ :minimumValue="input.minimumValue"
17
+ :possibleValues="input.possibleValues"
18
+ :stepValue="input.stepValue"
19
+ />
20
+ </div>
21
+ <div class="primary-button">
22
+ <el-button type="primary" size="small" @click="startSimulation()">Run Simulation</el-button>
23
+ </div>
24
+ <div class="secondary-button" v-if="uuid">
25
+ <el-button size="small" @click="runOnOsparc()">Run on oSPARC</el-button>
26
+ </div>
27
+ <div class="secondary-button">
28
+ <el-button size="small" @click="viewDataset()">View Dataset</el-button>
29
+ </div>
30
+ <p class="default note" v-if="uuid">Additional parameters are available on oSPARC</p>
31
+ </div>
32
+ <div class="main-right" ref="output" v-show="isSimulationValid">
33
+ <PlotVuer v-for="(outputPlot, index) in simulationUiInfo.output.plots"
34
+ :key="`output-${index}`"
35
+ :metadata="plotMetadata(index)"
36
+ :data-source="{data: simulationData[index]}"
37
+ :plotLayout="layout[index]"
38
+ :plotType="'plotly-only'"
39
+ :selectorUi="false"
40
+ />
41
+ </div>
42
+ <div class="main-right" v-show="!isSimulationValid">
43
+ <p class="default error"><span class="error">Error:</span> <span v-html="errorMessage"></span>.</p>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </template>
48
+
49
+ <script>
50
+ import { PlotVuer } from "@abi-software/plotvuer";
51
+ import "@abi-software/plotvuer/dist/style.css";
52
+ import SimulationVuerInput from "./SimulationVuerInput.vue";
53
+ import { ElButton, ElDivider, ElLoading } from "element-plus";
54
+ import { evaluateValue, evaluateSimulationValue, OPENCOR_SOLVER_NAME } from "./common.js";
55
+ import { validJson } from "./json.js";
56
+ import { initialiseUi, finaliseUi } from "./ui.js";
57
+
58
+ /**
59
+ * SimulationVuer
60
+ */
61
+ export default {
62
+ name: "SimulationVuer",
63
+ components: {
64
+ PlotVuer,
65
+ SimulationVuerInput,
66
+ ElButton,
67
+ ElDivider,
68
+ ElLoading,
69
+ },
70
+ props: {
71
+ /**
72
+ * The the URL to the API location.
73
+ */
74
+ apiLocation: {
75
+ required: true,
76
+ type: String,
77
+ },
78
+ /**
79
+ * The ID of the simulation-based dataset.
80
+ */
81
+ id: {
82
+ required: true,
83
+ type: Number,
84
+ },
85
+ },
86
+ data: function() {
87
+ let xmlhttp = new XMLHttpRequest();
88
+ let name = undefined;
89
+ let uuid = undefined;
90
+
91
+ xmlhttp.open("GET", this.apiLocation + "/sim/dataset/" + this.id, false);
92
+ xmlhttp.setRequestHeader("Content-type", "application/json");
93
+ xmlhttp.onreadystatechange = () => {
94
+ if (xmlhttp.readyState === 4) {
95
+ if (xmlhttp.status === 200) {
96
+ let datasetInfo = JSON.parse(xmlhttp.responseText);
97
+
98
+ name = datasetInfo.name;
99
+ uuid = (datasetInfo.study !== undefined)?datasetInfo.study.uuid:undefined;
100
+ }
101
+ }
102
+ };
103
+ xmlhttp.send();
104
+
105
+ return {
106
+ errorMessage: "",
107
+ hasFinalisedUi: false,
108
+ hasValidSimulationUiInfo: false,
109
+ isMounted: false,
110
+ isSimulationValid: true,
111
+ layout: [],
112
+ name: name,
113
+ perfectScollbarOptions: {
114
+ suppressScrollX: true,
115
+ },
116
+ showUserMessage: false,
117
+ simulationData: [],
118
+ simulationDataId: {},
119
+ simulationUiInfo: {},
120
+ userMessage: "",
121
+ ui: null,
122
+ uuid: uuid,
123
+ };
124
+ },
125
+ methods: {
126
+ /**
127
+ * @vuese
128
+ * Generate the metadata associated with the plot which `index` is given.
129
+ * @arg `index`
130
+ */
131
+ plotMetadata(index) {
132
+ return {
133
+ version: "1.1.0",
134
+ type: "plot",
135
+ attrs: {
136
+ style: "timeseries",
137
+ layout: this.layout[index],
138
+ },
139
+ };
140
+ },
141
+ /**
142
+ * @vuese
143
+ * Build the simulation UI using `simulationUiInfo`, a JSON object that describes the contents of the simulation UI.
144
+ * @arg `simulationUiInfo`
145
+ */
146
+ buildSimulationUi(simulationUiInfo) {
147
+ // Keep track of the simulation UI information.
148
+
149
+ this.simulationUiInfo = simulationUiInfo;
150
+
151
+ // Make sure that the simulation UI information is valid.
152
+
153
+ this.hasValidSimulationUiInfo = validJson(this.simulationUiInfo);
154
+
155
+ if (!this.hasValidSimulationUiInfo) {
156
+ return;
157
+ }
158
+
159
+ // Initialise our UI.
160
+
161
+ initialiseUi(this);
162
+
163
+ // Finalise our UI.
164
+ // Note: we try both here and in the mounted() function since we have no
165
+ // idea how long it's going to take to retrieve the simulation UI
166
+ // information.
167
+
168
+ this.$nextTick(() => {
169
+ finaliseUi(this);
170
+
171
+ this.simulationData.forEach((data, index) => {
172
+ this.simulationData[index] = [{
173
+ x: [],
174
+ y: [],
175
+ type: "scatter",
176
+ }];
177
+ });
178
+ });
179
+ },
180
+ /**
181
+ * @vuese
182
+ * Run the simulation-based dataset directly on oSPARC. Not all simulation-based datasets can be run directly on
183
+ * oSPARC, but for those that can the simulation UI shows a `Run on oSPARC` button which, when clicked, calls this
184
+ * method.
185
+ */
186
+ runOnOsparc() {
187
+ window.open(`https://osparc.io/study/${this.uuid}`, "_blank");
188
+ },
189
+ /**
190
+ * @vuese
191
+ * View the simulation-based dataset on the SPARC portal. The simulation UI has a `View Dataset` button which, when
192
+ * clicked, calls this method.
193
+ */
194
+ viewDataset() {
195
+ window.open(`https://sparc.science/datasets/${this.id}?type=dataset`, "_blank");
196
+ },
197
+ /**
198
+ * @vuese
199
+ * Finish creating the `request` that is going to be used by `startSimulation` to ask oSPARC to start the
200
+ * simulation. `request` is a JSON object that initially contains the solver to be used by oSPARC and to which
201
+ * additional is added.
202
+ * @arg `request`
203
+ */
204
+ retrieveRequest(request) {
205
+ // Settings specific to OpenCOR/oSPARC.
206
+
207
+ let isOpencorSimulation = request.solver.name === OPENCOR_SOLVER_NAME;
208
+
209
+ if (isOpencorSimulation) {
210
+ request.opencor = {
211
+ model_url: this.simulationUiInfo.simulation.opencor.resource,
212
+ json_config: {},
213
+ };
214
+ } else {
215
+ request.osparc = {};
216
+ }
217
+
218
+ // Specify the ending point and point interval, if we have some.
219
+
220
+ if ( isOpencorSimulation
221
+ && (this.simulationUiInfo.simulation.opencor.endingPoint !== undefined)
222
+ && (this.simulationUiInfo.simulation.opencor.pointInterval !== undefined)) {
223
+ request.opencor.json_config.simulation = {
224
+ "Ending point": this.simulationUiInfo.simulation.opencor.endingPoint,
225
+ "Point interval": this.simulationUiInfo.simulation.opencor.pointInterval,
226
+ };
227
+ }
228
+
229
+ // Specify the parameters, if any.
230
+
231
+ if (this.simulationUiInfo.parameters !== undefined) {
232
+ let parameters = {};
233
+
234
+ this.simulationUiInfo.parameters.forEach((parameter) => {
235
+ parameters[parameter.name] = evaluateValue(this, parameter.value);
236
+ });
237
+
238
+ if (isOpencorSimulation) {
239
+ request.opencor.json_config.parameters = parameters;
240
+ } else {
241
+ request.osparc.job_inputs = parameters;
242
+ }
243
+ }
244
+
245
+ // Specify what we want to retrieve, if anything.
246
+
247
+ if (isOpencorSimulation && (this.simulationUiInfo.output.data !== undefined)) {
248
+ let index = -1;
249
+
250
+ request.opencor.json_config.output = [];
251
+
252
+ this.simulationUiInfo.output.data.forEach((outputData) => {
253
+ request.opencor.json_config.output[++index] = outputData.name;
254
+ });
255
+ }
256
+
257
+ return request;
258
+ },
259
+ /**
260
+ * @vuese
261
+ * Process the simulation results retrieved by `checkSimulation`. The simulation results are post-processed, if
262
+ * needed, and then readied for use by `PlotVuer`.
263
+ * @arg `results`
264
+ */
265
+ processSimulationResults(results) {
266
+ // Convert, if needed, the results to a JSON format that is compatible
267
+ // with our OpenCOR results.
268
+
269
+ if (typeof(results) === "string") {
270
+ const SPACES = /[ \t]+/g;
271
+
272
+ let lines = results.trim().split("\n");
273
+ let iMax = lines[0].trim().split(SPACES).length;
274
+
275
+ results = {};
276
+
277
+ for (let i = 0; i < iMax; ++i) {
278
+ results[i] = [];
279
+ }
280
+
281
+ let i = -1;
282
+
283
+ lines.forEach((line) => {
284
+ ++i;
285
+
286
+ let j = -1;
287
+ let values = line.trim().split(SPACES);
288
+
289
+ values.forEach((value) => {
290
+ results[++j][i] = Number(value);
291
+ });
292
+ });
293
+ }
294
+
295
+ // Get the results ready for plotting.
296
+
297
+ let index = -1;
298
+ let iMax = results[this.simulationDataId[Object.keys(this.simulationDataId)[0]]].length;
299
+
300
+ this.simulationUiInfo.output.plots.forEach((outputPlot) => {
301
+ let xValue = [];
302
+ let yValue = [];
303
+
304
+ for (let i = 0; i < iMax; ++i) {
305
+ xValue[i] = evaluateSimulationValue(this, results, outputPlot.xValue, i);
306
+ yValue[i] = evaluateSimulationValue(this, results, outputPlot.yValue, i);
307
+ }
308
+
309
+ this.simulationData[++index] = [
310
+ {
311
+ x: xValue,
312
+ y: yValue,
313
+ type: "scatter",
314
+ },
315
+ ];
316
+ });
317
+ },
318
+ /**
319
+ * @vuese
320
+ * Check the progress of the simulation using the given `data`, a JSON object that contains the simulation job ID,
321
+ * as well as the solver name and version. This method is first called by `startSimulation` and then every second by
322
+ * itself until the simulation is finished.
323
+ * @arg `data`
324
+ */
325
+ checkSimulation(data) {
326
+ // Check the simulation.
327
+
328
+ let xmlhttp = new XMLHttpRequest();
329
+
330
+ xmlhttp.open("POST", this.apiLocation + "/check_simulation", true);
331
+ xmlhttp.setRequestHeader("Content-type", "application/json");
332
+ xmlhttp.onreadystatechange = () => {
333
+ if (xmlhttp.readyState === 4) {
334
+ if (xmlhttp.status === 200) {
335
+ let response = JSON.parse(xmlhttp.responseText);
336
+
337
+ this.isSimulationValid = response.status === "ok";
338
+
339
+ if (this.isSimulationValid) {
340
+ if (response.results !== undefined) {
341
+ // The simulation is finished, so process its results.
342
+
343
+ this.showUserMessage = false;
344
+
345
+ this.processSimulationResults(response.results);
346
+ } else {
347
+ // The simulation is not yet finished, so check again in a
348
+ // second.
349
+
350
+ let that = this;
351
+
352
+ setTimeout(function() {
353
+ that.checkSimulation(data);
354
+ }, 1000);
355
+ }
356
+ } else {
357
+ this.showUserMessage = false;
358
+ this.errorMessage = response.description;
359
+ }
360
+ } else {
361
+ this.isSimulationValid = false;
362
+ this.showUserMessage = false;
363
+ this.errorMessage = xmlhttp.statusText.toLowerCase() + " (<a href='https://httpstatuses.com/" + xmlhttp.status + "/' target='_blank'>" + xmlhttp.status + "</a>)";
364
+ }
365
+ }
366
+ };
367
+ xmlhttp.send(JSON.stringify(data));
368
+ },
369
+ /**
370
+ * @vuese
371
+ * Start the simulation associated with the simulation-based dataset. The simulation UI has a `Run Simulation`
372
+ * button which, when clicked, calls this method.
373
+ */
374
+ startSimulation() {
375
+ // Retrieve the solver to be used for the simulation.
376
+
377
+ let solver = undefined;
378
+
379
+ this.simulationUiInfo.simulation.solvers.forEach((crtSolver) => {
380
+ if ((crtSolver.if === undefined) || evaluateValue(this, crtSolver.if)) {
381
+ solver = crtSolver;
382
+ }
383
+ });
384
+
385
+ if (solver === undefined) {
386
+ console.warn("SIMULATION: no solver name and/or solver version specified.");
387
+
388
+ return;
389
+ }
390
+
391
+ // Start the simulation (after resetting our previous simulation data, in
392
+ // case there were sonme).
393
+ // Note: we use this.$nextTick() so that the user message is shown before
394
+ // we get to post our HTTP request.
395
+
396
+ this.userMessage = "Loading simulation results...";
397
+ this.showUserMessage = true;
398
+
399
+ this.$nextTick(() => {
400
+ this.simulationData = [];
401
+
402
+ let xmlhttp = new XMLHttpRequest();
403
+
404
+ xmlhttp.open("POST", this.apiLocation + "/start_simulation", true);
405
+ xmlhttp.setRequestHeader("Content-type", "application/json");
406
+ xmlhttp.onreadystatechange = () => {
407
+ if (xmlhttp.readyState === 4) {
408
+ if (xmlhttp.status === 200) {
409
+ let response = JSON.parse(xmlhttp.responseText);
410
+
411
+ this.isSimulationValid = response.status === "ok";
412
+
413
+ if (this.isSimulationValid) {
414
+ this.checkSimulation(response.data);
415
+ } else {
416
+ this.showUserMessage = false;
417
+ this.errorMessage = response.description;
418
+ }
419
+ } else {
420
+ this.isSimulationValid = false;
421
+ this.showUserMessage = false;
422
+ this.errorMessage = xmlhttp.statusText.toLowerCase() + " (<a href='https://httpstatuses.com/" + xmlhttp.status + "/' target='_blank'>" + xmlhttp.status + "</a>)";
423
+ }
424
+ }
425
+ };
426
+ xmlhttp.send(JSON.stringify(this.retrieveRequest({
427
+ solver: solver
428
+ })));
429
+ });
430
+ },
431
+ },
432
+ created: function() {
433
+ // Try to retrieve the UI information, but only if we have a name.
434
+
435
+ if (this.name !== undefined) {
436
+ this.userMessage = "Retrieving UI information...";
437
+ this.showUserMessage = true;
438
+
439
+ // Retrieve and build the simulation UI.
440
+ // Note: we use this.$nextTick() so that the user message is shown before
441
+ // we get to post our HTTP request.
442
+
443
+ this.$nextTick(() => {
444
+ let xmlhttp = new XMLHttpRequest();
445
+
446
+ xmlhttp.open("GET", this.apiLocation + "/simulation_ui_file/" + this.id, true);
447
+ xmlhttp.setRequestHeader("Content-type", "application/json");
448
+ xmlhttp.onreadystatechange = () => {
449
+ if (xmlhttp.readyState === 4) {
450
+ this.showUserMessage = false;
451
+
452
+ if (xmlhttp.status === 200) {
453
+ this.buildSimulationUi(JSON.parse(xmlhttp.responseText));
454
+ }
455
+ }
456
+ };
457
+ xmlhttp.send();
458
+ });
459
+ }
460
+ },
461
+ mounted: function() {
462
+ // Finalise our UI.
463
+ // Note: we try both here and in the created() function since we have no
464
+ // idea how long it's going to take to retrieve the simulation UI
465
+ // information.
466
+
467
+ this.isMounted = true;
468
+
469
+ finaliseUi(this);
470
+ },
471
+ };
472
+ </script>
473
+
474
+ <!-- Add "scoped" attribute to limit CSS to this component only -->
475
+ <style scoped lang="scss">
476
+
477
+ .simulation-vuer {
478
+ --el-color-primary: #8300BF;
479
+ --el-color-primary-light-7: #DAB3EC;
480
+ --el-color-primary-light-8: #E6CCF2;
481
+ --el-color-primary-light-9: #F3E6F9;
482
+ }
483
+
484
+ :deep( .el-button:hover) {
485
+ box-shadow: -3px 2px 4px #00000040;
486
+ }
487
+ :deep( .el-divider) {
488
+ margin: -8px 0 8px 0 !important;
489
+ width: 210px;
490
+ }
491
+ :deep( .el-loading-spinner) {
492
+ .path {
493
+ stroke: #8300BF;
494
+ }
495
+ i, .el-loading-text {
496
+ color: #8300BF;
497
+ }
498
+ }
499
+ div.input {
500
+ border: 1px solid #dcdfe6;
501
+ padding: 4px;
502
+ height: 300px;
503
+ }
504
+ div.main {
505
+ display: grid;
506
+ --mainLeftWidth: 243px;
507
+ grid-template-columns: var(--mainLeftWidth) calc(100% - var(--mainLeftWidth));
508
+ height: 100%;
509
+ }
510
+ div.main-left {
511
+ border-right: 1px solid #dcdfe6;
512
+ padding: 12px 20px 12px 12px;
513
+ height: 100%;
514
+ overflow: auto;
515
+ }
516
+ div.main-right.x1 {
517
+ height: 100%;
518
+ }
519
+ div.main-right.x2 {
520
+ height: 50%;
521
+ }
522
+ div.main-right.x3 {
523
+ height: 33.333%;
524
+ }
525
+ div.main-right.x4 {
526
+ height: 25%;
527
+ }
528
+ div.main-right.x5 {
529
+ height: 20%;
530
+ }
531
+ div.main-right.x6 {
532
+ height: 16.667%;
533
+ }
534
+ div.main-right.x7 {
535
+ height: 14.286%;
536
+ }
537
+ div.main-right.x8 {
538
+ height: 12.5%;
539
+ }
540
+ div.main-right.x9 {
541
+ height: 11.111%;
542
+ }
543
+ :deep( div.main-right div.controls) {
544
+ height: 0;
545
+ }
546
+ div.primary-button,
547
+ div.secondary-button {
548
+ display: flex;
549
+ justify-content: flex-end;
550
+ width: 210px;
551
+ }
552
+ div.primary-button {
553
+ margin-top: 14px;
554
+ }
555
+ div.secondary-button {
556
+ margin-top: 8px;
557
+ }
558
+ div.primary-button .el-button,
559
+ div.secondary-button .el-button,
560
+ div.primary-button .el-button:hover,
561
+ div.secondary-button .el-button:hover {
562
+ width: 121px;
563
+ border-color: #8300bf;
564
+ }
565
+ div.primary-button .el-button,
566
+ div.primary-button .el-button:hover {
567
+ background-color: #8300bf;
568
+ }
569
+ div.secondary-button .el-button,
570
+ div.secondary-button .el-button:hover {
571
+ background-color: #f9f2fc;
572
+ color: #8300bf;
573
+ }
574
+ div.scrollbar {
575
+ overflow-y: scroll;
576
+ scrollbar-width: thin;
577
+ }
578
+ div.scrollbar::-webkit-scrollbar {
579
+ width: 8px;
580
+ right: -8px;
581
+ background-color: #f5f5f5;
582
+ }
583
+ div.scrollbar::-webkit-scrollbar-thumb {
584
+ border-radius: 4px;
585
+ box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.06);
586
+ background-color: #979797;
587
+ }
588
+ div.scrollbar::-webkit-scrollbar-track {
589
+ border-radius: 10px;
590
+ background-color: #f5f5f5;
591
+ }
592
+ div.simulation-vuer {
593
+ height: 100%;
594
+ }
595
+ p.default {
596
+ font-family: Asap, sans-serif;
597
+ letter-spacing: 0;
598
+ margin: 16px 0;
599
+ text-align: start;
600
+ }
601
+ p.error {
602
+ margin-left: 16px;
603
+ }
604
+ p.input-parameters {
605
+ margin-bottom: 8px;
606
+ }
607
+ p.name,
608
+ p.input-parameters {
609
+ margin-top: 0;
610
+ font-weight: 500 /* Medium */;
611
+ }
612
+ p.name {
613
+ line-height: 20px;
614
+ }
615
+ p.note {
616
+ font-size: 12px;
617
+ line-height: 16px;
618
+ }
619
+ span.error {
620
+ font-weight: 500 /* Medium */;
621
+ }
622
+ </style>