linny-r 2.0.8 → 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.
Files changed (36) hide show
  1. package/README.md +3 -40
  2. package/package.json +6 -2
  3. package/server.js +19 -157
  4. package/static/images/solve-not-same-changed.png +0 -0
  5. package/static/images/solve-not-same-not-changed.png +0 -0
  6. package/static/images/solve-same-changed.png +0 -0
  7. package/static/images/solve-same-not-changed.png +0 -0
  8. package/static/index.html +137 -20
  9. package/static/linny-r.css +260 -23
  10. package/static/scripts/iro.min.js +7 -7
  11. package/static/scripts/linny-r-ctrl.js +126 -85
  12. package/static/scripts/linny-r-gui-actor-manager.js +23 -33
  13. package/static/scripts/linny-r-gui-chart-manager.js +56 -53
  14. package/static/scripts/linny-r-gui-constraint-editor.js +10 -14
  15. package/static/scripts/linny-r-gui-controller.js +644 -260
  16. package/static/scripts/linny-r-gui-dataset-manager.js +64 -66
  17. package/static/scripts/linny-r-gui-documentation-manager.js +11 -17
  18. package/static/scripts/linny-r-gui-equation-manager.js +22 -22
  19. package/static/scripts/linny-r-gui-experiment-manager.js +124 -141
  20. package/static/scripts/linny-r-gui-expression-editor.js +26 -12
  21. package/static/scripts/linny-r-gui-file-manager.js +42 -48
  22. package/static/scripts/linny-r-gui-finder.js +294 -55
  23. package/static/scripts/linny-r-gui-model-autosaver.js +2 -4
  24. package/static/scripts/linny-r-gui-monitor.js +35 -41
  25. package/static/scripts/linny-r-gui-paper.js +42 -70
  26. package/static/scripts/linny-r-gui-power-grid-manager.js +31 -34
  27. package/static/scripts/linny-r-gui-receiver.js +1 -2
  28. package/static/scripts/linny-r-gui-repository-browser.js +44 -46
  29. package/static/scripts/linny-r-gui-scale-unit-manager.js +32 -32
  30. package/static/scripts/linny-r-gui-sensitivity-analysis.js +61 -67
  31. package/static/scripts/linny-r-gui-undo-redo.js +94 -95
  32. package/static/scripts/linny-r-milp.js +20 -24
  33. package/static/scripts/linny-r-model.js +1932 -2274
  34. package/static/scripts/linny-r-utils.js +27 -27
  35. package/static/scripts/linny-r-vm.js +900 -998
  36. package/static/show-png.html +0 -113
@@ -32,7 +32,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32
32
  SOFTWARE.
33
33
  */
34
34
 
