linny-r 2.1.5 → 2.1.7

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.1.5",
3
+ "version": "2.1.7",
4
4
  "description": "Executable graphical language with WYSIWYG editor for MILP models",
5
5
  "main": "server.js",
6
6
  "scripts": {
package/static/index.html CHANGED
@@ -3178,6 +3178,12 @@ where X can be one or several of these letters: ABCDELPQ">
3178
3178
  <img id="finder-chart-btn" class="btn enab"
3179
3179
  src="images/chart.png"
3180
3180
  title="Add attribute to chart">
3181
+ <img id="finder-experiment-btn" class="btn enab"
3182
+ src="images/experiment.png"
3183
+ title="Only consider experiment outcomes">
3184
+ <img id="finder-table-btn" class="btn enab"
3185
+ src="images/table.png"
3186
+ title="View entity attribute values">
3181
3187
  <img id="finder-copy-btn" class="btn enab"
3182
3188
  src="images/table-to-clpbrd.png"
3183
3189
  title="Copy entity attributes to clipboard">
@@ -3193,6 +3199,14 @@ where X can be one or several of these letters: ABCDELPQ">
3193
3199
  <table id="finder-expression-table">
3194
3200
  </table>
3195
3201
  </div>
3202
+ <div id="finder-data-pane">
3203
+ <table id="finder-data-header">
3204
+ </table>
3205
+ <div id="finder-data-scroll-area">
3206
+ <table id="finder-data-table">
3207
+ </table>
3208
+ </div>
3209
+ </div>
3196
3210
  <div id="finder-resize" class="resizer"></div>
3197
3211
  </div>
3198
3212
 
@@ -3206,8 +3220,10 @@ where X can be one or several of these letters: ABCDELPQ">
3206
3220
  <img class="ok-btn" src="images/ok.png">
3207
3221
  </div>
3208
3222
  <div style="margin: 2px; padding-right: 2px; white-space: nowrap">
3209
- <select id="confirm-add-chart-variables-attribute"></select>
3210
- of <span id="confirm-add-chart-variables-count"></span>
3223
+ <div id="confirm-add-chart-variables-attr-of">
3224
+ <select id="confirm-add-chart-variables-attribute"></select> of
3225
+ </div>
3226
+ <span id="confirm-add-chart-variables-count"></span>
3211
3227
  </div>
3212
3228
  <div>
3213
3229
  <div id="confirm-add-chart-variables-absolute" class="box clear"></div>
@@ -1167,6 +1167,10 @@ table.power-flow th:not(:first-child) {
1167
1167
  filter: brightness(160%);
1168
1168
  }
1169
1169
 
1170
+ #settings-power-btn.ignore {
1171
+ filter: hue-rotate(230deg);
1172
+ }
1173
+
1170
1174
  #password-dlg {
1171
1175
  width: min-content;
1172
1176
  height: min-content;
@@ -2660,6 +2664,7 @@ div.io-box {
2660
2664
  #finder-table,
2661
2665
  #finder-item-table,
2662
2666
  #finder-expression-table,
2667
+ #finder-data-table,
2663
2668
  #boundline-data-series-table,
2664
2669
  #boundline-data-sel-table,
2665
2670
  #restore-table {
@@ -5160,6 +5165,8 @@ img.finder {
5160
5165
 
5161
5166
  #finder-edit-btn,
5162
5167
  #finder-chart-btn,
5168
+ #finder-experiment-btn,
5169
+ #finder-table-btn,
5163
5170
  #finder-copy-btn {
5164
5171
  height: 15px;
5165
5172
  width: 15px;
@@ -5211,9 +5218,46 @@ img.finder {
5211
5218
  border-top: 1px solid Silver;
5212
5219
  }
5213
5220
 
5221
+ #finder-data-pane {
5222
+ position: absolute;
5223
+ background-color: inherit;
5224
+ top: 23px;
5225
+ left: calc(50% + 4px);
5226
+ width: calc(50% - 4px);
5227
+ height: calc(100% - 30px);
5228
+ }
5229
+
5230
+ #finder-data-header {
5231
+ height: 20px;
5232
+ font-weight: bold;
5233
+ text-align: right;
5234
+ width: 100%
5235
+ }
5236
+
5237
+ #finder-data-scroll-area {
5238
+ width: calc(100% - 2px);
5239
+ height: calc(100% - 32px);
5240
+ overflow-y: auto;
5241
+ border-top: 1px solid Silver;
5242
+ }
5243
+
5244
+ #finder-data-table {
5245
+ text-align: right;
5246
+ }
5247
+
5248
+ #finder-data-header > tbody > tr > td:last-child,
5249
+ #finder-data-table > tbody > tr > td:last-child {
5250
+ padding-right: 3%;
5251
+ }
5252
+
5214
5253
  #confirm-add-chart-variables-dlg {
