linny-r 2.0.6 → 2.0.8
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 +1 -1
- package/static/index.html +16 -1
- package/static/linny-r.css +4 -2
- package/static/scripts/linny-r-ctrl.js +42 -40
- package/static/scripts/linny-r-gui-chart-manager.js +7 -4
- package/static/scripts/linny-r-gui-constraint-editor.js +7 -3
- package/static/scripts/linny-r-gui-controller.js +17 -10
- package/static/scripts/linny-r-gui-dataset-manager.js +130 -0
- package/static/scripts/linny-r-gui-file-manager.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 +23 -19
- package/static/scripts/linny-r-utils.js +41 -5
- package/static/scripts/linny-r-vm.js +173 -26
@@ -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;
|
@@ -10,7 +10,7 @@ the Linny-R project.
|
|
10
10
|
*/
|
11
11
|
|
12
12
|
/*
|
13
|
-
Copyright (c) 2017-
|
13
|
+
Copyright (c) 2017-2025 Delft University of Technology
|
14
14
|
|
15
15
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
16
16
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -5801,7 +5801,7 @@ class NodeBox extends ObjectWithXYWH {
|
|
5801
5801
|
}
|
5802
5802
|
|
5803
5803
|
resize() {
|
5804
|
-
//
|
5804
|
+
// Resize this node; returns TRUE iff size has changed.
|
5805
5805
|
// Therefore, keep track of original width and height.
|
5806
5806
|
const
|
5807
5807
|
ow = this.width,
|
@@ -5814,7 +5814,7 @@ class NodeBox extends ObjectWithXYWH {
|
|
5814
5814
|
w = Math.max(w, UI.textSize(`[${this.scale_unit}]`).width);
|
5815
5815
|
}
|
5816
5816
|
this.frame_width = w + 7;
|
5817
|
-
// Add 17 pixels height for actor name
|
5817
|
+
// Add 17 pixels height for actor name.
|
5818
5818
|
this.height = Math.max(50, this.bbox.height + 17);
|
5819
5819
|
if(this instanceof Process) {
|
5820
5820
|
this.width = Math.max(90, this.frame_width + 20);
|
@@ -5822,11 +5822,11 @@ class NodeBox extends ObjectWithXYWH {
|
|
5822
5822
|
} else if(this instanceof Cluster) {
|
5823
5823
|
this.width = Math.max(
|
5824
5824
|
CONFIGURATION.min_cluster_size, this.frame_width + 20);
|
5825
|
-
// Clusters have a square shape
|
5825
|
+
// Clusters have a square shape.
|
5826
5826
|
this.height = Math.max(this.width, this.height);
|
5827
5827
|
} else {
|
5828
5828
|
this.height += 8;
|
5829
|
-
// Reserve some extra space for UB/LB if defined
|
5829
|
+
// Reserve some extra space for UB/LB if defined.
|
5830
5830
|
if(this.lower_bound.defined || this.upper_bound.defined) {
|
5831
5831
|
this.frame_width += 16;
|
5832
5832
|
}
|
@@ -8282,20 +8282,20 @@ class Product extends Node {
|
|
8282
8282
|
// By default, processes have the letter p, products the letter q.
|
8283
8283
|
this.TEX_id = 'p';
|
8284
8284
|
// For products, the default bounds are [0, 0], and modeler-defined bounds
|
8285
|
-
// typically are equal
|
8285
|
+
// typically are equal.
|
8286
8286
|
this.equal_bounds = true;
|
8287
|
-
// In addition to LB, UB and IL, products has 1 input attribute: P
|
8287
|
+
// In addition to LB, UB and IL, products has 1 input attribute: P.
|
8288
8288
|
this.price = new Expression(this, 'P', '');
|
8289
|
-
// Products have a highest cost price, and may have a stock price (if storage)
|
8289
|
+
// Products have a highest cost price, and may have a stock price (if storage).
|
8290
8290
|
this.highest_cost_price = [];
|
8291
8291
|
this.stock_price = [];
|
8292
8292
|
// Stock level changing from 0 to positive counts as "start up", while
|
8293
|
-
// changing from positive to 0 counts as a "shut-down"
|
8294
|
-
// NOTE:
|
8295
|
-
// but store the numbers of the time steps in which they occurred
|
8293
|
+
// changing from positive to 0 counts as a "shut-down".
|
8294
|
+
// NOTE: Being relatively rare, start_ups and shut_downs are not vectors,
|
8295
|
+
// but store the numbers of the time steps in which they occurred.
|
8296
8296
|
this.start_ups = [];
|
8297
8297
|
this.shut_downs = [];
|
8298
|
-
// Modeler may set explicit properties
|
8298
|
+
// Modeler may set explicit properties.
|
8299
8299
|
this.is_source = false;
|
8300
8300
|
this.is_sink = false;
|
8301
8301
|
this.is_buffer = false;
|
@@ -8650,13 +8650,13 @@ class Product extends Node {
|
|
8650
8650
|
}
|
8651
8651
|
|
8652
8652
|
get defaultAttribute() {
|
8653
|
-
// Products have their level as default attribute
|
8653
|
+
// Products have their level as default attribute.
|
8654
8654
|
return 'L';
|
8655
8655
|
}
|
8656
8656
|
|
8657
8657
|
attributeValue(a) {
|
8658
|
-
//
|
8659
|
-
//
|
8658
|
+
// Return the computed result for attribute `a`.
|
8659
|
+
// NOTE: For products, this is always a vector except IL.
|
8660
8660
|
if(a === 'L') return this.level;
|
8661
8661
|
if(a === 'CP') return this.cost_price;
|
8662
8662
|
if(a === 'HCP') return this.highest_cost_price;
|
@@ -8664,7 +8664,7 @@ class Product extends Node {
|
|
8664
8664
|
}
|
8665
8665
|
|
8666
8666
|
attributeExpression(a) {
|
8667
|
-
// Products have four expression attributes
|
8667
|
+
// Products have four expression attributes.
|
8668
8668
|
if(a === 'LB') return this.lower_bound;
|
8669
8669
|
if(a === 'UB') {
|
8670
8670
|
return (this.equal_bounds ? this.lower_bound : this.upper_bound);
|
@@ -8748,7 +8748,7 @@ class Product extends Node {
|
|
8748
8748
|
}
|
8749
8749
|
|
8750
8750
|
copyPropertiesFrom(p) {
|
8751
|
-
// Set properties to be identical to those of product `p
|
8751
|
+
// Set properties to be identical to those of product `p`.
|
8752
8752
|
this.x = p.x;
|
8753
8753
|
this.y = p.y;
|
8754
8754
|
this.comments = p.comments;
|
@@ -8765,11 +8765,11 @@ class Product extends Node {
|
|
8765
8765
|
this.initial_level.text = p.initial_level.text;
|
8766
8766
|
this.integer_level = p.integer_level;
|
8767
8767
|
this.TEX_id = p.TEX_id;
|
8768
|
-
// NOTE:
|
8768
|
+
// NOTE: Do not copy the `no_links` property, nor the import/export status.
|
8769
8769
|
}
|
8770
8770
|
|
8771
8771
|
differences(p) {
|
8772
|
-
// Return "dictionary" of differences, or NULL if none
|
8772
|
+
// Return "dictionary" of differences, or NULL if none.
|
8773
8773
|
const d = differences(this, p, UI.MC.PRODUCT_PROPS);
|
8774
8774
|
if(Object.keys(d).length > 0) return d;
|
8775
8775
|
return null;
|
@@ -12699,6 +12699,10 @@ class BoundLine {
|
|
12699
12699
|
VM.constraint_codes[this.type] + '] bound line #' +
|
12700
12700
|
this.constraint.bound_lines.indexOf(this);
|
12701
12701
|
}
|
12702
|
+
|
12703
|
+
get name() {
|
12704
|
+
return this.displayName;
|
12705
|
+
}
|
12702
12706
|
|
12703
12707
|
get copy() {
|
12704
12708
|
// Return a "clone" of this bound line.
|
@@ -9,7 +9,7 @@ This JavaScript file (linny-r-utils.js) defines a variety of "helper" functions
|
|
9
9
|
that are used in other Linny-R modules.
|
10
10
|
*/
|
11
11
|
/*
|
12
|
-
Copyright (c) 2017-
|
12
|
+
Copyright (c) 2017-2025 Delft University of Technology
|
13
13
|
|
14
14
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
15
15
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -74,16 +74,16 @@ function safeStrToFloat(str, val=0) {
|
|
74
74
|
}
|
75
75
|
|
76
76
|
function safeStrToInt(str, val=0) {
|
77
|
-
//
|
77
|
+
// Return numeric value of integer string, IGNORING decimals after
|
78
78
|
// point or comma.
|
79
|
-
// NOTE:
|
79
|
+
// NOTE: Return default value `val` if `str` is empty, null or undefined.
|
80
80
|
const n = (str ? parseInt(str) : val);
|
81
81
|
return (isNaN(n) ? val : n);
|
82
82
|
}
|
83
83
|
|
84
84
|
function rangeToList(str, max=0) {
|
85
|
-
//
|
86
|
-
//
|
85
|
+
// Parse ranges "n-m/i" into a list of integers
|
86
|
+
// Return FALSE if range is not valid according to the convention below
|
87
87
|
// The part "/i" is optional and denotes the increment; by default, i = 1.
|
88
88
|
// The returned list will contain all integers starting at n and up to
|
89
89
|
// at most (!) m, with increments of i, so [n, n+i, n+2i, ...]
|
@@ -112,6 +112,33 @@ function rangeToList(str, max=0) {
|
|
112
112
|
return list;
|
113
113
|
}
|
114
114
|
|
115
|
+
function listToRange(list) {
|
116
|
+
// Return a string that represents the given list of integers as a series
|
117
|
+
// of subranges, e.g., [0,1,2,3,5,6,9,11] results in "0-3, 5-6, 9, 11".
|
118
|
+
const
|
119
|
+
n = list.length,
|
120
|
+
subs = [];
|
121
|
+
if(!n) return '';
|
122
|
+
let i = 0,
|
123
|
+
from = list[0],
|
124
|
+
to = from;
|
125
|
+
while(i < n) {
|
126
|
+
i++;
|
127
|
+
if(list[i] === to + 1) {
|
128
|
+
to++;
|
129
|
+
} else {
|
130
|
+
if(from === to) {
|
131
|
+
subs.push(from);
|
132
|
+
} else {
|
133
|
+
subs.push(`${from}-${to}`);
|
134
|
+
}
|
135
|
+
from = list[i];
|
136
|
+
to = from;
|
137
|
+
}
|
138
|
+
}
|
139
|
+
return subs.join(', ');
|
140
|
+
}
|
141
|
+
|
115
142
|
function dateToString(d) {
|
116
143
|
// Returns date-time `d` in UTC format, accounting for time zone
|
117
144
|
const offset = d.getTimezoneOffset();
|
@@ -198,6 +225,13 @@ function ellipsedText(text, n=50, m=10) {
|
|
198
225
|
return text.slice(0, n) + ' \u2026 ' + text.slice(text.length - m);
|
199
226
|
}
|
200
227
|
|
228
|
+
function unquoteCSV(s) {
|
229
|
+
// Returns a double-quoted string `s` without its quotes, and with
|
230
|
+
// quote pairs "" replaced by single " quotes.
|
231
|
+
if(!s.startsWith('"') || !s.endsWith('"')) return s;
|
232
|
+
return s.slice(1, -1).replaceAll('""', '"');
|
233
|
+
}
|
234
|
+
|
201
235
|
//
|
202
236
|
// Functions used when comparing two Linny-R models
|
203
237
|
//
|
@@ -1098,12 +1132,14 @@ if(NODE) module.exports = {
|
|
1098
1132
|
safeStrToFloat: safeStrToFloat,
|
1099
1133
|
safeStrToInt: safeStrToInt,
|
1100
1134
|
rangeToList: rangeToList,
|
1135
|
+
listToRange: listToRange,
|
1101
1136
|
dateToString: dateToString,
|
1102
1137
|
msecToTime: msecToTime,
|
1103
1138
|
compactClockTime: compactClockTime,
|
1104
1139
|
uniformDecimals: uniformDecimals,
|
1105
1140
|
capitalized: capitalized,
|
1106
1141
|
ellipsedText: ellipsedText,
|
1142
|
+
unquoteCSV: unquoteCSV,
|
1107
1143
|
earlierVersion: earlierVersion,
|
1108
1144
|
differences: differences,
|
1109
1145
|
markFirstDifference: markFirstDifference,
|