linny-r 1.5.8 → 1.6.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/package.json +1 -1
- package/static/index.html +7 -3
- package/static/linny-r.css +19 -0
- package/static/scripts/linny-r-gui-actor-manager.js +3 -3
- package/static/scripts/linny-r-gui-chart-manager.js +10 -9
- package/static/scripts/linny-r-gui-controller.js +49 -36
- package/static/scripts/linny-r-gui-dataset-manager.js +17 -49
- package/static/scripts/linny-r-gui-documentation-manager.js +71 -47
- package/static/scripts/linny-r-gui-equation-manager.js +26 -10
- package/static/scripts/linny-r-gui-experiment-manager.js +41 -23
- package/static/scripts/linny-r-gui-expression-editor.js +26 -21
- package/static/scripts/linny-r-gui-finder.js +1 -0
- package/static/scripts/linny-r-gui-monitor.js +4 -4
- package/static/scripts/linny-r-milp.js +18 -15
- package/static/scripts/linny-r-model.js +354 -137
- package/static/scripts/linny-r-utils.js +68 -18
- package/static/scripts/linny-r-vm.js +593 -300
@@ -309,35 +309,55 @@ class DocumentationManager {
|
|
309
309
|
}
|
310
310
|
|
311
311
|
update(e, shift) {
|
312
|
-
// Display name of entity under cursor on the infoline, and details
|
313
|
-
// the documentation dialog
|
312
|
+
// Display name of entity under cursor on the infoline, and details
|
313
|
+
// in the documentation dialog.
|
314
314
|
if(!e) return;
|
315
|
-
|
316
|
-
et = e.type,
|
315
|
+
let et = e.type,
|
317
316
|
edn = e.displayName;
|
318
|
-
|
317
|
+
if(et === 'Equation' && e.selector.startsWith(':')) et = 'Method';
|
318
|
+
// TO DO: when debugging, display additional data for nodes on the
|
319
|
+
// infoline.
|
319
320
|
UI.setMessage(
|
320
321
|
e instanceof NodeBox ? e.infoLineName : `<em>${et}:</em> ${edn}`);
|
321
|
-
// NOTE:
|
322
|
-
// to rapidly browse comments without having to click on
|
323
|
-
// release the shift key to move to the documentation
|
324
|
-
// Moreover, the documentation dialog must be visible,
|
325
|
-
// have the `comments` property
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
322
|
+
// NOTE: Update the dialog ONLY when shift is pressed. This permits
|
323
|
+
// modelers to rapidly browse comments without having to click on
|
324
|
+
// entities, and then release the shift key to move to the documentation
|
325
|
+
// dialog to edit. Moreover, the documentation dialog must be visible,
|
326
|
+
// and the entity must have the `comments` property.
|
327
|
+
// NOTE: Equations constitute an exception, as DatasetModifiers do
|
328
|
+
// not have the `comments` property. Now that methods can be defined
|
329
|
+
// (since version 1.6.0), the documentation window displays the eligible
|
330
|
+
// prefixes when the cursor is Shift-moved over the name of a method
|
331
|
+
// (in the Equation Manager).
|
332
|
+
if(!this.editing && shift && this.visible) {
|
333
|
+
if(e.hasOwnProperty('comments')) {
|
334
|
+
this.title.innerHTML = `<em>${et}:</em> ${edn}`;
|
335
|
+
this.entity = e;
|
336
|
+
this.markup = (e.comments ? e.comments : '');
|
337
|
+
this.editor.value = this.markup;
|
338
|
+
this.viewer.innerHTML = this.markdown;
|
339
|
+
this.edit_btn.classList.remove('disab');
|
340
|
+
this.edit_btn.classList.add('enab');
|
341
|
+
// NOTE: Permit documentation of the model by raising the dialog.
|
342
|
+
if(this.entity === MODEL) this.dialog.style.zIndex = 101;
|
343
|
+
} else if(e instanceof DatasetModifier) {
|
344
|
+
this.title.innerHTML = e.selector;
|
345
|
+
this.viewer.innerHTML = 'Method <tt>' + e.selector +
|
346
|
+
'</tt> does not apply to any entity';
|
347
|
+
if(e.expression.eligible_prefixes) {
|
348
|
+
const el = Object.keys(e.expression.eligible_prefixes)
|
349
|
+
.sort(compareSelectors);
|
350
|
+
if(el.length > 0) this.viewer.innerHTML = [
|
351
|
+
'Method <tt>', e.selector, '</tt> applies to ',
|
352
|
+
pluralS(el.length, 'prefixed entity group'),
|
353
|
+
':<ul><li>', el.join('</li><li>'), '</li></ul>'].join('');
|
354
|
+
}
|
355
|
+
}
|
336
356
|
}
|
337
357
|
}
|
338
358
|
|
339
359
|
rewrite(str) {
|
340
|
-
// Apply all the rewriting rules to `str
|
360
|
+
// Apply all the rewriting rules to `str`.
|
341
361
|
str = '\n' + str + '\n';
|
342
362
|
this.rules.forEach(
|
343
363
|
(rule) => { str = str.replace(rule.pattern, rule.rewrite); });
|
@@ -345,28 +365,29 @@ class DocumentationManager {
|
|
345
365
|
}
|
346
366
|
|
347
367
|
makeList(par, isp, type) {
|
348
|
-
// Split on the *global multi-line* item separator pattern
|
368
|
+
// Split on the *global multi-line* item separator pattern.
|
349
369
|
const splitter = new RegExp(isp, 'gm'),
|
350
370
|
list = par.split(splitter);
|
351
371
|
if(list.length < 2) return false;
|
352
|
-
// Now we know that the paragraph contains at least one list item line
|
372
|
+
// Now we know that the paragraph contains at least one list item line.
|
353
373
|
let start = 0;
|
354
|
-
// Paragraph may start with plain text, so check using the original
|
374
|
+
// Paragraph may start with plain text, so check using the original
|
375
|
+
// pattern.
|
355
376
|
if(!par.match(isp)) {
|
356
377
|
// If so, retain this first part as a separate paragraph...
|
357
378
|
start = 1;
|
358
|
-
// NOTE:
|
379
|
+
// NOTE: Add it only if it contains text.
|
359
380
|
par = (list[0].trim() ? `<p>${this.rewrite(list[0])}</p>` : '');
|
360
|
-
// ... and clear it as list item
|
381
|
+
// ... and clear it as list item.
|
361
382
|
list[0] = '';
|
362
383
|
} else {
|
363
384
|
par = '';
|
364
385
|
}
|
365
|
-
// Rewrite each list item fragment that contains text
|
386
|
+
// Rewrite each list item fragment that contains text.
|
366
387
|
for(let j = start; j < list.length; j++) {
|
367
388
|
list[j] = (list[j].trim() ? `<li>${this.rewrite(list[j])}</li>` : '');
|
368
389
|
}
|
369
|
-
// Return assembled parts
|
390
|
+
// Return assembled parts.
|
370
391
|
return [par, '<', type, 'l>', list.join(''), '</', type, 'l>'].join('');
|
371
392
|
}
|
372
393
|
|
@@ -375,16 +396,16 @@ class DocumentationManager {
|
|
375
396
|
const html = this.markup.split(/\n{2,}/);
|
376
397
|
let list;
|
377
398
|
for(let i = 0; i < html.length; i++) {
|
378
|
-
// Paragraph with only dashes and spaces becomes a horizontal rule
|
399
|
+
// Paragraph with only dashes and spaces becomes a horizontal rule.
|
379
400
|
if(html[i].match(/^( *-)+$/)) {
|
380
401
|
html[i] = '<hr>';
|
381
|
-
// Paragraph may contain a bulleted list
|
402
|
+
// Paragraph may contain a bulleted list.
|
382
403
|
} else if ((list = this.makeList(html[i], /^ *- +/, 'u')) !== false) {
|
383
404
|
html[i] = list;
|
384
|
-
// Paragraph may contain a numbered list
|
405
|
+
// Paragraph may contain a numbered list.
|
385
406
|
} else if ((list = this.makeList(html[i], /^ *\d+. +/, 'o')) !== false) {
|
386
407
|
html[i] = list;
|
387
|
-
// Otherwise: default HTML paragraph
|
408
|
+
// Otherwise: default HTML paragraph.
|
388
409
|
} else {
|
389
410
|
html[i] = `<p>${this.rewrite(html[i])}</p>`;
|
390
411
|
}
|
@@ -411,7 +432,7 @@ class DocumentationManager {
|
|
411
432
|
}
|
412
433
|
|
413
434
|
insertSymbol(sym) {
|
414
|
-
// Insert symbol (clicked item in list below text area) into text area
|
435
|
+
// Insert symbol (clicked item in list below text area) into text area.
|
415
436
|
this.editor.focus();
|
416
437
|
let p = this.editor.selectionStart;
|
417
438
|
const
|
@@ -434,7 +455,7 @@ class DocumentationManager {
|
|
434
455
|
} else if(this.entity instanceof Constraint) {
|
435
456
|
UI.paper.drawConstraint(this.entity);
|
436
457
|
} else if (typeof this.entity.draw === 'function') {
|
437
|
-
// Only draw if the entity responds to that method
|
458
|
+
// Only draw if the entity responds to that method.
|
438
459
|
this.entity.draw();
|
439
460
|
}
|
440
461
|
}
|
@@ -481,14 +502,14 @@ class DocumentationManager {
|
|
481
502
|
}
|
482
503
|
|
483
504
|
addMessage(msg) {
|
484
|
-
// Append message to the info messages list
|
505
|
+
// Append message to the info messages list.
|
485
506
|
if(msg) this.info_messages.push(msg);
|
486
|
-
// Update dialog only when it is showing
|
507
|
+
// Update dialog only when it is showing.
|
487
508
|
if(!UI.hidden(this.dialog.id)) this.showInfoMessages(true);
|
488
509
|
}
|
489
510
|
|
490
511
|
showInfoMessages(shift) {
|
491
|
-
// Show all messages that have appeared on the status line
|
512
|
+
// Show all messages that have appeared on the status line.
|
492
513
|
const
|
493
514
|
n = this.info_messages.length,
|
494
515
|
title = pluralS(n, 'message') + ' since the current model was loaded';
|
@@ -504,21 +525,21 @@ class DocumentationManager {
|
|
504
525
|
'<div class="', m.status, first, '-msg">', m.text, '</div></div>');
|
505
526
|
}
|
506
527
|
this.viewer.innerHTML = divs.join('');
|
507
|
-
// Set the dialog title
|
528
|
+
// Set the dialog title.
|
508
529
|
this.title.innerHTML = title;
|
509
530
|
}
|
510
531
|
}
|
511
532
|
|
512
533
|
showArrowLinks(arrow) {
|
513
|
-
// Show list of links represented by a composite arrow
|
534
|
+
// Show list of links represented by a composite arrow.
|
514
535
|
const
|
515
536
|
n = arrow.links.length,
|
516
537
|
msg = 'Arrow represents ' + pluralS(n, 'link');
|
517
538
|
UI.setMessage(msg);
|
518
539
|
if(this.visible && !this.editing) {
|
519
|
-
// Set the dialog title
|
540
|
+
// Set the dialog title.
|
520
541
|
this.title.innerHTML = msg;
|
521
|
-
// Show list
|
542
|
+
// Show list.
|
522
543
|
const lis = [];
|
523
544
|
let l, dn, c, af;
|
524
545
|
for(let i = 0; i < n; i++) {
|
@@ -550,7 +571,8 @@ class DocumentationManager {
|
|
550
571
|
}
|
551
572
|
|
552
573
|
showHiddenIO(node, arrow) {
|
553
|
-
// Show list of products or processes linked to node by an invisible
|
574
|
+
// Show list of products or processes linked to node by an invisible
|
575
|
+
// arrow (i.e., links represented by a block arrow).
|
554
576
|
let msg, iol;
|
555
577
|
if(arrow === UI.BLOCK_IN) {
|
556
578
|
iol = node.hidden_inputs;
|
@@ -566,9 +588,9 @@ class DocumentationManager {
|
|
566
588
|
UI.on_block_arrow = true;
|
567
589
|
UI.setMessage(msg);
|
568
590
|
if(this.visible && !this.editing) {
|
569
|
-
// Set the dialog title
|
591
|
+
// Set the dialog title.
|
570
592
|
this.title.innerHTML = msg;
|
571
|
-
// Show list
|
593
|
+
// Show list.
|
572
594
|
const lis = [];
|
573
595
|
for(let i = 0; i < iol.length; i++) {
|
574
596
|
lis.push(`<li>${iol[i].displayName}</li>`);
|
@@ -579,17 +601,19 @@ class DocumentationManager {
|
|
579
601
|
}
|
580
602
|
|
581
603
|
showAllDocumentation() {
|
604
|
+
// Show (as HTML) all model entities (categorized by type) with their
|
605
|
+
// associated comments (if added by the modeler).
|
582
606
|
const
|
583
607
|
html = [],
|
584
608
|
sl = MODEL.listOfAllComments;
|
585
609
|
for(let i = 0; i < sl.length; i++) {
|
586
610
|
if(sl[i].startsWith('_____')) {
|
587
|
-
// 5-underscore leader indicates: start of new category
|
611
|
+
// 5-underscore leader indicates: start of new category.
|
588
612
|
html.push('<h2>', sl[i].substring(5), '</h2>');
|
589
613
|
} else {
|
590
614
|
// Expect model element name...
|
591
615
|
html.push('<p><tt>', sl[i], '</tt><br><small>');
|
592
|
-
// ... immediately followed by its associated marked-up comments
|
616
|
+
// ... immediately followed by its associated marked-up comments.
|
593
617
|
i++;
|
594
618
|
this.markup = sl[i];
|
595
619
|
html.push(this.markdown, '</small></p>');
|
@@ -597,7 +621,7 @@ class DocumentationManager {
|
|
597
621
|
}
|
598
622
|
this.title.innerHTML = 'Complete model documentation';
|
599
623
|
this.viewer.innerHTML = html.join('');
|
600
|
-
// Deselect entity and disable editing
|
624
|
+
// Deselect entity and disable editing.
|
601
625
|
this.entity = null;
|
602
626
|
this.edit_btn.classList.remove('enab');
|
603
627
|
this.edit_btn.classList.add('disab');
|
@@ -139,16 +139,27 @@ class EquationManager {
|
|
139
139
|
const
|
140
140
|
m = ed.modifiers[UI.nameToID(msl[i])],
|
141
141
|
wild = (m.selector.indexOf('??') >= 0),
|
142
|
+
method = m.selector.startsWith(':'),
|
143
|
+
issue = (m.expression.compile_issue ? ' compile-issue' :
|
144
|
+
(m.expression.compute_issue ? ' compute-issue' : '')),
|
142
145
|
clk = '" onclick="EQUATION_MANAGER.selectModifier(event, \'' +
|
143
|
-
m.selector + '\''
|
146
|
+
m.selector + '\'',
|
147
|
+
mover = (method ? ' onmouseover="EQUATION_MANAGER.showInfo(\'' +
|
148
|
+
m.identifier + '\', event.shiftKey);"' : '');
|
144
149
|
if(m === sm) smid += i;
|
145
150
|
ml.push(['<tr id="eqmtr', i, '" class="dataset-modif',
|
146
151
|
(m === sm ? ' sel-set' : ''),
|
147
152
|
'"><td class="equation-selector',
|
148
|
-
(
|
149
|
-
|
153
|
+
(method ? ' method' : ''),
|
154
|
+
// Display in gray when method cannot be applied.
|
155
|
+
(m.expression.noMethodObject ? ' no-object' : ''),
|
156
|
+
(m.expression.isStatic ? '' : ' it'), issue,
|
157
|
+
(wild ? ' wildcard' : ''), clk, ', false);"', mover, '>',
|
150
158
|
(wild ? wildcardFormat(m.selector) : m.selector),
|
151
|
-
'</td><td class="equation-expression',
|
159
|
+
'</td><td class="equation-expression', issue,
|
160
|
+
(issue ? '"title="' +
|
161
|
+
safeDoubleQuotes(m.expression.compile_issue ||
|
162
|
+
m.expression.compute_issue) : ''),
|
152
163
|
clk, ');">', m.expression.text, '</td></tr>'].join(''));
|
153
164
|
}
|
154
165
|
this.table.innerHTML = ml.join('');
|
@@ -164,6 +175,8 @@ class EquationManager {
|
|
164
175
|
|
165
176
|
showInfo(id, shift) {
|
166
177
|
// @@TO DO: Display documentation for the equation => extra comments field?
|
178
|
+
const d = MODEL.equations_dataset.modifiers[id];
|
179
|
+
if(d) DOCUMENTATION_MANAGER.update(d, shift);
|
167
180
|
}
|
168
181
|
|
169
182
|
selectModifier(event, id, x=true) {
|
@@ -255,20 +268,23 @@ class EquationManager {
|
|
255
268
|
if(!this.selected_modifier) return;
|
256
269
|
const
|
257
270
|
sel = this.rename_modal.element('name').value,
|
258
|
-
// Keep track of old name
|
271
|
+
// Keep track of old name.
|
259
272
|
oldm = this.selected_modifier,
|
260
273
|
olds = oldm.selector,
|
261
|
-
// NOTE: addModifier returns existing one if selector not changed
|
274
|
+
// NOTE: addModifier returns existing one if selector not changed.
|
262
275
|
m = MODEL.equations_dataset.addModifier(sel);
|
263
|
-
// NULL indicates invalid name
|
276
|
+
// NULL indicates invalid name.
|
264
277
|
if(!m) return;
|
265
|
-
// If only case has changed, update the selector
|
266
|
-
// NOTE:
|
278
|
+
// If only case has changed, update the selector.
|
279
|
+
// NOTE: Equation names may contain spaces; if so, reduce to single space.
|
267
280
|
if(m === oldm) {
|
268
281
|
m.selector = sel.trim().replace(/\s+/g, ' ');
|
269
282
|
} else {
|
270
|
-
// When a new modifier has been added, more actions are needed
|
283
|
+
// When a new modifier has been added, more actions are needed.
|
271
284
|
m.expression = oldm.expression;
|
285
|
+
// NOTE: The `attribute` property of the expression must be updated
|
286
|
+
// because it identifies the "owner" of the expression.
|
287
|
+
m.expression.attribute = m.selector;
|
272
288
|
this.deleteEquation();
|
273
289
|
this.selected_modifier = m;
|
274
290
|
}
|
@@ -114,11 +114,14 @@ class GUIExperimentManager extends ExperimentManager {
|
|
114
114
|
document.getElementById('xv-download-btn').addEventListener(
|
115
115
|
'click', () => EXPERIMENT_MANAGER.promptForDownload());
|
116
116
|
// The viewer's drop-down selectors
|
117
|
-
document.getElementById('viewer-variable')
|
117
|
+
this.viewer_variable = document.getElementById('viewer-variable');
|
118
|
+
this.viewer_variable.addEventListener(
|
118
119
|
'change', () => EXPERIMENT_MANAGER.setVariable());
|
119
|
-
document.getElementById('viewer-statistic')
|
120
|
+
this.viewer_statistic = document.getElementById('viewer-statistic');
|
121
|
+
this.viewer_statistic.addEventListener(
|
120
122
|
'change', () => EXPERIMENT_MANAGER.setStatistic());
|
121
|
-
document.getElementById('viewer-scale')
|
123
|
+
this.viewer_scale = document.getElementById('viewer-scale');
|
124
|
+
this.viewer_scale.addEventListener(
|
122
125
|
'change', () => EXPERIMENT_MANAGER.setScale());
|
123
126
|
// The spin buttons
|
124
127
|
document.getElementById('xp-cd-minus').addEventListener(
|
@@ -272,12 +275,12 @@ class GUIExperimentManager extends ExperimentManager {
|
|
272
275
|
|
273
276
|
updateDialog() {
|
274
277
|
this.updateChartList();
|
275
|
-
// Warn modeler if no meaningful experiments can be defined
|
278
|
+
// Warn modeler if no meaningful experiments can be defined.
|
276
279
|
if(MODEL.outcomeNames.length === 0 && this.suitable_charts.length === 0) {
|
277
280
|
this.default_message.style.display = 'block';
|
278
281
|
this.params_div.style.display = 'none';
|
279
282
|
this.selected_experiment = null;
|
280
|
-
// Disable experiment dialog menu buttons
|
283
|
+
// Disable experiment dialog menu buttons.
|
281
284
|
UI.disableButtons('xp-new xp-rename xp-view xp-delete xp-ignore');
|
282
285
|
} else {
|
283
286
|
this.default_message.style.display = 'none';
|
@@ -388,7 +391,7 @@ class GUIExperimentManager extends ExperimentManager {
|
|
388
391
|
x.charts[i].title, '</td></tr>'].join(''));
|
389
392
|
}
|
390
393
|
this.chart_table.innerHTML = tr.join('');
|
391
|
-
// Do not show viewer unless at least 1 dependent variable has been defined
|
394
|
+
// Do not show viewer unless at least 1 dependent variable has been defined.
|
392
395
|
if(x.charts.length === 0 && MODEL.outcomeNames.length === 0) canview = false;
|
393
396
|
if(tr.length >= this.suitable_charts.length) {
|
394
397
|
document.getElementById('xp-c-add-btn').classList.add('v-disab');
|
@@ -409,7 +412,7 @@ class GUIExperimentManager extends ExperimentManager {
|
|
409
412
|
dbtn.classList.add('v-disab');
|
410
413
|
cbtn.classList.add('v-disab');
|
411
414
|
}
|
412
|
-
// Enable viewing only if > 1 dimensions and > 1 outcome variables
|
415
|
+
// Enable viewing only if > 1 dimensions and > 1 outcome variables.
|
413
416
|
if(canview) {
|
414
417
|
UI.enableButtons('xp-view');
|
415
418
|
} else {
|
@@ -478,38 +481,51 @@ class GUIExperimentManager extends ExperimentManager {
|
|
478
481
|
if(x) {
|
479
482
|
this.design.style.display = 'none';
|
480
483
|
document.getElementById('viewer-title').innerHTML = x.title;
|
481
|
-
|
484
|
+
this.viewer_statistic.value = x.selected_statistic;
|
482
485
|
this.updateViewerVariable();
|
483
486
|
// NOTE: calling updateSpinner with dir=0 will update without changes
|
484
487
|
this.updateSpinner('c', 0);
|
485
488
|
this.drawTable();
|
486
|
-
|
489
|
+
this.viewer_scale.value = x.selected_scale;
|
487
490
|
this.setColorScale(x.selected_color_scale);
|
488
491
|
this.viewer.style.display = 'block';
|
489
492
|
}
|
490
493
|
}
|
491
494
|
|
492
495
|
updateViewerVariable() {
|
493
|
-
// Update the variable drop-down selector of the viewer
|
496
|
+
// Update the variable drop-down selector of the viewer.
|
494
497
|
const x = this.selected_experiment;
|
495
498
|
if(x) {
|
496
499
|
x.inferVariables();
|
497
500
|
const
|
498
501
|
ol = [],
|
499
|
-
|
502
|
+
ov = MODEL.outcomeNames,
|
503
|
+
vl = [...ov];
|
500
504
|
for(let i = 0; i < x.variables.length; i++) {
|
501
|
-
|
505
|
+
const
|
506
|
+
vn = x.variables[i].displayName,
|
507
|
+
oi = ov.indexOf(vn);
|
508
|
+
// If an outcome dataset or equation is plotted in an experiment
|
509
|
+
// chart, remove its name from the outcome variable list.
|
510
|
+
if(oi >= 0) ov.splice(oi, 1);
|
511
|
+
addDistinct(vn, vl);
|
502
512
|
}
|
503
513
|
vl.sort((a, b) => UI.compareFullNames(a, b));
|
504
514
|
for(let i = 0; i < vl.length; i++) {
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
515
|
+
const vn = vl[i];
|
516
|
+
// NOTE: FireFox selector dropdown areas have a pale gray
|
517
|
+
// background that darkens when color is set, so always set it
|
518
|
+
// to white (like Chrome). Then set color of outcome variables
|
519
|
+
// to fuchsia to differentiate from variables for which time
|
520
|
+
// series are stored as experiment run results.
|
521
|
+
ol.push(['<option value="', vn, '" style="background-color: white',
|
522
|
+
(ov.indexOf(vn) >= 0 ? '; color: #b00080"' : '"'),
|
523
|
+
(vn == x.selected_variable ? ' selected="selected"' : ''),
|
524
|
+
'>', vn, '</option>'].join(''));
|
512
525
|
}
|
526
|
+
this.viewer_variable.innerHTML = ol.join('');
|
527
|
+
// Initially, select the first variable on the list.
|
528
|
+
if(x.selected_variable === '') x.selected_variable = vl[0];
|
513
529
|
}
|
514
530
|
}
|
515
531
|
|
@@ -684,8 +700,10 @@ class GUIExperimentManager extends ExperimentManager {
|
|
684
700
|
for(let j = 0; j < n; j++) {
|
685
701
|
const
|
686
702
|
c = r + j + i * nrows,
|
703
|
+
run = x.runs[c],
|
687
704
|
ic = x.chart_combinations.indexOf(c);
|
688
|
-
|
705
|
+
// NOTE: Only add if run has been executed and stored.
|
706
|
+
if(add && run) {
|
689
707
|
if(ic < 0) x.chart_combinations.push(c);
|
690
708
|
} else {
|
691
709
|
if(ic >= 0) x.chart_combinations.splice(ic, 1);
|
@@ -952,7 +970,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
|
|
952
970
|
// Update view for selected variable
|
953
971
|
const x = this.selected_experiment;
|
954
972
|
if(x) {
|
955
|
-
x.selected_variable =
|
973
|
+
x.selected_variable = this.viewer_variable.value;
|
956
974
|
this.updateData();
|
957
975
|
}
|
958
976
|
}
|
@@ -961,7 +979,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
|
|
961
979
|
// Update view for selected variable.
|
962
980
|
const x = this.selected_experiment;
|
963
981
|
if(x) {
|
964
|
-
x.selected_statistic =
|
982
|
+
x.selected_statistic = this.viewer_statistic.value;
|
965
983
|
this.updateData();
|
966
984
|
// NOTE: Update of Chart Manager is needed only when it is showing
|
967
985
|
// run statistics.
|
@@ -1027,7 +1045,7 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
|
|
1027
1045
|
// Update view for selected scale
|
1028
1046
|
const x = this.selected_experiment;
|
1029
1047
|
if(x) {
|
1030
|
-
x.selected_scale =
|
1048
|
+
x.selected_scale = this.viewer_scale.value;
|
1031
1049
|
this.updateData();
|
1032
1050
|
// NOTE: Update of Chart Manager is needed when it is showing
|
1033
1051
|
// run statistics because solver times may be plotted.
|
@@ -162,24 +162,24 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
|
|
162
162
|
Monadic operators take precedence over dyadic operators.
|
163
163
|
Use parentheses to override the default evaluation precedence.
|
164
164
|
</p>`;
|
165
|
-
// Add listeners to the GUI elements
|
165
|
+
// Add listeners to the GUI elements.
|
166
166
|
const md = UI.modals.expression;
|
167
167
|
md.ok.addEventListener('click', () => X_EDIT.parseExpression());
|
168
168
|
md.cancel.addEventListener('click', () => X_EDIT.cancel());
|
169
|
-
// NOTE:
|
169
|
+
// NOTE: This modal also has an information button in its header.
|
170
170
|
md.info.addEventListener(
|
171
171
|
'click', () => X_EDIT.toggleExpressionInfo());
|
172
|
-
|
172
|
+
this.obj.addEventListener(
|
173
173
|
'change', () => X_EDIT.updateVariableBar());
|
174
|
-
|
174
|
+
this.name.addEventListener(
|
175
175
|
'change', () => X_EDIT.updateAttributeSelector());
|
176
176
|
document.getElementById('variable-insert').addEventListener(
|
177
177
|
'click', () => X_EDIT.insertVariable());
|
178
178
|
}
|
179
179
|
|
180
180
|
editExpression(event) {
|
181
|
-
//
|
182
|
-
// that was clicked, and then opens the dialog
|
181
|
+
// Infer which entity property expression is to edited from the button
|
182
|
+
// that was clicked, and then opens the dialog.
|
183
183
|
const
|
184
184
|
btn = event.target,
|
185
185
|
ids = btn.id.split('-'), // 3-tuple [entity type, attribute, 'x']
|
@@ -196,7 +196,8 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
|
|
196
196
|
let n = '',
|
197
197
|
a = '';
|
198
198
|
if(ids[0] === 'link') {
|
199
|
-
n = document.getElementById('link-from-name').innerHTML +
|
199
|
+
n = document.getElementById('link-from-name').innerHTML +
|
200
|
+
UI.LINK_ARROW +
|
200
201
|
document.getElementById('link-to-name').innerHTML;
|
201
202
|
} else {
|
202
203
|
n = document.getElementById(ids[0] + '-name').value;
|
@@ -211,20 +212,19 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
|
|
211
212
|
this.edited_expression = UI.edited_object.attributeExpression(ids[1]);
|
212
213
|
}
|
213
214
|
const md = UI.modals.expression;
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
document.getElementById('variable-obj').value = 0;
|
215
|
+
this.property.innerHTML = prop;
|
216
|
+
this.text.value = document.getElementById(this.edited_input_id).value.trim();
|
217
|
+
this.obj.value = 0;
|
218
218
|
this.updateVariableBar();
|
219
219
|
this.clearStatusBar();
|
220
220
|
md.show('text');
|
221
221
|
}
|
222
222
|
|
223
223
|
cancel() {
|
224
|
-
//
|
224
|
+
// Close the expression editor dialog.
|
225
225
|
UI.modals.expression.hide();
|
226
226
|
// Clear the "shortcut flag" that may be set by Shift-clicking the
|
227
|
-
// "add chart variable" button in the chart dialog
|
227
|
+
// "add chart variable" button in the chart dialog.
|
228
228
|
EQUATION_MANAGER.add_to_chart = false;
|
229
229
|
// CLear other properties that relate to the edited expression.
|
230
230
|
this.edited_input_id = '';
|
@@ -232,20 +232,25 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
|
|
232
232
|
}
|
233
233
|
|
234
234
|
parseExpression() {
|
235
|
-
//
|
236
|
-
let xt = this.text.value;
|
237
|
-
// NOTE:
|
238
|
-
// the modeler clicks OK before Insert, leaving the expression empty
|
239
|
-
//
|
240
|
-
// but all three variable components have been selected
|
235
|
+
// Parse the contents of the expression editor.
|
236
|
+
let xt = this.text.value.trim();
|
237
|
+
// NOTE: The Insert button is quite close to the OK button, and often
|
238
|
+
// the modeler clicks OK before Insert, leaving the expression empty.
|
239
|
+
// Hence assume that modeler meant to insert a variable if text is
|
240
|
+
// empty but all three variable components have been selected.
|
241
241
|
if(xt === '') {
|
242
242
|
const
|
243
243
|
n = this.name.options[this.name.selectedIndex].innerHTML,
|
244
244
|
a = this.attr.options[this.attr.selectedIndex].innerHTML;
|
245
245
|
if(n && a) xt = `[${n}${UI.OA_SEPARATOR}${a}]`;
|
246
246
|
}
|
247
|
+
// Remove all non-functional whitespace from variable references.
|
248
|
+
xt = monoSpacedVariables(xt);
|
249
|
+
// Update the text shown in the editor, otherwise the position of
|
250
|
+
// errors in the text may be incorrect.
|
251
|
+
this.text.value = xt;
|
247
252
|
// NOTE: If the expression is a dataset modifier or an equation, pass
|
248
|
-
// the dataset and the selector as extra parameters for the parser
|
253
|
+
// the dataset and the selector as extra parameters for the parser.
|
249
254
|
let own = null,
|
250
255
|
sel = '';
|
251
256
|
if(!this.edited_input_id && DATASET_MANAGER.edited_expression) {
|
@@ -270,7 +275,7 @@ NOTE: Grouping groups results in a single group, e.g., (1;2);(3;4;5) evaluates a
|
|
270
275
|
} else {
|
271
276
|
if(this.edited_input_id) {
|
272
277
|
document.getElementById(this.edited_input_id).value = xp.expr;
|
273
|
-
// NOTE:
|
278
|
+
// NOTE: Entity properties must be exogenous parameters.
|
274
279
|
const eo = UI.edited_object;
|
275
280
|
if(eo && xp.is_level_based &&
|
276
281
|
!(eo instanceof Dataset || eo instanceof Note)) {
|
@@ -441,6 +441,7 @@ class Finder {
|
|
441
441
|
}
|
442
442
|
}
|
443
443
|
document.getElementById('finder-item-header').innerHTML = hdr;
|
444
|
+
occ.sort(compareSelectors);
|
444
445
|
for(let i = 0; i < occ.length; i++) {
|
445
446
|
const e = MODEL.objectByID(occ[i]);
|
446
447
|
el.push(['<tr id="eotr', i, '" class="dataset" onclick="FINDER.reveal(\'',
|
@@ -175,7 +175,7 @@ class GUIMonitor {
|
|
175
175
|
this.block_count = VM.block_count;
|
176
176
|
// Shows the appropriate text in the monitor's textarea
|
177
177
|
let b = this.shown_block;
|
178
|
-
// By default, show information on the block being calculated
|
178
|
+
// By default, show information on the block being calculated.
|
179
179
|
if(b === 0) b = this.block_count;
|
180
180
|
if(this.block_count === 0) {
|
181
181
|
this.messages_text.value = VM.no_messages;
|
@@ -184,9 +184,9 @@ class GUIMonitor {
|
|
184
184
|
this.messages_text.value = VM.messages[b - 1];
|
185
185
|
this.equations_text.value = VM.equations[b - 1];
|
186
186
|
}
|
187
|
-
// Legend to variables is not block-dependent
|
188
|
-
this.variables_text.value = VM.variablesLegend(
|
189
|
-
// Show the text area for the selected tab
|
187
|
+
// Legend to variables is not block-dependent.
|
188
|
+
this.variables_text.value = VM.variablesLegend();
|
189
|
+
// Show the text area for the selected tab.
|
190
190
|
if(this.tab !== tab) {
|
191
191
|
let mt = 'monitor-' + this.tab;
|
192
192
|
document.getElementById(mt).style.display = 'none';
|