linny-r 1.9.2 → 2.0.1
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 +1 -1
- package/README.md +4 -4
- package/package.json +1 -1
- package/server.js +1 -1
- package/static/images/eq-negated.png +0 -0
- package/static/images/power.png +0 -0
- package/static/images/tex.png +0 -0
- package/static/index.html +225 -10
- package/static/linny-r.css +458 -8
- package/static/scripts/linny-r-ctrl.js +6 -4
- package/static/scripts/linny-r-gui-actor-manager.js +1 -1
- package/static/scripts/linny-r-gui-chart-manager.js +20 -13
- package/static/scripts/linny-r-gui-constraint-editor.js +410 -50
- package/static/scripts/linny-r-gui-controller.js +127 -12
- package/static/scripts/linny-r-gui-dataset-manager.js +28 -20
- package/static/scripts/linny-r-gui-documentation-manager.js +11 -3
- package/static/scripts/linny-r-gui-equation-manager.js +1 -1
- package/static/scripts/linny-r-gui-experiment-manager.js +1 -1
- package/static/scripts/linny-r-gui-expression-editor.js +7 -1
- package/static/scripts/linny-r-gui-file-manager.js +31 -13
- package/static/scripts/linny-r-gui-finder.js +1 -1
- package/static/scripts/linny-r-gui-model-autosaver.js +1 -1
- package/static/scripts/linny-r-gui-monitor.js +1 -1
- package/static/scripts/linny-r-gui-paper.js +108 -25
- package/static/scripts/linny-r-gui-power-grid-manager.js +529 -0
- package/static/scripts/linny-r-gui-receiver.js +1 -1
- package/static/scripts/linny-r-gui-repository-browser.js +1 -1
- package/static/scripts/linny-r-gui-scale-unit-manager.js +1 -1
- package/static/scripts/linny-r-gui-sensitivity-analysis.js +1 -1
- package/static/scripts/linny-r-gui-tex-manager.js +110 -0
- package/static/scripts/linny-r-gui-undo-redo.js +1 -1
- package/static/scripts/linny-r-milp.js +1 -1
- package/static/scripts/linny-r-model.js +1016 -155
- package/static/scripts/linny-r-utils.js +3 -3
- package/static/scripts/linny-r-vm.js +714 -248
- package/static/show-diff.html +1 -1
- package/static/show-png.html +1 -1
@@ -11,7 +11,7 @@ dialog for the Linny-R constraint editor.
|
|
11
11
|
*/
|
12
12
|
|
13
13
|
/*
|
14
|
-
Copyright (c) 2017-
|
14
|
+
Copyright (c) 2017-2024 Delft University of Technology
|
15
15
|
|
16
16
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
17
17
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -40,7 +40,6 @@ class ConstraintEditor {
|
|
40
40
|
this.from_name = document.getElementById('constraint-from-name');
|
41
41
|
this.to_name = document.getElementById('constraint-to-name');
|
42
42
|
this.bl_type = document.getElementById('bl-type');
|
43
|
-
this.bl_selectors = document.getElementById('bl-selectors');
|
44
43
|
this.soc_direct = document.getElementById('constraint-soc-direct');
|
45
44
|
this.soc = document.getElementById('constraint-share-of-cost');
|
46
45
|
this.soc_div = document.getElementById('constraint-soc');
|
@@ -78,25 +77,69 @@ class ConstraintEditor {
|
|
78
77
|
() => CONSTRAINT_EDITOR.addBoundLine());
|
79
78
|
this.bl_type.addEventListener('change',
|
80
79
|
() => CONSTRAINT_EDITOR.changeLineType());
|
81
|
-
this.bl_selectors.addEventListener('blur',
|
82
|
-
() => CONSTRAINT_EDITOR.changeLineSelectors());
|
83
80
|
this.soc.addEventListener('blur',
|
84
81
|
() => CONSTRAINT_EDITOR.changeShareOfCost());
|
82
|
+
this.bl_data_btn = document.getElementById('bl-data-btn');
|
83
|
+
this.bl_data_btn.addEventListener('click',
|
84
|
+
() => CONSTRAINT_EDITOR.showBoundLineModal());
|
85
85
|
this.delete_bl_btn = document.getElementById('del-bl-btn');
|
86
86
|
this.delete_bl_btn.addEventListener('click',
|
87
87
|
() => CONSTRAINT_EDITOR.deleteBoundLine());
|
88
|
+
// Prepare the "precise point" dialog.
|
88
89
|
this.point_modal = new ModalDialog('boundline-point');
|
89
90
|
this.point_modal.ok.addEventListener(
|
90
91
|
'click', () => CONSTRAINT_EDITOR.setPointPosition());
|
91
92
|
this.point_modal.cancel.addEventListener(
|
92
93
|
'click', () => CONSTRAINT_EDITOR.point_modal.hide());
|
93
|
-
//
|
94
|
+
// Also prepare the boundline modal.
|
95
|
+
this.boundline_modal = new ModalDialog('boundline-data');
|
96
|
+
this.boundline_modal.close.addEventListener(
|
97
|
+
'click', () => CONSTRAINT_EDITOR.updateBoundLineProperties());
|
98
|
+
this.boundline_modal.element('edit-btn').addEventListener(
|
99
|
+
'click', () => CONSTRAINT_EDITOR.startEditing());
|
100
|
+
this.boundline_modal.element('save-btn').addEventListener(
|
101
|
+
'click', () => CONSTRAINT_EDITOR.stopEditing(true));
|
102
|
+
this.boundline_modal.element('cancel-btn').addEventListener(
|
103
|
+
'click', () => CONSTRAINT_EDITOR.stopEditing(false));
|
104
|
+
this.boundline_modal.element('url').addEventListener(
|
105
|
+
'blur', () => CONSTRAINT_EDITOR.loadPointData());
|
106
|
+
const bls = this.boundline_modal.element('series');
|
107
|
+
bls.addEventListener('keyup', () => CONSTRAINT_EDITOR.updateLine());
|
108
|
+
bls.addEventListener('click', () => CONSTRAINT_EDITOR.updateLine());
|
109
|
+
// NOTE: Chart should show default line when cursor is not over data.
|
110
|
+
this.boundline_modal.element('series-table').addEventListener(
|
111
|
+
'mouseout', () => CONSTRAINT_EDITOR.showDefaultBoundLine());
|
112
|
+
// Make boundline selector buttons responsive.
|
113
|
+
this.selector_btns = 'bl-rename-sel bl-edit-sel bl-delete-sel';
|
114
|
+
document.getElementById('bl-add-sel-btn').addEventListener(
|
115
|
+
'click', () => CONSTRAINT_EDITOR.promptForSelector());
|
116
|
+
document.getElementById('bl-rename-sel-btn').addEventListener(
|
117
|
+
'click', () => CONSTRAINT_EDITOR.promptForSelector('rename'));
|
118
|
+
document.getElementById('bl-edit-sel-btn').addEventListener(
|
119
|
+
'click', () => CONSTRAINT_EDITOR.editExpression());
|
120
|
+
document.getElementById('bl-delete-sel-btn').addEventListener(
|
121
|
+
'click', () => CONSTRAINT_EDITOR.deleteSelector());
|
122
|
+
// Prepare boundline selector modals.
|
123
|
+
this.new_selector_modal = new ModalDialog('new-selector');
|
124
|
+
this.new_selector_modal.ok.addEventListener(
|
125
|
+
'click', () => CONSTRAINT_EDITOR.newSelector());
|
126
|
+
this.new_selector_modal.cancel.addEventListener(
|
127
|
+
'click', () => CONSTRAINT_EDITOR.new_selector_modal.hide());
|
128
|
+
this.rename_selector_modal = new ModalDialog('rename-selector');
|
129
|
+
this.rename_selector_modal.ok.addEventListener(
|
130
|
+
'click', () => CONSTRAINT_EDITOR.renameSelector());
|
131
|
+
this.rename_selector_modal.cancel.addEventListener(
|
132
|
+
'click', () => CONSTRAINT_EDITOR.rename_selector_modal.hide());
|
133
|
+
// The chart is stored as an SVG string.
|
94
134
|
this.svg = '';
|
95
|
-
//
|
135
|
+
// The line path and contour path SVG.
|
136
|
+
this.line_path_svg = '';
|
137
|
+
this.contour_path_svg = '';
|
138
|
+
// Scale, origin X and Y assume a 300x300 px square chart area.
|
96
139
|
this.scale = 3;
|
97
140
|
this.oX = 25;
|
98
141
|
this.oY = 315;
|
99
|
-
// 0 => silver, LE => orange/red, GE => cyan/blue, EQ => purple
|
142
|
+
// 0 => silver, LE => orange/red, GE => cyan/blue, EQ => purple.
|
100
143
|
this.line_color = ['#a0a0a0', '#c04000', '#0040c0', '#9000a0'];
|
101
144
|
// Use brighter shades if selected (darker for gray)
|
102
145
|
this.selected_color = ['#808080', '#ff8040', '#00b0d0', '#a800ff'];
|
@@ -105,37 +148,41 @@ class ConstraintEditor {
|
|
105
148
|
// Cursor position in chart coordinates (100 x 100 grid)
|
106
149
|
this.pos_x = 0;
|
107
150
|
this.pos_y = 0;
|
108
|
-
// `on_line`: the first bound line object detected under the cursor
|
151
|
+
// `on_line`: the first bound line object detected under the cursor.
|
109
152
|
this.on_line = null;
|
110
|
-
// `on_point`: index of point under the cursor
|
153
|
+
// `on_point`: index of point under the cursor.
|
111
154
|
this.on_point = -1;
|
112
155
|
this.dragged_point = -1;
|
113
156
|
this.selected_point = -1;
|
157
|
+
this.selected_selector = false;
|
114
158
|
this.last_time_clicked = 0;
|
115
159
|
this.cursor = 'default';
|
160
|
+
// Start in data viewing mode.
|
161
|
+
this.stopEditing(false);
|
116
162
|
// Properties for tracking which constraint is being edited.
|
117
|
-
this.edited_constraint = null;
|
118
163
|
this.from_node = null;
|
119
164
|
this.to_node = null;
|
120
|
-
// The constraint
|
121
|
-
|
165
|
+
// The constraint (model entity) being added or modified.
|
166
|
+
this.edited_constraint = null;
|
167
|
+
// The constraint object that is being modified: either a new instance,
|
168
|
+
// or a *copy* of edited_constraint so changes can be ignored on "canel".
|
122
169
|
this.constraint = null;
|
123
170
|
// List of constraints when multiple constraints are edited.
|
124
171
|
this.group = [];
|
172
|
+
// Boundline selector expression being edited.
|
173
|
+
this.edited_expression = null;
|
125
174
|
// NOTE: All edits will be ignored unless the modeler clicks OK.
|
126
175
|
}
|
127
176
|
|
128
|
-
get
|
177
|
+
get twoClicks() {
|
178
|
+
// Return TRUE iff two mouse clicks occurred within 300 ms.
|
129
179
|
const
|
130
180
|
now = Date.now(),
|
131
181
|
dt = now - this.last_time_clicked;
|
132
182
|
this.last_time_clicked = now;
|
133
|
-
if(
|
134
|
-
|
135
|
-
|
136
|
-
this.last_time_clicked = 0;
|
137
|
-
return true;
|
138
|
-
}
|
183
|
+
if(dt < 300) {
|
184
|
+
this.last_time_clicked = 0;
|
185
|
+
return true;
|
139
186
|
}
|
140
187
|
return false;
|
141
188
|
}
|
@@ -162,14 +209,21 @@ class ConstraintEditor {
|
|
162
209
|
|
163
210
|
mouseDown(e) {
|
164
211
|
// The onMouseDown response of the constraint editor's graph area.
|
212
|
+
const two = this.twoClicks;
|
165
213
|
if(this.adding_point) {
|
166
214
|
this.doAddPointToLine();
|
167
|
-
} else if((e.altKey || this.doubleClicked) && this.on_point >= 0) {
|
168
|
-
this.positionPoint();
|
169
215
|
} else if(this.on_line) {
|
216
|
+
const
|
217
|
+
same_line = two && this.selected === this.on_line,
|
218
|
+
same_point = two && this.selected_point === this.on_point;
|
170
219
|
this.selectBoundLine(this.on_line);
|
171
220
|
this.dragged_point = this.on_point;
|
172
221
|
this.selected_point = this.on_point;
|
222
|
+
if(this.on_point >= 0 && (e.altKey || same_point)) {
|
223
|
+
this.positionPoint();
|
224
|
+
} else if(this.on_line && (e.altKey || same_line)) {
|
225
|
+
this.showBoundLineModal();
|
226
|
+
}
|
173
227
|
} else {
|
174
228
|
this.selected = null;
|
175
229
|
this.dragged_point = -1;
|
@@ -334,7 +388,7 @@ class ConstraintEditor {
|
|
334
388
|
}
|
335
389
|
|
336
390
|
doAddPointToLine() {
|
337
|
-
// Actually add point to selected line
|
391
|
+
// Actually add point to selected line.
|
338
392
|
if(!this.selected) return;
|
339
393
|
const
|
340
394
|
p = [this.pos_x, this.pos_y],
|
@@ -342,6 +396,7 @@ class ConstraintEditor {
|
|
342
396
|
let i = 0;
|
343
397
|
while(i < lp.length && lp[i][0] < p[0]) i++;
|
344
398
|
lp.splice(i, 0, p);
|
399
|
+
this.selected.storePoints();
|
345
400
|
this.selected_point = i;
|
346
401
|
this.dragged_point = i;
|
347
402
|
this.draw();
|
@@ -355,31 +410,330 @@ class ConstraintEditor {
|
|
355
410
|
if(this.selected && this.selected_point > 0 &&
|
356
411
|
this.selected_point < this.selected.points.length - 1) {
|
357
412
|
this.selected.points.splice(this.selected_point, 1);
|
413
|
+
this.selected.storePoints();
|
358
414
|
this.selected_point = -1;
|
359
415
|
this.draw();
|
360
416
|
}
|
361
417
|
}
|
362
418
|
|
363
419
|
changeLineType() {
|
364
|
-
//
|
420
|
+
// Change type of selected boundline.
|
365
421
|
if(this.selected) {
|
366
422
|
this.selected.type = parseInt(this.bl_type.value);
|
367
423
|
this.draw();
|
368
424
|
}
|
369
425
|
}
|
370
426
|
|
371
|
-
|
372
|
-
|
427
|
+
loadPointData() {
|
428
|
+
const md = this.boundline_modal;
|
429
|
+
let url = md.element('url').value.trim();
|
430
|
+
if(this.selected && url) {
|
431
|
+
FILE_MANAGER.getRemoteData(this.selected, url);
|
432
|
+
}
|
433
|
+
}
|
434
|
+
|
435
|
+
startEditing() {
|
436
|
+
const
|
437
|
+
md = this.boundline_modal,
|
438
|
+
edit_btn = md.element('edit-btn'),
|
439
|
+
save_btn = md.element('save-btn'),
|
440
|
+
cancel_btn = md.element('cancel-btn'),
|
441
|
+
tbl = md.element('series-table'),
|
442
|
+
txt = md.element('series');
|
443
|
+
edit_btn.classList.add('off');
|
444
|
+
save_btn.classList.remove('off');
|
445
|
+
cancel_btn.classList.remove('off');
|
446
|
+
tbl.style.display = 'none';
|
447
|
+
txt.value = this.selected.pointDataString;
|
448
|
+
txt.style.display = 'block';
|
449
|
+
txt.focus();
|
450
|
+
txt.selectionStart = 0;
|
451
|
+
txt.selectionEnd = 0;
|
452
|
+
md.element('line').style.display = 'block';
|
453
|
+
this.updateLine();
|
454
|
+
UI.disableButtons(this.selector_btns);
|
455
|
+
}
|
456
|
+
|
457
|
+
stopEditing(save=false) {
|
458
|
+
if(!this.selected) return;
|
459
|
+
const
|
460
|
+
bl = this.selected,
|
461
|
+
md = this.boundline_modal,
|
462
|
+
edit_btn = md.element('edit-btn'),
|
463
|
+
save_btn = md.element('save-btn'),
|
464
|
+
cancel_btn = md.element('cancel-btn'),
|
465
|
+
tbl = md.element('series-table'),
|
466
|
+
txt = md.element('series');
|
467
|
+
if(save) {
|
468
|
+
bl.unpackPointDataString(txt.value);
|
469
|
+
}
|
470
|
+
edit_btn.classList.remove('off');
|
471
|
+
save_btn.classList.add('off');
|
472
|
+
cancel_btn.classList.add('off');
|
473
|
+
txt.style.display = 'none';
|
474
|
+
md.element('line').style.display = 'none';
|
475
|
+
tbl.innerHTML = this.boundLineDataTable;
|
476
|
+
tbl.style.display = 'block';
|
477
|
+
if(this.selected_selector) {
|
478
|
+
UI.enableButtons(this.selector_btns);
|
479
|
+
} else {
|
480
|
+
UI.disableButtons(this.selector_btns);
|
481
|
+
}
|
482
|
+
}
|
483
|
+
|
484
|
+
updateLine() {
|
485
|
+
const
|
486
|
+
md = this.boundline_modal,
|
487
|
+
txt = md.element('series'),
|
488
|
+
ln = md.element('line-number'),
|
489
|
+
lc = md.element('line-count');
|
490
|
+
ln.innerHTML = 'line ' + txt.value.substring(0, txt.selectionStart)
|
491
|
+
.split(';').length;
|
492
|
+
lc.innerHTML = 'of ' + txt.value.split(';').length;
|
493
|
+
}
|
494
|
+
|
495
|
+
get boundLineDataTable() {
|
496
|
+
// Return *inner* HTML for point coordinates table.
|
497
|
+
if(!this.selected) return ;
|
498
|
+
const
|
499
|
+
bl = this.selected,
|
500
|
+
tr = '<tr class="dataset" onmouseover="CONSTRAINT_EDITOR.' +
|
501
|
+
'showDataBoundLine(%N)"><td class="bl-odnr">%N.</td>' +
|
502
|
+
'<td class="bl-od">%D</td></tr>',
|
503
|
+
lines = [tr.replaceAll('%N', 0).replace('%D',
|
504
|
+
bl.pointsDataString + '<span class="grit">(default)</span>')];
|
505
|
+
for(let i = 0; i < bl.point_data.length; i++) {
|
506
|
+
lines.push(tr.replaceAll('%N', i + 1)
|
507
|
+
.replace('%D', bl.point_data[i].join(' ')));
|
508
|
+
}
|
509
|
+
return lines.join('');
|
510
|
+
}
|
511
|
+
|
512
|
+
get boundLineSelectorTable() {
|
513
|
+
// Return *inner* HTML for the boundline selector table.
|
514
|
+
if(!this.selected) return '';
|
515
|
+
const
|
516
|
+
bl = this.selected,
|
517
|
+
html = [],
|
518
|
+
onclk = ` onclick="CONSTRAINT_EDITOR.selectSelector(event, '');"`;
|
519
|
+
for(let i = 0; i < bl.selectors.length; i++) {
|
520
|
+
const
|
521
|
+
sel = bl.selectors[i],
|
522
|
+
ocr = onclk.replace("''", `'${sel.selector}'`),
|
523
|
+
ss = (sel.selector === this.selected_selector ? ' sel-set' : '');
|
524
|
+
html.push(`<tr id="blstr${i}" class="dataset-modif${ss}">`,
|
525
|
+
'<td class="dataset-selector"');
|
526
|
+
if(i === 0) {
|
527
|
+
html.push(' style="background-color: #e0e0e0; font-style: italic" ',
|
528
|
+
'title="Default line index will be used when no experiment is running"');
|
529
|
+
}
|
530
|
+
let ls = '',
|
531
|
+
rs = '';
|
532
|
+
if(sel.grouping) {
|
533
|
+
ls = '<span class="blpoints">';
|
534
|
+
rs = '</span>';
|
535
|
+
}
|
536
|
+
if(!sel.expression.isStatic) {
|
537
|
+
ls += '<em>';
|
538
|
+
rs = '</em>' + rs;
|
539
|
+
}
|
540
|
+
html.push(ocr.replace("');", "', false);"), '>', sel.selector,
|
541
|
+
'</td><td class="dataset-expression"', ocr, '>',
|
542
|
+
ls, sel.expression.text, rs, '</td></tr>');
|
543
|
+
}
|
544
|
+
return html.join('');
|
545
|
+
}
|
546
|
+
|
547
|
+
selectSelector(event, id, x=true) {
|
548
|
+
// Select selector, or when double-clicked, edit its expression when
|
549
|
+
// x = TRUE, or the name of the selector when x = FALSE.
|
550
|
+
if(!this.selected) return;
|
551
|
+
const edit = (event.altKey ||
|
552
|
+
(this.twoClicks && id === this.selected_selector));
|
553
|
+
this.selected_selector = id;
|
554
|
+
if(edit) {
|
555
|
+
if(x) {
|
556
|
+
this.editExpression();
|
557
|
+
} else {
|
558
|
+
this.promptForSelector('rename');
|
559
|
+
}
|
560
|
+
return;
|
561
|
+
}
|
562
|
+
this.updateSelectorTable();
|
563
|
+
UI.enableButtons(this.selector_btns);
|
564
|
+
// Do not permit deleting the default selector.
|
565
|
+
if(id === '(default)') UI.disableButtons('bl-delete-sel');
|
566
|
+
}
|
567
|
+
|
568
|
+
promptForSelector(dlg) {
|
569
|
+
let ms = '',
|
570
|
+
md = this.new_selector_modal;
|
571
|
+
if(dlg === 'rename') {
|
572
|
+
if(this.selected_selector) ms = this.selected_selector;
|
573
|
+
md = this.rename_selector_modal;
|
574
|
+
}
|
575
|
+
md.element('type').innerText = 'boundline selector';
|
576
|
+
md.element('name').value = ms;
|
577
|
+
md.show('name');
|
578
|
+
}
|
579
|
+
|
580
|
+
newSelector() {
|
581
|
+
if(!this.selected) return;
|
582
|
+
const md = this.new_selector_modal;
|
583
|
+
// NOTE: Selector modal is also used by constraint editor.
|
584
|
+
if(md.element('type').innerText !== 'boundline selector') return;
|
585
|
+
const
|
586
|
+
bl = this.selected,
|
587
|
+
sel = md.element('name').value.trim(),
|
588
|
+
bls = bl.addSelector(sel);
|
589
|
+
if(bls) {
|
590
|
+
this.selected_selector = bls.selector;
|
591
|
+
// NOTE: Update dimensions only if boundline now has 2 or more
|
592
|
+
// selectors.
|
593
|
+
const sl = bl.selectorList;
|
594
|
+
if(sl.length > 1) MODEL.expandDimension(sl);
|
595
|
+
md.hide();
|
596
|
+
this.updateSelectorTable();
|
597
|
+
}
|
598
|
+
}
|
599
|
+
|
600
|
+
renameSelector() {
|
601
|
+
if(!this.selected) return;
|
602
|
+
const md = this.rename_selector_modal;
|
603
|
+
// NOTE: Selector modal is also used by constraint editor.
|
604
|
+
if(md.element('type').innerText !== 'boundline selector') return;
|
605
|
+
const
|
606
|
+
bl = this.selected,
|
607
|
+
sel = MODEL.validSelector(md.element('name').value.trim()),
|
608
|
+
bls = bl.selectorByName(this.selected_selector);
|
609
|
+
if(bls && sel) {
|
610
|
+
bls.selector = sel;
|
611
|
+
bl.selectors.sort((a, b) => compareSelectors(a.selector, b.selector));
|
612
|
+
}
|
613
|
+
md.hide();
|
614
|
+
this.updateSelectorTable();
|
615
|
+
}
|
616
|
+
|
617
|
+
editExpression() {
|
618
|
+
if(!this.selected) return;
|
619
|
+
const
|
620
|
+
bl = this.selected,
|
621
|
+
bls = bl.selectorByName(this.selected_selector);
|
622
|
+
if(bls) {
|
623
|
+
this.edited_expression = bls.expression;
|
624
|
+
const md = UI.modals.expression;
|
625
|
+
md.element('property').innerHTML = 'boundline selector';
|
626
|
+
md.element('text').value = bls.expression.text;
|
627
|
+
document.getElementById('variable-obj').value = 0;
|
628
|
+
X_EDIT.updateVariableBar();
|
629
|
+
X_EDIT.clearStatusBar();
|
630
|
+
md.show('text');
|
631
|
+
}
|
632
|
+
}
|
633
|
+
|
634
|
+
modifyExpression(x, grouping) {
|
635
|
+
// Update boundline index expression.
|
636
|
+
if(!this.selected) return;
|
637
|
+
const
|
638
|
+
bl = this.selected,
|
639
|
+
bls = bl.selectorByName(this.selected_selector);
|
640
|
+
if(!bls) return;
|
641
|
+
const blsx = bls.expression;
|
642
|
+
// Double-check that selector expression is indeed being edited.
|
643
|
+
if(blsx !== this.edited_expression) {
|
644
|
+
console.log('ERROR: boundline selector expression mismatch',
|
645
|
+
x, bls, this.edited_expression);
|
646
|
+
return;
|
647
|
+
}
|
648
|
+
bls.grouping = grouping;
|
649
|
+
// Update and compile expression only if it has been changed.
|
650
|
+
if(x != blsx.text) {
|
651
|
+
blsx.text = x;
|
652
|
+
blsx.compile();
|
653
|
+
if(grouping && blsx.isStatic) {
|
654
|
+
// Check whether the point coordinates are valid.
|
655
|
+
const
|
656
|
+
r1 = blsx.result(1),
|
657
|
+
r2 = r1.slice();
|
658
|
+
bls.boundline.validatePoints(r2);
|
659
|
+
if(r1.join(';') !== r2.join(';')) {
|
660
|
+
UI.warn('Points expression for <tt>' + bls.selector +
|
661
|
+
'</tt> will evaluate as ' + r2.join('; '));
|
662
|
+
}
|
663
|
+
}
|
664
|
+
}
|
665
|
+
// Clear expression results, just to be neat.
|
666
|
+
blsx.reset();
|
667
|
+
// Clear the `selected_expression` property of the constraint editor.
|
668
|
+
this.edited_expression = null;
|
669
|
+
this.updateSelectorTable();
|
670
|
+
}
|
671
|
+
|
672
|
+
deleteSelector() {
|
673
|
+
// Delete modifier from selected dataset
|
674
|
+
if(!this.selected) return;
|
675
|
+
const
|
676
|
+
bl = this.selected,
|
677
|
+
bls = this.selected.selectorByName(this.selected_selector);
|
678
|
+
if(bls && bls.selector) {
|
679
|
+
// If it is not the default selector, simply remove it from the list.
|
680
|
+
const i = bl.selectors.indexOf(bls);
|
681
|
+
if(i > 0) bl.selectors.splice(i, 1);
|
682
|
+
this.selected_selector = false;
|
683
|
+
this.updateSelectorTable();
|
684
|
+
MODEL.updateDimensions();
|
685
|
+
}
|
686
|
+
}
|
687
|
+
|
688
|
+
updateSelectorTable() {
|
689
|
+
this.boundline_modal.element('sel-table')
|
690
|
+
.innerHTML = this.boundLineSelectorTable;
|
691
|
+
}
|
692
|
+
|
693
|
+
showBoundLineModal() {
|
694
|
+
// Open modal to modify data properties of selected boundline.
|
695
|
+
if(!this.selected) return;
|
696
|
+
// Ensure that bound line does not have a selected or dragged point.
|
697
|
+
this.on_point = -1;
|
698
|
+
this.dragged_point = -1;
|
699
|
+
this.selected_point = -1;
|
700
|
+
const
|
701
|
+
bl = this.selected,
|
702
|
+
md = this.boundline_modal;
|
703
|
+
md.element('url').value = bl.url;
|
704
|
+
this.updateSelectorTable();
|
705
|
+
this.stopEditing();
|
706
|
+
md.show();
|
707
|
+
}
|
708
|
+
|
709
|
+
showDefaultBoundLine() {
|
710
|
+
// Restore and redraw default bound line.
|
711
|
+
if(this.selected) this.selected.restorePoints();
|
712
|
+
this.draw();
|
713
|
+
}
|
714
|
+
|
715
|
+
updateBoundLineProperties() {
|
716
|
+
// Change experiment run selectors of selected boundline.
|
373
717
|
if(this.selected) {
|
374
|
-
const
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
this.
|
379
|
-
|
718
|
+
const
|
719
|
+
bl = this.selected,
|
720
|
+
md = this.boundline_modal;
|
721
|
+
bl.url = md.element('url').value;
|
722
|
+
this.showDefaultBoundLine();
|
723
|
+
md.hide();
|
380
724
|
}
|
381
725
|
}
|
382
726
|
|
727
|
+
showDataBoundLine(index) {
|
728
|
+
// Redraw diagram with selected boundline now having its points based
|
729
|
+
// on data for the given index.
|
730
|
+
if(!this.selected) return;
|
731
|
+
const bl = this.selected;
|
732
|
+
bl.setPointsFromData(index);
|
733
|
+
this.draw();
|
734
|
+
bl.restorePoints();
|
735
|
+
}
|
736
|
+
|
383
737
|
changeShareOfCost() {
|
384
738
|
// Validates input of share-of-cost field
|
385
739
|
const soc = UI.validNumericInput('constraint-share-of-cost', 'share of cost');
|
@@ -485,6 +839,8 @@ class ConstraintEditor {
|
|
485
839
|
positionPoint() {
|
486
840
|
// Prompt modeler for precise point coordinates.
|
487
841
|
if(this.selected_point < 0) return;
|
842
|
+
// Prevent that "drag point" state persists after ESC o cancel.
|
843
|
+
this.dragged_point = -1;
|
488
844
|
const
|
489
845
|
md = this.point_modal,
|
490
846
|
pc = this.point_div.innerHTML.split(', ');
|
@@ -531,7 +887,7 @@ class ConstraintEditor {
|
|
531
887
|
l = this.selected,
|
532
888
|
pi = this.dragged_point,
|
533
889
|
lpi = l.points.length - 1;
|
534
|
-
// Check -- just in case
|
890
|
+
// Check -- just in case.
|
535
891
|
if(!l || pi < 0 || pi > lpi) return;
|
536
892
|
let p = l.points[pi],
|
537
893
|
px = p[0],
|
@@ -540,22 +896,23 @@ class ConstraintEditor {
|
|
540
896
|
maxx = (pi === 0 ? 0 : (pi === lpi ? 100 : l.points[pi + 1][0])),
|
541
897
|
newx = Math.min(maxx, Math.max(minx, x)),
|
542
898
|
newy = Math.min(100, Math.max(0, y));
|
543
|
-
// No action needed unless point has been moved
|
899
|
+
// No action needed unless point has been moved.
|
544
900
|
if(newx !== px || newy !== py) {
|
545
901
|
p[0] = newx;
|
546
902
|
p[1] = newy;
|
903
|
+
l.storePoints();
|
547
904
|
this.draw();
|
548
905
|
this.updateEquation();
|
549
906
|
}
|
550
907
|
}
|
551
908
|
|
552
909
|
updateStatus() {
|
553
|
-
//
|
554
|
-
// controls.
|
910
|
+
// Display cursor position as X and Y (in chart coordinates), and
|
911
|
+
// update controls.
|
555
912
|
this.pos_x_div.innerHTML = 'X = ' + this.pos_x.toPrecision(3);
|
556
913
|
this.pos_y_div.innerHTML = 'Y = ' + this.pos_y.toPrecision(3);
|
557
914
|
this.point_div.innerHTML = '';
|
558
|
-
const blbtns = 'add-point del-bl';
|
915
|
+
const blbtns = 'add-point bl-data del-bl';
|
559
916
|
if(this.selected) {
|
560
917
|
if(this.selected_point >= 0) {
|
561
918
|
const p = this.selected.points[this.selected_point];
|
@@ -568,26 +925,20 @@ class ConstraintEditor {
|
|
568
925
|
.replace(/\.$/, '');
|
569
926
|
this.point_div.innerHTML = `(${px}, ${py})`;
|
570
927
|
}
|
571
|
-
// Check whether selected point is an end point
|
928
|
+
// Check whether selected point is an end point.
|
572
929
|
const ep = this.selected_point === 0 ||
|
573
930
|
this.selected_point === this.selected.points.length - 1;
|
574
|
-
// If so, do not allow deletion
|
931
|
+
// If so, do not allow deletion.
|
575
932
|
UI.enableButtons(blbtns + (ep ? '' : ' del-point'));
|
576
933
|
if(this.adding_point) this.add_point_btn.classList.add('activ');
|
577
934
|
this.bl_type.value = this.selected.type;
|
578
935
|
this.bl_type.style.color = 'black';
|
579
936
|
this.bl_type.disabled = false;
|
580
|
-
this.bl_selectors.value = this.selected.selectors;
|
581
|
-
this.bl_selectors.style.backgroundColor = 'white';
|
582
|
-
this.bl_selectors.disabled = false;
|
583
937
|
} else {
|
584
938
|
UI.disableButtons(blbtns + ' del-point');
|
585
939
|
this.bl_type.value = VM.EQ;
|
586
940
|
this.bl_type.style.color = 'silver';
|
587
941
|
this.bl_type.disabled = true;
|
588
|
-
this.bl_selectors.value = '';
|
589
|
-
this.bl_selectors.style.backgroundColor = 'inherit';
|
590
|
-
this.bl_selectors.disabled = true;
|
591
942
|
}
|
592
943
|
}
|
593
944
|
|
@@ -685,11 +1036,18 @@ class ConstraintEditor {
|
|
685
1036
|
'100</text>']);
|
686
1037
|
}
|
687
1038
|
|
688
|
-
|
689
|
-
//
|
1039
|
+
setContourPath(l) {
|
1040
|
+
// Computes the contour path (which is the line path for EQ bounds)
|
1041
|
+
// without drawing them in the chart -- used when drawing thumbnails.
|
1042
|
+
this.drawContour(l, false);
|
1043
|
+
this.drawLine(l, false);
|
1044
|
+
}
|
1045
|
+
|
1046
|
+
drawContour(l, display=true) {
|
1047
|
+
// Draws infeasible area for bound line `l`.
|
690
1048
|
let cp;
|
691
1049
|
if(l.type === VM.EQ) {
|
692
|
-
// Whole area is infeasible except for the bound line itself
|
1050
|
+
// Whole area is infeasible except for the bound line itself.
|
693
1051
|
cp = ['M', this.point(0, 0), 'L', this.point(100 ,0), 'L',
|
694
1052
|
this.point(100, 100), 'L', this.point(0, 100), 'z'].join('');
|
695
1053
|
} else {
|
@@ -700,9 +1058,10 @@ class ConstraintEditor {
|
|
700
1058
|
cp += `L${this.point(p[0], p[1])}`;
|
701
1059
|
}
|
702
1060
|
cp += 'L' + this.point(100, base_y) + 'z';
|
1061
|
+
// Save the contour for rapid display of thumbnails.
|
1062
|
+
l.contour_path = cp;
|
703
1063
|
}
|
704
|
-
|
705
|
-
l.contour_path = cp;
|
1064
|
+
if(!display) return;
|
706
1065
|
// NOTE: the selected bound lines have their infeasible area filled
|
707
1066
|
// with a *colored* line pattern
|
708
1067
|
const sel = l === this.selected;
|
@@ -711,7 +1070,7 @@ class ConstraintEditor {
|
|
711
1070
|
(sel ? 1 : 0.4), '"></path>']);
|
712
1071
|
}
|
713
1072
|
|
714
|
-
drawLine(l) {
|
1073
|
+
drawLine(l, display=true) {
|
715
1074
|
let color,
|
716
1075
|
width,
|
717
1076
|
pp = [],
|
@@ -741,6 +1100,7 @@ class ConstraintEditor {
|
|
741
1100
|
// For EQ bound lines, the line path is the contour; this will be
|
742
1101
|
// drawn in miniature black against a silver background
|
743
1102
|
if(l.type === VM.EQ) l.contour_path = cp;
|
1103
|
+
if(!display) return;
|
744
1104
|
this.addSVG(['<path fill="none" stroke="', color, '" d="', cp,
|
745
1105
|
'" stroke-width="', width, '"></path>', dots]);
|
746
1106
|
}
|