linny-r 2.0.9 → 2.0.10

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linny-r",
3
- "version": "2.0.9",
3
+ "version": "2.0.10",
4
4
  "description": "Executable graphical language with WYSIWYG editor for MILP models",
5
5
  "main": "server.js",
6
6
  "scripts": {
@@ -21,7 +21,11 @@
21
21
  "optimization",
22
22
  "simulation",
23
23
  "unit commitment",
24
- "generation expansion"
24
+ "industrial ecology",
25
+ "linearized power flow",
26
+ "loss approximation",
27
+ "generation expansion",
28
+ "grid expansion"
25
29
  ],
26
30
  "author": "Pieter W.G. Bots",
27
31
  "license": "MIT",
package/static/index.html CHANGED
@@ -1730,7 +1730,86 @@ Each line should represent the points of a different bound line.">
1730
1730
  </div>
1731
1731
  </div>
1732
1732
  </div>
1733
+
1734
+ <!-- the DATASET GROUP modal allows editing properties of multiple datasets -->
1735
+ <div id="datasetgroup-modal" class="modal">
1736
+ <div id="datasetgroup-dlg" class="inp-dlg">
1737
+ <div class="dlg-title">
1738
+ Dataset group properties
1739
+ <span id="datasetgroup-group"></span>
1740
+ <img class="cancel-btn" src="images/cancel.png">
1741
+ <img class="ok-btn" src="images/ok.png">
1742
+ </div>
1743
+ <div id="datasetgroup-prefix-lbl">Prefix:</div>
1744
+ <input id="datasetgroup-prefix" type="text" autocomplete="off">
1745
+ <div id="datasetgroup-default-lbl">Default value:</div>
1746
+ <input id="datasetgroup-default" type="text" autocomplete="off">
1747
+ <div id="datasetgroup-unit-lbl">Unit:</div>
1748
+ <input id="datasetgroup-unit" type="text" list="units-data" autocomplete="off">
1749
+ <div id="datasetgroup-periodic" class="box clear"></div>
1750
+ <div id="datasetgroup-periodic-lbl">Periodic</div>
1751
+ <div id="datasetgroup-array" class="box clear"></div>
1752
+ <div id="datasetgroup-array-lbl">Array</div>
1753
+ <div id="datasetgroup-no-time-msg">
1754
+ Arrays ignore time dimension
1755
+ </div>
1756
+ <div id="datasetgroup-time-step-lbl">Time step:</div>
1757
+ <input id="datasetgroup-time-scale" type="text" autocomplete="off">
1758
+ <select id="datasetgroup-time-unit">
1759
+ <option value="year">yr</option>
1760
+ <option value="week">wk</option>
1761
+ <option value="day">d</option>
1762
+ <option value="hour">h</option>
1763
+ <option value="minute">m</option>
1764
+ <option value="second">s</option>
1765
+ </select>
1766
+ <div id="datasetgroup-method-lbl">Method:</div>
1767
+ <select id="datasetgroup-method">
1768
+ <option value="nearest">at nearest t</option>
1769
+ <option value="w-mean">weighted mean</option>
1770
+ <option value="w-sum">weighted sum</option>
1771
+ <option value="max">maximum</option>
1772
+ </select>
1773
+ <div id="datasetgroup-separator"></div>
1774
+ <div id="datasetgroup-modif-header">Modifiers</div>
1775
+ <div id="datasetgroup-modif-titles">
1776
+ <span>Selector</span>
1777
+ <span style="margin-left: 35px">Expression</span>
1778
+ </div>
1779
+ <div id="datasetgroup-modif-scroll-area">
1780
+ <table id="datasetgroup-modif-table">
1781
+ </table>
1782
+ </div>
1783
+ <div id="datasetgroup-modif-buttons">
1784
+ <img id="dsg-add-modif-btn" class="btn enab"
1785
+ src="images/add-selector.png" title="Add new selector">
1786
+ <img id="dsg-rename-modif-btn" class="btn disab" src="images/rename.png"
1787
+ title="Rename selected modifier">
1788
+ <img id="dsg-edit-modif-btn" class="btn disab"
1789
+ src="images/edit.png" title="Edit expression of selected modifier">
1790
+ <img id="dsg-delete-modif-btn" class="btn disab"
1791
+ src="images/delete.png" title="Delete selected modifier"
1792
+ style="margin-left: 20px">
1793
+ </div>
1794
+ </div>
1795
+ </div>
1733
1796
 