35
- // CLASS GUIDatasetManager provides the dataset dialog functionality
35
+ // CLASS GUIDatasetManager provides the dataset dialog functionality.
36
36
  class GUIDatasetManager extends DatasetManager {
37
37
  constructor() {
38
38
  super();
@@ -44,7 +44,7 @@ class GUIDatasetManager extends DatasetManager {
44
44
  'click', (event) => UI.toggleDialog(event));
45
45
  document.getElementById('ds-new-btn').addEventListener(
46
46
  // Shift-click on New button => add prefix of selected dataset
47
- // (if any) to the name field of the dialog
47
+ // (if any) to the name field of the dialog.
48
48
  'click', () => DATASET_MANAGER.promptForDataset(event.shiftKey));
49
49
  document.getElementById('ds-data-btn').addEventListener(
50
50
  'click', () => DATASET_MANAGER.editData());
@@ -58,14 +58,14 @@ class GUIDatasetManager extends DatasetManager {
58
58
  'click', () => DATASET_MANAGER.deleteDataset());
59
59
  document.getElementById('ds-filter-btn').addEventListener(
60
60
  'click', () => DATASET_MANAGER.toggleFilter());
61
- // Update when filter input text changes
61
+ // Update when filter input text changes.
62
62
  this.filter_text = document.getElementById('ds-filter-text');
63
63
  this.filter_text.addEventListener(
64
64
  'input', () => DATASET_MANAGER.changeFilter());
65
65
  this.dataset_table = document.getElementById('dataset-table');
66
- // Data properties pane
66
+ // Data properties pane.
67
67
  this.properties = document.getElementById('dataset-properties');
68
- // Toggle buttons at bottom of dialog
68
+ // Toggle buttons at bottom of dialog.
69
69
  this.blackbox = document.getElementById('dataset-blackbox');
70
70
  this.blackbox.addEventListener(
71
71
  'click', () => DATASET_MANAGER.toggleBlackBox());
@@ -75,7 +75,7 @@ class GUIDatasetManager extends DatasetManager {
75
75
  this.io_box = document.getElementById('dataset-io');
76
76
  this.io_box.addEventListener(
77
77
  'click', () => DATASET_MANAGER.toggleImportExport());
78
- // Modifier pane buttons
78
+ // Modifier pane buttons.
79
79
  document.getElementById('ds-add-modif-btn').addEventListener(
80
80
  'click', () => DATASET_MANAGER.promptForSelector('new'));
81
81
  document.getElementById('ds-rename-modif-btn').addEventListener(
@@ -86,9 +86,9 @@ class GUIDatasetManager extends DatasetManager {
86
86
  'click', () => DATASET_MANAGER.deleteModifier());
87
87
  document.getElementById('ds-convert-modif-btn').addEventListener(
88
88
  'click', () => DATASET_MANAGER.promptToConvertModifiers());
89
- // Modifier table
89
+ // Modifier table.
90
90
  this.modifier_table = document.getElementById('dataset-modif-table');
91
- // Modal dialogs
91
+ // Modal dialogs.
92
92
  this.new_modal = new ModalDialog('new-dataset');
93
93
  this.new_modal.ok.addEventListener(
94
94
  'click', () => DATASET_MANAGER.newDataset());
@@ -170,7 +170,8 @@ class GUIDatasetManager extends DatasetManager {
170
170
  }
171
171
 
172
172
  enterKey() {
173
- // Open "edit" dialog for the selected dataset or modifier expression
173
+ // Open "edit" dialog for the selected dataset or modifier expression.
174
+ if(!this.focal_table) this.focal_table = this.dataset_table;
174
175
  const srl = this.focal_table.getElementsByClassName('sel-set');
175
176
  if(srl.length > 0) {
176
177
  const r = this.focal_table.rows[srl[0].rowIndex];
@@ -190,7 +191,8 @@ class GUIDatasetManager extends DatasetManager {
190
191
  }
191
192
 
192
193
  upDownKey(dir) {
193
- // Select row above or below the selected one (if possible)
194
+ // Select row above or below the selected one (if possible).
195
+ if(!this.focal_table) this.focal_table = this.dataset_table;
194
196
  const srl = this.focal_table.getElementsByClassName('sel-set');
195
197
  if(srl.length > 0) {
196
198
  let r = this.focal_table.rows[srl[0].rowIndex + dir];
@@ -206,12 +208,24 @@ class GUIDatasetManager extends DatasetManager {
206
208
  }
207
209
  }
208
210
 
211
+ expandToShow(name) {
212
+ // Expand all prefix rows for dataset having `name` so as to make it visible.
213
+ const pn = UI.prefixesAndName(name);
214
+ if(pn.length > 1) {
215
+ pn.pop();
216
+ while(pn.length) {
217
+ addDistinct(pn.join(UI.PREFIXER).toLowerCase(), this.expanded_rows);
218
+ pn.pop();
219
+ }
220
+ this.updateDialog();
221
+ }
222
+ }
223
+
209
224
  hideCollapsedRows() {
210
- // Hides all rows except top level and immediate children of expanded
211
- for(let i = 0; i < this.dataset_table.rows.length; i++) {
225
+ // Hide all rows except top level and immediate children of expanded.
226
+ for(const row of this.dataset_table.rows) {
212
227
  const
213
- row = this.dataset_table.rows[i],
214
- // Get the first DIV in the first TD of this row
228
+ // Get the first DIV in the first TD of this row.
215
229
  first_div = row.firstChild.firstElementChild,
216
230
  btn = first_div.dataset.prefix === 'x';
217
231
  let p = row.dataset.prefix,
@@ -219,18 +233,18 @@ class GUIDatasetManager extends DatasetManager {
219
233
  show = !p || x;
220
234
  if(btn) {
221
235
  const btn_div = row.getElementsByClassName('tree-btn')[0];
222
- // Special expand/collapse row
236
+ // Special expand/collapse row.
223
237
  if(show) {
224
- // Set triangle to point down
238
+ // Set triangle to point down.
225
239
  btn_div.innerText = '\u25BC';
226
240
  } else {
227
- // Set triangle to point right
241
+ // Set triangle to point right.
228
242
  btn_div.innerText = '\u25BA';
229
- // See whether "parent prefix" is expanded
243
+ // See whether "parent prefix" is expanded.
230
244
  p = p.split(UI.PREFIXER);
231
245
  p.pop();
232
246
  p = p.join(UI.PREFIXER);
233
- // If so, then also show the row
247
+ // If so, then also show the row.
234
248
  show = (!p || this.expanded_rows.indexOf(p) >= 0);
235
249
  }
236
250
  }
@@ -239,7 +253,7 @@ class GUIDatasetManager extends DatasetManager {
239
253
  }
240
254
 
241
255
  togglePrefixRow(e) {
242
- // Shows list items of the next prefix level
256
+ // Show list items of the next prefix level.
243
257
  let r = e.target;
244
258
  while(r.tagName !== 'TR') r = r.parentNode;
245
259
  const
@@ -247,7 +261,7 @@ class GUIDatasetManager extends DatasetManager {
247
261
  i = this.expanded_rows.indexOf(p);
248
262
  if(i >= 0) {
249
263
  this.expanded_rows.splice(i, 1);
250
- // Also remove all prefixes that have `p` as prefix
264
+ // Also remove all prefixes that have `p` as prefix.
251
265
  for(let j = this.expanded_rows.length - 1; j >= 0; j--) {
252
266
  if(this.expanded_rows[j].startsWith(p + UI.PREFIXER)) {
253
267
  this.expanded_rows.splice(j, 1);
@@ -260,7 +274,7 @@ class GUIDatasetManager extends DatasetManager {
260
274
  }
261
275
 
262
276
  rowByPrefix(prefix) {
263
- // Returns first table row with the specified prefix
277
+ // Return the first table row with the specified prefix.
264
278
  if(!prefix) return null;
265
279
  let lcp = prefix.toLowerCase(),
266
280
  pl = lcp.split(': ');
@@ -274,20 +288,17 @@ class GUIDatasetManager extends DatasetManager {
274
288
  pl.pop();
275
289
  }
276
290
  this.hideCollapsedRows();
277
- for(let i = 0; i < this.dataset_table.rows.length; i++) {
278
- const r = this.dataset_table.rows[i];
279
- if(r.dataset.prefix === lcp) return r;
280
- }
291
+ for(const r of this.dataset_table.rows) if(r.dataset.prefix === lcp) return r;
281
292
  return null;
282
293
  }
283
294
 
284
295
  selectPrefixRow(e) {
285
- // Selects expand/collapse prefix row
296
+ // Select expand/collapse prefix row.
286
297
  this.focal_table = this.dataset_table;
287
- // NOTE: `e` can also be a string specifying the prefix to select
298
+ // NOTE: `e` can also be a string specifying the prefix to select.
288
299
  let r = e.target || this.rowByPrefix(e);
289
300
  if(!r) return;
290
- // Modeler may have clicked on the expand/collapse triangle;
301
+ // Modeler may have clicked on the expand/collapse triangle.
291
302
  const toggle = r.classList.contains('tree-btn');
292
303
  while(r.tagName !== 'TR') r = r.parentNode;
293
304
  this.selected_prefix_row = r;
@@ -328,8 +339,8 @@ class GUIDatasetManager extends DatasetManager {
328
339
  names = [],
329
340
  pref_names = {},
330
341
  xids = [];
331
- for(let i = 0; i < dnl.length; i++) {
332
- const pref = UI.prefixesAndName(MODEL.datasets[dnl[i]].name);
342
+ for(const dn of dnl) {
343
+ const pref = UI.prefixesAndName(MODEL.datasets[dn].name);
333
344
  // NOTE: only the name part (so no prefixes at all) will be shown
334
345
  names.push(pref.pop());
335
346
  indent.push(pref.length);
@@ -851,8 +862,7 @@ class GUIDatasetManager extends DatasetManager {
851
862
  // Update all chartvariables referencing this dataset + old selector.
852
863
  const vl = MODEL.datasetVariables;
853
864
  let cv_cnt = 0;
854
- for(let i = 0; i < vl.length; i++) {
855
- const v = vl[i];
865
+ for(const v of vl) {
856
866
  if(v.object === this.selected_dataset && v.attribute === oldm.selector) {
857
867
  v.attribute = m.selector;
858
868
  cv_cnt++;
@@ -949,16 +959,10 @@ class GUIDatasetManager extends DatasetManager {
949
959
  return;
950
960
  }
951
961
  prefix += UI.PREFIXER;
952
- const
953
- dsn = ds.displayName,
954
- pml = ds.inferPrefixableModifiers,
955
- xl = MODEL.allExpressions,
956
- vl = MODEL.datasetVariables,
957
- nl = MODEL.notesWithTags;
958
- for(let i = 0; i < pml.length; i++) {
962
+ const dsn = ds.displayName;
963
+ for(const m of ds.inferPrefixableModifiers) {
959
964
  // Create prefixed dataset with correct default value
960
965
  const
961
- m = pml[i],
962
966
  sel = m.selector,
963
967
  newds = MODEL.addDataset(prefix + sel);
964
968
  if(newds) {
@@ -976,17 +980,16 @@ class GUIDatasetManager extends DatasetManager {
976
980
  const
977
981
  from = dsn + UI.OA_SEPARATOR + sel,
978
982
  to = newds.displayName;
979
- for(let j = 0; j < vl.length; j++) {
980
- const v = vl[j];
981
- // NOTE: variable should match original dataset + selector
983
+ for(const v of MODEL.datasetVariables) {
984
+ // NOTE: variable should match original dataset + selector.
982
985
  if(v.displayName === from) {
983
- // Change to new dataset WITHOUT selector
986
+ // Change to new dataset WITHOUT selector.
984
987
  v.object = newds;
985
988
  v.attribute = '';
986
989
  vcount++;
987
990
  }
988
991
  }
989
- // Rename variable in the Sensitivity Analysis
992
+ // Rename variable in the Sensitivity Analysis.
990
993
  for(let j = 0; j < MODEL.sensitivity_parameters.length; j++) {
991
994
  if(MODEL.sensitivity_parameters[j] === from) {
992
995
  MODEL.sensitivity_parameters[j] = to;
@@ -1008,10 +1011,8 @@ class GUIDatasetManager extends DatasetManager {
1008
1011
  // Pattern ends at any character that is invalid for a
1009
1012
  // dataset modifier selector (unlike equation names)
1010
1013
  '\\s*[^a-zA-Z0-9\\+\\-\\%\\_]', 'gi');
1011
- for(let j = 0; j < xl.length; j++) {
1012
- const
1013
- x = xl[j],
1014
- matches = x.text.match(re);
1014
+ for(const x of MODEL.allExpressions) {
1015
+ const matches = x.text.match(re);
1015
1016
  if(matches) {
1016
1017
  for(let k = 0; k < matches.length; k++) {
1017
1018
  // NOTE: each match will start with the opening bracket,
@@ -1025,10 +1026,8 @@ class GUIDatasetManager extends DatasetManager {
1025
1026
  x.code = null;
1026
1027
  }
1027
1028
  }
1028
- for(let j = 0; j < nl.length; j++) {
1029
- const
1030
- n = nl[j],
1031
- matches = n.contents.match(re);
1029
+ for(const n of MODEL.notesWithTags) {
1030
+ const matches = n.contents.match(re);
1032
1031
  if(matches) {
1033
1032
  for(let k = 0; k < matches.length; k++) {
1034
1033
  // See NOTE above for the use of `slice` here
@@ -1063,7 +1062,7 @@ class GUIDatasetManager extends DatasetManager {
1063
1062
  }
1064
1063
 
1065
1064
  editData() {
1066
- // Show the Edit time series dialog
1065
+ // Show the Edit time series dialog.
1067
1066
  const
1068
1067
  ds = this.selected_dataset,
1069
1068
  md = this.series_modal,
@@ -1073,7 +1072,7 @@ class GUIDatasetManager extends DatasetManager {
1073
1072
  md.element('unit').value = ds.scale_unit;
1074
1073
  cover.style.display = (ds.array ? 'block' : 'none');
1075
1074
  md.element('time-scale').value = VM.sig4Dig(ds.time_scale);
1076
- // Add options for time unit selector
1075
+ // Add options for time unit selector.
1077
1076
  const ol = [];
1078
1077
  for(let u in VM.time_unit_shorthand) {
1079
1078
  if(VM.time_unit_shorthand.hasOwnProperty(u)) {
@@ -1083,7 +1082,7 @@ class GUIDatasetManager extends DatasetManager {
1083
1082
  }
1084
1083
  }
1085
1084
  md.element('time-unit').innerHTML = ol.join('');
1086
- // Add options for(dis)aggregation method selector
1085
+ // Add options for(dis)aggregation method selector.
1087
1086
  ol.length = 0;
1088
1087
  for(let i = 0; i < this.methods.length; i++) {
1089
1088
  ol.push(['<option value="', this.methods[i],
@@ -1091,12 +1090,12 @@ class GUIDatasetManager extends DatasetManager {
1091
1090
  '">', this.method_names[i], '</option>'].join(''));
1092
1091
  }
1093
1092
  md.element('method').innerHTML = ol.join('');
1094
- // Update the "periodic" box
1093
+ // Update the "periodic" box.
1095
1094
  UI.setBox('series-periodic', ds.periodic);
1096
- // Update the "array" box
1095
+ // Update the "array" box.
1097
1096
  UI.setBox('series-array', ds.array);
1098
1097
  md.element('url').value = ds.url;
1099
- // Show data as decimal numbers (JS default notation) on separate lines
1098
+ // Show data as decimal numbers (JS default notation) on separate lines.
1100
1099
  this.series_data.value = ds.data.join('\n');
1101
1100
  md.show('default');
1102
1101
  }
@@ -1181,9 +1180,8 @@ class GUIDatasetManager extends DatasetManager {
1181
1180
  parts = lines[0].split(sep),
1182
1181
  dsn = [];
1183
1182
  let quoted = false;
1184
- for(let i = 0; i < parts.length; i++) {
1183
+ for(const p of parts) {
1185
1184
  const
1186
- p = parts[i],
1187
1185
  swq = /^\"(\"\")*($|[^\"])/.test(p),
1188
1186
  ewq = p.endsWith('"');
1189
1187
  if(!quoted && swq && !ewq) {
@@ -1220,7 +1218,8 @@ class GUIDatasetManager extends DatasetManager {
1220
1218
  UI.warn(`Invalid default value "${v}" in column ${i}`);
1221
1219
  return false;
1222
1220
  } else {
1223
- dsa.push([sf]);
1221
+ // Push empty list, as this will become the actual dataset without default value.
1222
+ dsa.push([]);
1224
1223
  }
1225
1224
  }
1226
1225
  for(let i = 2; i < n; i++) {
@@ -1229,16 +1228,15 @@ class GUIDatasetManager extends DatasetManager {
1229
1228
  UI.warn(`Number of values (${dsv.length}) on line ${i} does not match number of dataset names (${ncol})`);
1230
1229
  return false;
1231
1230
  }
1232
- for(let j = 0; j < dsv.length; j++) {
1231
+ for(let j = 0; j < ncol; j++) {
1233
1232
  const
1234
1233
  v = dsv[j].trim(),
1235
1234
  sf = safeStrToFloat(v, '');
1236
1235
  if(sf === '' && v !== '') {
1237
1236
  UI.warn(`Invalid numerical value "${v}" for <strong>${dsn[j]}</strong> on line ${i}`);
1238
1237
  return false;
1239
- } else {
1240
- dsa[j].push(sf);
1241
1238
  }
1239
+ dsa[j].push(sf);
1242
1240
  }
1243
1241
  }
1244
1242
  // Add or update datasets.
@@ -280,10 +280,10 @@ class DocumentationManager {
280
280
 
281
281
  clearEntity(list) {
282
282
  // To be called when entities are deleted
283
- if(list.indexOf(this.entity) >= 0) {
283
+ if(list === true || list.indexOf(this.entity) >= 0) {
284
284
  this.stopEditing();
285
285
  this.entity = null;
286
- this.title.innerHTML = 'Information and documentation';
286
+ this.title.innerHTML = 'About Linny-R';
287
287
  this.viewer.innerHTML = this.about_linny_r;
288
288
  }
289
289
  }
@@ -540,18 +540,17 @@ class DocumentationManager {
540
540
 
541
541
  showArrowLinks(arrow) {
542
542
  // Show list of links represented by a composite arrow.
543
- const
544
- n = arrow.links.length,
545
- msg = 'Arrow represents ' + pluralS(n, 'link');
543
+ const msg = 'Arrow represents ' + pluralS(arrow.links.length, 'link');
546
544
  UI.setMessage(msg);
547
545
  if(this.visible && !this.editing) {
548
546
  // Set the dialog title.
549
547
  this.title.innerHTML = msg;
550
548
  // Show list.
551
549
  const lis = [];
552
- let l, dn, c, af;
553
- for(let i = 0; i < n; i++) {
554
- l = arrow.links[i];
550
+ let dn,
551
+ c,
552
+ af;
553
+ for(const l of arrow.links) {
555
554
  dn = l.displayName;
556
555
  if(l.from_node instanceof Process) {
557
556
  c = UI.color.produced;
@@ -600,9 +599,7 @@ class DocumentationManager {
600
599
  this.title.innerHTML = msg;
601
600
  // Show list.
602
601
  const lis = [];
603
- for(let i = 0; i < iol.length; i++) {
604
- lis.push(`<li>${iol[i].displayName}</li>`);
605
- }
602
+ for(const io of iol) lis.push(`<li>${io.displayName}</li>`);
606
603
  lis.sort(ciCompare);
607
604
  this.viewer.innerHTML = `<ul>${lis.join('')}</ul>`;
608
605
  }
@@ -714,9 +711,8 @@ class DocumentationManager {
714
711
  this.differenceAsTable(d.settings));
715
712
  if('units' in d) html.push('<h2>Units</h2>',
716
713
  this.differenceAsTable(d.units));
717
- for(let i = 0; i < UI.MC.ENTITY_PROPS.length; i++) {
718
- const e = UI.MC.ENTITY_PROPS[i];
719
- if(e in d) html.push('<h2>' + this.propertyName(e) + '</h2>',
714
+ for(const e of UI.MC.ENTITY_PROPS) if(e in d) {
715
+ html.push('<h2>' + this.propertyName(e) + '</h2>',
720
716
  this.differenceAsTable(d[e]));
721
717
  }
722
718
  if('charts' in d) html.push('<h2><em>Charts</em></h2>',
@@ -759,9 +755,7 @@ class DocumentationManager {
759
755
  const
760
756
  html = ['<table>'],
761
757
  keys = Object.keys(d).sort();
762
- for(let i = 0; i < keys.length; i++) {
763
- html.push(this.differenceAsTableRow(d, keys[i]));
764
- }
758
+ for(const k of keys) html.push(this.differenceAsTableRow(d, k));
765
759
  html.push('</table>');
766
760
  return html.join('\n');
767
761
  }
@@ -148,7 +148,7 @@ class EquationManager {
148
148
  }
149
149
 
150
150
  updateDialog() {
151
- // Updates equation list, highlighting selected equation (if any)
151
+ // Updates equation list, highlighting selected equation (if any).
152
152
  const
153
153
  ed = MODEL.equations_dataset,
154
154
  ml = [],
@@ -160,34 +160,36 @@ class EquationManager {
160
160
  this.outcome_btn.classList.add('not-selected');
161
161
  }
162
162
  let smid = 'eqmtr';
163
+ // NOTE> Selector list `msl` contains names, not IDs.
163
164
  for(let i = 0; i < msl.length; i++) {
164
165
  const
165
- m = ed.modifiers[UI.nameToID(msl[i])],
166
- wild = (m.selector.indexOf('??') >= 0),
167
- method = m.selector.startsWith(':'),
166
+ id = UI.nameToID(msl[i]),
167
+ m = ed.modifiers[id],
168
+ sel = safeDoubleQuotes(m.selector),
169
+ expr = m.expression,
170
+ wild = (sel.indexOf('??') >= 0),
171
+ method = sel.startsWith(':'),
168
172
  multi = (this.multi_line ? '-multi' : ''),
169
- issue = (m.expression.compile_issue ? ' compile-issue' :
170
- (m.expression.compute_issue ? ' compute-issue' : '')),
171
- clk = '" onclick="EQUATION_MANAGER.selectModifier(event, \'' +
172
- escapedSingleQuotes(m.selector) + '\'',
173
- mover = (method ? ' onmouseover="EQUATION_MANAGER.showInfo(\'' +
174
- m.identifier + '\', event.shiftKey);"' : '');
173
+ issue = (expr.compile_issue ? ' compile-issue' :
174
+ (expr.compute_issue ? ' compute-issue' : '')),
175
+ clk = `" onclick="EQUATION_MANAGER.selectModifier(event, '${id}'`,
176
+ mover = (!method ? '' :
177
+ `onmouseover="EQUATION_MANAGER.showInfo('${id}', event.shiftKey);"`);
175
178
  if(m === sm) smid += i;
176
179
  ml.push(['<tr id="eqmtr', i, '" class="dataset-modif',
177
180
  (m === sm ? ' sel-set' : ''),
178
181
  '"><td class="equation-selector',
179
182
  (method ? ' method' : ''),
180
183
  // Display in gray when method cannot be applied.
181
- (m.expression.noMethodObject ? ' no-object' : ''),
182
- (m.expression.isStatic ? '' : ' it'), issue,
184
+ (expr.noMethodObject ? ' no-object' : ''),
185
+ (expr.isStatic ? '' : ' it'), issue,
183
186
  (wild ? ' wildcard' : ''), clk, ', false);"', mover, '>',
184
187
  (m.outcome_equation ? '<span class="outcome"></span>' : ''),
185
- (wild ? wildcardFormat(m.selector) : m.selector),
188
+ (wild ? wildcardFormat(sel) : sel),
186
189
  '</td><td class="equation-expression', multi, issue,
187
190
  (issue ? '"title="' +
188
- safeDoubleQuotes(m.expression.compile_issue ||
189
- m.expression.compute_issue) : ''),
190
- clk, ');">', m.expression.text, '</td></tr>'].join(''));
191
+ safeDoubleQuotes(expr.compile_issue || expr.compute_issue) : ''),
192
+ clk, ');">', expr.text, '</td></tr>'].join(''));
191
193
  }
192
194
  this.table.innerHTML = ml.join('');
193
195
  this.scroll_area.style.display = 'block';
@@ -208,10 +210,10 @@ class EquationManager {
208
210
 
209
211
  selectModifier(event, id, x=true) {
210
212
  // Select modifier, or when Alt- or double-clicked, edit its expression
211
- // or the equation name (= name of the modifier)
213
+ // or the equation name (= name of the modifier).
212
214
  if(MODEL.equations_dataset) {
213
215
  const
214
- m = MODEL.equations_dataset.modifiers[UI.nameToID(id)] || null,
216
+ m = MODEL.equations_dataset.modifiers[id] || null,
215
217
  edit = event.altKey || this.doubleClicked(m);
216
218
  this.selected_modifier = m;
217
219
  if(m && edit) {
@@ -327,10 +329,8 @@ class EquationManager {
327
329
  }
328
330
  // Update all chartvariables referencing this dataset + old selector
329
331
  let cv_cnt = 0;
330
- for(let i = 0; i < MODEL.charts.length; i++) {
331
- const c = MODEL.charts[i];
332
- for(let j = 0; j < c.variables.length; j++) {
333
- const v = c.variables[j];
332
+ for(const c of MODEL.charts) {
333
+ for(const v of c.variables) {
334
334
  if(v.object === MODEL.equations_dataset && v.attribute === olds) {
335
335
  v.attribute = m.selector;
336
336
  cv_cnt++;