@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.
- package/LICENSE +201 -201
- package/README.md +93 -74
- package/dist/index.html +17 -17
- package/dist/simulationvuer.js +3648 -3634
- package/dist/simulationvuer.umd.cjs +13 -13
- package/dist/style.css +1 -1
- package/package.json +92 -81
- package/public/index.html +17 -17
- package/src/App.vue +101 -101
- package/src/assets/_variables.scss +43 -43
- package/src/assets/styles.scss +6 -7
- package/src/components/SimulationVuer.vue +622 -534
- package/src/components/SimulationVuerInput.vue +146 -140
- package/src/components/common.js +31 -31
- package/src/components/index.js +7 -7
- package/src/components/json.js +522 -522
- package/src/components/ui.js +85 -75
- package/src/main.js +19 -20
- package/vite.config.js +60 -73
- package/vuese-generator.js +66 -0
- package/package-lock.json +0 -6692
|
@@ -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"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
</div>
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
</
|
|
32
|
-
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
let
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
}
|
|
504
|
-
div.
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
}
|
|
534
|
-
|
|
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>
|