linny-r 2.0.11 → 2.1.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/package.json +1 -1
- package/static/images/update.png +0 -0
- package/static/index.html +34 -3
- package/static/linny-r.css +42 -3
- package/static/scripts/linny-r-ctrl.js +24 -13
- package/static/scripts/linny-r-gui-actor-manager.js +6 -6
- package/static/scripts/linny-r-gui-chart-manager.js +1 -7
- package/static/scripts/linny-r-gui-controller.js +18 -17
- package/static/scripts/linny-r-gui-dataset-manager.js +10 -17
- package/static/scripts/linny-r-gui-experiment-manager.js +3 -3
- package/static/scripts/linny-r-gui-finder.js +10 -1
- package/static/scripts/linny-r-gui-paper.js +10 -1
- package/static/scripts/linny-r-gui-repository-browser.js +304 -17
- package/static/scripts/linny-r-gui-undo-redo.js +41 -52
- package/static/scripts/linny-r-model.js +341 -265
- package/static/scripts/linny-r-utils.js +9 -11
- package/static/scripts/linny-r-vm.js +118 -16
package/package.json
CHANGED
Binary file
|
package/static/index.html
CHANGED
@@ -914,6 +914,8 @@ NOTE: Products directly linked to such processes should have a proportional unit
|
|
914
914
|
title="Include module in current model">
|
915
915
|
<img id="repo-load-btn" class="btn disab" src="images/open.png"
|
916
916
|
title="Load model from repository">
|
917
|
+
<img id="repo-update-btn" class="btn disab" src="images/update.png"
|
918
|
+
title="Update clusters that have been included in the model">
|
917
919
|
<img id="repo-store-btn" class="btn enab" src="images/store.png"
|
918
920
|
title="Store model as module in repository">
|
919
921
|
<img id="repo-black-box-btn" class="btn enab" src="images/black-box.png"
|
@@ -1061,9 +1063,6 @@ NOTE: Products directly linked to such processes should have a proportional unit
|
|
1061
1063
|
<img class="cancel-btn" src="images/cancel.png">
|
1062
1064
|
<img class="ok-btn" src="images/ok.png">
|
1063
1065
|
</div>
|
1064
|
-
<div style="margin:2px; background-color: Yellow">
|
1065
|
-
<strong>NOTE:</strong> <em>This action cannot be undone!</em>
|
1066
|
-
</div>
|
1067
1066
|
<div style="margin:2px">
|
1068
1067
|
<label>Cluster:</label>
|
1069
1068
|
<input id="include-prefix" type="text"
|
@@ -1082,6 +1081,38 @@ NOTE: Products directly linked to such processes should have a proportional unit
|
|
1082
1081
|
</div>
|
1083
1082
|
</div>
|
1084
1083
|
|
1084
|
+
<!-- the UPDATE dialog prompts for a module name -->
|
1085
|
+
<div id="update-modal" class="modal">
|
1086
|
+
<div id="update-dlg" class="inp-dlg" style="min-width: 320px">
|
1087
|
+
<div class="dlg-title">
|
1088
|
+
Update clusters previously included from a module
|
1089
|
+
<img class="cancel-btn" src="images/cancel.png">
|
1090
|
+
<img class="ok-btn" src="images/ok.png">
|
1091
|
+
</div>
|
1092
|
+
<div style="padding: 2px">
|
1093
|
+
<div>
|
1094
|
+
Update using module: <span id="update-name"></span>
|
1095
|
+
</div>
|
1096
|
+
<div>
|
1097
|
+
<label>Clusters based on:</label>
|
1098
|
+
<select id="update-module">
|
1099
|
+
</select>
|
1100
|
+
<div id="update-count" class="update-cc"></div>
|
1101
|
+
</div>
|
1102
|
+
<div id="update-issues">
|
1103
|
+
<div id="update-issues-header"></div>
|
1104
|
+
<div id="update-issues-area"></div>
|
1105
|
+
</div>
|
1106
|
+
<div id="update-remove">
|
1107
|
+
<div id="update-remove-header">
|
1108
|
+
<span id="update-remove-count"></span> will be removed
|
1109
|
+
</div>
|
1110
|
+
<div id="update-remove-area"></div>
|
1111
|
+
</div>
|
1112
|
+
</div>
|
1113
|
+
</div>
|
1114
|
+
</div>
|
1115
|
+
|
1085
1116
|
<!-- the CONFIRM LOAD modal asks to confirm to load model from repository -->
|
1086
1117
|
<div id="confirm-load-from-repo-modal" class="modal">
|
1087
1118
|
<div id="confirm-load-from-repo-dlg" class="inp-dlg">
|
package/static/linny-r.css
CHANGED
@@ -607,6 +607,11 @@ span.node-details {
|
|
607
607
|
margin-left: 15px;
|
608
608
|
}
|
609
609
|
|
610
|
+
span.mod-name {
|
611
|
+
font-style: normal;
|
612
|
+
color: #403080;
|
613
|
+
}
|
614
|
+
|
610
615
|
#issue-panel {
|
611
616
|
display: none;
|
612
617
|
background-color: Yellow;
|
@@ -707,6 +712,11 @@ img.inline-cancel-btn:hover {
|
|
707
712
|
filter: brightness(120%);
|
708
713
|
}
|
709
714
|
|
715
|
+
img.ok-btn.disab {
|
716
|
+
filter: saturate(0%) brightness(150%) contrast(70%);
|
717
|
+
pointer-events: none;
|
718
|
+
}
|
719
|
+
|
710
720
|
#actor-group,
|
711
721
|
#constraint-group,
|
712
722
|
#cluster-group,
|
@@ -4152,8 +4162,9 @@ td.sa-not-run {
|
|
4152
4162
|
|
4153
4163
|
#xp-ignore-count {
|
4154
4164
|
position: absolute;
|
4155
|
-
right:
|
4165
|
+
right: 2px;
|
4156
4166
|
bottom: 3px;
|
4167
|
+
width: 18px;
|
4157
4168
|
height: 16px;
|
4158
4169
|
font-size: 9px;
|
4159
4170
|
color: #806070;
|
@@ -4882,6 +4893,7 @@ span.sd-clear {
|
|
4882
4893
|
}
|
4883
4894
|
|
4884
4895
|
#paste-dlg,
|
4896
|
+
#update-dlg,
|
4885
4897
|
#include-dlg {
|
4886
4898
|
width: 320px;
|
4887
4899
|
height: min-content;
|
@@ -4907,9 +4919,29 @@ span.sd-clear {
|
|
4907
4919
|
margin-left: 9px;
|
4908
4920
|
}
|
4909
4921
|
|
4922
|
+
#update-name {
|
4923
|
+
font-family: monospace;
|
4924
|
+
font-size: 14px;
|
4925
|
+
}
|
4926
|
+
|
4927
|
+
div.update-cc {
|
4928
|
+
display: inline-block;
|
4929
|
+
margin-left: 6px;
|
4930
|
+
font-style: italic;
|
4931
|
+
color: Gray;
|
4932
|
+
}
|
4933
|
+
|
4934
|
+
#update-remove-header,
|
4935
|
+
#update-issues-header {
|
4936
|
+
font-weight: bold;
|
4937
|
+
margin: 4px 2px 2px 2px;
|
4938
|
+
}
|
4939
|
+
|
4910
4940
|
#paste-scroll-area,
|
4911
|
-
#include-scroll-area
|
4912
|
-
|
4941
|
+
#include-scroll-area,
|
4942
|
+
#update-remove-area,
|
4943
|
+
#update-issues-area {
|
4944
|
+
margin: 0 2px;
|
4913
4945
|
height: min-content;
|
4914
4946
|
max-height: 350px !important;
|
4915
4947
|
width: calc(100% - 4px);
|
@@ -4918,6 +4950,13 @@ span.sd-clear {
|
|
4918
4950
|
overflow-y: auto;
|
4919
4951
|
}
|
4920
4952
|
|
4953
|
+
#update-remove-area,
|
4954
|
+
#update-issues-area {
|
4955
|
+
max-height: 150px !important;
|
4956
|
+
background-color: white;
|
4957
|
+
border: 1px solid Silver;
|
4958
|
+
}
|
4959
|
+
|
4921
4960
|
div.paste-tactic {
|
4922
4961
|
display: inline-block;
|
4923
4962
|
vertical-align: top;
|
@@ -311,6 +311,12 @@ class Controller {
|
|
311
311
|
(name.startsWith(this.BLACK_BOX) || name[0].match(/[\w]/));
|
312
312
|
}
|
313
313
|
|
314
|
+
realActorName(name) {
|
315
|
+
// Return `name` unless it is '(no actor)'; then return empty string.
|
316
|
+
if(name === this.NO_ACTOR) return '';
|
317
|
+
return name;
|
318
|
+
}
|
319
|
+
|
314
320
|
prefixesAndName(name, key=false) {
|
315
321
|
// Returns name split exclusively at '[non-space]: [non-space]'
|
316
322
|
let sep = this.PREFIXER,
|
@@ -1266,30 +1272,30 @@ class ExperimentManager {
|
|
1266
1272
|
}
|
1267
1273
|
|
1268
1274
|
startExperiment(n=-1) {
|
1269
|
-
// Recompile expressions, as these may have been changed by the modeler
|
1275
|
+
// Recompile expressions, as these may have been changed by the modeler.
|
1270
1276
|
MODEL.compileExpressions();
|
1271
|
-
// Start sequence of solving model parametrizations
|
1277
|
+
// Start sequence of solving model parametrizations.
|
1272
1278
|
const x = this.selected_experiment;
|
1273
1279
|
if(x) {
|
1274
|
-
// Store original model settings
|
1280
|
+
// Store original model settings.
|
1275
1281
|
x.original_model_settings = MODEL.settingsString;
|
1276
1282
|
x.original_round_sequence = MODEL.round_sequence;
|
1277
|
-
// NOTE:
|
1283
|
+
// NOTE: Switch off run chart display.
|
1278
1284
|
CHART_MANAGER.setRunsChart(false);
|
1279
1285
|
// When Chart manager is showing, close it and notify modeler that charts
|
1280
|
-
// should not be viewed during experiments
|
1286
|
+
// should not be viewed during experiments.
|
1281
1287
|
if(CHART_MANAGER.visible) {
|
1282
1288
|
UI.buttons.chart.dispatchEvent(new Event('click'));
|
1283
1289
|
UI.notify(UI.NOTICE.NO_CHARTS);
|
1284
1290
|
}
|
1285
|
-
// Change the buttons -- will return TRUE if experiment was paused
|
1291
|
+
// Change the buttons -- will return TRUE if experiment was paused.
|
1286
1292
|
const paused = this.resumeButtons();
|
1287
1293
|
if(x.completed && n >= 0) {
|
1288
1294
|
x.single_run = n;
|
1289
1295
|
x.active_combination_index = n;
|
1290
1296
|
MODEL.running_experiment = x;
|
1291
1297
|
} else if(!paused) {
|
1292
|
-
// Clear previous run results (if any) unless resuming
|
1298
|
+
// Clear previous run results (if any) unless resuming.
|
1293
1299
|
x.clearRuns();
|
1294
1300
|
x.inferVariables();
|
1295
1301
|
x.time_started = new Date().getTime();
|
@@ -1369,11 +1375,14 @@ class ExperimentManager {
|
|
1369
1375
|
// Perform post-processing after run results have been added.
|
1370
1376
|
const x = MODEL.running_experiment;
|
1371
1377
|
if(!x) return;
|
1372
|
-
const
|
1378
|
+
const
|
1379
|
+
aci = x.active_combination_index,
|
1380
|
+
single = (aci == x.single_run);
|
1373
1381
|
// Always add solver messages.
|
1374
1382
|
x.runs[aci].addMessages();
|
1375
1383
|
const n = x.combinations.length;
|
1376
|
-
if(!VM.halted && aci < n - 1 &&
|
1384
|
+
if(!VM.halted && aci < n - 1 && !single) {
|
1385
|
+
// Continue with the next run.
|
1377
1386
|
if(this.must_pause) {
|
1378
1387
|
this.pausedButtons(aci);
|
1379
1388
|
this.must_pause = false;
|
@@ -1384,12 +1393,13 @@ class ExperimentManager {
|
|
1384
1393
|
// NOTE: When executing a remote command, wait for 1 second to
|
1385
1394
|
// allow enough time for report writing.
|
1386
1395
|
if(RECEIVER.active && RECEIVER.experiment) {
|
1387
|
-
UI.setMessage('Reporting run #' +
|
1396
|
+
UI.setMessage('Reporting run #' + aci);
|
1388
1397
|
delay = 1000;
|
1389
1398
|
}
|
1390
1399
|
setTimeout(() => EXPERIMENT_MANAGER.runModel(), delay);
|
1391
1400
|
}
|
1392
1401
|
} else {
|
1402
|
+
// Stop the run sequence.
|
1393
1403
|
x.time_stopped = new Date().getTime();
|
1394
1404
|
if(x.single_run >= 0) {
|
1395
1405
|
x.single_run = -1;
|
@@ -1409,18 +1419,19 @@ class ExperimentManager {
|
|
1409
1419
|
RECEIVER.experiment = '';
|
1410
1420
|
RECEIVER.callBack();
|
1411
1421
|
}
|
1412
|
-
// Restore original model settings
|
1422
|
+
// Restore original model settings.
|
1413
1423
|
MODEL.running_experiment = null;
|
1414
1424
|
MODEL.parseSettings(x.original_model_settings);
|
1415
1425
|
MODEL.round_sequence = x.original_round_sequence;
|
1416
1426
|
// Reset the Virtual Machine so t=0 at the status line,
|
1417
1427
|
// and ALL expressions are reset as well.
|
1418
|
-
VM.reset();
|
1428
|
+
if(!single) VM.reset();
|
1419
1429
|
this.readyButtons();
|
1420
1430
|
}
|
1421
1431
|
this.drawTable();
|
1422
1432
|
// Reset the model, as results of last run will be showing still.
|
1423
|
-
|
1433
|
+
// NOTE: Do NOT do this after a single run.
|
1434
|
+
if(!single) UI.resetModel();
|
1424
1435
|
CHART_MANAGER.resetChartVectors();
|
1425
1436
|
// NOTE: Clear chart only when done; charts do not update when an
|
1426
1437
|
// experiment is running.
|
@@ -224,10 +224,10 @@ class ActorManager {
|
|
224
224
|
}
|
225
225
|
|
226
226
|
showEditActorDialog(name, expr) {
|
227
|
-
// Display modal for editing properties of one actor
|
227
|
+
// Display modal for editing properties of one actor.
|
228
228
|
this.actor_span.innerHTML = name;
|
229
229
|
this.actor_name.value = name;
|
230
|
-
// Do not allow modification of the name '(no actor)'
|
230
|
+
// Do not allow modification of the name '(no actor)'.
|
231
231
|
if(name === UI.NO_ACTOR) {
|
232
232
|
this.actor_name.disabled = true;
|
233
233
|
this.actor_io.style.display = 'none';
|
@@ -242,22 +242,22 @@ class ActorManager {
|
|
242
242
|
|
243
243
|
modifyActorEntry() {
|
244
244
|
// This method is called when the modeler submits the "actor properties"
|
245
|
-
// dialog
|
245
|
+
// dialog.
|
246
246
|
let n = this.actor_span.innerHTML,
|
247
247
|
nn = UI.NO_ACTOR,
|
248
248
|
x = this.actor_weight.value.trim(),
|
249
249
|
xp = new ExpressionParser(x);
|
250
250
|
if(n !== UI.NO_ACTOR) {
|
251
251
|
nn = this.actor_name.value.trim();
|
252
|
-
// NOTE:
|
253
|
-
// prefixed entities
|
252
|
+
// NOTE: Prohibit colons in actor names to avoid confusion with
|
253
|
+
// prefixed entities.
|
254
254
|
if(!UI.validName(nn) || nn.indexOf(':') >= 0) {
|
255
255
|
UI.warn(UI.WARNING.INVALID_ACTOR_NAME);
|
256
256
|
return false;
|
257
257
|
}
|
258
258
|
}
|
259
259
|
if(xp.error) {
|
260
|
-
// NOTE:
|
260
|
+
// NOTE: Do not pass the actor, as its name is being edited as well.
|
261
261
|
UI.warningInvalidWeightExpression(null, xp.error);
|
262
262
|
return false;
|
263
263
|
}
|
@@ -637,13 +637,7 @@ class GUIChartManager extends ChartManager {
|
|
637
637
|
deleteChart() {
|
638
638
|
// Delete the shown chart (if any).
|
639
639
|
if(this.chart_index >= 0) {
|
640
|
-
|
641
|
-
if(MODEL.charts[this.chart_index].title === this.new_chart_title) {
|
642
|
-
MODEL.charts[this.chart_index].reset();
|
643
|
-
} else {
|
644
|
-
MODEL.charts.splice(this.chart_index, 1);
|
645
|
-
this.chart_index = -1;
|
646
|
-
}
|
640
|
+
MODEL.deleteChart(this.chart_index);
|
647
641
|
// Also update the experiment viewer, because this chart may be
|
648
642
|
// one of the output charts of the selected experiment.
|
649
643
|
UI.updateControllerDialogs('CFX');
|
@@ -1710,14 +1710,14 @@ class GUIController extends Controller {
|
|
1710
1710
|
//
|
1711
1711
|
|
1712
1712
|
draggableDialog(d) {
|
1713
|
-
// Make dialog draggable
|
1713
|
+
// Make dialog draggable.
|
1714
1714
|
const
|
1715
1715
|
dlg = document.getElementById(d + '-dlg'),
|
1716
1716
|
hdr = document.getElementById(d + '-hdr');
|
1717
1717
|
let cx = 0,
|
1718
1718
|
cy = 0;
|
1719
1719
|
if(dlg && hdr) {
|
1720
|
-
// NOTE:
|
1720
|
+
// NOTE: Dialogs are draggable only by their header.
|
1721
1721
|
hdr.onmousedown = dialogHeaderMouseDown;
|
1722
1722
|
dlg.onmousedown = dialogMouseDown;
|
1723
1723
|
return dlg;
|
@@ -1728,13 +1728,13 @@ class GUIController extends Controller {
|
|
1728
1728
|
|
1729
1729
|
function dialogMouseDown(e) {
|
1730
1730
|
e = e || window.event;
|
1731
|
-
// NOTE:
|
1732
|
-
// Find the dialog element
|
1731
|
+
// NOTE: No `preventDefault`, as this disables selector elements.
|
1732
|
+
// Find the dialog element.
|
1733
1733
|
let de = e.target;
|
1734
1734
|
while(de && !de.id.endsWith('-dlg')) { de = de.parentElement; }
|
1735
|
-
//
|
1735
|
+
// Move the dialog (`this`) to the top of the order.
|
1736
1736
|
const doi = UI.dr_dialog_order.indexOf(de);
|
1737
|
-
// NOTE:
|
1737
|
+
// NOTE: Do not reorder when already at end of list (= at top).
|
1738
1738
|
if(doi >= 0 && doi !== UI.dr_dialog_order.length - 1) {
|
1739
1739
|
UI.dr_dialog_order.splice(doi, 1);
|
1740
1740
|
UI.dr_dialog_order.push(de);
|
@@ -1745,12 +1745,12 @@ class GUIController extends Controller {
|
|
1745
1745
|
function dialogHeaderMouseDown(e) {
|
1746
1746
|
e = e || window.event;
|
1747
1747
|
e.preventDefault();
|
1748
|
-
// Find the dialog element
|
1748
|
+
// Find the dialog element.
|
1749
1749
|
let de = e.target;
|
1750
1750
|
while(de && !de.id.endsWith('-dlg')) { de = de.parentElement; }
|
1751
|
-
// Record the affected dialog
|
1751
|
+
// Record the affected dialog.
|
1752
1752
|
UI.dr_dialog = de;
|
1753
|
-
// Get the mouse cursor position at startup
|
1753
|
+
// Get the mouse cursor position at startup.
|
1754
1754
|
cx = e.clientX;
|
1755
1755
|
cy = e.clientY;
|
1756
1756
|
document.onmouseup = stopDragDialog;
|
@@ -2258,6 +2258,8 @@ class GUIController extends Controller {
|
|
2258
2258
|
this.net_move_y = 0;
|
2259
2259
|
// Get the paper coordinates indicated by the cursor.
|
2260
2260
|
const cp = this.paper.cursorPosition(e.pageX, e.pageY);
|
2261
|
+
this.mouse_x = cp[0];
|
2262
|
+
this.mouse_y = cp[1];
|
2261
2263
|
this.mouse_down_x = cp[0];
|
2262
2264
|
this.mouse_down_y = cp[1];
|
2263
2265
|
// De-activate "stay active" buttons if dysfunctional, or if SHIFT,
|
@@ -2977,7 +2979,7 @@ class GUIController extends Controller {
|
|
2977
2979
|
//
|
2978
2980
|
|
2979
2981
|
validNames(nn, an='') {
|
2980
|
-
// Check whether names meet conventions
|
2982
|
+
// Check whether names meet conventions. If not, warn user.
|
2981
2983
|
if(!UI.validName(nn) || nn.indexOf(UI.BLACK_BOX) >= 0) {
|
2982
2984
|
this.warningInvalidName(nn);
|
2983
2985
|
return false;
|
@@ -3146,10 +3148,10 @@ class GUIController extends Controller {
|
|
3146
3148
|
UI.info_line.classList.remove(...UI.info_line.classList);
|
3147
3149
|
}
|
3148
3150
|
|
3149
|
-
setMessage(msg, type=null) {
|
3151
|
+
setMessage(msg, type=null, cause=null) {
|
3150
3152
|
// Display `msg` on infoline unless no type (= plain text) and some
|
3151
3153
|
// info, warning or error message is already displayed.
|
3152
|
-
super.setMessage(msg, type);
|
3154
|
+
super.setMessage(msg, type, cause);
|
3153
3155
|
const types = ['notification', 'warning', 'error'];
|
3154
3156
|
let d = new Date(),
|
3155
3157
|
t = d.getTime(),
|
@@ -3664,7 +3666,7 @@ class GUIController extends Controller {
|
|
3664
3666
|
// proceed to paste.
|
3665
3667
|
const
|
3666
3668
|
md = this.paste_modal,
|
3667
|
-
mapping = Object.assign(md.mapping
|
3669
|
+
mapping = Object.assign({}, md.mapping),
|
3668
3670
|
tc = (mapping.top_clusters ?
|
3669
3671
|
Object.keys(mapping.top_clusters).sort(ciCompare) : []),
|
3670
3672
|
ft = (mapping.from_to ?
|
@@ -3906,8 +3908,8 @@ class GUIController extends Controller {
|
|
3906
3908
|
mapping.shared_prefix = sp;
|
3907
3909
|
mapping.from_prefix = (fpn ? sp + fpn + UI.PREFIXER : sp);
|
3908
3910
|
mapping.to_prefix = (tpn ? sp + tpn + UI.PREFIXER : sp);
|
3909
|
-
mapping.from_actor =
|
3910
|
-
mapping.to_actor =
|
3911
|
+
mapping.from_actor = UI.realActorName(ca);
|
3912
|
+
mapping.to_actor = UI.realActorName(fca);
|
3911
3913
|
// Prompt for mapping when pasting to the same model and cluster.
|
3912
3914
|
if(parseInt(mts) === MODEL.time_created.getTime() &&
|
3913
3915
|
ca === fca && mapping.from_prefix === mapping.to_prefix &&
|
@@ -4544,8 +4546,7 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
4544
4546
|
md.group = group;
|
4545
4547
|
md.element('action').innerText = 'Edit';
|
4546
4548
|
md.element('name').value = c.name;
|
4547
|
-
md.element('actor').value = (c.actor.name
|
4548
|
-
'' : c.actor.name);
|
4549
|
+
md.element('actor').value = UI.realActorName(c.actor.name);
|
4549
4550
|
md.element('options').style.display = 'block';
|
4550
4551
|
this.setBox('cluster-collapsed', c.collapsed);
|
4551
4552
|
this.setBox('cluster-ignore', c.ignore);
|
@@ -289,17 +289,6 @@ class GUIDatasetManager extends DatasetManager {
|
|
289
289
|
return null;
|
290
290
|
}
|
291
291
|
|
292
|
-
datasetsByPrefix(prefix) {
|
293
|
-
// Return the list of datasets having the specified prefix.
|
294
|
-
const
|
295
|
-
pid = UI.nameToID(prefix + UI.PREFIXER),
|
296
|
-
dsl = [];
|
297
|
-
for(const k of Object.keys(MODEL.datasets)) {
|
298
|
-
if(k.startsWith(pid)) dsl.push(k);
|
299
|
-
}
|
300
|
-
return dsl;
|
301
|
-
}
|
302
|
-
|
303
292
|
selectPrefixRow(e) {
|
304
293
|
// Select expand/collapse prefix row.
|
305
294
|
this.focal_table = this.dataset_table;
|
@@ -310,7 +299,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
310
299
|
const toggle = r.classList.contains('tree-btn');
|
311
300
|
while(r.tagName !== 'TR') r = r.parentNode;
|
312
301
|
this.selected_prefix_row = r;
|
313
|
-
this.prefixed_datasets =
|
302
|
+
this.prefixed_datasets = MODEL.datasetKeysByPrefix(r.dataset.prefix);
|
314
303
|
const sel = this.dataset_table.getElementsByClassName('sel-set');
|
315
304
|
this.selected_dataset = null;
|
316
305
|
if(sel.length > 0) {
|
@@ -746,7 +735,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
746
735
|
}
|
747
736
|
|
748
737
|
get selectedAsList() {
|
749
|
-
|
738
|
+
// Return list of datasets selected directly or by prefix.
|
750
739
|
const dsl = [];
|
751
740
|
// Prevent including the equations dataset (just in case).
|
752
741
|
if(this.selected_dataset && this.selected_dataset !== MODEL.equations_dataset) {
|
@@ -1243,12 +1232,14 @@ class GUIDatasetManager extends DatasetManager {
|
|
1243
1232
|
for(let j = 0; j < ncol; j++) {
|
1244
1233
|
const
|
1245
1234
|
v = dsv[j].trim(),
|
1246
|
-
sf = safeStrToFloat(v,
|
1247
|
-
|
1235
|
+
sf = safeStrToFloat(v, NaN);
|
1236
|
+
// NOTE: Ignore empty strings, but this may "shift up" numerical values on later rows.
|
1237
|
+
if(isNaN(sf) && v !== '') {
|
1248
1238
|
UI.warn(`Invalid numerical value "${v}" for <strong>${dsn[j]}</strong> on line ${i}`);
|
1249
1239
|
return false;
|
1240
|
+
} else if(!isNaN(sf)) {
|
1241
|
+
dsa[j].push(sf);
|
1250
1242
|
}
|
1251
|
-
dsa[j].push(sf);
|
1252
1243
|
}
|
1253
1244
|
}
|
1254
1245
|
// Add or update datasets.
|
@@ -1261,7 +1252,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
1261
1252
|
ods = MODEL.namedObjectByID(id),
|
1262
1253
|
ds = ods || MODEL.addDataset(n);
|
1263
1254
|
// NOTE: `ds` will now be either a new dataset or an existing one.
|
1264
|
-
if(ds) {
|
1255
|
+
if(ds instanceof Dataset) {
|
1265
1256
|
// Keep track of added/updated datasets.
|
1266
1257
|
const
|
1267
1258
|
odv = ds.default_value,
|
@@ -1274,6 +1265,8 @@ class GUIDatasetManager extends DatasetManager {
|
|
1274
1265
|
added++;
|
1275
1266
|
}
|
1276
1267
|
ds.computeStatistics();
|
1268
|
+
} else {
|
1269
|
+
UI.warn(`Name conflict: ${ds.type} "${ds.displayName}" already exists`);
|
1277
1270
|
}
|
1278
1271
|
}
|
1279
1272
|
// Notify modeler of changes (if any).
|
@@ -844,8 +844,8 @@ class GUIExperimentManager extends ExperimentManager {
|
|
844
844
|
}
|
845
845
|
|
846
846
|
showInfo(n, shift) {
|
847
|
-
// Display documentation for the n-th experiment defined in the model
|
848
|
-
// NOTE:
|
847
|
+
// Display documentation for the n-th experiment defined in the model.
|
848
|
+
// NOTE: Skip when viewer is showing!
|
849
849
|
if(!UI.hidden('experiment-viewer')) return;
|
850
850
|
if(n < MODEL.experiments.length) {
|
851
851
|
// NOTE: mouse move over title in viewer passes n = -1
|
@@ -856,7 +856,7 @@ class GUIExperimentManager extends ExperimentManager {
|
|
856
856
|
|
857
857
|
showRunInfo(n, shift) {
|
858
858
|
// Display information on the n-th combination if docu-viewer is visible
|
859
|
-
// and cursor is moved over run cell while Shift button is held down
|
859
|
+
// and cursor is moved over run cell while Shift button is held down.
|
860
860
|
if(shift && DOCUMENTATION_MANAGER.visible) {
|
861
861
|
const info = this.runInfo(n);
|
862
862
|
if(info) {
|
@@ -949,7 +949,16 @@ class Finder {
|
|
949
949
|
}
|
950
950
|
}
|
951
951
|
// Set the new default selector (if changed).
|
952
|
-
if(md.new_defsel !== false)
|
952
|
+
if(md.new_defsel !== false) {
|
953
|
+
// NOTE: `new_defsel` is a key; the actual selector name may have upper case
|
954
|
+
// letters, so get the selector name.
|
955
|
+
const dsm = ds.modifiers[md.new_defsel];
|
956
|
+
if(dsm) {
|
957
|
+
ds.default_selector = dsm.selector;
|
958
|
+
} else {
|
959
|
+
throw(`Unknown selector: ${md.new_defsel}`);
|
960
|
+
}
|
961
|
+
}
|
953
962
|
}
|
954
963
|
// Notify modeler of changes (if any).
|
955
964
|
const msg = [];
|
@@ -1955,7 +1955,7 @@ class Paper {
|
|
1955
1955
|
let l = (MODEL.solved ? proc.actualLevel(MODEL.t) : VM.NOT_COMPUTED),
|
1956
1956
|
lb = proc.lower_bound.result(MODEL.t),
|
1957
1957
|
ub = (proc.equal_bounds ? lb : proc.upper_bound.result(MODEL.t));
|
1958
|
-
// NOTE:
|
1958
|
+
// NOTE: By default, lower bound = 0 (but do show exceptional values).
|
1959
1959
|
if(lb === VM.UNDEFINED && !proc.lower_bound.defined) lb = 0;
|
1960
1960
|
let hw,
|
1961
1961
|
hh,
|
@@ -2766,6 +2766,15 @@ class Paper {
|
|
2766
2766
|
'h-', w - shadow_width, 'v-', shadow_width,
|
2767
2767
|
'h', w - 2*shadow_width, 'z'],
|
2768
2768
|
{fill:stroke_color, stroke:stroke_color, 'stroke-width':stroke_width});
|
2769
|
+
if(clstr.module) {
|
2770
|
+
// Add three white dots at middle of bottom shade.
|
2771
|
+
const
|
2772
|
+
ely = y + hh - shadow_width / 2,
|
2773
|
+
elfill = {fill: 'white'};
|
2774
|
+
clstr.shape.addEllipse(x - 4, ely, 1, 1, elfill);
|
2775
|
+
clstr.shape.addEllipse(x, ely, 1, 1, elfill);
|
2776
|
+
clstr.shape.addEllipse(x + 4, ely, 1, 1, elfill);
|
2777
|
+
}
|
2769
2778
|
// Set fill color if slack used by some product contained by this cluster
|
2770
2779
|
if(MODEL.t in clstr.slack_info) {
|
2771
2780
|
const s = clstr.slack_info[MODEL.t];
|