1797
+ <!-- the GROUP SELECTOR dialog prompts for a new selector -->
1798
+ <div id="group-selector-modal" class="modal">
1799
+ <div id="group-selector-dlg" class="inp-dlg">
1800
+ <div class="dlg-title">
1801
+ <span id="group-selector-action">Add</span> selector
1802
+ <img class="cancel-btn" src="images/cancel.png">
1803
+ <img class="ok-btn" src="images/ok.png">
1804
+ </div>
1805
+ <input id="group-selector-name" type="text"
1806
+ title="Can contain only alphanumeric characters, +, -, and %
1807
+ NOTE: * and ? will be interpreted as wildcards"
1808
+ maxlength="10" autocomplete="off">
1809
+ <div id="group-selector-label">(1 to 10 characters)</div>
1810
+ </div>
1811
+ </div>
1812
+
1734
1813
  <!-- The CLONE dialog prompts for a prefix -->
1735
1814
  <div id="clone-modal" class="modal">
1736
1815
  <div id="clone-dlg" class="inp-dlg">
@@ -396,6 +396,28 @@ input[type="text"]:disabled {
396
396
  background-color: #f4f0f2;
397
397
  }
398
398
 
399
+ td.same-not-changed {
400
+ color: #5060b0;
401
+ background-color: #f4f8ff;
402
+ }
403
+
404
+ td.not-same-not-changed {
405
+ color: #b06050;
406
+ background-color: #fff6f4;
407
+ }
408
+
409
+ td.same-changed {
410
+ color: #0000e0;
411
+ background-color: #f4f8ff;
412
+ text-shadow: 0px 0px 10px #00b0ff;
413
+ }
414
+
415
+ td.not-same-changed {
416
+ color: #e00000;
417
+ background-color: #fff6f4;
418
+ text-shadow: 0px 0px 10px #ff6000;
419
+ }
420
+
399
421
  input[type="text"].same-not-changed {
400
422
  color: #5060b0;
401
423
  background-color: #f4f8ff;
@@ -426,10 +448,12 @@ select.not-same-not-changed {
426
448
 
427
449
  select.same-changed {
428
450
  color: #0000e0;
451
+ text-shadow: 0px 0px 10px #00b0ff;
429
452
  }
430
453
 
431
454
  select.not-same-changed {
432
455
  color: #e00000;
456
+ text-shadow: 0px 0px 10px #ff6000;
433
457
  }
434
458
 
435
459
  textarea {
@@ -685,6 +709,7 @@ img.inline-cancel-btn:hover {
685
709
  #actor-group,
686
710
  #constraint-group,
687
711
  #cluster-group,
712
+ #datasetgroup-group,
688
713
  #link-group,
689
714
  #process-group,
690
715
  #product-group {
@@ -1910,6 +1935,7 @@ td.export {
1910
1935
 
1911
1936
  #actor-export,
1912
1937
  #dataset-export,
1938
+ #datasetgroup-export,
1913
1939
  #product-export {
1914
1940
  position: absolute;
1915
1941
  bottom: 10px;
@@ -1921,6 +1947,7 @@ td.export {
1921
1947
 
1922
1948
  #actor-import,
1923
1949
  #dataset-import,
1950
+ #datasetgroup-import,
1924
1951
  #product-import {
1925
1952
  position: absolute;
1926
1953
  bottom: 9px;
@@ -2607,6 +2634,7 @@ div.io-box {
2607
2634
  #power-grids-table,
2608
2635
  #dataset-table,
2609
2636
  #dataset-modif-table,
2637
+ #datasetgroup-modif-table,
2610
2638
  #equation-table,
2611
2639
  #experiment-table,
2612
2640
  #experiment-dim-table,
@@ -2647,6 +2675,7 @@ div.io-box {
2647
2675
  }
2648
2676
 
2649
2677
  #dataset-outcome.not-selected,
2678
+ #datasetgroup-outcome.not-selected,
2650
2679
  #equation-outcome.not-selected {
2651
2680
  filter: saturate(0) contrast(50%) brightness(170%);
2652
2681
  }
@@ -2703,7 +2732,8 @@ div.io-box {
2703
2732
  left: calc(40% + 3px);
2704
2733
  }
2705
2734
 
2706
- #dataset-modif-buttons > img.btn {
2735
+ #dataset-modif-buttons > img.btn,
2736
+ #datasetgroup-modif-buttons > img.btn {
2707
2737
  height: 20px;
2708
2738
  width: 20px;
2709
2739
  }
@@ -2729,6 +2759,11 @@ tr.sel-set {
2729
2759
  background-color: #eff0ff;
2730
2760
  }
2731
2761
 
2762
+ tr.sel-set > td,
2763
+ tr.dataset-modif:hover > td {
2764
+ background-color: inherit !important;
2765
+ }
2766
+
2732
2767
  tr.def-sel {
2733
2768
  color: #400090;
2734
2769
  font-weight: bold;
@@ -2931,11 +2966,13 @@ td.equation-expression-multi {
2931
2966
  color: #e0e0e0;
2932
2967
  }
2933
2968
 
2934
- #dataset-blackbox.off {
2969
+ #dataset-blackbox.off,
2970
+ #datasetgroup-blackbox.off {
2935
2971
  color: #e0e0e0;
2936
2972
  }
2937
2973
 
2938
- #dataset-blackbox.on {
2974
+ #dataset-blackbox.on,
2975
+ #datasetgroup-blackbox.on {
2939
2976
  color: #403848;
2940
2977
  }
2941
2978
 
@@ -2999,18 +3036,21 @@ td.equation-expression-multi {
2999
3036
 
3000
3037
  /* NOTE: New and Rename modals must be above boundline data modal */
3001
3038
  #new-selector-modal,
3002
- #rename-selector-modal {
3039
+ #rename-selector-modal,
3040
+ #group-selector-modal {
3003
3041
  z-index: 102;
3004
3042
  }
3005
3043
 
3006
3044
  #new-selector-dlg,
3007
- #rename-selector-dlg {
3045
+ #rename-selector-dlg,
3046
+ #group-selector-dlg {
3008
3047
  width: 215px;
3009
3048
  height: 45px;
3010
3049
  }
3011
3050
 
3012
3051
  #new-selector-name,
3013
- #rename-selector-name {
3052
+ #rename-selector-name,
3053
+ #group-selector-name {
3014
3054
  position: absolute;
3015
3055
  bottom: 2px;
3016
3056
  left: 2px;
@@ -3018,7 +3058,8 @@ td.equation-expression-multi {
3018
3058
  }
3019
3059
 
3020
3060
  #new-selector-label,
3021
- #rename-selector-label {
3061
+ #rename-selector-label,
3062
+ #group-selector-label {
3022
3063
  position: absolute;
3023
3064
  left: 110px;
3024
3065
  bottom: 5px;
@@ -5104,6 +5145,198 @@ img.finder {
5104
5145
  font-size: 12px;
5105
5146
  }
5106
5147
 
5148
+ /* the DATASET GROUP modal permits editing properties of multiple datasets */
5149
+ #datasetgroup-dlg {
5150
+ width: 440px;
5151
+ height: 190px;
5152
+ }
5153
+
5154
+ #datasetgroup-prefix-lbl {
5155
+ position: absolute;
5156
+ top: 25px;
5157
+ left: 2px;
5158
+ }
5159
+
5160
+ #datasetgroup-prefix {
5161
+ position: absolute;
5162
+ top: 24px;
5163
+ left: 38px;
5164
+ width: 121px;
5165
+ font-size: 12px;
5166
+ }
5167
+
5168
+ #datasetgroup-default-lbl {
5169
+ position: absolute;
5170
+ top: 48px;
5171
+ left: 2px;
5172
+ }
5173
+
5174
+ #datasetgroup-default {
5175
+ position: absolute;
5176
+ top: 47px;
5177
+ left: 79px;
5178
+ width: 80px;
5179
+ font-size: 12px;
5180
+ }
5181
+
5182
+ #datasetgroup-unit-lbl {
5183
+ position: absolute;
5184
+ top: 71px;
5185
+ left: 2px;
5186
+ }
5187
+
5188
+ #datasetgroup-unit {
5189
+ position: absolute;
5190
+ top: 70px;
5191
+ left: 32px;
5192
+ width: 70px;
5193
+ font-size: 12px;
5194
+ }
5195
+
5196
+ #datasetgroup-periodic {
5197
+ position: absolute;
5198
+ top: 90px;
5199
+ left: 0px;
5200
+ }
5201
+
5202
+ #datasetgroup-periodic-lbl {
5203
+ position: absolute;
5204
+ top: 92px;
5205
+ left: 22px;
5206
+ }
5207
+
5208
+ #datasetgroup-array {
5209
+ position: absolute;
5210
+ top: 90px;
5211
+ left: 77px;
5212
+ }
5213
+
5214
+ #datasetgroup-array-lbl {
5215
+ position: absolute;
5216
+ top: 92px;
5217
+ left: 99px;
5218
+ }
5219
+
5220
+ #datasetgroup-no-time-msg {
5221
+ position: absolute;
5222
+ top: 111px;
5223
+ left: 1px;
5224
+ width: 164px;
5225
+ height: 31px;
5226
+ z-index: 1;
5227
+ background-color: inherit;
5228
+ font-style: italic;
5229
+ padding-top: 15px;
5230
+ text-align: center;
5231
+ display: none;
5232
+ }
5233
+
5234
+ #datasetgroup-time-step-lbl {
5235
+ position: absolute;
5236
+ top: 113px;
5237
+ left: 2px;
5238
+ }
5239
+
5240
+ #datasetgroup-time-scale {
5241
+ position: absolute;
5242
+ top: 112px;
5243
+ left: 60px;
5244
+ width: 50px;
5245
+ font-size: 12px;
5246
+ }
5247
+
5248
+ #datasetgroup-time-unit {
5249
+ position: absolute;
5250
+ top: 113px;
5251
+ left: 116px;
5252
+ height: 19px;
5253
+ width: 45px;
5254
+ font-size: 12px;
5255
+ }
5256
+
5257
+ #datasetgroup-method-lbl {
5258
+ position: absolute;
5259
+ top: 137px;
5260
+ left: 2px;
5261
+ }
5262
+
5263
+ #datasetgroup-method {
5264
+ position: absolute;
5265
+ top: 135px;
5266
+ left: 50px;
5267
+ height: 21px;
5268
+ width: 111px;
5269
+ font-size: 12px;
5270
+ }
5271
+
5272
+ #datasetgroup-blackbox {
5273
+ position: absolute;
5274
+ bottom: 7px;
5275
+ left: 124px;
5276
+ width: 16px;
5277
+ height: 16px;
5278
+ font-size: 19px;
5279
+ cursor: pointer;
5280
+ color: #e0e0e0;
5281
+ }
5282
+
5283
+ #datasetgroup-outcome {
5284
+ position: absolute;
5285
+ bottom: 1px;
5286
+ left: 144px;
5287
+ width: 16px;
5288
+ height: 16px;
5289
+ cursor: pointer;
5290
+ }
5291
+
5292
+ #datasetgroup-io {
5293
+ position: absolute;
5294
+ bottom: -2px;
5295
+ left: 144px;
5296
+ font-size: 18px;
5297
+ color: Silver;
5298
+ cursor: pointer;
5299
+ }
5300
+
5301
+ #datasetgroup-separator {
5302
+ position: absolute;
5303
+ top: 23px;
5304
+ left: 165px;
5305
+ height: 164px;
5306
+ width: 3px;
5307
+ border-left: 1.5px Silver ridge;
5308
+ }
5309
+
5310
+ #datasetgroup-modif-header {
5311
+ position: absolute;
5312
+ top: 22px;
5313
+ left: 170px;
5314
+ font-style: italic;
5315
+ }
5316
+
5317
+ #datasetgroup-modif-titles {
5318
+ position: absolute;
5319
+ top: 38px;
5320
+ left: 170px;
5321
+ font-weight: 600;
5322
+ }
5323
+
5324
+ #datasetgroup-modif-scroll-area {
5325
+ position: absolute;
5326
+ top: 54px;
5327
+ left: 170px;
5328
+ width: 267px;
5329
+ height: 110px;
5330
+ overflow-y: auto;
5331
+ border-top: 1px solid Silver;
5332
+ }
5333
+
5334
+ #datasetgroup-modif-buttons {
5335
+ position: absolute;
5336
+ bottom: 1px;
5337
+ left: 170px;
5338
+ }
5339
+
5107
5340
  /* the MONITOR DIALOG displays solver progress and messages */
