linny-r 1.5.4 → 1.5.5

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": "1.5.4",
3
+ "version": "1.5.5",
4
4
  "description": "Executable graphical language with WYSIWYG editor for MILP models",
5
5
  "main": "server.js",
6
6
  "scripts": {
@@ -3634,6 +3634,11 @@ th.scen-hdr {
3634
3634
 
3635
3635
  th.scen-hdr {
3636
3636
  text-align: left;
3637
+ cursor: pointer;
3638
+ }
3639
+
3640
+ th.scen-hdr:hover {
3641
+ box-shadow: 0px 0px 0px 2px rgba(96,64,0,0.5) inset;
3637
3642
  }
3638
3643
 
3639
3644
  th.leaf-conf {
@@ -1140,6 +1140,7 @@ class ExperimentManager {
1140
1140
  this.callback = null;
1141
1141
  this.selected_experiment = null;
1142
1142
  this.suitable_charts = [];
1143
+ this.plot_dimensions = [];
1143
1144
  }
1144
1145
 
1145
1146
  updateChartList() {
@@ -360,11 +360,15 @@ class GUIExperimentManager extends ExperimentManager {
360
360
  this.params_div.style.display = 'block';
361
361
  const tr = [];
362
362
  for(let i = 0; i < x.dimensions.length; i++) {
363
+ const pi = 'd' + i;
363
364
  tr.push(['<tr class="dataset',
364
- (this.selected_parameter == 'd'+i ? ' sel-set' : ''),
365
- '" onclick="EXPERIMENT_MANAGER.selectParameter(\'d',
366
- i, '\');"><td>',
367
- setString(x.dimensions[i]),
365
+ // Highlight selected dimension with background color.
366
+ (this.selected_parameter == pi ? ' sel-set' : ''),
367
+ // Show dimension in bold purple if it is a plot dimension.
368
+ (x.plot_dimensions.indexOf(i) >= 0 ? ' def-sel' : ''),
369
+ '" onclick="EXPERIMENT_MANAGER.selectParameter(\'', pi,
370
+ // Click selects, shift-click will also toggle plot/no plot.
371
+ '\', event.shiftKey);"><td>', setString(x.dimensions[i]),
368
372
  '</td></tr>'].join(''));
369
373
  }
370
374
  this.dimension_table.innerHTML = tr.join('');
@@ -635,11 +639,13 @@ class GUIExperimentManager extends ExperimentManager {
635
639
  } else {
636
640
  rsp = '';
637
641
  }
638
- // Calculate the dimension selector index
642
+ // Calculate the dimension selector index.
639
643
  const dsi = Math.floor(
640
644
  i / rowsperdim[j]) % this.clean_rows[j].length;
641
645
  lth += ['<th', rsp, ' class="scen-hdr" style="background-color: ',
642
- 'rgba(100, 170, 255, ', 1 - j * bstep, ')">',
646
+ 'rgba(100, 170, 255, ', 1 - j * bstep,
647
+ ')" onclick="EXPERIMENT_MANAGER.toggleChartRow(', i,
648
+ ', ', rowsperdim[j], ', event.shiftKey);">',
643
649
  this.clean_rows[j][dsi], '</th>'].join('');
644
650
  }
645
651
  }
@@ -661,22 +667,29 @@ class GUIExperimentManager extends ExperimentManager {
661
667
  }
662
668
  }
663
669
 
664
- toggleChartRow(r, n, shift) {
670
+ toggleChartRow(r, n=1, shift=false) {
665
671
  // Toggle `n` consecutive rows, starting at row `r` (0 = top), to be
666
- // (no longer) part of the chart combination set
672
+ // (no longer) part of the chart combination set.
673
+ // @@TO DO: shift-key indicates "add row(s) to selection"
667
674
  const
668
675
  x = this.selected_experiment,
669
- // Let `n` be the number of the first run on row `r`
670
- nconf = r * this.nr_of_configurations;
671
- if(x && r < x.combinations.length / this.nr_of_configurations) {
672
- // NOTE: first cell of row determines ADD or REMOVE
673
- const add = x.chart_combinations.indexOf(n) < 0;
674
- for(let i = 0; i < this.nr_of_configurations; i++) {
675
- const ic = x.chart_combinations.indexOf(i);
676
- if(add) {
677
- if(ic < 0) x.chart_combinations.push(nconf + i);
678
- } else {
679
- if(!add) x.chart_combinations.splice(nconf + i, 1);
676
+ // Let `first` be the number of the first run on row `r`.
677
+ ncols = this.nr_of_configurations,
678
+ nrows = x.combinations.length / ncols;
679
+ if(x && r < nrows) {
680
+ // NOTE: First cell in rows determines ADD or REMOVE.
681
+ const add = shift || x.chart_combinations.indexOf(r) < 0;
682
+ if(!shift) x.chart_combinations.length = 0;
683
+ for(let i = 0; i < ncols; i++) {
684
+ for(let j = 0; j < n; j++) {
685
+ const
686
+ c = r + j + i * nrows,
687
+ ic = x.chart_combinations.indexOf(c);
688
+ if(add) {
689
+ if(ic < 0) x.chart_combinations.push(c);
690
+ } else {
691
+ if(ic >= 0) x.chart_combinations.splice(ic, 1);
692
+ }
680
693
  }
681
694
  }
682
695
  this.updateData();
@@ -1048,10 +1061,22 @@ N = ${rr.N}, vector length = ${rr.vector.length}` : '')].join('');
1048
1061
  }
1049
1062
  }
1050
1063
 
1051
- selectParameter(p) {
1064
+ selectParameter(p, shift=false) {
1065
+ const dim = p.startsWith('d');
1052
1066
  this.selected_parameter = p;
1053
- this.focal_table = (p.startsWith('d') ? this.dimension_table :
1054
- this.chart_table);
1067
+ this.focal_table = (dim ? this.dimension_table : this.chart_table);
1068
+ if(dim && shift) {
1069
+ const
1070
+ x = this.selected_experiment,
1071
+ di = parseInt(p.substring(1)),
1072
+ pi = x.plot_dimensions.indexOf(di);
1073
+ if(pi < 0) {
1074
+ x.plot_dimensions.push(di);
1075
+ } else {
1076
+ x.plot_dimensions.splice(pi, 1);
1077
+ }
1078
+ if(CHART_MANAGER.runs_stat) CHART_MANAGER.updateDialog();
1079
+ }
1055
1080
  this.updateDialog();
1056
1081
  }
1057
1082
 
@@ -9522,13 +9522,26 @@ class Chart {
9522
9522
  rh = height - rt - margin - font_height,
9523
9523
  // Keep track of the number of visible chart variables
9524
9524
  vv = 0;
9525
- // Reserve vertical space for title (if shown)
9525
+ // Reserve vertical space for title (if shown).
9526
9526
  if(this.show_title) {
9527
9527
  // NOTE: use title font size 120% of default
9528
9528
  const th = 1.2 * font_height + margin;
9529
9529
  rt += th;
9530
9530
  rh -= th;
9531
9531
  }
9532
+ // If run result statistics are plotted, reserve vertical space for
9533
+ // bar chart run dimension selectors.
9534
+ const
9535
+ selx = EXPERIMENT_MANAGER.selected_experiment,
9536
+ stat_bars = CHART_MANAGER.runs_stat;
9537
+ if(stat_bars) {
9538
+ if(selx) {
9539
+ // First plot dimension will replace run numbers, so only reserve
9540
+ // space for additional plot dimensions.
9541
+ const dh = 1.2 * font_height * (selx.plot_dimensions.length - 1);
9542
+ if(dh > 0) rh -= dh;
9543
+ }
9544
+ }
9532
9545
  if(this.variables.length > 0) {
9533
9546
  // Count visible variables and estimate total width of their names
9534
9547
  // as well as the width of the longest name
@@ -9584,7 +9597,7 @@ class Chart {
9584
9597
  if(v.visible) {
9585
9598
  // Add arrow indicating sort direction to name if applicable.
9586
9599
  const vn = v.displayName + CHART_MANAGER.sort_arrows[v.sorted];
9587
- if(v.stacked || this.histogram) {
9600
+ if(v.stacked || this.histogram || stat_bars) {
9588
9601
  this.addSVG(['<rect x="', x, '" y="', y - sym_size + 2,
9589
9602
  '" width="', sym_size, '" height="', sym_size,
9590
9603
  '" fill="', v.color,'" fill-opacity="0.35" stroke="',
@@ -9612,10 +9625,7 @@ class Chart {
9612
9625
 
9613
9626
  // NOTE: chart may display experiment run results, rather than MODEL results
9614
9627
  let runnrs = '';
9615
- const
9616
- selx = EXPERIMENT_MANAGER.selected_experiment,
9617
- runs = EXPERIMENT_MANAGER.selectedRuns(this),
9618
- stat_bars = CHART_MANAGER.runs_stat;
9628
+ const runs = EXPERIMENT_MANAGER.selectedRuns(this);
9619
9629
  if(runs.length > 0) {
9620
9630
  const stat = (stat_bars ?
9621
9631
  EXPERIMENT_MANAGER.selectedStatisticName + ' for ': '');
@@ -9786,7 +9796,10 @@ class Chart {
9786
9796
  }
9787
9797
 
9788
9798
  if(time_steps > 0) {
9789
- let dx = 0, dy = 0, x = 0, y = rt + rh + font_height;
9799
+ let dx = 0,
9800
+ dy = 0,
9801
+ x = 0,
9802
+ y = rt + rh + font_height;
9790
9803
  if(this.histogram) {
9791
9804
  // Draw bin boundaries along the horizontal axis
9792
9805
  dx = rw / this.bins;
@@ -9818,8 +9831,18 @@ class Chart {
9818
9831
  '" x2="', x, '" y2="', rt + rh + 3,
9819
9832
  '" stroke="black" stroke-width="1.5"/>']);
9820
9833
  }
9821
- // Draw experiment number in middle of its horizontal area.
9822
- this.addText(x - dx / 2, y, '#' + runs[i]);
9834
+ if(selx.plot_dimensions.length > 0) {
9835
+ // Draw run selectors for each plot dimension above each other.
9836
+ const ac = selx.combinations[runs[i]];
9837
+ let pdy = y;
9838
+ for(let j = 0; j < selx.plot_dimensions.length; j++) {
9839
+ this.addText(x - dx / 2, pdy, ac[selx.plot_dimensions[j]]);
9840
+ pdy += font_height;
9841
+ }
9842
+ } else {
9843
+ // Draw experiment number in middle of its horizontal area.
9844
+ this.addText(x - dx / 2, y, '#' + runs[i]);
9845
+ }
9823
9846
  x += dx;
9824
9847
  }
9825
9848
  } else {
@@ -10850,6 +10873,7 @@ class Experiment {
10850
10873
  this.dimensions = [];
10851
10874
  this.charts = [];
10852
10875
  this.actual_dimensions = [];
10876
+ this.plot_dimensions = [];
10853
10877
  this.combinations = [];
10854
10878
  this.variables = [];
10855
10879
  this.configuration_dims = 0;
@@ -11058,7 +11082,8 @@ class Experiment {
11058
11082
  '"><title>', xmlEncoded(this.title),
11059
11083
  '</title><notes>', xmlEncoded(this.comments),
11060
11084
  '</notes><dimensions>', d,
11061
- '</dimensions><chart-titles>', ct,
11085
+ '</dimensions><plot-dimensions>', this.plot_dimensions.join(','),
11086
+ '</plot-dimensions><chart-titles>', ct,
11062
11087
  '</chart-titles><settings-selectors>', ss,
11063
11088
  '</settings-selectors><settings-dimensions>', sd,
11064
11089
  '</settings-dimensions><combination-selectors>', cs,
@@ -11103,6 +11128,13 @@ class Experiment {
11103
11128
  }
11104
11129
  }
11105
11130
  }
11131
+ n = nodeContentByTag(node, 'plot-dimensions');
11132
+ if(n) {
11133
+ this.plot_dimensions = n.split(',');
11134
+ for(let i = 0; i < this.plot_dimensions.length; i++) {
11135
+ this.plot_dimensions[i] = parseInt(this.plot_dimensions[i]);
11136
+ }
11137
+ }
11106
11138
  n = childNodeByTag(node, 'chart-titles');
11107
11139
  if(n && n.childNodes) {
11108
11140
  for(let i = 0; i < n.childNodes.length; i++) {
@@ -590,9 +590,64 @@ function mergeDistinct(list, into) {
590
590
  }
591
591
  }
592
592
 
593
+ function iteratorSet(list) {
594
+ // Returns TRUE iff list is something like ['i=1', 'i=2', 'i=3'].
595
+ if(list.length === 0) return false;
596
+ // Analyze the first element: must start with i=, j= or k=.
597
+ const
598
+ parts = list[0].split('='),
599
+ iterator = parts[0];
600
+ if(parts.length !== 2 || 'ijk'.indexOf(iterator) < 0) return false;
601
+ // Left-hand part must be an integer number: the first iterator value.
602
+ const first = parts[1] - 0;
603
+ if(first != parts[1]) return false;
604
+ // If OK, generate the list one would expect.
605
+ const
606
+ series = [],
607
+ last = first + list.length - 1;
608
+ for(let i = first; i <= last; i++) {
609
+ series.push(iterator + '=' + i);
610
+ }
611
+ // Then compare with the list to be tested.
612
+ if(list.join(',') === series.join(',')) {
613
+ // If match, return a shorthand string like 'i=1, ..., i=5'.
614
+ // NOTE: No ellipsis if fewer than 4 steps.
615
+ if(list.length < 4) return `{${list.join(', ')}}`;
616
+ return `{${list[0]}, ..., ${list[list.length - 1]}}`;
617
+ }
618
+ return false;
619
+ }
620
+
621
+ function integerSet(list) {
622
+ // Returns TRUE iff all elements in list evaluate as integer numbers.
623
+ if(list.length === 0) return false;
624
+ for(let i = 0; i < list.length; i++) {
625
+ if(list[i] - 0 != list[i]) return false;
626
+ }
627
+ return true;
628
+ }
629
+
593
630
  function setString(sl) {
594
631
  // Returns elements of stringlist `sl` in set notation
595
- return '{' + sl.join(', ') + '}';
632
+ if(integerSet(sl)) {
633
+ // If all set elements are integers, return a range shorthand.
634
+ const sorted = sl.slice().sort();
635
+ let i = 0,
636
+ j = 1;
637
+ while(i < sorted.length) {
638
+ while(j < sorted.length && sorted[j] - sorted[j - 1] === 1) j++;
639
+ if(j - i > 2) {
640
+ sorted[i] += ', ... , ' + sorted[j - 1];
641
+ sorted.splice(i + 1, j - i - 1);
642
+ j = i + 1;
643
+ }
644
+ i = j;
645
+ j++;
646
+ }
647
+ return '{' + sorted.join(', ') + '}';
648
+ }
649
+ // Otherwise, return an iterator set shorthand, or the complete set.
650
+ return iteratorSet(sl) || '{' + sl.join(', ') + '}';
596
651
  }
597
652
 
598
653
  function tupelString(sl) {