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.
- package/README.md +6 -6
- package/package.json +32 -32
- package/server.js +27 -19
- package/static/images/icon.svg +23 -23
- package/static/index.html +6 -0
- package/static/linny-r.css +2 -2
- package/static/scripts/iro.min.js +7 -7
- package/static/scripts/linny-r-ctrl.js +40 -39
- package/static/scripts/linny-r-gui-constraint-editor.js +7 -3
- package/static/scripts/linny-r-gui-controller.js +19 -10
- package/static/scripts/linny-r-gui-finder.js +13 -0
- package/static/scripts/linny-r-gui-model-autosaver.js +2 -2
- package/static/scripts/linny-r-gui-monitor.js +1 -1
- package/static/scripts/linny-r-gui-paper.js +29 -28
- package/static/scripts/linny-r-model.js +47 -21
- package/static/scripts/linny-r-utils.js +33 -5
- package/static/scripts/linny-r-vm.js +182 -40
@@ -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:
|
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:
|
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
|
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:
|
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:
|
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
|
-
//
|
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:
|
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:
|
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:
|
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
|
-
|
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
|
-
//
|
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-
|
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:
|
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
|
-
|
4332
|
-
|
4333
|
-
|
4334
|
-
|
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-
|
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(
|
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-
|
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-
|
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:
|
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:
|
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
|
-
//
|
994
|
-
// NOTE:
|
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
|
-
//
|
1031
|
+
// Determine which product is involved.
|
1031
1032
|
prod = (lnk.from_node instanceof Product ? lnk.from_node : lnk.to_node);
|
1032
|
-
// NOTE:
|
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:
|
1051
|
+
// NOTE: For processes, products cannot be BOTH input and output.
|
1051
1052
|
}
|
1052
1053
|
}
|
1053
1054
|
cnb.hidden_inputs = ip;
|