5108
5341
  #monitor-dlg {
5109
5342
  display: none;
@@ -279,19 +279,19 @@ class Controller {
279
279
  // Methods to ensure proper naming of entities.
280
280
 
281
281
  cleanName(name) {
282
- // Returns `name` without the object-attribute separator |, backslashes,
282
+ // Return `name` without the object-attribute separator |, backslashes,
283
283
  // and leading and trailing whitespace, and with all internal whitespace
284
284
  // reduced to a single space.
285
285
  name = name.replace(this.OA_SEPARATOR, ' ')
286
286
  .replace(/\||\\/g, ' ').trim()
287
287
  .replace(/\s\s+/g, ' ');
288
- // NOTE: this may still result in a single space, which is not a name
288
+ // NOTE: This may still result in a single space, which is not a name.
289
289
  if(name === ' ') return '';
290
290
  return name;
291
291
  }
292
292
 
293
293
  validName(name) {
294
- // Returns TRUE if `name` is a valid Linny-R entity name. These names
294
+ // Return TRUE if `name` is a valid Linny-R entity name. These names
295
295
  // must not be empty strings, may not contain brackets, backslashes or
296
296
  // vertical bars, may not end with a colon, and must start with an
297
297
  // underscore, a letter or a digit.
@@ -361,20 +361,84 @@ class Controller {
361
361
  // Replace a leading colon in `name` by `prefix`.
362
362
  // If `name` identifies a link or a constraint, this is applied to
363
363
  // both node names.
364
+ // NOTE: To give the modeler more control over what to use as prefix,
365
+ // successive colons indicate that a shorter prefix should be used.
366
+ // For example, when the prefix is XXX: YYY: ZZZ, two colons mean
367
+ // "use only XXX: YYY:", three colons "use only XXX:", etc.
364
368
  const
365
369
  arrow = (name.indexOf(this.LINK_ARROW) >= 0 ?
366
370
  this.LINK_ARROW : this.CONSTRAINT_ARROW),
367
371
  nodes = name.split(arrow);
368
372
  for(let i = 0; i < nodes.length; i++) {
369
- nodes[i] = nodes[i].replace(/^:\s*/, prefix)
373
+ // First check for the special case of just a leading colon plus
374
+ // possibly an entity attribute.
375
+ // NOTE:
376
+ const m = nodes[i].match(/^(:+)\s*(\|[^\|]*)/);
377
+ if(m) {
378
+ // Split prefix in parts.
379
+ const p = prefix.split(UI.PREFIXER);
380
+ // Remove last (empty!) substring.
381
+ p.pop();
382
+ // Shorten prefix by the number of successive colons minus 1.
383
+ for(let i = 1; i < m[1].length; i++) p.pop();
384
+ p.push('');
385
+ // New name is just the (shortened) prefix without its last ": ".
386
+ nodes[i] = p.join(UI.PREFIXER).replace(/:\s*$/, '') + m[2];
387
+ } else {
388
+ // Prefix is typically leading, so try to replace this first.
389
+ const m = nodes[i].match(/^(:+)/);
390
+ if(m) {
391
+ // Shorten prefix by the number of successive colons minus 1.
392
+ let p = prefix.split(UI.PREFIXER);
393
+ p.pop();
394
+ for(let i = 1; i < m[1].length; i++) p.pop();
395
+ p.push('');
396
+ nodes[i] = nodes[i].replace(/^:+\s*/, p.join(UI.PREFIXER));
397
+ } else {
398
+ // If no change, try to replace an embedded double prefix.
370
399
  // NOTE: An embedded double prefix, e.g., "xxx: : yyy" indicates
371
400
  // that the second colon+space should be replaced by the prefix.
372
- // This "double prefix" may occur only once in an entity name,
373
- // hence no global regexp.
374
- .replace(/(\w+):\s+:\s+(\w+)/, `$1: ${prefix}$2`);
401
+ const m = nodes[i].match(/(\w+):\s+(:+)\s+(\w+)/);
402
+ if(m) {
403
+ // Shorten prefix by the number of successive colons minus 1.
404
+ let p = prefix.split(UI.PREFIXER);
405
+ p.pop();
406
+ for(let i = 1; i < m[2].length; i++) p.pop();
407
+ p.push('');
408
+ prefix = p.join(UI.PREFIXER);
409
+ nodes[i] = `${m[1]}: ${prefix}${m[3]}`;
410
+ }
411
+ }
412
+ }
375
413
  }
376
414
  return nodes.join(arrow);
377
415
  }
416
+
417
+ entityPrefix(name) {
418
+ // Return the prefix of `name` with its trailing colon+space.
419
+ const
420
+ arrow = (name.indexOf(this.LINK_ARROW) >= 0 ?
421
+ this.LINK_ARROW : this.CONSTRAINT_ARROW),
422
+ nodes = name.split(arrow);
423
+ if(nodes.length === 1) return this.completePrefix(name);
424
+ // For names of links and constraints, it depends:
425
+ const
426
+ fn = nodes[0],
427
+ tn = nodes[1];
428
+ if(fn.indexOf(UI.PREFIXER) >= 0) {
429
+ if(tn.indexOf(UI.PREFIXER) >= 0) {
430
+ // If BOTH nodes are prefixed, use the longest prefix that these
431
+ // nodes have in common...
432
+ return UI.sharedPrefix(fn, tn) + UI.PREFIXER;
433
+ }
434
+ // .. otherwise, return the FROM node prefix.
435
+ return UI.completePrefix(fn);
436
+ }
437
+ // No FROM node prefix => return the TO node prefix (if any)
438
+ if(tn.indexOf(UI.PREFIXER) >= 0) return UI.completePrefix(tn);
439
+ // No prefixers => empty prefix.
440
+ return '';
441
+ }
378
442
 
379
443
  tailNumber(name) {
380
444
  // Return the string of digits at the end of `name`. If not there,
@@ -429,7 +493,6 @@ class Controller {
429
493
  return pan1.length - pan2.length;
430
494
  }
431
495
 
432
-
433
496
  nameToID(name) {
434
497
  // Return a name in lower case with link arrow replaced by three
435
498
  // underscores, constraint link arrow by four underscores, and spaces
@@ -1272,7 +1335,7 @@ class ExperimentManager {
1272
1335
  MODEL.round_sequence = asel.round_sequence;
1273
1336
  }
1274
1337
  }
1275
- // Only now compute the simulation run time (number of time steps)
1338
+ // Only now compute the simulation run time (number of time steps).
1276
1339
  xr.time_steps = MODEL.end_period - MODEL.start_period + 1;
1277
1340
  VM.callback = this.callback;
1278
1341
  // NOTE: Asynchronous call. All follow-up actions must be performed
@@ -1282,18 +1345,18 @@ class ExperimentManager {
1282
1345
  }
1283
1346
 
1284
1347
  processRun() {
1285
- // This method is called by the solveBlocks method of the Virtual Machine
1348
+ // This method is called by the solveBlocks method of the Virtual Machine.
1286
1349
  const x = MODEL.running_experiment;
1287
1350
  if(!x) return;
1288
1351
  const aci = x.active_combination_index;
1289
1352
  if(MODEL.solved) {
1290
- // NOTE: addresults will call processRestOfRun when completed
1353
+ // NOTE: addresults will call processRestOfRun when completed.
1291
1354
  x.runs[aci].addResults();
1292
1355
  } else {
1293
1356
  // Do not add results...
1294
1357
  UI.warn(`Model run #${aci} incomplete -- results will be invalid`);
1295
- // ... but do perform the usual post-processing
1296
- // NOTE: when sensitivity analysis is being performed, switch back to SA
1358
+ // ... but do perform the usual post-processing.
1359
+ // NOTE: When sensitivity analysis is being performed, switch back to SA.
1297
1360
  if(SENSITIVITY_ANALYSIS.experiment) {
1298
1361
  SENSITIVITY_ANALYSIS.processRestOfRun();
1299
1362
  } else {
@@ -331,7 +331,7 @@ class GUIChartManager extends ChartManager {
331
331
  }
332
332
 
333
333
  updateSelector() {
334
- // Adds one option to the selector for each chart defined for the model.
334
+ // Add one option to the selector for each chart defined for the model.
335
335
  // NOTE: Add the "new chart" option if it is not in the list.
336
336
  MODEL.addChart(this.new_chart_title);
337
337
  if(this.chart_index < 0) this.chart_index = 0;
@@ -545,16 +545,16 @@ class GUIChartManager extends ChartManager {
545
545
  }
546
546
 
547
547
  selectChart() {
548
- // Sets the selected chart to be the "active" chart
548
+ // Set the selected chart to be the "active" chart.
549
549
  const ci = parseInt(this.chart_selector.value);
550
- // Deselect variable only if different chart is selected
550
+ // Deselect variable only if different chart is selected.
551
551
  if(ci !== this.chart_index) this.variable_index = -1;
552
552
  this.chart_index = ci;
553
553
  this.updateDialog();
554
554
  }
555
555
 
556
556
  promptForTitle() {
557
- // Prompts modeler for a new title for the current chart
557
+ // Prompt modeler for a new title for the current chart.
558
558
  if(this.chart_index >= 0) {
559
559
  this.rename_chart_modal.show();
560
560
  const nct = document.getElementById('new-chart-title');
@@ -564,29 +564,29 @@ class GUIChartManager extends ChartManager {
564
564
  }
565
565
 
566
566
  renameChart() {
567
- // Renames the current chart
567
+ // Rename the current chart.
568
568
  if(this.chart_index >= 0) {
569
- const t = document.getElementById('new-chart-title').value.trim();
570
- // Check if a chart with this title already exists
569
+ const t = UI.cleanName(document.getElementById('new-chart-title').value);
570
+ // Check if a chart with this title already exists.
571
571
  const ci = MODEL.indexOfChart(t);
572
572
  if(ci >= 0 && ci != this.chart_index) {
573
573
  UI.warn(`A chart with title "${t}" already exists`);
574
574
  } else {
575
575
  const c = MODEL.charts[this.chart_index];
576
- // Remember the old title of the chart-to-be-renamed
576
+ // Remember the old title of the chart-to-be-renamed.
577
577
  const ot = c.title;
578
578
  c.title = t;
579
- // If the default '(new chart)' has been renamed, create a new one
579
+ // If the default '(new chart)' has been renamed, create a new one.
580
580
  if(ot === this.new_chart_title) {
581
581
  MODEL.addChart(ot);
582
582
  }
583
- // Update the chart index so that it points to the renamed chart
583
+ // Update the chart index so that it points to the renamed chart.
584
584
  this.chart_index = MODEL.indexOfChart(t);
585
585
  this.updateSelector();
586
- // Redraw the chart if title is shown
586
+ // Redraw the chart if title is shown.
587
587
  if(c.show_title) this.drawChart();
588
588
  }
589
- // Update experiment viewer in case its current experiment uses this chart
589
+ // Update dialogs that may refer to this chart.
590
590
  UI.updateControllerDialogs('CFX');
591
591
  }
592
592
  this.rename_chart_modal.hide();
@@ -744,6 +744,7 @@ class GUIChartManager extends ChartManager {
744
744
  chart.addWildcardVariables(dsm, indices);
745
745
  } else if(dsm.selector.startsWith(':')) {
746
746
  UI.notify('Plotting methods is work-in-progress!');
747
+ console.log('HERE dsm', dsm, 'expr', dsm.expression.text, 'indices', indices);
747
748
  } else {
748
749
  UI.notify(`Variable "${dsm.displayName}" cannot be plotted`);
749
750
  }