linny-r 1.1.23 → 1.2.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/README.md +7 -6
- package/package.json +1 -1
- package/static/images/combination.png +0 -0
- package/static/images/iterator.png +0 -0
- package/static/images/scale.png +0 -0
- package/static/index.html +198 -13
- package/static/linny-r.css +214 -33
- package/static/scripts/linny-r-config.js +6 -0
- package/static/scripts/linny-r-ctrl.js +23 -7
- package/static/scripts/linny-r-gui.js +666 -111
- package/static/scripts/linny-r-model.js +873 -224
- package/static/scripts/linny-r-utils.js +5 -0
- package/static/scripts/linny-r-vm.js +310 -89
@@ -589,7 +589,7 @@ class Paper {
|
|
589
589
|
}
|
590
590
|
|
591
591
|
numberSize(number, fsize=8, fweight=400) {
|
592
|
-
// Returns the boundingbox {width: ..., height: ...} of a
|
592
|
+
// Returns the boundingbox {width: ..., height: ...} of a numeric
|
593
593
|
// string (in pixels)
|
594
594
|
// NOTE: this routine is about 500x faster than textSize because it
|
595
595
|
// does not use the DOM tree
|
@@ -939,6 +939,8 @@ class Paper {
|
|
939
939
|
}
|
940
940
|
// Resize paper if necessary
|
941
941
|
this.extend();
|
942
|
+
// Display model name in browser
|
943
|
+
document.title = mdl.name || 'Linny-R';
|
942
944
|
}
|
943
945
|
|
944
946
|
drawSelection(mdl, dx=0, dy=0) {
|
@@ -2965,6 +2967,10 @@ class GUIController extends Controller {
|
|
2965
2967
|
// NOTE: responding to `mouseenter` is needed to update the cursor position
|
2966
2968
|
// after closing a modal dialog
|
2967
2969
|
this.cc.addEventListener('mouseenter', (event) => UI.mouseMove(event));
|
2970
|
+
// Products can be dragged from the Finder to add a placeholder for
|
2971
|
+
// it to the focal cluster
|
2972
|
+
this.cc.addEventListener('dragover', (event) => UI.dragOver(event));
|
2973
|
+
this.cc.addEventListener('drop', (event) => UI.drop(event));
|
2968
2974
|
|
2969
2975
|
// Disable dragging on all images
|
2970
2976
|
const
|
@@ -3123,6 +3129,10 @@ class GUIController extends Controller {
|
|
3123
3129
|
// Ensure that model documentation can no longer be edited
|
3124
3130
|
DOCUMENTATION_MANAGER.clearEntity([MODEL]);
|
3125
3131
|
});
|
3132
|
+
// Make the scale units button of the settings dialog responsive
|
3133
|
+
this.modals.settings.element('scale-units-btn').addEventListener('click',
|
3134
|
+
// Open the scale units modal dialog on top of the settings dialog
|
3135
|
+
() => SCALE_UNIT_MANAGER.show());
|
3126
3136
|
|
3127
3137
|
// Modals related to vertical toolbar buttons
|
3128
3138
|
this.modals['add-process'].ok.addEventListener('click',
|
@@ -3265,7 +3275,7 @@ class GUIController extends Controller {
|
|
3265
3275
|
if(letters.indexOf('C') >= 0) CHART_MANAGER.updateDialog();
|
3266
3276
|
if(letters.indexOf('D') >= 0) DATASET_MANAGER.updateDialog();
|
3267
3277
|
if(letters.indexOf('E') >= 0) EQUATION_MANAGER.updateDialog();
|
3268
|
-
if(letters.indexOf('F') >= 0) FINDER.
|
3278
|
+
if(letters.indexOf('F') >= 0) FINDER.updateDialog();
|
3269
3279
|
if(letters.indexOf('I') >= 0) DOCUMENTATION_MANAGER.updateDialog();
|
3270
3280
|
if(letters.indexOf('J') >= 0) SENSITIVITY_ANALYSIS.updateDialog();
|
3271
3281
|
if(letters.indexOf('X') >= 0) EXPERIMENT_MANAGER.updateDialog();
|
@@ -3276,6 +3286,7 @@ class GUIController extends Controller {
|
|
3276
3286
|
const loaded = MODEL.parseXML(xml);
|
3277
3287
|
// If not a valid Linny-R model, ensure that the current model is clean
|
3278
3288
|
if(!loaded) MODEL = new LinnyRModel();
|
3289
|
+
this.updateScaleUnitList();
|
3279
3290
|
this.drawDiagram(MODEL);
|
3280
3291
|
// Cursor may have been set to `waiting` when decrypting
|
3281
3292
|
this.normalCursor();
|
@@ -3798,15 +3809,26 @@ class GUIController extends Controller {
|
|
3798
3809
|
//
|
3799
3810
|
// Handlers for mouse/cursor events
|
3800
3811
|
//
|
3801
|
-
|
3802
|
-
|
3803
|
-
//
|
3804
|
-
this.on_node = null;
|
3812
|
+
|
3813
|
+
updateCursorPosition(e) {
|
3814
|
+
// Updates the cursor coordinates and displays them on the status bar
|
3805
3815
|
const cp = this.paper.cursorPosition(e.pageX, e.pageY);
|
3806
3816
|
this.mouse_x = cp[0];
|
3807
3817
|
this.mouse_y = cp[1];
|
3808
3818
|
document.getElementById('pos-x').innerHTML = 'X = ' + this.mouse_x;
|
3809
|
-
document.getElementById('pos-y').innerHTML = 'Y = ' + this.mouse_y;
|
3819
|
+
document.getElementById('pos-y').innerHTML = 'Y = ' + this.mouse_y;
|
3820
|
+
this.on_note = null;
|
3821
|
+
this.on_node = null;
|
3822
|
+
this.on_cluster = null;
|
3823
|
+
this.on_cluster_edge = false;
|
3824
|
+
this.on_arrow = null;
|
3825
|
+
this.on_link = null;
|
3826
|
+
this.on_constraint = false;
|
3827
|
+
}
|
3828
|
+
|
3829
|
+
mouseMove(e) {
|
3830
|
+
// Responds to mouse cursor moving over Linny-R diagram area
|
3831
|
+
this.updateCursorPosition(e);
|
3810
3832
|
|
3811
3833
|
// NOTE: check, as MODEL might still be undefined
|
3812
3834
|
if(!MODEL) return;
|
@@ -3829,8 +3851,6 @@ class GUIController extends Controller {
|
|
3829
3851
|
}
|
3830
3852
|
}
|
3831
3853
|
}
|
3832
|
-
this.on_arrow = null;
|
3833
|
-
this.on_link = null;
|
3834
3854
|
for(let i = 0; i < fc.arrows.length; i++) {
|
3835
3855
|
const arr = fc.arrows[i];
|
3836
3856
|
if(arr) {
|
@@ -3853,8 +3873,6 @@ class GUIController extends Controller {
|
|
3853
3873
|
}
|
3854
3874
|
}
|
3855
3875
|
}
|
3856
|
-
this.on_cluster = null;
|
3857
|
-
this.on_cluster_edge = false;
|
3858
3876
|
for(let i = fc.sub_clusters.length-1; i >= 0; i--) {
|
3859
3877
|
const obj = fc.sub_clusters[i];
|
3860
3878
|
// NOTE: ignore cluster that is being dragged, so that a cluster it is
|
@@ -3874,7 +3892,6 @@ class GUIController extends Controller {
|
|
3874
3892
|
// NOTE: element is persistent, so semi-transparency must also be undone
|
3875
3893
|
c.shape.element.setAttribute('opacity', 1);
|
3876
3894
|
}
|
3877
|
-
this.on_note = null;
|
3878
3895
|
for(let i = fc.notes.length-1; i >= 0; i--) {
|
3879
3896
|
const obj = fc.notes[i];
|
3880
3897
|
if(obj.containsPoint(this.mouse_x, this.mouse_y)) {
|
@@ -4294,6 +4311,29 @@ class GUIController extends Controller {
|
|
4294
4311
|
this.start_sel_y = -1;
|
4295
4312
|
this.updateButtons();
|
4296
4313
|
}
|
4314
|
+
|
4315
|
+
dragOver(e) {
|
4316
|
+
// Accepts products that are dragged from the Finder and do not have
|
4317
|
+
// a placeholder in the focal cluster
|
4318
|
+
this.updateCursorPosition(e);
|
4319
|
+
const p = MODEL.products[e.dataTransfer.getData('text')];
|
4320
|
+
if(p && MODEL.focal_cluster.indexOfProduct(p) < 0) e.preventDefault();
|
4321
|
+
}
|
4322
|
+
|
4323
|
+
drop(e) {
|
4324
|
+
// Adds a product that is dragged from the Finder to the focal cluster
|
4325
|
+
// at the cursor position if it does not have a placeholder yet
|
4326
|
+
const p = MODEL.products[e.dataTransfer.getData('text')];
|
4327
|
+
if(p && MODEL.focal_cluster.indexOfProduct(p) < 0) {
|
4328
|
+
e.preventDefault();
|
4329
|
+
MODEL.focal_cluster.addProductPosition(p, this.mouse_x, this.mouse_y);
|
4330
|
+
UNDO_STACK.push('add', p);
|
4331
|
+
this.selectNode(p);
|
4332
|
+
this.drawDiagram(MODEL);
|
4333
|
+
}
|
4334
|
+
// NOTE: update afterwards, as the modeler may target a precise (X, Y)
|
4335
|
+
this.updateCursorPosition(e);
|
4336
|
+
}
|
4297
4337
|
|
4298
4338
|
//
|
4299
4339
|
// Handler for keyboard events
|
@@ -4632,7 +4672,18 @@ class GUIController extends Controller {
|
|
4632
4672
|
if(name === 'initial level') x.is_static = true;
|
4633
4673
|
return true;
|
4634
4674
|
}
|
4635
|
-
|
4675
|
+
|
4676
|
+
updateScaleUnitList() {
|
4677
|
+
// Update the HTML datalist element to reflect all scale units
|
4678
|
+
const
|
4679
|
+
ul = [],
|
4680
|
+
keys = Object.keys(MODEL.scale_units).sort(ciCompare);
|
4681
|
+
for(let i = 0; i < keys.length; i++) {
|
4682
|
+
ul.push(`<option value="${MODEL.scale_units[keys[i]].name}">`);
|
4683
|
+
}
|
4684
|
+
document.getElementById('units-data').innerHTML = ul.join('');
|
4685
|
+
}
|
4686
|
+
|
4636
4687
|
//
|
4637
4688
|
// Navigation in the cluster hierarchy
|
4638
4689
|
//
|
@@ -4824,6 +4875,7 @@ class GUIController extends Controller {
|
|
4824
4875
|
// Create a brand new model with (optionally) specified name and author
|
4825
4876
|
MODEL = new LinnyRModel(
|
4826
4877
|
md.element('name').value.trim(), md.element('author').value.trim());
|
4878
|
+
MODEL.addPreconfiguredScaleUnits();
|
4827
4879
|
md.hide();
|
4828
4880
|
this.updateTimeStep(MODEL.simulationTimeStep);
|
4829
4881
|
this.drawDiagram(MODEL);
|
@@ -5135,9 +5187,15 @@ class GUIController extends Controller {
|
|
5135
5187
|
md.element('time-limit').focus();
|
5136
5188
|
return false;
|
5137
5189
|
}
|
5190
|
+
const
|
5191
|
+
e = md.element('product-unit'),
|
5192
|
+
dsu = UI.cleanName(e.value) || '1';
|
5138
5193
|
model.name = md.element('name').value.trim();
|
5194
|
+
// Display model name in browser unless blank
|
5195
|
+
document.title = model.name || 'Linny-R';
|
5139
5196
|
model.author = md.element('author').value.trim();
|
5140
|
-
model.
|
5197
|
+
if(!model.scale_units.hasOwnProperty(dsu)) model.addScaleUnit(dsu);
|
5198
|
+
model.default_unit = dsu;
|
5141
5199
|
model.currency_unit = md.element('currency-unit').value.trim();
|
5142
5200
|
model.encrypt = UI.boxChecked('settings-encrypt');
|
5143
5201
|
model.decimal_comma = UI.boxChecked('settings-decimal-comma');
|
@@ -5312,9 +5370,11 @@ class GUIController extends Controller {
|
|
5312
5370
|
this.setBox('product-sink', p.is_sink);
|
5313
5371
|
this.setBox('product-data', p.is_data);
|
5314
5372
|
this.setBox('product-stock', p.is_buffer);
|
5373
|
+
// NOTE: price label includes the currency unit and the product unit,
|
5374
|
+
// e.g., EUR/ton
|
5315
5375
|
md.element('P').value = p.price.text;
|
5316
5376
|
md.element('P-unit').innerHTML =
|
5317
|
-
(p.scale_unit === '1' ? '' : p.scale_unit);
|
5377
|
+
(p.scale_unit === '1' ? '' : '/' + p.scale_unit);
|
5318
5378
|
md.element('currency').innerHTML = MODEL.currency_unit;
|
5319
5379
|
md.element('IL').value = p.initial_level.text;
|
5320
5380
|
this.setBox('product-integer', p.integer_level);
|
@@ -5395,7 +5455,7 @@ class GUIController extends Controller {
|
|
5395
5455
|
}
|
5396
5456
|
}
|
5397
5457
|
// Update other properties
|
5398
|
-
p.
|
5458
|
+
p.changeScaleUnit(md.element('unit').value);
|
5399
5459
|
p.equal_bounds = this.getEqualBounds('product-UB-equal');
|
5400
5460
|
p.is_source = this.boxChecked('product-source');
|
5401
5461
|
p.is_sink = this.boxChecked('product-sink');
|
@@ -5851,8 +5911,12 @@ class GUIMonitor {
|
|
5851
5911
|
document.getElementById('call-stack-error').innerHTML =
|
5852
5912
|
`ERROR at t=${t}: ` + VM.errorMessage(err);
|
5853
5913
|
for(let i = 0; i < csl; i++) {
|
5854
|
-
const
|
5855
|
-
|
5914
|
+
const
|
5915
|
+
x = VM.call_stack[i],
|
5916
|
+
// For equations, only show the attribute
|
5917
|
+
ons = (x.object === MODEL.equations_dataset ? '' :
|
5918
|
+
x.object.displayName + '|');
|
5919
|
+
vlist.push(ons + x.attribute);
|
5856
5920
|
// Trim spaces around all object-attribute separators in the expression
|
5857
5921
|
xlist.push(x.text.replace(/\s*\|\s*/g, '|'));
|
5858
5922
|
}
|
@@ -6010,24 +6074,30 @@ class GUIMonitor {
|
|
6010
6074
|
return false;
|
6011
6075
|
}
|
6012
6076
|
|
6013
|
-
submitBlockToSolver(
|
6077
|
+
submitBlockToSolver() {
|
6014
6078
|
let top = MODEL.timeout_period;
|
6015
6079
|
if(VM.max_solver_time && top > VM.max_solver_time) {
|
6016
6080
|
top = VM.max_solver_time;
|
6017
6081
|
UI.notify('Solver time limit for this server is ' +
|
6018
6082
|
VM.max_solver_time + ' seconds');
|
6019
6083
|
}
|
6020
|
-
|
6084
|
+
UI.logHeapSize(`BEFORE creating post data`);
|
6085
|
+
const
|
6086
|
+
bwr = VM.blockWithRound,
|
6087
|
+
pd = postData({
|
6088
|
+
action: 'solve',
|
6089
|
+
user: VM.solver_user,
|
6090
|
+
token: VM.solver_token,
|
6091
|
+
block: VM.block_count,
|
6092
|
+
round: VM.round_sequence[VM.current_round],
|
6093
|
+
data: VM.lines,
|
6094
|
+
timeout: top
|
6095
|
+
});
|
6096
|
+
UI.logHeapSize(`AFTER creating post data`);
|
6097
|
+
// Immediately free the memory taken up by VM.lines
|
6098
|
+
VM.lines = '';
|
6021
6099
|
UI.logHeapSize(`BEFORE submitting block #${bwr} to solver`);
|
6022
|
-
fetch('solver/',
|
6023
|
-
action: 'solve',
|
6024
|
-
user: VM.solver_user,
|
6025
|
-
token: VM.solver_token,
|
6026
|
-
block: VM.block_count,
|
6027
|
-
round: VM.round_sequence[VM.current_round],
|
6028
|
-
data: bcode,
|
6029
|
-
timeout: top
|
6030
|
-
}))
|
6100
|
+
fetch('solver/', pd)
|
6031
6101
|
.then((response) => {
|
6032
6102
|
if(!response.ok) {
|
6033
6103
|
const msg = `ERROR ${response.status}: ${response.statusText}`;
|
@@ -6039,6 +6109,7 @@ class GUIMonitor {
|
|
6039
6109
|
.then((data) => {
|
6040
6110
|
try {
|
6041
6111
|
VM.processServerResponse(JSON.parse(data));
|
6112
|
+
UI.logHeapSize('After processing results for block #' + this.block_count);
|
6042
6113
|
// If no errors, solve next block (if any)
|
6043
6114
|
// NOTE: use setTimeout so that this calling function returns,
|
6044
6115
|
// and browser can update its DOM to display progress
|
@@ -6063,6 +6134,8 @@ class GUIMonitor {
|
|
6063
6134
|
UI.alert(msg);
|
6064
6135
|
VM.stopSolving();
|
6065
6136
|
});
|
6137
|
+
pd.body = '';
|
6138
|
+
UI.logHeapSize(`after calling FETCH and clearing POST data body`);
|
6066
6139
|
VM.logMessage(VM.block_count,
|
6067
6140
|
`POSTing block #${bwr} took ${VM.elapsedTime} seconds.`);
|
6068
6141
|
UI.logHeapSize(`AFTER posting block #${bwr} to solver`);
|
@@ -6466,20 +6539,24 @@ Attributes, however, are case sensitive!">[Actor X|CF]</code> for cash flow.
|
|
6466
6539
|
<code title="Number of rounds in the sequence">nr</code>,
|
6467
6540
|
<code title="Number of current experiment run (starts at 0)">x</code>,
|
6468
6541
|
<code title="Number of runs in the experiment">nx</code>,
|
6542
|
+
<span title="Index variables of iterator dimensions)">
|
6543
|
+
<code>i</code>, <code>j</code>, <code>k</code>,
|
6544
|
+
</span>
|
6469
6545
|
<code title="Number of time steps in 1 year)">yr</code>,
|
6470
6546
|
<code title="Number of time steps in 1 week)">wk</code>,
|
6471
6547
|
<code title="Number of time steps in 1 day)">d</code>,
|
6472
6548
|
<code title="Number of time steps in 1 hour)">h</code>,
|
6473
6549
|
<code title="Number of time steps in 1 minute)">m</code>,
|
6474
6550
|
<code title="Number of time steps in 1 second)">s</code>,
|
6475
|
-
<code title="A random number from the uniform distribution U(0, 1)">random</code>)
|
6476
|
-
|
6551
|
+
<code title="A random number from the uniform distribution U(0, 1)">random</code>),
|
6552
|
+
constants (<code title="Mathematical constant π = ${Math.PI}">pi</code>,
|
6477
6553
|
<code title="Logical constant true = 1
|
6478
6554
|
NOTE: any non-zero value evaluates as true">true</code>,
|
6479
6555
|
<code title="Logical constant false = 0">false</code>,
|
6480
6556
|
<code title="The value used for ‘unbounded’ variables (` +
|
6481
|
-
VM.PLUS_INFINITY.toExponential() + `)">infinity</code>)
|
6482
|
-
are <strong><em>not</em></strong> enclosed by brackets.
|
6557
|
+
VM.PLUS_INFINITY.toExponential() + `)">infinity</code>) and scale units
|
6558
|
+
are <strong><em>not</em></strong> enclosed by brackets. Scale units
|
6559
|
+
may be enclosed by single quotes.
|
6483
6560
|
</p>
|
6484
6561
|
<h4>Operators</h4>
|
6485
6562
|
<p><em>Monadic:</em>
|
@@ -6542,7 +6619,8 @@ considers X0, …, Xn as a variable cash flow time series.">npv</code><br>
|
|
6542
6619
|
<em>Grouping:</em>
|
6543
6620
|
<code title="X ; Y evaluates as a group or “tuple” (X, Y)
|
6544
6621
|
NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates as (1;2;3;4;5)">X ; Y</code>
|
6545
|
-
(use only in combination with <code>max</code>, <code>min</code
|
6622
|
+
(use only in combination with <code>max</code>, <code>min</code>, <code>npv</code>
|
6623
|
+
and probabilistic operators)<br>
|
6546
6624
|
</p>
|
6547
6625
|
<p>
|
6548
6626
|
Monadic operators take precedence over dyadic operators.
|
@@ -6574,7 +6652,7 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
|
|
6574
6652
|
UI.edited_object = UI.dbl_clicked_node;
|
6575
6653
|
this.edited_input_id = 'note-C';
|
6576
6654
|
if(UI.edited_object) {
|
6577
|
-
this.edited_expression = UI.edited_object.
|
6655
|
+
this.edited_expression = UI.edited_object.color;
|
6578
6656
|
} else {
|
6579
6657
|
this.edited_expression = null;
|
6580
6658
|
}
|
@@ -6702,7 +6780,7 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
|
|
6702
6780
|
// is passed to differentiate between the DOM elements to be used
|
6703
6781
|
const
|
6704
6782
|
type = document.getElementById(prefix + 'variable-obj').value,
|
6705
|
-
n_list = this.namesByType(VM.object_types[type]).sort(),
|
6783
|
+
n_list = this.namesByType(VM.object_types[type]).sort(ciCompare),
|
6706
6784
|
vn = document.getElementById(prefix + 'variable-name'),
|
6707
6785
|
options = [];
|
6708
6786
|
// Add "empty" as first and initial option, but disable it.
|
@@ -6744,7 +6822,7 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
|
|
6744
6822
|
slist.push(d.modifiers[m].selector);
|
6745
6823
|
}
|
6746
6824
|
// Sort to present equations in alphabetical order
|
6747
|
-
slist.sort();
|
6825
|
+
slist.sort(ciCompare);
|
6748
6826
|
for(let i = 0; i < slist.length; i++) {
|
6749
6827
|
options.push(`<option value="${slist[i]}">${slist[i]}</option>`);
|
6750
6828
|
}
|
@@ -7029,6 +7107,218 @@ class ModelAutoSaver {
|
|
7029
7107
|
} // END of class ModelAutoSaver
|
7030
7108
|
|
7031
7109
|
|
7110
|
+
// CLASS ScaleUnitManager (modal dialog!)
|
7111
|
+
class ScaleUnitManager {
|
7112
|
+
constructor() {
|
7113
|
+
// Add the scale units modal
|
7114
|
+
this.dialog = new ModalDialog('scale-units');
|
7115
|
+
this.dialog.close.addEventListener('click',
|
7116
|
+
() => SCALE_UNIT_MANAGER.dialog.hide());
|
7117
|
+
// Make the add, edit and delete buttons of this modal responsive
|
7118
|
+
this.dialog.element('new-btn').addEventListener('click',
|
7119
|
+
() => SCALE_UNIT_MANAGER.promptForScaleUnit());
|
7120
|
+
this.dialog.element('edit-btn').addEventListener('click',
|
7121
|
+
() => SCALE_UNIT_MANAGER.editScaleUnit());
|
7122
|
+
this.dialog.element('delete-btn').addEventListener('click',
|
7123
|
+
() => SCALE_UNIT_MANAGER.deleteScaleUnit());
|
7124
|
+
// Add the scale unit definition modal
|
7125
|
+
this.new_scale_unit_modal = new ModalDialog('new-scale-unit');
|
7126
|
+
this.new_scale_unit_modal.ok.addEventListener(
|
7127
|
+
'click', () => SCALE_UNIT_MANAGER.addNewScaleUnit());
|
7128
|
+
this.new_scale_unit_modal.cancel.addEventListener(
|
7129
|
+
'click', () => SCALE_UNIT_MANAGER.new_scale_unit_modal.hide());
|
7130
|
+
this.scroll_area = this.dialog.element('scroll-area');
|
7131
|
+
this.table = this.dialog.element('table');
|
7132
|
+
}
|
7133
|
+
|
7134
|
+
get selectedUnitIsBaseUnit() {
|
7135
|
+
// Returns TRUE iff selected unit is used as base unit for some unit
|
7136
|
+
for(let u in this.scale_units) if(this.scale_units.hasOwnProperty(u)) {
|
7137
|
+
if(this.scale_units[u].base_unit === this.selected_unit) return true;
|
7138
|
+
}
|
7139
|
+
return false;
|
7140
|
+
}
|
7141
|
+
|
7142
|
+
show() {
|
7143
|
+
// Show the user-defined scale units for the current model
|
7144
|
+
// NOTE: add/edit/delete actions operate on this list, so changes
|
7145
|
+
// take immediate effect
|
7146
|
+
MODEL.cleanUpScaleUnits();
|
7147
|
+
// NOTE: unit name is key in the scale units object
|
7148
|
+
this.selected_unit = '';
|
7149
|
+
this.last_time_selected = 0;
|
7150
|
+
this.updateDialog();
|
7151
|
+
this.dialog.show();
|
7152
|
+
}
|
7153
|
+
|
7154
|
+
updateDialog() {
|
7155
|
+
// Create the HTML for the scale units table and update the state
|
7156
|
+
// of the action buttons
|
7157
|
+
if(!MODEL.scale_units.hasOwnProperty(this.selected_unit)) {
|
7158
|
+
this.selected_unit = '';
|
7159
|
+
}
|
7160
|
+
const
|
7161
|
+
keys = Object.keys(MODEL.scale_units).sort(ciCompare),
|
7162
|
+
sl = [],
|
7163
|
+
ss = this.selected_unit;
|
7164
|
+
let ssid = 'scntr';
|
7165
|
+
if(keys.length <= 1) {
|
7166
|
+
// Only one key => must be the default '1'
|
7167
|
+
sl.push('<tr><td><em>No units defined</em></td></tr>');
|
7168
|
+
} else {
|
7169
|
+
for(let i = 1; i < keys.length; i++) {
|
7170
|
+
const
|
7171
|
+
s = keys[i],
|
7172
|
+
clk = '" onclick="SCALE_UNIT_MANAGER.selectScaleUnit(event, \'' +
|
7173
|
+
s + '\'';
|
7174
|
+
if(s === ss) ssid += i;
|
7175
|
+
sl.push(['<tr id="scntr', i, '" class="dataset-modif',
|
7176
|
+
(s === ss ? ' sel-set' : ''),
|
7177
|
+
'"><td class="dataset-selector', clk, ');">',
|
7178
|
+
s, '</td><td class="dataset-selector', clk, ', \'scalar\');">',
|
7179
|
+
MODEL.scale_units[s].scalar, '</td><td class="dataset-selector',
|
7180
|
+
clk, ', \'base\');">', MODEL.scale_units[s].base_unit,
|
7181
|
+
'</td></tr>'].join(''));
|
7182
|
+
}
|
7183
|
+
}
|
7184
|
+
this.table.innerHTML = sl.join('');
|
7185
|
+
if(ss) UI.scrollIntoView(document.getElementById(ssid));
|
7186
|
+
let btns = 'scale-units-edit';
|
7187
|
+
if(!this.selectedUnitIsBaseUnit) btns += ' scale-units-delete';
|
7188
|
+
if(ss) {
|
7189
|
+
UI.enableButtons(btns);
|
7190
|
+
} else {
|
7191
|
+
UI.disableButtons(btns);
|
7192
|
+
}
|
7193
|
+
}
|
7194
|
+
|
7195
|
+
selectScaleUnit(event, symbol, focus) {
|
7196
|
+
// Select scale unit, and when double-clicked, allow to edit it
|
7197
|
+
const
|
7198
|
+
ss = this.selected_unit,
|
7199
|
+
now = Date.now(),
|
7200
|
+
dt = now - this.last_time_selected,
|
7201
|
+
// NOTE: Alt-click and double-click indicate: edit
|
7202
|
+
// Consider click to be "double" if the same modifier was clicked
|
7203
|
+
// less than 300 ms ago
|
7204
|
+
edit = event.altKey || (symbol === ss && dt < 300);
|
7205
|
+
this.selected_unit = symbol;
|
7206
|
+
this.last_time_selected = now;
|
7207
|
+
if(edit) {
|
7208
|
+
this.last_time_selected = 0;
|
7209
|
+
this.promptForScaleUnit('Edit', focus);
|
7210
|
+
return;
|
7211
|
+
}
|
7212
|
+
this.updateDialog();
|
7213
|
+
}
|
7214
|
+
|
7215
|
+
promptForScaleUnit(action='Define new', focus='name') {
|
7216
|
+
// Show the Add/Edit scale unit dialog for the indicated action
|
7217
|
+
const md = this.new_scale_unit_modal;
|
7218
|
+
// NOTE: by default, let name and base unit be empty strings, not '1'
|
7219
|
+
let sv = {name: '', scalar: '1', base_unit: '' };
|
7220
|
+
if(action === 'Edit' && this.selected_unit) {
|
7221
|
+
sv = MODEL.scale_units[this.selected_unit];
|
7222
|
+
}
|
7223
|
+
md.element('action').innerText = action;
|
7224
|
+
md.element('name').value = sv.name;
|
7225
|
+
md.element('scalar').value = sv.scalar;
|
7226
|
+
md.element('base').value = sv.base_unit;
|
7227
|
+
UI.updateScaleUnitList();
|
7228
|
+
this.new_scale_unit_modal.show(focus);
|
7229
|
+
}
|
7230
|
+
|
7231
|
+
addNewScaleUnit() {
|
7232
|
+
// Add the new scale unit or update the one being edited
|
7233
|
+
const
|
7234
|
+
md = this.new_scale_unit_modal,
|
7235
|
+
edited = md.element('action').innerText === 'Edit',
|
7236
|
+
// NOTE: unit name cannot contain single quotes
|
7237
|
+
s = UI.cleanName(md.element('name').value).replace("'", ''),
|
7238
|
+
v = md.element('scalar').value.trim(),
|
7239
|
+
// NOTE: accept empty base unit to denote '1'
|
7240
|
+
b = md.element('base').value.trim() || '1';
|
7241
|
+
if(!s) {
|
7242
|
+
// Do not accept empty string as name
|
7243
|
+
UI.warn('Scale unit must have a name');
|
7244
|
+
md.element('name').focus();
|
7245
|
+
return;
|
7246
|
+
}
|
7247
|
+
if(MODEL.scale_units.hasOwnProperty(s) && !edited) {
|
7248
|
+
// Do not accept existing unit as name for new unit
|
7249
|
+
UI.warn(`Scale unit "${s}" is already defined`);
|
7250
|
+
md.element('name').focus();
|
7251
|
+
return;
|
7252
|
+
}
|
7253
|
+
if(b !== s && !MODEL.scale_units.hasOwnProperty(b)) {
|
7254
|
+
UI.warn(`Base unit "${b}" is undefined`);
|
7255
|
+
md.element('base').focus();
|
7256
|
+
return;
|
7257
|
+
}
|
7258
|
+
if(UI.validNumericInput('new-scale-unit-scalar', 'scalar')) {
|
7259
|
+
const ucs = Math.abs(safeStrToFloat(v));
|
7260
|
+
if(ucs < VM.NEAR_ZERO) {
|
7261
|
+
UI.warn(`Unit conversion scalar cannot be zero`);
|
7262
|
+
md.element('scalar').focus();
|
7263
|
+
return;
|
7264
|
+
}
|
7265
|
+
if(b === s && ucs !== 1) {
|
7266
|
+
UI.warn(`When base unit = scale unit, scalar must equal 1`);
|
7267
|
+
md.element('scalar').focus();
|
7268
|
+
return;
|
7269
|
+
}
|
7270
|
+
const selu = this.selected_unit;
|
7271
|
+
if(edited && b !== s) {
|
7272
|
+
// Prevent inconsistencies across scalars
|
7273
|
+
const cr = MODEL.scale_units[b].conversionRates();
|
7274
|
+
if(cr.hasOwnProperty(s)) {
|
7275
|
+
UI.warn(`Defining ${s} in terms of ${b} introduces a circular reference`);
|
7276
|
+
md.element('base').focus();
|
7277
|
+
return;
|
7278
|
+
}
|
7279
|
+
}
|
7280
|
+
if(edited && s !== selu) {
|
7281
|
+
// First rename base units
|
7282
|
+
for(let u in MODEL.scale_units) if(MODEL.scale_units.hasOwnProperty(u)) {
|
7283
|
+
if(MODEL.scale_units[u].base_unit === selu) {
|
7284
|
+
MODEL.scale_units[u].base_unit = s;
|
7285
|
+
}
|
7286
|
+
}
|
7287
|
+
// NOTE: renameScaleUnit replaces references to `s`, not the entry
|
7288
|
+
MODEL.renameScaleUnit(selu, s);
|
7289
|
+
delete MODEL.scale_units[this.selected_unit];
|
7290
|
+
}
|
7291
|
+
MODEL.scale_units[s] = new ScaleUnit(s, v, b);
|
7292
|
+
MODEL.selected_unit = s;
|
7293
|
+
this.new_scale_unit_modal.hide();
|
7294
|
+
UI.updateScaleUnitList();
|
7295
|
+
this.updateDialog();
|
7296
|
+
}
|
7297
|
+
}
|
7298
|
+
|
7299
|
+
editScaleUnit() {
|
7300
|
+
// Allow user to edit name and/or value
|
7301
|
+
if(this.selected_unit) this.promptForScaleUnit('Edit', 'scalar');
|
7302
|
+
}
|
7303
|
+
|
7304
|
+
deleteScaleUnit() {
|
7305
|
+
// Allow user to delete
|
7306
|
+
// @@@TO DO: check whether scale unit is used in the model
|
7307
|
+
if(this.selected_unit && !this.selectedUnitIsBaseUnit) {
|
7308
|
+
delete MODEL.scale_units[this.selected_unit];
|
7309
|
+
this.updateDialog();
|
7310
|
+
}
|
7311
|
+
}
|
7312
|
+
|
7313
|
+
updateScaleUnits() {
|
7314
|
+
// Replace scale unit definitions of model by the new definitions
|
7315
|
+
UI.updateScaleUnitList();
|
7316
|
+
this.dialog.hide();
|
7317
|
+
}
|
7318
|
+
|
7319
|
+
} // END of class ScaleUnitManager
|
7320
|
+
|
7321
|
+
|
7032
7322
|
// CLASS ActorManager (modal dialog!)
|
7033
7323
|
class ActorManager {
|
7034
7324
|
constructor() {
|
@@ -8833,21 +9123,26 @@ class GUIDatasetManager extends DatasetManager {
|
|
8833
9123
|
dnl.push(d);
|
8834
9124
|
}
|
8835
9125
|
}
|
8836
|
-
dnl.sort();
|
9126
|
+
dnl.sort(ciCompare);
|
8837
9127
|
let sdid = 'dstr';
|
8838
9128
|
for(let i = 0; i < dnl.length; i++) {
|
8839
9129
|
const d = MODEL.datasets[dnl[i]];
|
8840
9130
|
let cls = ioclass[MODEL.ioType(d)];
|
8841
9131
|
if(d.outcome) {
|
8842
|
-
cls
|
9132
|
+
cls += ' outcome';
|
8843
9133
|
} else if(d.array) {
|
8844
|
-
cls
|
9134
|
+
cls += ' array';
|
9135
|
+
} else if(d.data.length > 0) {
|
9136
|
+
cls += ' series';
|
8845
9137
|
}
|
8846
|
-
if(d.
|
9138
|
+
if(Object.keys(d.modifiers).length > 0) cls += ' modif';
|
9139
|
+
if(d.black_box) cls += ' blackbox';
|
9140
|
+
cls = cls.trim();
|
8847
9141
|
if(cls) cls = ' class="'+ cls + '"';
|
8848
9142
|
if(d === sd) sdid += i;
|
8849
9143
|
dl.push(['<tr id="dstr', i, '" class="dataset',
|
8850
9144
|
(d === sd ? ' sel-set' : ''),
|
9145
|
+
(d.default_selector ? ' def-sel' : ''),
|
8851
9146
|
'" onclick="DATASET_MANAGER.selectDataset(event, \'',
|
8852
9147
|
dnl[i], '\');" onmouseover="DATASET_MANAGER.showInfo(\'', dnl[i],
|
8853
9148
|
'\', event.shiftKey);"><td', cls, '>', d.displayName,
|
@@ -8859,7 +9154,8 @@ class GUIDatasetManager extends DatasetManager {
|
|
8859
9154
|
this.table.innerHTML = dl.join('');
|
8860
9155
|
this.properties.style.display = 'block';
|
8861
9156
|
document.getElementById('dataset-default').innerHTML =
|
8862
|
-
VM.sig4Dig(sd.default_value)
|
9157
|
+
VM.sig4Dig(sd.default_value) +
|
9158
|
+
(sd.scale_unit === '1' ? '' : ' ' + sd.scale_unit);
|
8863
9159
|
document.getElementById('dataset-count').innerHTML = sd.data.length;
|
8864
9160
|
document.getElementById('dataset-special').innerHTML = sd.propertiesString;
|
8865
9161
|
if(sd.data.length > 0) {
|
@@ -9012,11 +9308,16 @@ class GUIDatasetManager extends DatasetManager {
|
|
9012
9308
|
edit = event.altKey || (m === this.selected_modifier && dt < 300);
|
9013
9309
|
this.last_time_selected = now;
|
9014
9310
|
if(event.shiftKey) {
|
9311
|
+
// NOTE: prepare to update HTML class of selected dataset
|
9312
|
+
const el = document.getElementById('dataset-table')
|
9313
|
+
.getElementsByClassName('sel-set')[0];
|
9015
9314
|
// Toggle dataset default selector
|
9016
9315
|
if(m.selector === this.selected_dataset.default_selector) {
|
9017
9316
|
this.selected_dataset.default_selector = '';
|
9317
|
+
el.classList.remove('def-sel');
|
9018
9318
|
} else {
|
9019
9319
|
this.selected_dataset.default_selector = m.selector;
|
9320
|
+
el.classList.add('def-sel');
|
9020
9321
|
}
|
9021
9322
|
}
|
9022
9323
|
this.selected_modifier = m;
|
@@ -9094,6 +9395,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
9094
9395
|
// Copy properties of d to nd
|
9095
9396
|
nd.comments = `${d.comments}`;
|
9096
9397
|
nd.default_value = d.default_value;
|
9398
|
+
nd.scale_unit = d.scale_unit;
|
9097
9399
|
nd.time_scale = d.time_scale;
|
9098
9400
|
nd.time_unit = d.time_unit;
|
9099
9401
|
nd.method = d.method;
|
@@ -9189,6 +9491,8 @@ class GUIDatasetManager extends DatasetManager {
|
|
9189
9491
|
const
|
9190
9492
|
hw = this.selected_modifier.hasWildcards,
|
9191
9493
|
sel = this.rename_selector_modal.element('name').value,
|
9494
|
+
// NOTE: normal dataset selector, so remove all invalid characters
|
9495
|
+
clean_sel = sel.replace(/[^a-zA-z0-9\%\+\-]/g, ''),
|
9192
9496
|
// Keep track of old name
|
9193
9497
|
oldm = this.selected_modifier,
|
9194
9498
|
// NOTE: addModifier returns existing one if selector not changed
|
@@ -9199,10 +9503,10 @@ class GUIDatasetManager extends DatasetManager {
|
|
9199
9503
|
if(oldm.selector === this.selected_dataset.default_selector) {
|
9200
9504
|
this.selected_dataset.default_selector = m.selector;
|
9201
9505
|
}
|
9506
|
+
MODEL.renameSelectorInExperiments(oldm.selector, clean_sel);
|
9202
9507
|
// If only case has changed, just update the selector
|
9203
|
-
// NOTE: normal dataset selector, so remove all invalid characters
|
9204
9508
|
if(m === oldm) {
|
9205
|
-
m.selector =
|
9509
|
+
m.selector = clean_sel;
|
9206
9510
|
this.updateDialog();
|
9207
9511
|
return;
|
9208
9512
|
}
|
@@ -9238,12 +9542,8 @@ class GUIDatasetManager extends DatasetManager {
|
|
9238
9542
|
if(msg.length) {
|
9239
9543
|
UI.notify('Updated ' + msg.join(' and '));
|
9240
9544
|
// Also update these stay-on-top dialogs, as they may display a
|
9241
|
-
// variable name for this dataset + modifier
|
9242
|
-
|
9243
|
-
DATASET_MANAGER.updateDialog();
|
9244
|
-
EQUATION_MANAGER.updateDialog();
|
9245
|
-
EXPERIMENT_MANAGER.updateDialog();
|
9246
|
-
FINDER.changeFilter();
|
9545
|
+
// variable name for this dataset + modifier
|
9546
|
+
UI.updateControllerDialogs('CDEFX');
|
9247
9547
|
}
|
9248
9548
|
// NOTE: update dimensions only if dataset now has 2 or more modifiers
|
9249
9549
|
// (ignoring those with wildcards)
|
@@ -9312,6 +9612,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
9312
9612
|
cover = md.element('no-time-msg');
|
9313
9613
|
if(ds) {
|
9314
9614
|
md.element('default').value = ds.default_value;
|
9615
|
+
md.element('unit').value = ds.scale_unit;
|
9315
9616
|
cover.style.display = (ds.array ? 'block' : 'none');
|
9316
9617
|
md.element('time-scale').value = VM.sig4Dig(ds.time_scale);
|
9317
9618
|
// Add options for time unit selector
|
@@ -9376,6 +9677,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
9376
9677
|
}
|
9377
9678
|
// Save the data
|
9378
9679
|
ds.default_value = dv;
|
9680
|
+
ds.changeScaleUnit(this.series_modal.element('unit').value);
|
9379
9681
|
ds.time_scale = ts;
|
9380
9682
|
ds.time_unit = this.series_modal.element('time-unit').value;
|
9381
9683
|
ds.method = this.series_modal.element('method').value;
|
@@ -9455,7 +9757,6 @@ class EquationManager {
|
|
9455
9757
|
for(let i = 0; i < msl.length; i++) {
|
9456
9758
|
const
|
9457
9759
|
m = ed.modifiers[UI.nameToID(msl[i])],
|
9458
|
-
mp = (m.parameters ? '\\' + m.parameters.join('\\') : ''),
|
9459
9760
|
clk = '" onclick="EQUATION_MANAGER.selectModifier(event, \'' +
|
9460
9761
|
m.selector + '\'';
|
9461
9762
|
if(m === sm) smid += i;
|
@@ -9464,7 +9765,7 @@ class EquationManager {
|
|
9464
9765
|
'"><td class="equation-selector',
|
9465
9766
|
(m.expression.isStatic ? '' : ' it'),
|
9466
9767
|
clk, ', false);">',
|
9467
|
-
m.selector,
|
9768
|
+
m.selector, '</td><td class="equation-expression',
|
9468
9769
|
clk, ');">', m.expression.text, '</td></tr>'].join(''));
|
9469
9770
|
}
|
9470
9771
|
this.table.innerHTML = ml.join('');
|
@@ -9590,7 +9891,6 @@ class EquationManager {
|
|
9590
9891
|
} else {
|
9591
9892
|
// When a new modifier has been added, more actions are needed
|
9592
9893
|
m.expression = oldm.expression;
|
9593
|
-
m.parameters = oldm.parameters;
|
9594
9894
|
this.deleteEquation();
|
9595
9895
|
this.selected_modifier = m;
|
9596
9896
|
}
|
@@ -9618,11 +9918,7 @@ class EquationManager {
|
|
9618
9918
|
UI.notify('Updated ' + msg.join(' and '));
|
9619
9919
|
// Also update these stay-on-top dialogs, as they may display a
|
9620
9920
|
// variable name for this dataset + modifier
|
9621
|
-
|
9622
|
-
DATASET_MANAGER.updateDialog();
|
9623
|
-
EQUATION_MANAGER.updateDialog();
|
9624
|
-
EXPERIMENT_MANAGER.updateDialog();
|
9625
|
-
FINDER.changeFilter();
|
9921
|
+
UI.updateControllerDialogs('CDEFX');
|
9626
9922
|
}
|
9627
9923
|
// Always close the name prompt dialog, and update the equation manager
|
9628
9924
|
this.rename_modal.hide();
|
@@ -9925,6 +10221,11 @@ class GUIChartManager extends ChartManager {
|
|
9925
10221
|
u_btn = 'chart-variable-up ',
|
9926
10222
|
d_btn = 'chart-variable-down ',
|
9927
10223
|
ed_btns = 'chart-edit-variable chart-delete-variable ';
|
10224
|
+
// Just in case variable index has not been adjusted after some
|
10225
|
+
// variables have been deleted
|
10226
|
+
if(this.variable_index >= c.variables.length) {
|
10227
|
+
this.variable_index = -1;
|
10228
|
+
}
|
9928
10229
|
if(this.variable_index < 0) {
|
9929
10230
|
UI.disableButtons(ed_btns + u_btn + d_btn);
|
9930
10231
|
} else {
|
@@ -9942,7 +10243,7 @@ class GUIChartManager extends ChartManager {
|
|
9942
10243
|
// If the Edit variable dialog is showing, update its header
|
9943
10244
|
if(this.variable_index >= 0 && !UI.hidden('variable-dlg')) {
|
9944
10245
|
document.getElementById('variable-dlg-name').innerHTML =
|
9945
|
-
|
10246
|
+
c.variables[this.variable_index].displayName;
|
9946
10247
|
}
|
9947
10248
|
}
|
9948
10249
|
this.add_variable_modal.element('obj').value = 0;
|
@@ -10047,8 +10348,7 @@ class GUIChartManager extends ChartManager {
|
|
10047
10348
|
if(c.show_title) this.drawChart();
|
10048
10349
|
}
|
10049
10350
|
// Update experiment viewer in case its current experiment uses this chart
|
10050
|
-
|
10051
|
-
FINDER.changeFilter();
|
10351
|
+
UI.updateControllerDialogs('CFX');
|
10052
10352
|
}
|
10053
10353
|
this.rename_chart_modal.hide();
|
10054
10354
|
}
|
@@ -10098,10 +10398,8 @@ class GUIChartManager extends ChartManager {
|
|
10098
10398
|
MODEL.charts.splice(this.chart_index, 1);
|
10099
10399
|
this.chart_index = -1;
|
10100
10400
|
}
|
10101
|
-
this.updateDialog();
|
10102
10401
|
// Also update the experiment viewer (charts define the output variables)
|
10103
|
-
|
10104
|
-
FINDER.changeFilter();
|
10402
|
+
UI.updateControllerDialogs('CFX');
|
10105
10403
|
}
|
10106
10404
|
}
|
10107
10405
|
|
@@ -10176,12 +10474,11 @@ class GUIChartManager extends ChartManager {
|
|
10176
10474
|
this.variable_index = MODEL.charts[this.chart_index].addVariable(o, a);
|
10177
10475
|
if(this.variable_index >= 0) {
|
10178
10476
|
this.add_variable_modal.hide();
|
10179
|
-
this.updateDialog();
|
10180
10477
|
// Also update the experiment viewer (charts define the output variables)
|
10181
10478
|
if(EXPERIMENT_MANAGER.selected_experiment) {
|
10182
10479
|
EXPERIMENT_MANAGER.selected_experiment.inferVariables();
|
10183
|
-
EXPERIMENT_MANAGER.updateDialog();
|
10184
10480
|
}
|
10481
|
+
UI.updateControllerDialogs('CFX');
|
10185
10482
|
}
|
10186
10483
|
}
|
10187
10484
|
}
|
@@ -10317,10 +10614,7 @@ class GUIChartManager extends ChartManager {
|
|
10317
10614
|
this.updateDialog();
|
10318
10615
|
// Also update the experiment viewer (charts define the output variables)
|
10319
10616
|
// and finder dialog
|
10320
|
-
if(EXPERIMENT_MANAGER.selected_experiment)
|
10321
|
-
EXPERIMENT_MANAGER.updateDialog();
|
10322
|
-
FINDER.changeFilter();
|
10323
|
-
}
|
10617
|
+
if(EXPERIMENT_MANAGER.selected_experiment) UI.updateControllerDialogs('FX');
|
10324
10618
|
}
|
10325
10619
|
this.variable_modal.hide();
|
10326
10620
|
}
|
@@ -10710,12 +11004,12 @@ class GUISensitivityAnalysis extends SensitivityAnalysis {
|
|
10710
11004
|
this.showBaseCaseInfo();
|
10711
11005
|
return;
|
10712
11006
|
}
|
10713
|
-
// Otherwise, display list of all
|
11007
|
+
// Otherwise, display list of all dataset selectors in docu-viewer
|
10714
11008
|
if(DOCUMENTATION_MANAGER.visible) {
|
10715
11009
|
const
|
10716
11010
|
ds_dict = MODEL.listOfAllSelectors,
|
10717
11011
|
html = [],
|
10718
|
-
sl = Object.keys(ds_dict).sort();
|
11012
|
+
sl = Object.keys(ds_dict).sort(ciCompare);
|
10719
11013
|
for(let i = 0; i < sl.length; i++) {
|
10720
11014
|
const
|
10721
11015
|
s = sl[i],
|
@@ -11131,7 +11425,7 @@ class GUISensitivityAnalysis extends SensitivityAnalysis {
|
|
11131
11425
|
}
|
11132
11426
|
this.table.innerHTML = html.join('');
|
11133
11427
|
if(this.selected_run >= 0) document.getElementById(
|
11134
|
-
`sa-r${this.selected_run}c0`).
|
11428
|
+
`sa-r${this.selected_run}c0`).parentNode.classList.add('sa-p-sel');
|
11135
11429
|
this.updateData();
|
11136
11430
|
}
|
11137
11431
|
|
@@ -11194,7 +11488,7 @@ class GUISensitivityAnalysis extends SensitivityAnalysis {
|
|
11194
11488
|
} else if(n < MODEL.sensitivity_runs.length) {
|
11195
11489
|
this.selected_run = n;
|
11196
11490
|
if(n >= 0) document.getElementById(
|
11197
|
-
`sa-r${n}c0`).
|
11491
|
+
`sa-r${n}c0`).parentNode.classList.add('sa-p-sel');
|
11198
11492
|
}
|
11199
11493
|
VM.setRunMessages(this.selected_run);
|
11200
11494
|
}
|
@@ -11294,6 +11588,10 @@ class GUIExperimentManager extends ExperimentManager {
|
|
11294
11588
|
'click', () => EXPERIMENT_MANAGER.moveDimension(1));
|
11295
11589
|
document.getElementById('xp-d-settings-btn').addEventListener(
|
11296
11590
|
'click', () => EXPERIMENT_MANAGER.editSettingsDimensions());
|
11591
|
+
document.getElementById('xp-d-iterator-btn').addEventListener(
|
11592
|
+
'click', () => EXPERIMENT_MANAGER.editIteratorRanges());
|
11593
|
+
document.getElementById('xp-d-combination-btn').addEventListener(
|
11594
|
+
'click', () => EXPERIMENT_MANAGER.editCombinationDimensions());
|
11297
11595
|
document.getElementById('xp-d-actor-btn').addEventListener(
|
11298
11596
|
'click', () => EXPERIMENT_MANAGER.editActorDimension());
|
11299
11597
|
document.getElementById('xp-d-delete-btn').addEventListener(
|
@@ -11354,6 +11652,12 @@ class GUIExperimentManager extends ExperimentManager {
|
|
11354
11652
|
this.parameter_modal.cancel.addEventListener(
|
11355
11653
|
'click', () => EXPERIMENT_MANAGER.parameter_modal.hide());
|
11356
11654
|
|
11655
|
+
this.iterator_modal = new ModalDialog('xp-iterator');
|
11656
|
+
this.iterator_modal.ok.addEventListener(
|
11657
|
+
'click', () => EXPERIMENT_MANAGER.modifyIteratorRanges());
|
11658
|
+
this.iterator_modal.cancel.addEventListener(
|
11659
|
+
'click', () => EXPERIMENT_MANAGER.iterator_modal.hide());
|
11660
|
+
|
11357
11661
|
this.settings_modal = new ModalDialog('xp-settings');
|
11358
11662
|
this.settings_modal.close.addEventListener(
|
11359
11663
|
'click', () => EXPERIMENT_MANAGER.closeSettingsDimensions());
|
@@ -11374,6 +11678,26 @@ class GUIExperimentManager extends ExperimentManager {
|
|
11374
11678
|
this.settings_dimension_modal.cancel.addEventListener(
|
11375
11679
|
'click', () => EXPERIMENT_MANAGER.settings_dimension_modal.hide());
|
11376
11680
|
|
11681
|
+
this.combination_modal = new ModalDialog('xp-combination');
|
11682
|
+
this.combination_modal.close.addEventListener(
|
11683
|
+
'click', () => EXPERIMENT_MANAGER.closeCombinationDimensions());
|
11684
|
+
this.combination_modal.element('s-add-btn').addEventListener(
|
11685
|
+
'click', () => EXPERIMENT_MANAGER.editCombinationSelector(-1));
|
11686
|
+
this.combination_modal.element('d-add-btn').addEventListener(
|
11687
|
+
'click', () => EXPERIMENT_MANAGER.editCombinationDimension(-1));
|
11688
|
+
|
11689
|
+
this.combination_selector_modal = new ModalDialog('xp-combination-selector');
|
11690
|
+
this.combination_selector_modal.ok.addEventListener(
|
11691
|
+
'click', () => EXPERIMENT_MANAGER.modifyCombinationSelector());
|
11692
|
+
this.combination_selector_modal.cancel.addEventListener(
|
11693
|
+
'click', () => EXPERIMENT_MANAGER.combination_selector_modal.hide());
|
11694
|
+
|
11695
|
+
this.combination_dimension_modal = new ModalDialog('xp-combination-dimension');
|
11696
|
+
this.combination_dimension_modal.ok.addEventListener(
|
11697
|
+
'click', () => EXPERIMENT_MANAGER.modifyCombinationDimension());
|
11698
|
+
this.combination_dimension_modal.cancel.addEventListener(
|
11699
|
+
'click', () => EXPERIMENT_MANAGER.combination_dimension_modal.hide());
|
11700
|
+
|
11377
11701
|
this.actor_dimension_modal = new ModalDialog('xp-actor-dimension');
|
11378
11702
|
this.actor_dimension_modal.close.addEventListener(
|
11379
11703
|
'click', () => EXPERIMENT_MANAGER.closeActorDimension());
|
@@ -11423,6 +11747,7 @@ class GUIExperimentManager extends ExperimentManager {
|
|
11423
11747
|
this.selected_parameter = '';
|
11424
11748
|
this.edited_selector_index = -1;
|
11425
11749
|
this.edited_dimension_index = -1;
|
11750
|
+
this.edited_combi_selector_index = -1;
|
11426
11751
|
this.color_scale = new ColorScale('no');
|
11427
11752
|
this.designMode();
|
11428
11753
|
}
|
@@ -11448,7 +11773,7 @@ class GUIExperimentManager extends ExperimentManager {
|
|
11448
11773
|
for(let i = 0; i < MODEL.experiments.length; i++) {
|
11449
11774
|
xtl.push(MODEL.experiments[i].title);
|
11450
11775
|
}
|
11451
|
-
xtl.sort();
|
11776
|
+
xtl.sort(ciCompare);
|
11452
11777
|
for(let i = 0; i < xtl.length; i++) {
|
11453
11778
|
const
|
11454
11779
|
xi = MODEL.indexOfExperiment(xtl[i]),
|
@@ -11490,24 +11815,25 @@ class GUIExperimentManager extends ExperimentManager {
|
|
11490
11815
|
|
11491
11816
|
updateParameters() {
|
11492
11817
|
MODEL.inferDimensions();
|
11493
|
-
let
|
11494
|
-
canview = true;
|
11818
|
+
let canview = true;
|
11495
11819
|
const
|
11496
11820
|
dim_count = document.getElementById('experiment-dim-count'),
|
11497
11821
|
combi_count = document.getElementById('experiment-combi-count'),
|
11498
11822
|
header = document.getElementById('experiment-params-header'),
|
11499
11823
|
x = this.selected_experiment;
|
11500
11824
|
if(!x) {
|
11501
|
-
dim_count.innerHTML = pluralS(
|
11825
|
+
dim_count.innerHTML = pluralS(
|
11826
|
+
MODEL.dimensions.length, ' data dimension') + ' in model';
|
11502
11827
|
combi_count.innerHTML = '';
|
11503
11828
|
header.innerHTML = '(no experiment selected)';
|
11504
11829
|
this.params_div.style.display = 'none';
|
11505
11830
|
return;
|
11506
11831
|
}
|
11507
11832
|
x.updateActorDimension();
|
11508
|
-
|
11509
|
-
|
11510
|
-
dim_count.innerHTML = pluralS(
|
11833
|
+
x.updateIteratorDimensions();
|
11834
|
+
x.inferAvailableDimensions();
|
11835
|
+
dim_count.innerHTML = pluralS(x.available_dimensions.length,
|
11836
|
+
'more dimension');
|
11511
11837
|
x.inferActualDimensions();
|
11512
11838
|
x.inferCombinations();
|
11513
11839
|
combi_count.innerHTML = pluralS(x.combinations.length, 'combination');
|
@@ -11525,11 +11851,10 @@ class GUIExperimentManager extends ExperimentManager {
|
|
11525
11851
|
}
|
11526
11852
|
document.getElementById('experiment-dim-table').innerHTML = tr.join('');
|
11527
11853
|
// Add button must be enabled only if there still are unused dimensions
|
11528
|
-
if(x.
|
11529
|
-
x.settings_dimensions.length + x.actor_dimensions.length) {
|
11530
|
-
document.getElementById('xp-d-add-btn').classList.add('v-disab');
|
11531
|
-
} else {
|
11854
|
+
if(x.available_dimensions.length > 0) {
|
11532
11855
|
document.getElementById('xp-d-add-btn').classList.remove('v-disab');
|
11856
|
+
} else {
|
11857
|
+
document.getElementById('xp-d-add-btn').classList.add('v-disab');
|
11533
11858
|
}
|
11534
11859
|
this.updateUpDownButtons();
|
11535
11860
|
tr.length = 0;
|
@@ -11655,7 +11980,7 @@ class GUIExperimentManager extends ExperimentManager {
|
|
11655
11980
|
for(let i = 0; i < x.variables.length; i++) {
|
11656
11981
|
vl.push(x.variables[i].displayName);
|
11657
11982
|
}
|
11658
|
-
vl.sort();
|
11983
|
+
vl.sort(ciCompare);
|
11659
11984
|
for(let i = 0; i < vl.length; i++) {
|
11660
11985
|
ol.push(['<option value="', vl[i], '"',
|
11661
11986
|
(vl[i] == x.selected_variable ? ' selected="selected"' : ''),
|
@@ -12250,6 +12575,61 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
|
|
12250
12575
|
}
|
12251
12576
|
}
|
12252
12577
|
|
12578
|
+
editIteratorRanges() {
|
12579
|
+
// Open dialog for editing iterator ranges
|
12580
|
+
const
|
12581
|
+
x = this.selected_experiment,
|
12582
|
+
md = this.iterator_modal,
|
12583
|
+
il = ['i', 'j', 'k'];
|
12584
|
+
if(x) {
|
12585
|
+
// NOTE: there are always 3 iterators (i, j k) so these have fixed
|
12586
|
+
// FROM and TO input fields in the dialog
|
12587
|
+
for(let i = 0; i < 3; i++) {
|
12588
|
+
const k = il[i];
|
12589
|
+
md.element(k + '-from').value = x.iterator_ranges[i][0];
|
12590
|
+
md.element(k + '-to').value = x.iterator_ranges[i][1];
|
12591
|
+
}
|
12592
|
+
this.iterator_modal.show();
|
12593
|
+
}
|
12594
|
+
}
|
12595
|
+
|
12596
|
+
modifyIteratorRanges() {
|
12597
|
+
const
|
12598
|
+
x = this.selected_experiment,
|
12599
|
+
md = this.iterator_modal;
|
12600
|
+
if(x) {
|
12601
|
+
// First validate all input fields (must be integer values)
|
12602
|
+
// NOTE: test using a copy so as not to overwrite values until OK
|
12603
|
+
const
|
12604
|
+
il = ['i', 'j', 'k'],
|
12605
|
+
ir = [[0, 0], [0, 0], [0, 0]],
|
12606
|
+
re = /^[\+\-]?[0-9]+$/;
|
12607
|
+
let el, f, t;
|
12608
|
+
for(let i = 0; i < 3; i++) {
|
12609
|
+
const k = il[i];
|
12610
|
+
el = md.element(k + '-from');
|
12611
|
+
f = el.value.trim() || '0';
|
12612
|
+
if(f === '' || re.test(f)) {
|
12613
|
+
el = md.element(k + '-to');
|
12614
|
+
t = el.value.trim() || '0';
|
12615
|
+
if(t === '' || re.test(t)) el = null;
|
12616
|
+
}
|
12617
|
+
// NULL value signals that field inputs are valid
|
12618
|
+
if(el === null) {
|
12619
|
+
ir[i] = [f, t];
|
12620
|
+
} else {
|
12621
|
+
el.focus();
|
12622
|
+
UI.warn('Iterator range limits must be integers (or default to 0)');
|
12623
|
+
return;
|
12624
|
+
}
|
12625
|
+
}
|
12626
|
+
// Input validated, so modify the iterator dimensions
|
12627
|
+
x.iterator_ranges = ir;
|
12628
|
+
this.updateDialog();
|
12629
|
+
}
|
12630
|
+
md.hide();
|
12631
|
+
}
|
12632
|
+
|
12253
12633
|
editSettingsDimensions() {
|
12254
12634
|
// Open dialog for editing model settings dimensions
|
12255
12635
|
const x = this.selected_experiment, rows = [];
|
@@ -12299,7 +12679,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
|
|
12299
12679
|
md.element('clear').innerHTML = clear;
|
12300
12680
|
md.element('code').value = sel[0];
|
12301
12681
|
md.element('string').value = sel[1];
|
12302
|
-
md.show('string');
|
12682
|
+
md.show(sel[0] ? 'string' : 'code');
|
12303
12683
|
}
|
12304
12684
|
|
12305
12685
|
modifySettingsSelector() {
|
@@ -12350,10 +12730,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
|
|
12350
12730
|
// NOTE: rename occurrence of code in dimension (should at most be 1)
|
12351
12731
|
const oc = x.settings_selectors[this.edited_selector_index].split('|')[0];
|
12352
12732
|
x.settings_selectors[this.edited_selector_index] = sel;
|
12353
|
-
|
12354
|
-
const si = x.settings_dimensions[i].indexOf(oc);
|
12355
|
-
if(si >= 0) x.settings_dimensions[i][si] = code;
|
12356
|
-
}
|
12733
|
+
x.renameSelectorInDimensions(oc, code);
|
12357
12734
|
}
|
12358
12735
|
}
|
12359
12736
|
md.hide();
|
@@ -12432,6 +12809,190 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
|
|
12432
12809
|
this.editSettingsDimensions();
|
12433
12810
|
}
|
12434
12811
|
|
12812
|
+
editCombinationDimensions() {
|
12813
|
+
// Open dialog for editing combination dimensions
|
12814
|
+
const
|
12815
|
+
x = this.selected_experiment,
|
12816
|
+
rows = [];
|
12817
|
+
if(x) {
|
12818
|
+
// Initialize selector list
|
12819
|
+
for(let i = 0; i < x.combination_selectors.length; i++) {
|
12820
|
+
const sel = x.combination_selectors[i].split('|');
|
12821
|
+
rows.push('<tr onclick="EXPERIMENT_MANAGER.editCombinationSelector(', i,
|
12822
|
+
');"><td width="25%">', sel[0], '</td><td>', sel[1], '</td></tr>');
|
12823
|
+
}
|
12824
|
+
this.combination_modal.element('s-table').innerHTML = rows.join('');
|
12825
|
+
// Initialize combination list
|
12826
|
+
rows.length = 0;
|
12827
|
+
for(let i = 0; i < x.combination_dimensions.length; i++) {
|
12828
|
+
const dim = x.combination_dimensions[i];
|
12829
|
+
rows.push('<tr onclick="EXPERIMENT_MANAGER.editCombinationDimension(', i,
|
12830
|
+
');"><td>', setString(dim), '</td></tr>');
|
12831
|
+
}
|
12832
|
+
this.combination_modal.element('d-table').innerHTML = rows.join('');
|
12833
|
+
this.combination_modal.show();
|
12834
|
+
// NOTE: clear infoline because dialog can generate warnings that would
|
12835
|
+
// otherwise remain visible while no longer relevant
|
12836
|
+
UI.setMessage('');
|
12837
|
+
}
|
12838
|
+
}
|
12839
|
+
|
12840
|
+
closeCombinationDimensions() {
|
12841
|
+
// Hide editor, and then update the experiment manager to reflect changes
|
12842
|
+
this.combination_modal.hide();
|
12843
|
+
this.updateDialog();
|
12844
|
+
}
|
12845
|
+
|
12846
|
+
editCombinationSelector(selnr) {
|
12847
|
+
const x = this.selected_experiment;
|
12848
|
+
if(!x) return;
|
12849
|
+
let action = 'Add',
|
12850
|
+
clear = '',
|
12851
|
+
sel = ['', ''];
|
12852
|
+
this.edited_combi_selector_index = selnr;
|
12853
|
+
if(selnr >= 0) {
|
12854
|
+
action = 'Edit';
|
12855
|
+
clear = '(clear to remove)';
|
12856
|
+
sel = x.combination_selectors[selnr].split('|');
|
12857
|
+
}
|
12858
|
+
const md = this.combination_selector_modal;
|
12859
|
+
md.element('action').innerHTML = action;
|
12860
|
+
md.element('clear').innerHTML = clear;
|
12861
|
+
md.element('code').value = sel[0];
|
12862
|
+
md.element('string').value = sel[1];
|
12863
|
+
md.show(sel[0] ? 'string' : 'code');
|
12864
|
+
}
|
12865
|
+
|
12866
|
+
modifyCombinationSelector() {
|
12867
|
+
// Accepts an "orthogonal" set of selectors
|
12868
|
+
let x = this.selected_experiment;
|
12869
|
+
if(x) {
|
12870
|
+
const
|
12871
|
+
md = this.combination_selector_modal,
|
12872
|
+
sc = md.element('code'),
|
12873
|
+
ss = md.element('string'),
|
12874
|
+
// Ignore invalid characters in the combination selector
|
12875
|
+
code = sc.value.replace(/[^\w\+\-\%]/g, ''),
|
12876
|
+
// Reduce comma's, semicolons and multiple spaces in the
|
12877
|
+
// combination string to a single space
|
12878
|
+
value = ss.value.trim().replace(/[\,\;\s]+/g, ' '),
|
12879
|
+
add = this.edited_combi_selector_index < 0;
|
12880
|
+
// Remove selector if either field has been cleared
|
12881
|
+
if(code.length === 0 || value.length === 0) {
|
12882
|
+
if(!add) {
|
12883
|
+
x.combination_selectors.splice(this.edited_combi_selector_index, 1);
|
12884
|
+
}
|
12885
|
+
} else {
|
12886
|
+
let ok = x.allDimensionSelectors.indexOf(code) < 0;
|
12887
|
+
if(ok) {
|
12888
|
+
// Check for uniqueness of code
|
12889
|
+
for(let i = 0; i < x.combination_selectors.length; i++) {
|
12890
|
+
// NOTE: ignore selector being edited, as this selector can be renamed
|
12891
|
+
if(i != this.edited_combi_selector_index &&
|
12892
|
+
x.combination_selectors[i].startsWith(code + '|')) ok = false;
|
12893
|
+
}
|
12894
|
+
}
|
12895
|
+
if(!ok) {
|
12896
|
+
UI.warn(`Combination selector "${code}" already defined`);
|
12897
|
+
sc.focus();
|
12898
|
+
return;
|
12899
|
+
}
|
12900
|
+
// Test for orthogonality (and existence!) of the selectors
|
12901
|
+
if(!x.orthogonalSelectors(value.split(' '))) {
|
12902
|
+
ss.focus();
|
12903
|
+
return;
|
12904
|
+
}
|
12905
|
+
// Combination selector has format code|space-separated selectors
|
12906
|
+
const sel = code + '|' + value;
|
12907
|
+
if(add) {
|
12908
|
+
x.combination_selectors.push(sel);
|
12909
|
+
} else {
|
12910
|
+
// NOTE: rename occurrence of code in dimension (should at most be 1)
|
12911
|
+
const oc = x.combination_selectors[this.edited_combi_selector_index].split('|')[0];
|
12912
|
+
x.combination_selectors[this.edited_combi_selector_index] = sel;
|
12913
|
+
for(let i = 0; i < x.combination_dimensions.length; i++) {
|
12914
|
+
const si = x.combination_dimensions[i].indexOf(oc);
|
12915
|
+
if(si >= 0) x.combination_dimensions[i][si] = code;
|
12916
|
+
}
|
12917
|
+
}
|
12918
|
+
}
|
12919
|
+
md.hide();
|
12920
|
+
}
|
12921
|
+
// Update combination dimensions dialog
|
12922
|
+
this.editCombinationDimensions();
|
12923
|
+
}
|
12924
|
+
|
12925
|
+
editCombinationDimension(dimnr) {
|
12926
|
+
const x = this.selected_experiment;
|
12927
|
+
if(!x) return;
|
12928
|
+
let action = 'Add',
|
12929
|
+
clear = '',
|
12930
|
+
value = '';
|
12931
|
+
this.edited_combi_dimension_index = dimnr;
|
12932
|
+
if(dimnr >= 0) {
|
12933
|
+
action = 'Edit';
|
12934
|
+
clear = '(clear to remove)';
|
12935
|
+
// NOTE: present to modeler as space-separated string
|
12936
|
+
value = x.combination_dimensions[dimnr].join(' ');
|
12937
|
+
}
|
12938
|
+
const md = this.combination_dimension_modal;
|
12939
|
+
md.element('action').innerHTML = action;
|
12940
|
+
md.element('clear').innerHTML = clear;
|
12941
|
+
md.element('string').value = value;
|
12942
|
+
md.show('string');
|
12943
|
+
}
|
12944
|
+
|
12945
|
+
modifyCombinationDimension() {
|
12946
|
+
let x = this.selected_experiment;
|
12947
|
+
if(x) {
|
12948
|
+
const
|
12949
|
+
add = this.edited_combi_dimension_index < 0,
|
12950
|
+
// Trim whitespace and reduce inner spacing to a single space
|
12951
|
+
dimstr = this.combination_dimension_modal.element('string').value.trim();
|
12952
|
+
// Remove dimension if field has been cleared
|
12953
|
+
if(dimstr.length === 0) {
|
12954
|
+
if(!add) {
|
12955
|
+
x.combination_dimensions.splice(this.edited_combi_dimension_index, 1);
|
12956
|
+
}
|
12957
|
+
} else {
|
12958
|
+
// Check for valid selector list
|
12959
|
+
const
|
12960
|
+
dim = dimstr.split(/\s+/g),
|
12961
|
+
ssl = [];
|
12962
|
+
// Get this experiment's combination selector list
|
12963
|
+
for(let i = 0; i < x.combination_selectors.length; i++) {
|
12964
|
+
ssl.push(x.combination_selectors[i].split('|')[0]);
|
12965
|
+
}
|
12966
|
+
// All selectors in string should have been defined
|
12967
|
+
let c = complement(dim, ssl);
|
12968
|
+
if(c.length > 0) {
|
12969
|
+
UI.warn('Combination dimension contains ' +
|
12970
|
+
pluralS(c.length, 'unknown selector') + ': ' + c.join(' '));
|
12971
|
+
return;
|
12972
|
+
}
|
12973
|
+
// All selectors should expand to non-overlapping selector sets
|
12974
|
+
if(!x.orthogonalCombinationDimensions(dim)) return;
|
12975
|
+
// Do not add when a (setwise) identical combination dimension exists
|
12976
|
+
for(let i = 0; i < x.combination_dimensions.length; i++) {
|
12977
|
+
const cd = x.combination_dimensions[i];
|
12978
|
+
if(intersection(dim, cd).length === dim.length) {
|
12979
|
+
UI.notify('Combination already defined: ' + setString(cd));
|
12980
|
+
return;
|
12981
|
+
}
|
12982
|
+
}
|
12983
|
+
// OK? Then add or modify
|
12984
|
+
if(add) {
|
12985
|
+
x.combination_dimensions.push(dim);
|
12986
|
+
} else {
|
12987
|
+
x.combination_dimensions[this.edited_combi_dimension_index] = dim;
|
12988
|
+
}
|
12989
|
+
}
|
12990
|
+
}
|
12991
|
+
this.combination_dimension_modal.hide();
|
12992
|
+
// Update combination dimensions dialog
|
12993
|
+
this.editCombinationDimensions();
|
12994
|
+
}
|
12995
|
+
|
12435
12996
|
editActorDimension() {
|
12436
12997
|
// Open dialog for editing the actor dimension
|
12437
12998
|
const x = this.selected_experiment, rows = [];
|
@@ -12655,22 +13216,10 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
|
|
12655
13216
|
const ol = [];
|
12656
13217
|
this.parameter_modal.element('type').innerHTML = type;
|
12657
13218
|
if(type === 'dimension') {
|
12658
|
-
|
12659
|
-
|
12660
|
-
|
12661
|
-
|
12662
|
-
dl.push(x.settings_dimensions[i]);
|
12663
|
-
}
|
12664
|
-
for(let i = 0; i < x.actor_dimensions.length; i++) {
|
12665
|
-
dl.push(x.actor_dimensions[i]);
|
12666
|
-
}
|
12667
|
-
for(let i = 0; i < dl.length; i++) {
|
12668
|
-
const d = dl[i];
|
12669
|
-
// NOTE: exclude dimensions already in the selected experiment
|
12670
|
-
if (x.hasDimension(d) < 0) {
|
12671
|
-
const ds = setString(d);
|
12672
|
-
ol.push(`<option value="${ds}">${ds}</option>`);
|
12673
|
-
}
|
13219
|
+
x.inferAvailableDimensions();
|
13220
|
+
for(let i = 0; i < x.available_dimensions.length; i++) {
|
13221
|
+
const ds = setString(x.available_dimensions[i]);
|
13222
|
+
ol.push(`<option value="${ds}">${ds}</option>`);
|
12674
13223
|
}
|
12675
13224
|
} else {
|
12676
13225
|
for(let i = 0; i < this.suitable_charts.length; i++) {
|
@@ -12737,7 +13286,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
|
|
12737
13286
|
if(x) {
|
12738
13287
|
x.excluded_selectors = this.exclude.value.replace(
|
12739
13288
|
/[\;\,]/g, ' ').trim().replace(
|
12740
|
-
/[^a-zA-Z0-9
|
13289
|
+
/[^a-zA-Z0-9\+\-\=\%\_\s]/g, '').split(/\s+/).join(' ');
|
12741
13290
|
this.exclude.value = x.excluded_selectors;
|
12742
13291
|
this.updateParameters();
|
12743
13292
|
}
|
@@ -13384,7 +13933,7 @@ class DocumentationManager {
|
|
13384
13933
|
}
|
13385
13934
|
lis.push(`<li>${dn}</li>`);
|
13386
13935
|
}
|
13387
|
-
lis.sort();
|
13936
|
+
lis.sort(ciCompare);
|
13388
13937
|
this.viewer.innerHTML = `<ul>${lis.join('')}</ul>`;
|
13389
13938
|
}
|
13390
13939
|
}
|
@@ -13413,7 +13962,7 @@ class DocumentationManager {
|
|
13413
13962
|
for(let i = 0; i < iol.length; i++) {
|
13414
13963
|
lis.push(`<li>${iol[i].displayName}</li>`);
|
13415
13964
|
}
|
13416
|
-
lis.sort();
|
13965
|
+
lis.sort(ciCompare);
|
13417
13966
|
this.viewer.innerHTML = `<ul>${lis.join('')}</ul>`;
|
13418
13967
|
}
|
13419
13968
|
}
|
@@ -13731,7 +14280,7 @@ class Finder {
|
|
13731
14280
|
}
|
13732
14281
|
}
|
13733
14282
|
}
|
13734
|
-
enl.sort();
|
14283
|
+
enl.sort(ciCompare);
|
13735
14284
|
}
|
13736
14285
|
document.getElementById('finder-entity-imgs').innerHTML = imgs;
|
13737
14286
|
let seid = 'etr';
|
@@ -14584,6 +15133,9 @@ class UndoStack {
|
|
14584
15133
|
this.undoables.push(ue);
|
14585
15134
|
// Update the GUI buttons
|
14586
15135
|
UI.updateButtons();
|
15136
|
+
// NOTE: update the Finder only if needed, and with a delay because
|
15137
|
+
// the "prepare for undo" is performed before the actual change
|
15138
|
+
if(action !== 'move') setTimeout(() => { FINDER.updateDialog(); }, 5);
|
14587
15139
|
//console.log('push ' + action);
|
14588
15140
|
//console.log(UNDO_STACK);
|
14589
15141
|
}
|
@@ -14880,6 +15432,8 @@ if (MODEL.focal_cluster === fc) {
|
|
14880
15432
|
MODEL.focal_cluster.clearAllProcesses();
|
14881
15433
|
UI.drawDiagram(MODEL);
|
14882
15434
|
UI.updateButtons();
|
15435
|
+
// Update the Finder if needed
|
15436
|
+
if(ue.action !== 'move') FINDER.updateDialog();
|
14883
15437
|
}
|
14884
15438
|
//console.log('undo');
|
14885
15439
|
//console.log(UNDO_STACK);
|
@@ -14931,6 +15485,7 @@ if (MODEL.focal_cluster === fc) {
|
|
14931
15485
|
MODEL.focal_cluster.clearAllProcesses();
|
14932
15486
|
UI.drawDiagram(MODEL);
|
14933
15487
|
UI.updateButtons();
|
15488
|
+
if(re.action !== 'move') FINDER.updateDialog();
|
14934
15489
|
}
|
14935
15490
|
}
|
14936
15491
|
} // END of class UndoStack
|