5215
5254
  width: min-content;
5216
5255
  height: min-content;
5256
+ min-width: 135px;
5257
+ }
5258
+
5259
+ #confirm-add-chart-variables-attr-of {
5260
+ display: inline-block;
5217
5261
  }
5218
5262
 
5219
5263
  #confirm-add-chart-variables-attribute {
@@ -11,7 +11,7 @@ warning or information messages are displayed.
11
11
  */
12
12
 
13
13
  /*
14
- Copyright (c) 2017-2024 Delft University of Technology
14
+ Copyright (c) 2017-2025 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
@@ -41,7 +41,7 @@ const CONFIGURATION = {
41
41
  // To keep model files compact, floating point values in datasets and run
42
42
  // results are stored with a limited number of significant digits
43
43
  dataset_precision: 8,
44
- results_precision: 6,
44
+ results_precision: 8,
45
45
  // Default properties for new models
46
46
  default_currency_unit: 'EUR',
47
47
  default_time_unit: 'hour',
@@ -209,7 +209,7 @@ class GUIChartManager extends ChartManager {
209
209
  this.variable_index = -1;
210
210
  this.stretch_factor = 1;
211
211
  this.drawing_graph = false;
212
- // Clear the model-related DOM elements
212
+ // Clear the model-related DOM elements.
213
213
  this.chart_selector.innerHTML = '';
214
214
  this.variables_table.innerHTML = '';
215
215
  this.options_shown = true;
@@ -641,7 +641,8 @@ class GUIChartManager extends ChartManager {
641
641
  for(const cv of c.variables) {
642
642
  const nv = new ChartVariable(nc);
643
643
  nv.setProperties(cv.object, cv.attribute, cv.stacked,
644
- cv.color, cv.scale_factor, cv.absolute, cv.line_width, cv.sorted);
644
+ cv.color, cv.scale_factor, cv.absolute, cv.line_width,
645
+ cv.visible, cv.sorted);
645
646
  nc.variables.push(nv);
646
647
  }
647
648
  this.chart_index = MODEL.indexOfChart(nc.title);
@@ -3078,6 +3078,7 @@ class GUIController extends Controller {
3078
3078
  MODEL.t = Math.max(1, MODEL.t - dt);
3079
3079
  UI.updateTimeStep();
3080
3080
  UI.drawDiagram(MODEL);
3081
+ if(FINDER.visible && FINDER.tabular_view) FINDER.updateTabularView();
3081
3082
  }
3082
3083
  }
3083
3084
 
@@ -3088,6 +3089,7 @@ class GUIController extends Controller {
3088
3089
  MODEL.t = Math.min(MODEL.end_period - MODEL.start_period + 1, MODEL.t + dt);
3089
3090
  UI.updateTimeStep();
3090
3091
  UI.drawDiagram(MODEL);
3092
+ if(FINDER.visible && FINDER.tabular_view) FINDER.updateTabularView();
3091
3093
  }
3092
3094
  }
3093
3095
 
@@ -3096,39 +3098,39 @@ class GUIController extends Controller {
3096
3098
  //
3097
3099
 
3098
3100
  copyStringToClipboard(string) {
3099
- // Copies string to clipboard and notifies user of #lines copied
3100
- let msg = pluralS(string.split('\n').length, 'line') +
3101
- ' copied to clipboard',
3102
- type = 'notification';
3101
+ // Copy string to clipboard and notifies user of #lines copied.
3103
3102
  if(navigator.clipboard) {
3104
- navigator.clipboard.writeText(string).catch(
3105
- () => UI.setMessage('Failed to copy to clipboard', 'warning'));
3103
+ const msg = pluralS(string.split('\n').length, 'line') +
3104
+ ' copied to clipboard';
3105
+ navigator.clipboard.writeText(string)
3106
+ .then(() => UI.setMessage(msg, 'notification'))
3107
+ .catch(() => UI.setMessage('Failed to copy to clipboard', 'warning'));
3106
3108
  } else {
3107
- // Workaround using deprecated execCommand
3108
- const ta = document.createElement('textarea');
3109
- document.body.appendChild(ta);
3110
- ta.value = string;
3111
- ta.select();
3112
- document.execCommand('copy');
3113
- document.body.removeChild(ta);
3109
+ UI.setMessage('Your browser does not support copying to clipboard',
3110
+ 'warning');
3114
3111
  }
3115
- UI.setMessage(msg, type);
3116
3112
  }
3117
3113
 
3118
- copyHtmlToClipboard(html) {
3119
- // Copy HTML to clipboard
3120
- function listener(event) {
3121
- event.clipboardData.setData('text/html', html);
3122
- event.preventDefault();
3114
+ copyHtmlToClipboard(html, plain=false) {
3115
+ // Copy HTML (as such or as plain text) to clipboard and notify user.
3116
+ if(navigator.clipboard) {
3117
+ const
3118
+ item = (plain ? {'text/plain': html} : {'text/html': html}),
3119
+ data = [new ClipboardItem(item)],
3120
+ msg = 'HTML copied to clipboard' + (plain ? ' as plain text' : '');
3121
+ navigator.clipboard.write(data)
3122
+ .then(() => UI.setMessage(msg, 'notification'))
3123
+ .catch((error) => UI.setMessage('Failed to copy HTML to clipboard',
3124
+ 'warning', error));
3125
+ } else {
3126
+ UI.setMessage('Your browser does not support copying HTML to clipboard',
3127
+ 'warning');
3123
3128
  }
3124
- document.addEventListener('copy', listener);
3125
- document.execCommand('copy');
3126
- document.removeEventListener('copy', listener);
3127
3129
  }
3128
3130
 
3129
3131
  logHeapSize(msg='') {
3130
- // Logs MB's of used heap memory to console (to detect memory leaks)
3131
- // NOTE: this feature is supported only by Chrome
3132
+ // Log MB's of used heap memory to console (to detect memory leaks).
3133
+ // NOTE: This feature is supported only by Chrome.
3132
3134
  if(msg) msg += ' -- ';
3133
3135
  if(performance.memory !== undefined) {
3134
3136
  console.log(msg + 'Allocated memory: ' + Math.round(
@@ -4035,11 +4037,16 @@ console.log('HERE name conflicts', name_conflicts, mapping);
4035
4037
  this.setBox('settings-encrypt', model.encrypt);
4036
4038
  const pg_btn = md.element('power-btn');
4037
4039
  pg_btn.style.display = (model.with_power_flow ? 'inline-block' : 'none');
4040
+ if(model.ignore_grid_capacity || model.ignore_KVL || model.ignore_power_losses) {
4041
+ pg_btn.classList.add('ignore');
4042
+ } else {
4043
+ pg_btn.classList.remove('ignore');
4044
+ }
4038
4045
  md.show('name');
4039
4046
  }
4040
4047
 
4041
4048
  updateSettings(model) {
4042
- // Valdidate inputs
4049
+ // Valdidate inputs.
4043
4050
  const px = this.validNumericInput('settings-grid-pixels', 'grid resolution');
4044
4051
  if(px === false) return false;
4045
4052
  const ts = this.validNumericInput('settings-time-scale', 'time step');
@@ -288,7 +288,7 @@ class GUIDatasetManager extends DatasetManager {
288
288
  for(const r of this.dataset_table.rows) if(r.dataset.prefix === lcp) return r;
289
289
  return null;
290
290
  }
291
-
291
+
292
292
  selectPrefixRow(e) {
293
293
  // Select expand/collapse prefix row.
294
294
  this.focal_table = this.dataset_table;
@@ -579,6 +579,13 @@ class GUIDatasetManager extends DatasetManager {
579
579
  // NOTE: Updating entire dialog may be very time-consuming
580
580
  // when model contains numerous prefixed datasets.
581
581
  this.updatePanes();
582
+ // NOTE: The selected row now has to be highlighted, as the table
583
+ // HTML is not changed.
584
+ let r = event.target;
585
+ while(r && r.tagName !== 'TR') r = r.parentNode;
586
+ const sel = this.dataset_table.getElementsByClassName('sel-set');
587
+ if(sel.length > 0) sel[0].classList.remove('sel-set');
588
+ r.classList.add('sel-set');
582
589
  }
583
590
 
584
591
  selectModifier(event, id, x=true) {
@@ -343,6 +343,8 @@ class GUIExperimentManager extends ExperimentManager {
343
343
  // NOTE: When UpdateDialog is called after an entity has been renamed,
344
344
  // its variable list should be updated.
345
345
  this.updateViewerVariable();
346
+ // NOTE: Finder may need updating as well.
347
+ if(FINDER.experiment_view) FINDER.updateDialog();
346
348
  }
347
349
 
348
350
  updateParameters() {
@@ -727,6 +729,11 @@ class GUIExperimentManager extends ExperimentManager {
727
729
  // Toggle `n` consecutive rows, starting at row `r` (0 = top), to be
728
730
  // (no longer) part of the chart combination set.
729
731
  // @@TO DO: shift-key indicates "add row(s) to selection"
732
+ if(MODEL.running_experiment) {
733
+ // NOTE: do NOT change run selection while VM is solving!
734
+ UI.notify('Run selection cannot be changed when an experiment is running');
735
+ return;
736
+ }
730
737
  const
731
738
  x = this.selected_experiment,
732
739
  // Let `first` be the number of the first run on row `r`.
@@ -751,7 +758,10 @@ class GUIExperimentManager extends ExperimentManager {
751
758
  }
752
759
  }
753
760
  this.updateData();
761
+ CHART_MANAGER.resetChartVectors();
754
762
  CHART_MANAGER.updateDialog();
763
+ // NOTE: Finder may need updating as well.
764
+ if(FINDER.experiment_view) FINDER.updateDialog();
755
765
  }
756
766
  }
757
767
 
@@ -761,34 +771,36 @@ class GUIExperimentManager extends ExperimentManager {
761
771
 
762
772
  toggleChartCombi(n, shift, alt) {
763
773
  // Set `n` to be the chart combination, or toggle if Shift-key is pressed,
764
- // or execute single run if Alt-key is pressed
774
+ // or execute single run if Alt-key is pressed.
775
+ if(MODEL.running_experiment) {
776
+ // NOTE: do NOT do this while VM is solving, as this would interfere!
777
+ UI.notify('Run selection cannot be changed when an experiment is running');
778
+ return;
779
+ }
765
780
  const x = this.selected_experiment;
766
781
  if(x && alt && n >= 0) {
767
782
  this.startExperiment(n);
768
783
  return;
769
784
  }
770
785
  if(x && n < x.combinations.length) {
771
- // Clear current selection unless Shift-key is pressed
772
- if(!shift) x.chart_combinations.length = 0;
773
- // Toggle => add if not in selection, otherwise remove
786
+ // Toggle => add if not in selection, otherwise remove.
774
787
  const ci = x.chart_combinations.indexOf(n);
775
788
  if(ci < 0) {
789
+ // Clear current selection unless Shift-key is pressed.
790
+ if(!shift) x.chart_combinations.length = 0;
776
791
  x.chart_combinations.push(n);
777
792
  } else {
778
793
  x.chart_combinations.splice(ci, 1);
779
794
  }
780
795
  }
781
796
  this.updateData();
782
- if(MODEL.running_experiment) {
783
- // NOTE: do NOT do this while VM is solving, as this would interfer!
784
- UI.notify('Selected run cannot be viewed while running an experiment');
785
- } else {
786
- // Show the messages for this run in the monitor
787
- VM.setRunMessages(n);
788
- // Update the chart
789
- CHART_MANAGER.resetChartVectors();
790
- CHART_MANAGER.updateDialog();
791
- }
797
+ // Show the messages for this run in the monitor.
798
+ VM.setRunMessages(n);
799
+ // Update the chart.
800
+ CHART_MANAGER.resetChartVectors();
801
+ CHART_MANAGER.updateDialog();
802
+ // NOTE: Finder may need updating as well.
803
+ if(FINDER.experiment_view) FINDER.updateDialog();
792
804
  }
793
805
 
794
806
  runInfo(n) {
@@ -839,7 +851,7 @@ class GUIExperimentManager extends ExperimentManager {
839
851
  info.html = html.join('');
840
852
  return info;
841
853
  }
842
- // Fall-through (should not occur)
854
+ // Fall-through (should not occur).
843
855
  return null;
844
856
  }
845
857
 
@@ -848,7 +860,7 @@ class GUIExperimentManager extends ExperimentManager {
848
860
  // NOTE: Skip when viewer is showing!
849
861
  if(!UI.hidden('experiment-viewer')) return;
850
862
  if(n < MODEL.experiments.length) {
851
- // NOTE: mouse move over title in viewer passes n = -1
863
+ // NOTE: Mouse move over title in viewer passes n = -1.
852
864
  const x = (n < 0 ? this.selected_experiment : MODEL.experiments[n]);
853
865
  DOCUMENTATION_MANAGER.update(x, shift);
854
866
  }
@@ -90,7 +90,8 @@ class GUIFileManager {
90
90
  if(!UI.hidden('series-modal')) {
91
91
  DATASET_MANAGER.series_data.value = data.split(';').join('\n');
92
92
  } else {
93
- dataset.unpackDataString(data);
93
+ // NOTE: FALSE indicates that data is *not* B62-encoded.
94
+ dataset.unpackDataString(data, false);
94
95
  }
95
96
  }
96
97
  // NOTE: remove dataset from the "loading" list