linny-r 2.0.5 → 2.0.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.
@@ -875,9 +875,9 @@ class ChartManager {
875
875
  // CLASS SensitivityAnalysis provides the sensitivity analysis functionality
876
876
  class SensitivityAnalysis {
877
877
  constructor() {
878
- // Initialize main dialog properties
878
+ // Initialize main dialog properties.
879
879
  this.reset();
880
- // Sensitivity analysis creates & disposes an experiment and a chart
880
+ // Sensitivity analysis creates & disposes an experiment and a chart.
881
881
  this.experiment_title = '___SENSITIVITY_ANALYSIS___';
882
882
  this.chart_title = '___SENSITIVITY_ANALYSIS_CHART___';
883
883
  }
@@ -912,7 +912,7 @@ class SensitivityAnalysis {
912
912
  // a dummy chart is created that includes all these outcomes as *chart*
913
913
  // variables.
914
914
  if(!this.experiment) {
915
- // Clear results from previous analysis
915
+ // Clear results from previous analysis.
916
916
  this.clearResults();
917
917
  this.parameters = [];
918
918
  for(let i = 0; i < MODEL.sensitivity_parameters.length; i++) {
@@ -924,7 +924,7 @@ class SensitivityAnalysis {
924
924
  if(oax) {
925
925
  this.parameters.push(oax);
926
926
  } else if(vn.length === 1 && obj instanceof Dataset) {
927
- // Dataset without selector => push the dataset vector
927
+ // Dataset without selector => push the dataset vector.
928
928
  this.parameters.push(obj.vector);
929
929
  } else {
930
930
  UI.alert(`Parameter ${p} is not a dataset or expression`);
@@ -938,23 +938,24 @@ class SensitivityAnalysis {
938
938
  this.experiment = new Experiment(this.experiment_title);
939
939
  this.experiment.charts = [this.chart];
940
940
  this.experiment.inferVariables();
941
- // This experiment always uses the same combination: the base selectors
941
+ // This experiment always uses the same combination: the base selectors.
942
942
  const bs = MODEL.base_case_selectors.split(' ');
943
943
  this.experiment.combinations = [];
944
- // Add this combination N+1 times for N parameters
944
+ // Add this combination N+1 times for N parameters.
945
945
  for(let i = 0; i <= this.parameters.length; i++) {
946
946
  this.experiment.combinations.push(bs);
947
947
  }
948
- // NOTE: model settings will not be changed, but nevertheless restored
948
+ // NOTE: Model settings will not be changed, but will be restored after
949
+ // each run => store the original settings.
949
950
  this.experiment.original_model_settings = MODEL.settingsString;
950
951
  this.experiment.original_round_sequence = MODEL.round_sequence;
951
952
  }
952
- // Change the button (GUI only -- console will return FALSE)
953
+ // Change the button (GUI only -- console will return FALSE).
953
954
  const paused = this.resumeButtons();
954
955
  if(!paused) {
955
956
  this.experiment.time_started = new Date().getTime();
956
957
  this.experiment.active_combination_index = 0;
957
- // NOTE: start with base case run, hence no active parameter yet
958
+ // NOTE: Start with base case run, hence no active parameter yet.
958
959
  MODEL.running_experiment = this.experiment;
959
960
  }
960
961
  // Let the experiment manager do the work!!
@@ -962,31 +963,31 @@ class SensitivityAnalysis {
962
963
  }
963
964
 
964
965
  processRestOfRun() {
965
- // This method is called by the experiment manager after an SA run
966
+ // This method is called by the experiment manager after a SA run.
966
967
  const x = MODEL.running_experiment;
967
968
  if(!x) return;
968
- // Double-check that indeed the SA experiment is running
969
+ // Double-check that indeed the SA experiment is running.
969
970
  if(x !== this.experiment) {
970
971
  UI.alert('ERROR: Expected SA experiment run, but got ' + x.title);
971
972
  return;
972
973
  }
973
974
  const aci = x.active_combination_index;
974
- // Always add solver messages
975
+ // Always add solver messages.
975
976
  x.runs[aci].addMessages();
976
- // NOTE: use a "dummy experiment object" to ensure proper XML saving and
977
- // loading , as the actual experiment is not stored
977
+ // NOTE: Use a "dummy experiment object" to ensure proper XML saving and
978
+ // loading , as the actual experiment is not stored.
978
979
  x.runs.experiment = {title: SENSITIVITY_ANALYSIS.experiment_title};
979
- // Add run to the sensitivity analysis
980
+ // Add run to the sensitivity analysis.
980
981
  MODEL.sensitivity_runs.push(x.runs[aci]);
981
982
  this.showProgress('Run #' + aci);
982
- // See if more runs should be done
983
+ // See if more runs should be done.
983
984
  const n = x.combinations.length;
984
985
  if(!VM.halted && aci < n - 1) {
985
986
  if(this.must_pause) {
986
987
  this.pausedButtons(aci);
987
988
  UI.setMessage('');
988
989
  } else {
989
- // NOTE: use aci because run #0 is the base case w/o active parameter
990
+ // NOTE: Use aci because run #0 is the base case w/o active parameter.
990
991
  MODEL.active_sensitivity_parameter = this.parameters[aci];
991
992
  x.active_combination_index++;
992
993
  setTimeout(() => EXPERIMENT_MANAGER.runModel(), 5);
@@ -1001,31 +1002,31 @@ class SensitivityAnalysis {
1001
1002
  } else {
1002
1003
  this.showCheckmark(msecToTime(x.time_stopped - x.time_started));
1003
1004
  }
1004
- // No more runs => perform wrap-up
1005
- // Restore original model settings
1005
+ // No more runs => perform wrap-up.
1006
+ // (1) Restore original model settings.
1006
1007
  MODEL.running_experiment = null;
1007
1008
  MODEL.active_sensitivity_parameter = null;
1008
1009
  MODEL.parseSettings(x.original_model_settings);
1009
1010
  MODEL.round_sequence = x.original_round_sequence;
1010
- // Reset the Virtual Machine so t=0 at the status line,
1011
- // and ALL expressions are reset as well
1011
+ // (2) Reset the Virtual Machine so t=0 at the status line, and ALL
1012
+ // expressions are reset as well.
1012
1013
  VM.reset();
1013
- // Free the SA experiment and SA chart
1014
+ // Free the SA experiment and SA chart.
1014
1015
  this.experiment = null;
1015
1016
  this.chart = null;
1016
- // Reset buttons (GUI only)
1017
+ // Reset buttons (GUI only).
1017
1018
  this.readyButtons();
1018
1019
  }
1019
1020
  this.updateDialog();
1020
- // Reset the model, as results of last run will be showing still
1021
+ // Reset the model, as results of last run will be showing still.
1021
1022
  UI.resetModel();
1022
1023
  CHART_MANAGER.resetChartVectors();
1023
- // NOTE: clear chart only when done (charts do not update during experiment)
1024
+ // NOTE: Clear chart only when done (charts do not update during experiment).
1024
1025
  if(!MODEL.running_experiment) CHART_MANAGER.updateDialog();
1025
1026
  }
1026
1027
 
1027
1028
  stop() {
1028
- // Interrupt solver but retain data on server (and no resume)
1029
+ // Interrupt solver but retain data on server (and no resume).
1029
1030
  VM.halt();
1030
1031
  this.readyButtons();
1031
1032
  this.showProgress('');
@@ -1033,13 +1034,13 @@ class SensitivityAnalysis {
1033
1034
  }
1034
1035
 
1035
1036
  clearResults() {
1036
- // Clear results and reset control buttons
1037
+ // Clear results, and reset control buttons.
1037
1038
  MODEL.sensitivity_runs.length = 0;
1038
1039
  this.selected_run = -1;
1039
1040
  }
1040
1041
 
1041
1042
  computeData(sas) {
1042
- // Compute data value or status for statistic `sas`
1043
+ // Compute data value or status for statistic `sas`.
1043
1044
  this.perc = {};
1044
1045
  this.shade = {};
1045
1046
  this.data = {};
@@ -1047,12 +1048,12 @@ class SensitivityAnalysis {
1047
1048
  ol = MODEL.sensitivity_outcomes.length,
1048
1049
  rl = MODEL.sensitivity_runs.length;
1049
1050
  if(ol === 0) return;
1050
- // Always find highest relative change
1051
+ // Always find highest relative change.
1051
1052
  let max_dif = 0;
1052
1053
  for(let i = 0; i < ol; i++) {
1053
1054
  this.data[i] = [];
1054
1055
  for(let j = 0; j < rl; j++) {
1055
- // Get the selected statistic for each run to get an array of numbers
1056
+ // Get the selected statistic for each run to get an array of numbers.
1056
1057
  const rr = MODEL.sensitivity_runs[j].results[i];
1057
1058
  if(!rr) {
1058
1059
  this.data[i].push(VM.UNDEFINED);
@@ -1076,7 +1077,7 @@ class SensitivityAnalysis {
1076
1077
  this.data[i].push(rr.last);
1077
1078
  }
1078
1079
  }
1079
- // Compute relative change
1080
+ // Compute the relative change.
1080
1081
  let bsv = this.data[i][0];
1081
1082
  if(Math.abs(bsv) < VM.NEAR_ZERO) bsv = 0;
1082
1083
  this.perc[i] = [];
@@ -1097,26 +1098,26 @@ class SensitivityAnalysis {
1097
1098
  for(let j = 1; j < this.data[i].length; j++) this.perc[i].push('-');
1098
1099
  }
1099
1100
  }
1100
- // Now use max_dif to compute shades
1101
+ // Now use max_dif to compute shades.
1101
1102
  for(let i = 0; i < ol; i++) {
1102
1103
  this.shade[i] = [];
1103
- // Color scale range is -max ... +max (0 in center => white)
1104
+ // Color scale range is -max ... +max (0 in center => white).
1104
1105
  for(let j = 0; j < this.perc[i].length; j++) {
1105
1106
  const p = this.perc[i][j];
1106
1107
  this.shade[i].push(p === VM.UNDEFINED || max_dif < VM.NEAR_ZERO ?
1107
1108
  0.5 : (p / max_dif + 1) / 2);
1108
1109
  }
1109
- // Convert to sig4Dig
1110
+ // Convert to sig4Dig.
1110
1111
  for(let j = 0; j < this.data[i].length; j++) {
1111
1112
  this.data[i][j] = VM.sig4Dig(this.data[i][j]);
1112
1113
  }
1113
- // Format data such that they all have same number of decimals
1114
+ // Format data such that they all have same number of decimals.
1114
1115
  if(this.relative_scale && this.perc[i][0] !== '-') {
1115
1116
  for(let j = 0; j < this.perc[i].length; j++) {
1116
1117
  this.perc[i][j] = VM.sig4Dig(this.perc[i][j]);
1117
1118
  }
1118
1119
  uniformDecimals(this.perc[i]);
1119
- // NOTE: only consider data of base scenario
1120
+ // NOTE: Only consider data of base scenario.
1120
1121
  this.data[i][0] = VM.sig4Dig(this.data[i][0]);
1121
1122
  } else {
1122
1123
  uniformDecimals(this.data[i]);
@@ -1125,11 +1126,11 @@ class SensitivityAnalysis {
1125
1126
  }
1126
1127
 
1127
1128
  resumeButtons() {
1128
- // Console experiments cannot be paused, and hence not resumed
1129
+ // Console experiments cannot be paused, and hence not resumed.
1129
1130
  return false;
1130
1131
  }
1131
1132
 
1132
- // Dummy methods: actions that are meaningful only for the graphical UI
1133
+ // Dummy methods: actions that are meaningful only for the graphical UI.
1133
1134
  updateDialog() {}
1134
1135
  showCheckmark() {}
1135
1136
  showProgress() {}
@@ -1143,7 +1144,7 @@ class SensitivityAnalysis {
1143
1144
  // Class ExperimentManager controls the collection of experiments of the model
1144
1145
  class ExperimentManager {
1145
1146
  constructor() {
1146
- // NOTE: the properties below are relevant only for the GUI
1147
+ // NOTE: The properties below are relevant only for the GUI.
1147
1148
  this.experiment_table = null;
1148
1149
  this.focal_table = null;
1149
1150
  }
@@ -283,7 +283,8 @@ class ConstraintEditor {
283
283
  i = this.selected_point,
284
284
  pts = this.selected.points,
285
285
  li = pts.length - 1,
286
- p = pts[this.selected_point],
286
+ // NOTE: Use a copy of the selected point, or it will not be updated.
287
+ p = pts[this.selected_point].slice(),
287
288
  minx = (i === 0 ? 0 : (i === li ? 100 : pts[i - 1][0])),
288
289
  maxx = (i === 0 ? 0 : (i === li ? 100 : pts[i + 1][0]));
289
290
  let cx = false,
@@ -317,6 +318,9 @@ class ConstraintEditor {
317
318
  p[1] = Math.round(3 * p[1]) / 3;
318
319
  }
319
320
  }
321
+ this.dragged_point = this.selected_point;
322
+ this.movePoint(p[0], p[1]);
323
+ this.dragged_point = -1;
320
324
  this.draw();
321
325
  this.updateEquation();
322
326
  }
@@ -670,7 +674,7 @@ class ConstraintEditor {
670
674
  }
671
675
 
672
676
  deleteSelector() {
673
- // Delete modifier from selected dataset
677
+ // Delete modifier from selected dataset.
674
678
  if(!this.selected) return;
675
679
  const
676
680
  bl = this.selected,
@@ -1156,7 +1160,7 @@ class ConstraintEditor {
1156
1160
  }
1157
1161
 
1158
1162
  updateConstraint() {
1159
- // Updates the edited constraint, or adds a new constraint to the model
1163
+ // Update the edited constraint, or add a new constraint to the model.
1160
1164
  // TO DO: prepare for undo
1161
1165
  if(this.edited_constraint === null) {
1162
1166
  this.edited_constraint = MODEL.addConstraint(this.from_node, this.to_node);
@@ -13,7 +13,7 @@ handler functions.
13
13
  */
14
14
 
15
15
  /*
16
- Copyright (c) 2017-2024 Delft University of Technology
16
+ Copyright (c) 2017-2025 Delft University of Technology
17
17
 
18
18
  Permission is hereby granted, free of charge, to any person obtaining a copy
19
19
  of this software and associated documentation files (the "Software"), to deal
@@ -3608,6 +3608,7 @@ console.log('HERE name conflicts', name_conflicts, mapping);
3608
3608
  this.setBox('settings-diagnose', model.always_diagnose);
3609
3609
  this.setBox('settings-power', model.with_power_flow);
3610
3610
  this.setBox('settings-cost-prices', model.infer_cost_prices);
3611
+ this.setBox('settings-negative-flows', model.ignore_negative_flows);
3611
3612
  this.setBox('settings-report-results', model.report_results);
3612
3613
  this.setBox('settings-encrypt', model.encrypt);
3613
3614
  const pg_btn = md.element('power-btn');
@@ -3672,7 +3673,7 @@ console.log('HERE name conflicts', name_conflicts, mapping);
3672
3673
  UI.notify('To diagnose unbounded problems, values beyond 1e+10 ' +
3673
3674
  'are considered as infinite (\u221E)');
3674
3675
  }
3675
- // Some changes may necessitate redrawing the diagram
3676
+ // Some changes may necessitate redrawing the diagram.
3676
3677
  let cb = UI.boxChecked('settings-align-to-grid'),
3677
3678
  redraw = !model.align_to_grid && cb;
3678
3679
  model.align_to_grid = cb;
@@ -3683,10 +3684,11 @@ console.log('HERE name conflicts', name_conflicts, mapping);
3683
3684
  cb = UI.boxChecked('settings-cost-prices');
3684
3685
  redraw = redraw || cb !== model.infer_cost_prices;
3685
3686
  model.infer_cost_prices = cb;
3687
+ model.ignore_negative_flows = UI.boxChecked('settings-negative-flows');
3686
3688
  cb = UI.boxChecked('settings-block-arrows');
3687
3689
  redraw = redraw || cb !== model.show_block_arrows;
3688
3690
  model.show_block_arrows = cb;
3689
- // Changes affecting run length (hence vector lengths) require a model reset
3691
+ // Changes affecting run length (hence vector lengths) require a model reset.
3690
3692
  let reset = false;
3691
3693
  reset = reset || (ts != model.time_scale);
3692
3694
  model.time_scale = ts;
@@ -4281,7 +4283,7 @@ console.log('HERE name conflicts', name_conflicts, mapping);
4281
4283
  const
4282
4284
  md = this.modals.link,
4283
4285
  l = this.edited_object;
4284
- // Check whether all input fields are valid
4286
+ // Check whether all input fields are valid.
4285
4287
  if(!this.updateExpressionInput('link-R', 'rate', l.relative_rate)) {
4286
4288
  return false;
4287
4289
  }
@@ -4316,8 +4318,10 @@ console.log('HERE name conflicts', name_conflicts, mapping);
4316
4318
  `</strong> will cause issues for ${VM.LM_SYMBOLS[m]} link`);
4317
4319
  }
4318
4320
  }
4319
- // NOTE: share of cost is input as a percentage, but stored as a floating
4320
- // point value between 0 and 1
4321
+ // NOTE: Share of cost is input as a percentage, but stored as a floating
4322
+ // point value between 0 and 1.
4323
+ // If SoC is changed, *all* output links must be redrawn.
4324
+ const soc_change = (l.share_of_cost !== soc / 100);
4321
4325
  l.share_of_cost = soc / 100;
4322
4326
  if(md.group.length > 1) {
4323
4327
  // NOTE: Special care must be taken to not set special multipliers
@@ -4328,10 +4332,15 @@ console.log('HERE name conflicts', name_conflicts, mapping);
4328
4332
  MODEL.focal_cluster.clearAllProcesses();
4329
4333
  UI.drawDiagram(MODEL);
4330
4334
  } else {
4331
- // Redraw the arrow shape that represents the edited link
4332
- this.paper.drawArrow(this.on_arrow);
4333
- // Redraw the FROM node if link has become (or no longer is) "first commit"
4334
- if(redraw) this.drawObject(this.on_arrow.from_node);
4335
+ if(soc_change) {
4336
+ // Redraw process with its links so that all SoC labels are updated.
4337
+ this.on_arrow.from_node.drawWithLinks();
4338
+ } else {
4339
+ // Only redraw the arrow shape that represents the edited link.
4340
+ this.paper.drawArrow(this.on_arrow);
4341
+ // Redraw the FROM node if link has become (or no longer is) "first commit".
4342
+ if(redraw) this.drawObject(this.on_arrow.from_node);
4343
+ }
4335
4344
  }
4336
4345
  md.hide();
4337
4346
  }
@@ -458,6 +458,19 @@ class Finder {
458
458
  xol.push(l.identifier);
459
459
  }
460
460
  }
461
+ // Check all constraint boundline index expressions.
462
+ for(let k in MODEL.constraints) if(MODEL.constraints.hasOwnProperty(k)) {
463
+ const c = MODEL.constraints[k];
464
+ for(let i = 0; i < c.bound_lines.length; i++) {
465
+ const bl = c.bound_lines[i];
466
+ for(let j = 0; j < bl.selectors.length; j++) {
467
+ if(re.test(bl.selectors[j].expression.text)) {
468
+ xal.push('I' + (i + 1));
469
+ xol.push(c.identifier);
470
+ }
471
+ }
472
+ }
473
+ }
461
474
  // Check all dataset modifier expressions.
462
475
  for(let k in MODEL.datasets) if(MODEL.datasets.hasOwnProperty(k)) {
463
476
  const ds = MODEL.datasets[k];
@@ -12,7 +12,7 @@ dialogs, the main drawing canvas, and event handler functions.
12
12
  */
13
13
 
14
14
  /*
15
- Copyright (c) 2017-2024 Delft University of Technology
15
+ Copyright (c) 2017-2025 Delft University of Technology
16
16
 
17
17
  Permission is hereby granted, free of charge, to any person obtaining a copy
18
18
  of this software and associated documentation files (the "Software"), to deal
@@ -165,7 +165,7 @@ class ModelAutoSaver {
165
165
  html += ['<tr class="dataset" style="color: gray" ',
166
166
  'onclick="FILE_MANAGER.loadAutoSavedModel(\'',
167
167
  m.name,'\');"><td class="restore-name">', m.name, '</td><td>',
168
- m.date.substring(1, 16).replace('T', ' '),
168
+ m.date.substring(0, 16).replace('T', ' '),
169
169
  '</td><td style="text-align: right">',
170
170
  bytes[0], '</td><td>', bytes[1], '</td><td style="width:15px">',
171
171
  '<img class="del-asm-btn" src="images/delete.png" ',
@@ -11,7 +11,7 @@ for the Linny-R Monitor dialog.
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
@@ -11,7 +11,7 @@ functionality for the Linny-R model editor.
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
@@ -909,16 +909,17 @@ class Paper {
909
909
  }
910
910
 
911
911
  //
912
- // Diagram-drawing method draws the diagram for the focal cluster
912
+ // Diagram-drawing method draws the diagram for the focal cluster.
913
913
  //
914
914
 
915
915
  drawModel(mdl) {
916
- // Draw the diagram for the focal cluster
916
+ // Draw the diagram for the focal cluster.
917
917
  this.clear();
918
- // Prepare to draw all elements in the focal cluster
918
+ // Prepare to draw all elements in the focal cluster.
919
919
  const fc = mdl.focal_cluster;
920
920
  fc.categorizeEntities();
921
- // NOTE: product positions must be updated before links are drawn
921
+ // NOTE: Product positions must be updated before links are drawn, so
922
+ // that links arrows will be drawn over their shapes.
922
923
  fc.positionProducts();
923
924
  for(let i = 0; i < fc.processes.length; i++) {
924
925
  fc.processes[i].clearHiddenIO();
@@ -926,10 +927,10 @@ class Paper {
926
927
  for(let i = 0; i < fc.sub_clusters.length; i++) {
927
928
  fc.sub_clusters[i].clearHiddenIO();
928
929
  }
929
- // NOTE: also ensure that notes will update their fields
930
+ // NOTE: Also ensure that notes will update their fields.
930
931
  fc.resetNoteFields();
931
932
  // Draw link arrows and constraints first, as all other entities are
932
- // slightly transparent so they cannot completely hide these lines
933
+ // slightly transparent so they cannot completely hide these lines.
933
934
  for(let i = 0; i < fc.arrows.length; i++) {
934
935
  this.drawArrow(fc.arrows[i]);
935
936
  }
@@ -945,25 +946,25 @@ class Paper {
945
946
  for(let i = 0; i < fc.sub_clusters.length; i++) {
946
947
  this.drawCluster(fc.sub_clusters[i]);
947
948
  }
948
- // Draw notes last, as they are semi-transparent (and can be quite small)
949
+ // Draw notes last, as they are semi-transparent (and can be quite small).
949
950
  for(let i = 0; i < fc.notes.length; i++) {
950
951
  this.drawNote(fc.notes[i]);
951
952
  }
952
- // Resize paper if necessary
953
+ // Resize paper if necessary.
953
954
  this.extend();
954
- // Display model name in browser
955
+ // Display model name in browser.
955
956
  document.title = mdl.name || 'Linny-R';
956
957
  }
957
958
 
958
959
  drawSelection(mdl, dx=0, dy=0) {
959
960
  // NOTE: Clear this global, as Bezier curves move from under the cursor
960
- // without a mouseout event
961
+ // without a mouseout event.
961
962
  this.constraint_under_cursor = null;
962
- // Draw the selected entities and associated links, and also constraints
963
+ // Draw the selected entities and associated links, and also constraints.
963
964
  for(let i = 0; i < mdl.selection.length; i++) {
964
965
  const obj = mdl.selection[i];
965
966
  // Links and constraints are drawn separately, so do not draw those
966
- // contained in the selection
967
+ // contained in the selection.
967
968
  if(!(obj instanceof Link || obj instanceof Constraint)) {
968
969
  if(obj instanceof Note) obj.parsed = false;
969
970
  UI.drawObject(obj, dx, dy);
@@ -972,12 +973,12 @@ class Paper {
972
973
  if(mdl.selection_related_arrows.length === 0) {
973
974
  mdl.selection_related_arrows = mdl.focal_cluster.selectedArrows();
974
975
  }
975
- // Only draw the arrows that relate to the selection
976
+ // Only draw the arrows that relate to the selection.
976
977
  for(let i = 0; i < mdl.selection_related_arrows.length; i++) {
977
978
  this.drawArrow(mdl.selection_related_arrows[i]);
978
979
  }
979
980
  // As they typically are few, simply redraw all constraints that relate to
980
- // the focal cluster
981
+ // the focal cluster.
981
982
  for(let i = 0; i < mdl.focal_cluster.related_constraints.length; i++) {
982
983
  this.drawConstraint(mdl.focal_cluster.related_constraints[i]);
983
984
  }
@@ -990,23 +991,23 @@ class Paper {
990
991
  //
991
992
 
992
993
  drawArrow(arrw, dx=0, dy=0) {
993
- // Draws an arrow from FROM nodebox to TO nodebox
994
- // NOTE: first erase previously drawn arrow
994
+ // Draw an arrow from FROM nodebox to TO nodebox.
995
+ // NOTE: First erase previously drawn arrow.
995
996
  arrw.shape.clear();
996
997
  arrw.hidden_nodes.length = 0;
997
- // Use local variables so as not to change any "real" attribute values
998
+ // Use local variables so as not to change any "real" attribute values.
998
999
  let cnb, proc, prod, fnx, fny, fnw, fnh, tnx, tny, tnw, tnh,
999
1000
  cp, rr, aa, bb, dd, nn, af, l, s, w, tw, th, bpx, bpy, epx, epy,
1000
1001
  sda, stroke_color, stroke_width, arrow_start, arrow_end,
1001
1002
  font_color, font_weight, luc = null, grid = null;
1002
- // Get the main arrow attributes
1003
+ // Get the main arrow attributes.
1003
1004
  const
1004
1005
  from_nb = arrw.from_node,
1005
1006
  to_nb = arrw.to_node;
1006
- // Use "let" because `ignored` may also be set later on (for single link)
1007
+ // Use "let" because `ignored` may also be set later on (for single link).
1007
1008
  let ignored = (from_nb && MODEL.ignored_entities[from_nb.identifier]) ||
1008
1009
  (to_nb && MODEL.ignored_entities[to_nb.identifier]);
1009
- // First check if this is a block arrow (ONE node being null)
1010
+ // First check if this is a block arrow (ONE node being null).
1010
1011
  if(!from_nb) {
1011
1012
  cnb = to_nb;
1012
1013
  } else if(!to_nb) {
@@ -1014,22 +1015,22 @@ class Paper {
1014
1015
  } else {
1015
1016
  cnb = null;
1016
1017
  }
1017
- // If not NULL `cnb` is the cluster or node box (product or process) having
1018
+ // If not NULL, `cnb` is the cluster or node box (product or process) having
1018
1019
  // links to entities outside the focal cluster. Such links are summarized
1019
1020
  // by "block arrows": on the left edge of the box to indicate inflows,
1020
1021
  // on the right edge to indicate outflows, and two-headed on the top edge
1021
1022
  // to indicate two-way flows. When the cursor is moved over a block arrow,
1022
1023
  // the Documentation dialog will display the list of associated nodes
1023
- // (with their actual flows if non-zero)
1024
+ // (with their actual flows if non-zero).
1024
1025
  if(cnb) {
1025
- // Distinguish between input, output and io products
1026
+ // Distinguish between input, output and io products.
1026
1027
  let ip = [], op = [], iop = [];
1027
1028
  if(cnb instanceof Cluster) {
1028
1029
  for(let i = 0; i < arrw.links.length; i++) {
1029
1030
  const lnk = arrw.links[i];
1030
- // determine which product is involved
1031
+ // Determine which product is involved.
1031
1032
  prod = (lnk.from_node instanceof Product ? lnk.from_node : lnk.to_node);
1032
- // NOTE: clusters "know" their input/output products
1033
+ // NOTE: Clusters "know" their input/output products.
1033
1034
  if(cnb.io_products.indexOf(prod) >= 0) {
1034
1035
  addDistinct(prod, iop);
1035
1036
  } else if(cnb.consumed_products.indexOf(prod) >= 0) {
@@ -1039,7 +1040,7 @@ class Paper {
1039
1040
  }
1040
1041
  }
1041
1042
  } else {
1042
- // cnb is process or product => knows its inputs and outputs
1043
+ // `cnb` is process or product => knows its inputs and outputs.
1043
1044
  for(let i = 0; i < arrw.links.length; i++) {
1044
1045
  const lnk = arrw.links[i];
1045
1046
  if(lnk.from_node === cnb) {
@@ -1047,7 +1048,7 @@ class Paper {
1047
1048
  } else {
1048
1049
  addDistinct(lnk.from_node, ip);
1049
1050
  }
1050
- // NOTE: for processes, products cannot be BOTH input and output
1051
+ // NOTE: For processes, products cannot be BOTH input and output.
1051
1052
  }
1052
1053
  }
1053
1054
  cnb.hidden_inputs = ip;