aiphoria 0.0.1__py3-none-any.whl → 0.8.0__py3-none-any.whl
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.
- aiphoria/__init__.py +59 -0
- aiphoria/core/__init__.py +55 -0
- aiphoria/core/builder.py +305 -0
- aiphoria/core/datachecker.py +1808 -0
- aiphoria/core/dataprovider.py +806 -0
- aiphoria/core/datastructures.py +1686 -0
- aiphoria/core/datavisualizer.py +431 -0
- aiphoria/core/datavisualizer_data/LICENSE +21 -0
- aiphoria/core/datavisualizer_data/datavisualizer_plotly.html +5561 -0
- aiphoria/core/datavisualizer_data/pako.min.js +2 -0
- aiphoria/core/datavisualizer_data/plotly-3.0.0.min.js +3879 -0
- aiphoria/core/flowmodifiersolver.py +1754 -0
- aiphoria/core/flowsolver.py +1472 -0
- aiphoria/core/logger.py +113 -0
- aiphoria/core/network_graph.py +136 -0
- aiphoria/core/network_graph_data/ECHARTS_LICENSE +202 -0
- aiphoria/core/network_graph_data/echarts_min.js +45 -0
- aiphoria/core/network_graph_data/network_graph.html +76 -0
- aiphoria/core/network_graph_data/network_graph.js +1391 -0
- aiphoria/core/parameters.py +269 -0
- aiphoria/core/types.py +20 -0
- aiphoria/core/utils.py +362 -0
- aiphoria/core/visualizer_parameters.py +7 -0
- aiphoria/data/example_scenario.xlsx +0 -0
- aiphoria/example.py +66 -0
- aiphoria/lib/docs/dynamic_stock.py +124 -0
- aiphoria/lib/odym/modules/ODYM_Classes.py +362 -0
- aiphoria/lib/odym/modules/ODYM_Functions.py +1299 -0
- aiphoria/lib/odym/modules/__init__.py +1 -0
- aiphoria/lib/odym/modules/dynamic_stock_model.py +808 -0
- aiphoria/lib/odym/modules/test/DSM_test_known_results.py +762 -0
- aiphoria/lib/odym/modules/test/ODYM_Classes_test_known_results.py +107 -0
- aiphoria/lib/odym/modules/test/ODYM_Functions_test_known_results.py +136 -0
- aiphoria/lib/odym/modules/test/__init__.py +2 -0
- aiphoria/runner.py +678 -0
- aiphoria-0.8.0.dist-info/METADATA +119 -0
- aiphoria-0.8.0.dist-info/RECORD +40 -0
- {aiphoria-0.0.1.dist-info → aiphoria-0.8.0.dist-info}/WHEEL +1 -1
- aiphoria-0.8.0.dist-info/licenses/LICENSE +21 -0
- aiphoria-0.0.1.dist-info/METADATA +0 -5
- aiphoria-0.0.1.dist-info/RECORD +0 -5
- {aiphoria-0.0.1.dist-info → aiphoria-0.8.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1391 @@
|
|
|
1
|
+
const chart = echarts.init(document.getElementById("main"));
|
|
2
|
+
|
|
3
|
+
// ***************
|
|
4
|
+
// * Global data *
|
|
5
|
+
// ***************
|
|
6
|
+
|
|
7
|
+
// Global variables
|
|
8
|
+
const globals = {
|
|
9
|
+
// Scenario data, contains baseline unit name and value
|
|
10
|
+
scenarioData: {scenario_data},
|
|
11
|
+
|
|
12
|
+
// Original data, this will get replaced with JSON object from Python
|
|
13
|
+
originalYearToData: {year_to_data},
|
|
14
|
+
|
|
15
|
+
// Updated year to data: contains various mappings e.g. node ID to position
|
|
16
|
+
yearToData: new Map(),
|
|
17
|
+
|
|
18
|
+
// Data mappings
|
|
19
|
+
yearToProcessIdToProcess: new Map(),
|
|
20
|
+
yearToProcessIdToFlowIds: new Map(),
|
|
21
|
+
yearToFlowIdToFlow: new Map(),
|
|
22
|
+
|
|
23
|
+
// Edge colors
|
|
24
|
+
edgeColors: {
|
|
25
|
+
absolute: "rgba(59, 162, 114, 1)",
|
|
26
|
+
relative: "rgba(255, 50, 50, 1)",
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
virtualNodeColor: "rgba(100, 100, 100, 0.8)",
|
|
30
|
+
virtualFlowColor: "rgba(100, 100, 100, 0.8)",
|
|
31
|
+
|
|
32
|
+
// Transformation stage name to color mapping, build in initialize()
|
|
33
|
+
transformationStageNameToColor: new Map(),
|
|
34
|
+
|
|
35
|
+
// Scenario name
|
|
36
|
+
scenarioName: "Scenario",
|
|
37
|
+
|
|
38
|
+
// List of all years
|
|
39
|
+
years: [],
|
|
40
|
+
|
|
41
|
+
// Current timeline year
|
|
42
|
+
currentYear: 0,
|
|
43
|
+
currentYearIndex: 0,
|
|
44
|
+
|
|
45
|
+
// Current in-use year data
|
|
46
|
+
graphData: {
|
|
47
|
+
data: [],
|
|
48
|
+
links: [],
|
|
49
|
+
categories: [],
|
|
50
|
+
legendData: [],
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
selectedNodeIndex: null,
|
|
54
|
+
|
|
55
|
+
// All data for timeline years
|
|
56
|
+
initialOption: {},
|
|
57
|
+
|
|
58
|
+
// ***********************
|
|
59
|
+
// * Changeable settings *
|
|
60
|
+
// ***********************
|
|
61
|
+
// If true, use process_id as label, otherwise use process_label
|
|
62
|
+
useProcessIdAsLabel: true,
|
|
63
|
+
|
|
64
|
+
// If true, use flow type (ABS/%) as flow label, otherwise use flow value
|
|
65
|
+
// useFlowTypeAsLabel: true,
|
|
66
|
+
useFlowTypeAsLabel: false,
|
|
67
|
+
|
|
68
|
+
// If true, color processes by their transformation stage
|
|
69
|
+
// If transformation stage color mapping is not found then use default color palette
|
|
70
|
+
useTransformationStageColors: true,
|
|
71
|
+
|
|
72
|
+
// If true, hide processes that have no inflows and outflows
|
|
73
|
+
hideUnconnectedProcesses: false,
|
|
74
|
+
|
|
75
|
+
// Freeze node positions, disabled by default to allow force layout
|
|
76
|
+
// to find node positions
|
|
77
|
+
freezeNodePositions: false,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// **************
|
|
81
|
+
// * Formatters *
|
|
82
|
+
// **************
|
|
83
|
+
|
|
84
|
+
function formatValue(val, options = { numDecimals: 3}) {
|
|
85
|
+
// Format value to fixed number of digits (defaults to 3)
|
|
86
|
+
return parseFloat(val.toFixed(options.numDecimals))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function isSameValue(a, b, eps = 0.001) {
|
|
90
|
+
// Check if value is same with maximum allowed difference (= epsilon)
|
|
91
|
+
return Math.abs(a - b) < eps
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function getTooltipFormatter(params) {
|
|
95
|
+
const isLegend = params.componentType == "legend"
|
|
96
|
+
const isNode = params.dataType == "node"
|
|
97
|
+
const isLink = params.dataType == "edge"
|
|
98
|
+
|
|
99
|
+
let result = ""
|
|
100
|
+
result += `
|
|
101
|
+
<style>
|
|
102
|
+
.tooltip-wrapper {
|
|
103
|
+
min-width: 300px;
|
|
104
|
+
min-height: 100px;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.tooltip-title {
|
|
108
|
+
font-size: 16px;
|
|
109
|
+
font-weight: bold;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.tooltip-type-title {
|
|
113
|
+
font-size: 12px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.tooltip-body-wrapper {
|
|
117
|
+
display: flex;
|
|
118
|
+
flex-direction: row;
|
|
119
|
+
margin-right: 1rem;
|
|
120
|
+
column-gap: 10px;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.tooltip-col {
|
|
124
|
+
font-size: 14px;
|
|
125
|
+
font-weight: normal;
|
|
126
|
+
/*flex-grow: 1;*/
|
|
127
|
+
/*height: 100%;*/
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.tooltip-table {
|
|
131
|
+
--border: solid 1px #ccc;
|
|
132
|
+
--font-size-header: 12px;
|
|
133
|
+
--font-size-data: 12px;
|
|
134
|
+
border: var(--border);
|
|
135
|
+
border-collapse: collapse;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.tooltip-table-title {
|
|
139
|
+
font-size: 14px;
|
|
140
|
+
font-weight: bold;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* Table headings */
|
|
144
|
+
.tooltip-table > thead > tr > th {
|
|
145
|
+
font-size: var(--font-size-header);
|
|
146
|
+
font-weight: bold;
|
|
147
|
+
text-align: left;
|
|
148
|
+
border: var(--border);
|
|
149
|
+
width: 100%;
|
|
150
|
+
padding: 0 4px 0 4px;
|
|
151
|
+
background: #eee;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* Table rows */
|
|
155
|
+
.tooltip-table > tbody > tr > td {
|
|
156
|
+
font-size: var(--font-size-data);
|
|
157
|
+
text-align: left;
|
|
158
|
+
border: var(--border);
|
|
159
|
+
padding: 0 4px 0 4px;
|
|
160
|
+
}
|
|
161
|
+
</style>
|
|
162
|
+
`
|
|
163
|
+
|
|
164
|
+
// TODO: Check with || isLegend and show tooltip when hovering over legend items
|
|
165
|
+
if (isNode) {
|
|
166
|
+
// let nodeData = null
|
|
167
|
+
// let nodeId = null
|
|
168
|
+
// if(isNode) {
|
|
169
|
+
// nodeData = params.data
|
|
170
|
+
// nodeId = nodeData.id
|
|
171
|
+
// }
|
|
172
|
+
// if(isLegend) {
|
|
173
|
+
// nodeId = params.name
|
|
174
|
+
// }
|
|
175
|
+
|
|
176
|
+
const nodeData = params.data
|
|
177
|
+
const nodeId = nodeData.id;
|
|
178
|
+
|
|
179
|
+
// Get flow IDs
|
|
180
|
+
const year = globals.currentYear;
|
|
181
|
+
const processId = nodeId
|
|
182
|
+
const process = globals.yearToProcessIdToProcess.get(year).get(processId)
|
|
183
|
+
const processIdToFlowIds =
|
|
184
|
+
globals.yearToProcessIdToFlowIds.get(year);
|
|
185
|
+
const flowIdToFlow = globals.yearToFlowIdToFlow.get(year)
|
|
186
|
+
const inflowIds = processIdToFlowIds.get(nodeId).in;
|
|
187
|
+
const outflowIds = processIdToFlowIds.get(nodeId).out;
|
|
188
|
+
|
|
189
|
+
// Build stock info
|
|
190
|
+
let stockInfoHTML = ""
|
|
191
|
+
const hasStock = nodeData.isStock
|
|
192
|
+
if (hasStock) {
|
|
193
|
+
let paramsHTML = ""
|
|
194
|
+
const stockDistributionParams = nodeData.stockDistributionParams
|
|
195
|
+
let hasStockParams = !(stockDistributionParams == undefined || stockDistributionParams == null)
|
|
196
|
+
|
|
197
|
+
// Unpack stock distribution params
|
|
198
|
+
if (hasStockParams && typeof stockDistributionParams == "object") {
|
|
199
|
+
const params = []
|
|
200
|
+
for (const [k, v] of Object.entries(stockDistributionParams)) {
|
|
201
|
+
params.push(`${k}=${v}`)
|
|
202
|
+
}
|
|
203
|
+
paramsHTML = params.join(", ")
|
|
204
|
+
} else {
|
|
205
|
+
paramsHTML = stockDistributionParams
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
stockInfoHTML = `
|
|
209
|
+
<table class="tooltip-table">
|
|
210
|
+
<span class="tooltip-table-title">Stock</span>
|
|
211
|
+
<thead>
|
|
212
|
+
<tr>
|
|
213
|
+
<th>Type</th>
|
|
214
|
+
<th>Lifetime</th>
|
|
215
|
+
${hasStockParams ? "<th>Parameters</th>" : ""}
|
|
216
|
+
</tr>
|
|
217
|
+
</thead>
|
|
218
|
+
<tbody>
|
|
219
|
+
<tr>
|
|
220
|
+
<td>${nodeData.stockDistributionType}</td>
|
|
221
|
+
<td>${nodeData.stockLifetime}</td>
|
|
222
|
+
${hasStockParams ? `<td>${paramsHTML}</td>` : ""}
|
|
223
|
+
</tr>
|
|
224
|
+
</tbody>
|
|
225
|
+
</table>
|
|
226
|
+
<br/>
|
|
227
|
+
`
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Build list of inflow IDs, outflow IDs, total inflows and total outflows
|
|
231
|
+
let totalInflowsBaseline = 0.0
|
|
232
|
+
let totalInflowsIndicators = new Map()
|
|
233
|
+
const inflows = [];
|
|
234
|
+
for (const flowId of inflowIds) {
|
|
235
|
+
const flow = flowIdToFlow.get(flowId)
|
|
236
|
+
totalInflowsBaseline += flow.evaluated_value
|
|
237
|
+
for (const [k, v] of Object.entries(flow.indicators)) {
|
|
238
|
+
if (!totalInflowsIndicators.has(k)) {
|
|
239
|
+
totalInflowsIndicators.set(k, 0.0)
|
|
240
|
+
}
|
|
241
|
+
const prevTotal = totalInflowsIndicators.get(k)
|
|
242
|
+
const newTotal = prevTotal + v
|
|
243
|
+
totalInflowsIndicators.set(k, newTotal)
|
|
244
|
+
}
|
|
245
|
+
inflows.push(flow);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
let totalOutflowsBaseline = 0.0
|
|
249
|
+
let totalOutflowsIndicators = new Map()
|
|
250
|
+
const outflows = [];
|
|
251
|
+
for (const flowId of outflowIds) {
|
|
252
|
+
const flow = flowIdToFlow.get(flowId)
|
|
253
|
+
totalOutflowsBaseline += flow.evaluated_value
|
|
254
|
+
for (const [k, v] of Object.entries(flow.indicators)) {
|
|
255
|
+
if (!totalOutflowsIndicators.has(k)) {
|
|
256
|
+
totalOutflowsIndicators.set(k, 0.0)
|
|
257
|
+
}
|
|
258
|
+
const prevTotal = totalOutflowsIndicators.get(k)
|
|
259
|
+
const newTotal = prevTotal + v
|
|
260
|
+
totalOutflowsIndicators.set(k, newTotal)
|
|
261
|
+
}
|
|
262
|
+
outflows.push(flow);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Get indicator names from either inflows or from outflows
|
|
266
|
+
const indicatorNames = [];
|
|
267
|
+
const hasInflows = inflows.length > 0;
|
|
268
|
+
const hasOutflows = outflows.length > 0;
|
|
269
|
+
if (hasInflows && !indicatorNames.length) {
|
|
270
|
+
for (const key of Object.keys(inflows[0].indicators)) {
|
|
271
|
+
indicatorNames.push(key);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (hasOutflows && !indicatorNames.length) {
|
|
275
|
+
for (const key of Object.keys(outflows[0].indicators)) {
|
|
276
|
+
indicatorNames.push(key);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Make headers for inflows and outflows columns
|
|
281
|
+
const baseline = globals.scenarioData.baseline_value_name
|
|
282
|
+
const inflowHeaders = ["Source", baseline, ...indicatorNames.map(elem => elem)]
|
|
283
|
+
const outflowHeaders = ["Target", baseline, ...indicatorNames.map(elem => elem)]
|
|
284
|
+
|
|
285
|
+
// Build inflow headers and inflow items
|
|
286
|
+
let inflowHeadersHTML = ""
|
|
287
|
+
inflowHeadersHTML += "<tr>"
|
|
288
|
+
for (const elem of inflowHeaders) {
|
|
289
|
+
inflowHeadersHTML += `<th>${elem}</th>`
|
|
290
|
+
}
|
|
291
|
+
inflowHeadersHTML += "</tr>"
|
|
292
|
+
|
|
293
|
+
// Build inflow items as HTML
|
|
294
|
+
let inflowItemsHTML = ""
|
|
295
|
+
for (const flow of inflows) {
|
|
296
|
+
inflowItemsHTML += "<tr>"
|
|
297
|
+
inflowItemsHTML += `<td>${flow.source_process_id}</td>`
|
|
298
|
+
inflowItemsHTML += `<td>${formatValue(flow.evaluated_value)}</td>`
|
|
299
|
+
for (const name of indicatorNames) {
|
|
300
|
+
inflowItemsHTML += `<td>${formatValue(flow.indicators[name])}</td>`
|
|
301
|
+
}
|
|
302
|
+
inflowItemsHTML += "</tr>"
|
|
303
|
+
}
|
|
304
|
+
if (hasInflows) {
|
|
305
|
+
// Add total row
|
|
306
|
+
inflowItemsHTML += "<tr>"
|
|
307
|
+
inflowItemsHTML += "<td><span style='font-weight: bold'>Total</span></td>"
|
|
308
|
+
inflowItemsHTML += `<td><span style='font-weight: bold'>${formatValue(totalInflowsBaseline)}</span></td>`
|
|
309
|
+
for (const [k, v] of totalInflowsIndicators.entries()) {
|
|
310
|
+
inflowItemsHTML += `<td><span style='font-weight: bold'>${formatValue(v)}</span></td>`
|
|
311
|
+
}
|
|
312
|
+
inflowItemsHTML += "</tr>"
|
|
313
|
+
} else {
|
|
314
|
+
inflowHeadersHTML = "<tr>No inflows</tr>"
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Build outflow headers and inflow items
|
|
318
|
+
let outflowHeadersHTML = ""
|
|
319
|
+
outflowHeadersHTML += "<tr>"
|
|
320
|
+
for (const elem of outflowHeaders) {
|
|
321
|
+
outflowHeadersHTML += `<th>${elem}</th>`
|
|
322
|
+
}
|
|
323
|
+
outflowHeadersHTML += "</tr>"
|
|
324
|
+
|
|
325
|
+
// Build inflow items as HTML
|
|
326
|
+
let outflowItemsHTML = ""
|
|
327
|
+
for (const flow of outflows) {
|
|
328
|
+
outflowItemsHTML += "<tr>"
|
|
329
|
+
outflowItemsHTML += `<td>${flow.target_process_id}</td>`
|
|
330
|
+
outflowItemsHTML += `<td>${formatValue(flow.evaluated_value)}</td>`
|
|
331
|
+
for (const name of indicatorNames) {
|
|
332
|
+
outflowItemsHTML += `<td>${formatValue(flow.indicators[name])}</td>`
|
|
333
|
+
}
|
|
334
|
+
outflowItemsHTML += "</tr>"
|
|
335
|
+
}
|
|
336
|
+
if (hasOutflows) {
|
|
337
|
+
// Add total row
|
|
338
|
+
outflowItemsHTML += "<tr>"
|
|
339
|
+
outflowItemsHTML += "<td><span style='font-weight: bold'>Total</span></td>"
|
|
340
|
+
outflowItemsHTML += `<td><span style='font-weight: bold'>${formatValue(totalOutflowsBaseline)}</span></td>`
|
|
341
|
+
for (const [k, v] of totalOutflowsIndicators.entries()) {
|
|
342
|
+
outflowItemsHTML += `<td><span style='font-weight: bold'>${formatValue(v)}</span></td>`
|
|
343
|
+
}
|
|
344
|
+
outflowItemsHTML += "</tr>"
|
|
345
|
+
} else {
|
|
346
|
+
outflowHeadersHTML = "<tr>No outflows</tr>"
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Determine type for process
|
|
350
|
+
let nodeType = "Process"
|
|
351
|
+
if (hasStock) {
|
|
352
|
+
nodeType = "Stock"
|
|
353
|
+
}
|
|
354
|
+
if (nodeData.isVirtual) {
|
|
355
|
+
nodeType = "Virtual process"
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Node tooltip result
|
|
359
|
+
result += `
|
|
360
|
+
<div class="tooltip-wrapper">
|
|
361
|
+
<div class="tooltip-title">${nodeData.name}</div>
|
|
362
|
+
<span class="tooltip-type-title">Type: ${nodeType}</span><br/>
|
|
363
|
+
<span class="tooltip-type-title">ID: ${nodeId}</span><br/>
|
|
364
|
+
<br/>
|
|
365
|
+
<div class="tooltip-body-wrapper">
|
|
366
|
+
<div class="tooltip-col">
|
|
367
|
+
${hasStock ? stockInfoHTML : ""}
|
|
368
|
+
</div>
|
|
369
|
+
</div>
|
|
370
|
+
<div class="tooltip-body-wrapper">
|
|
371
|
+
<div class="tooltip-col">
|
|
372
|
+
<table class="tooltip-table">
|
|
373
|
+
<span class="tooltip-table-title">Inflows</span><br/>
|
|
374
|
+
<thead>
|
|
375
|
+
${inflowHeadersHTML}
|
|
376
|
+
</thead>
|
|
377
|
+
<tbody>
|
|
378
|
+
${inflowItemsHTML}
|
|
379
|
+
</tbody>
|
|
380
|
+
</table>
|
|
381
|
+
</div>
|
|
382
|
+
<br/>
|
|
383
|
+
<div class="tooltip-col">
|
|
384
|
+
<span class="tooltip-table-title">Outflows</span><br/>
|
|
385
|
+
<table class="tooltip-table">
|
|
386
|
+
<thead>
|
|
387
|
+
${outflowHeadersHTML}
|
|
388
|
+
</thead>
|
|
389
|
+
<tbody>
|
|
390
|
+
${outflowItemsHTML}
|
|
391
|
+
</tbody>
|
|
392
|
+
</table>
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
</div>
|
|
396
|
+
`;
|
|
397
|
+
|
|
398
|
+
return result;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (isLink) {
|
|
402
|
+
const linkData = params.data
|
|
403
|
+
const linkId = params.data.id
|
|
404
|
+
const year = globals.currentYear;
|
|
405
|
+
const flow = globals.yearToFlowIdToFlow.get(year).get(linkId)
|
|
406
|
+
|
|
407
|
+
// Flow indicators
|
|
408
|
+
// Show flow indicators that have value more than 0.0
|
|
409
|
+
const epsilon = 0.001
|
|
410
|
+
const indicatorNameToValue = new Map()
|
|
411
|
+
for(const [k, v] of Object.entries(flow.indicators)) {
|
|
412
|
+
console.log(k, v)
|
|
413
|
+
if(!isSameValue(v, 0.0, epsilon)) {
|
|
414
|
+
indicatorNameToValue.set(k, v)
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const baseline = globals.scenarioData.baseline_value_name
|
|
419
|
+
const headers = ["Source", "Target", baseline, ...indicatorNameToValue.keys().map(elem => elem)]
|
|
420
|
+
|
|
421
|
+
// Build headers
|
|
422
|
+
let headersHTML = ""
|
|
423
|
+
headersHTML += "<tr>"
|
|
424
|
+
for(const entry of headers) {
|
|
425
|
+
headersHTML += `<th>${entry}</th>`
|
|
426
|
+
}
|
|
427
|
+
headersHTML += "</tr>"
|
|
428
|
+
|
|
429
|
+
// Build data rows
|
|
430
|
+
let bodyHTML = ""
|
|
431
|
+
bodyHTML += `<td>${flow.source_process_id}</td>`
|
|
432
|
+
bodyHTML += `<td>${flow.target_process_id}</td>`
|
|
433
|
+
bodyHTML += `<td>${formatValue(flow.evaluated_value)}</td>`
|
|
434
|
+
for(const [k, v] of indicatorNameToValue.entries()) {
|
|
435
|
+
bodyHTML += `<td>${formatValue(v)}</td>`
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Determine the type of the link
|
|
439
|
+
let linkType = "Flow"
|
|
440
|
+
if (linkData.isVirtual) {
|
|
441
|
+
linkType = "Virtual flow"
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Flow tooltip result
|
|
445
|
+
result += `
|
|
446
|
+
<div class="tooltip-wrapper">
|
|
447
|
+
<div class="tooltip-title">${linkData.name}</div>
|
|
448
|
+
<span class="tooltip-type-title">Type: ${linkType}</span><br/>
|
|
449
|
+
<span class="tooltip-type-title">ID: ${linkId}</span><br/>
|
|
450
|
+
<br/>
|
|
451
|
+
<div class="tooltip-body-wrapper">
|
|
452
|
+
<div class="tooltip-col">
|
|
453
|
+
<table class="tooltip-table">
|
|
454
|
+
<span class="tooltip-table-title">Flow</span><br/>
|
|
455
|
+
<thead>
|
|
456
|
+
${headersHTML}
|
|
457
|
+
</thead>
|
|
458
|
+
<tbody>
|
|
459
|
+
${bodyHTML}
|
|
460
|
+
</tbody>
|
|
461
|
+
</table>
|
|
462
|
+
</div>
|
|
463
|
+
</div>
|
|
464
|
+
</div>
|
|
465
|
+
`;
|
|
466
|
+
return result
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// *******************
|
|
471
|
+
// * Event listeners *
|
|
472
|
+
// *******************
|
|
473
|
+
|
|
474
|
+
// Resize chart when window size has changed
|
|
475
|
+
addEventListener("resize", (e) => {
|
|
476
|
+
chart.resize();
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
addEventListener("keydown", (e) => {
|
|
480
|
+
// Change year using keyboard
|
|
481
|
+
// Arrow left = previous year
|
|
482
|
+
// Arrow right = next year
|
|
483
|
+
// Home = First year
|
|
484
|
+
// End = Last year
|
|
485
|
+
let nextYearIndex = -1
|
|
486
|
+
if(e.code == "ArrowLeft") {
|
|
487
|
+
const currentYearIndex = globals.currentYearIndex
|
|
488
|
+
nextYearIndex = currentYearIndex - 1
|
|
489
|
+
}
|
|
490
|
+
if(e.code == "ArrowRight") {
|
|
491
|
+
const currentYearIndex = globals.currentYearIndex
|
|
492
|
+
nextYearIndex = currentYearIndex + 1
|
|
493
|
+
}
|
|
494
|
+
if(e.code == "Home") {
|
|
495
|
+
nextYearIndex = 0
|
|
496
|
+
}
|
|
497
|
+
if(e.code == "End") {
|
|
498
|
+
nextYearIndex = globals.years.length - 1
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if(nextYearIndex < 0 || nextYearIndex >= globals.years.length) {
|
|
502
|
+
return
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
chart.dispatchAction({
|
|
506
|
+
type: "timelineChange",
|
|
507
|
+
currentIndex: nextYearIndex,
|
|
508
|
+
})
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
// Listen when user changes process label type
|
|
512
|
+
document
|
|
513
|
+
.getElementById("processLabelType")
|
|
514
|
+
.addEventListener("change", (event) => {
|
|
515
|
+
const value = event.target.options[event.target.selectedIndex].value;
|
|
516
|
+
switch (value) {
|
|
517
|
+
case "id":
|
|
518
|
+
globals.useProcessIdAsLabel = true;
|
|
519
|
+
break;
|
|
520
|
+
|
|
521
|
+
case "label":
|
|
522
|
+
globals.useProcessIdAsLabel = false;
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Toggle between node ID and node label for all years
|
|
527
|
+
for (const year of globals.years) {
|
|
528
|
+
const yearData = getYearData(year);
|
|
529
|
+
for (const [nodeIndex, nodeData] of yearData.data.entries()) {
|
|
530
|
+
if (globals.useProcessIdAsLabel) {
|
|
531
|
+
nodeData.name = nodeData.id
|
|
532
|
+
} else {
|
|
533
|
+
if (nodeData.label) {
|
|
534
|
+
nodeData.name = nodeData.label
|
|
535
|
+
} else {
|
|
536
|
+
nodeData.name = `Missing label (${nodeData.id})`;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
chart.setOption(globals.initialOption);
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
document
|
|
546
|
+
.getElementById("flowLabelType")
|
|
547
|
+
.addEventListener("change", (event) => {
|
|
548
|
+
const value = event.target.options[event.target.selectedIndex].value;
|
|
549
|
+
switch (value) {
|
|
550
|
+
case "type":
|
|
551
|
+
globals.useFlowTypeAsLabel = true
|
|
552
|
+
break;
|
|
553
|
+
|
|
554
|
+
case "value":
|
|
555
|
+
globals.useFlowTypeAsLabel = false
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
chart.setOption(globals.initialOption);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
document
|
|
563
|
+
.getElementById("useTransformationStageColors")
|
|
564
|
+
.addEventListener("change", (event) => {
|
|
565
|
+
const value = event.target.options[event.target.selectedIndex].value;
|
|
566
|
+
switch (value) {
|
|
567
|
+
case "yes":
|
|
568
|
+
globals.useTransformationStageColors = true
|
|
569
|
+
break;
|
|
570
|
+
|
|
571
|
+
case "no":
|
|
572
|
+
globals.useTransformationStageColors = false
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
update({resetView: false})
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
document
|
|
580
|
+
.getElementById("hideUnconnectedProcesses")
|
|
581
|
+
.addEventListener("change", (event) => {
|
|
582
|
+
const value = event.target.options[event.target.selectedIndex].value;
|
|
583
|
+
switch (value) {
|
|
584
|
+
case "yes":
|
|
585
|
+
globals.hideUnconnectedProcesses = true;
|
|
586
|
+
break;
|
|
587
|
+
case "no":
|
|
588
|
+
globals.hideUnconnectedProcesses = false;
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
globals.initialOption.options = [];
|
|
593
|
+
for (const year of globals.years) {
|
|
594
|
+
const graphData = buildGraphDataForYear(year, {});
|
|
595
|
+
const newSeries = {
|
|
596
|
+
series: [
|
|
597
|
+
{
|
|
598
|
+
data: graphData.data,
|
|
599
|
+
links: graphData.links,
|
|
600
|
+
categories: graphData.categories,
|
|
601
|
+
},
|
|
602
|
+
],
|
|
603
|
+
};
|
|
604
|
+
globals.initialOption.options.push(newSeries);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const currentYearData = buildGraphDataForYear(globals.currentYear);
|
|
608
|
+
globals.initialOption.baseOption.legend[0].data =
|
|
609
|
+
currentYearData.legendData;
|
|
610
|
+
globals.initialOption.baseOption.timeline.data = globals.years;
|
|
611
|
+
chart.setOption(globals.initialOption);
|
|
612
|
+
|
|
613
|
+
// // Same as update
|
|
614
|
+
// const graphData = buildGraphDataForYear(globals.currentYear)
|
|
615
|
+
// const option = buildOption(graphData, { resetView: false })
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
document.getElementById("resetView").addEventListener("click", (event) => {
|
|
619
|
+
// Set default unfreezed state for nodes
|
|
620
|
+
setFreezeNodePositionButtonState("Freeze", false);
|
|
621
|
+
update({resetView: true});
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
document
|
|
625
|
+
.getElementById("freezeNodePositions")
|
|
626
|
+
.addEventListener("click", (event) => {
|
|
627
|
+
const nextState = !globals.freezeNodePositions;
|
|
628
|
+
if (nextState) {
|
|
629
|
+
setFreezeNodePositionButtonState("Unfreeze", nextState);
|
|
630
|
+
freezeNodePositions();
|
|
631
|
+
} else {
|
|
632
|
+
setFreezeNodePositionButtonState("Freeze", nextState);
|
|
633
|
+
unfreezeNodePositions();
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
// **************************
|
|
638
|
+
// * ECharts event handlers *
|
|
639
|
+
// **************************
|
|
640
|
+
|
|
641
|
+
chart.on("timelinechanged", function (params) {
|
|
642
|
+
const targetYear = globals.years[params.currentIndex];
|
|
643
|
+
changeCurrentYear(targetYear);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
chart.on("mousedown", {dataType: "node"}, (params) => {
|
|
647
|
+
globals.selectedNodeIndex = params.dataIndex;
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
chart.on("mousemove", {dataType: "node"}, (params) => {
|
|
651
|
+
if (!globals.selectedNodeIndex) {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const yearData = getYearData(globals.currentYear);
|
|
656
|
+
const nodeId = yearData.data[globals.selectedNodeIndex].id;
|
|
657
|
+
const nodePosition = calculateNodePosition(nodeId);
|
|
658
|
+
setNodePosition(globals.currentYear, nodeId, nodePosition);
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
chart.on("mouseup", {dataType: "node"}, (params) => {
|
|
662
|
+
globals.selectedNodeIndex = null;
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
// *************
|
|
666
|
+
// * Functions *
|
|
667
|
+
// *************
|
|
668
|
+
|
|
669
|
+
function getYearIndex(year) {
|
|
670
|
+
return parseInt(year - globals.years[0]);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function getYearData(year) {
|
|
674
|
+
const yearIndex = getYearIndex(year);
|
|
675
|
+
return globals.initialOption.options[yearIndex].series[0];
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function setFreezeNodePositionButtonState(title, state) {
|
|
679
|
+
const label = document.getElementById("freezeNodePositionsButtonLabel");
|
|
680
|
+
globals.freezeNodePositions = state;
|
|
681
|
+
label.innerHTML = title;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function getGraphNodeFromNodeData(year, nodeIndex) {
|
|
685
|
+
// Unpack Process data as Node data
|
|
686
|
+
const yearData = globals.yearToData.get(year);
|
|
687
|
+
const nodeIndexToData = yearData.get("nodeIndexToData");
|
|
688
|
+
const nodeIdToPosition = yearData.get("nodeIdToPosition");
|
|
689
|
+
const nodeData = nodeIndexToData.get(nodeIndex);
|
|
690
|
+
const processId = nodeData.process_id;
|
|
691
|
+
const newGraphNode = {
|
|
692
|
+
id: processId,
|
|
693
|
+
name: processId,
|
|
694
|
+
label: nodeData.process_label,
|
|
695
|
+
category: processId,
|
|
696
|
+
numInflows: parseInt(nodeData.num_inflows),
|
|
697
|
+
numOutflows: parseInt(nodeData.num_outflows),
|
|
698
|
+
value: 0,
|
|
699
|
+
text: `Process ${processId}`,
|
|
700
|
+
|
|
701
|
+
transformationStage: nodeData.transformation_stage,
|
|
702
|
+
isStock: nodeData.is_stock,
|
|
703
|
+
stockLifetime: nodeData.stock_lifetime,
|
|
704
|
+
stockDistributionType: nodeData.stock_distribution_type,
|
|
705
|
+
stockDistributionParams: nodeData.stock_distribution_params,
|
|
706
|
+
|
|
707
|
+
// Colors, injected at initialize
|
|
708
|
+
colorNormal: nodeData.color_normal,
|
|
709
|
+
colorTransformationStage: nodeData.color_transformation_stage,
|
|
710
|
+
|
|
711
|
+
// Default color, created at startup
|
|
712
|
+
isVirtual: nodeData.is_virtual,
|
|
713
|
+
|
|
714
|
+
// ECharts related
|
|
715
|
+
itemStyle: {}
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
// Make virtual nodes dark grey
|
|
719
|
+
if (newGraphNode.isVirtual) {
|
|
720
|
+
newGraphNode.itemStyle = {
|
|
721
|
+
color: globals.virtualNodeColor,
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// Make border for nodes that has stock
|
|
726
|
+
if (newGraphNode.isStock) {
|
|
727
|
+
newGraphNode.itemStyle.borderColor = "#333"
|
|
728
|
+
newGraphNode.itemStyle.borderWidth = 3
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
if (nodeIdToPosition.has(processId)) {
|
|
732
|
+
const nodePosition = nodeIdToPosition.get(processId);
|
|
733
|
+
newGraphNode.x = nodePosition.x;
|
|
734
|
+
newGraphNode.y = nodePosition.y;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const hasColorNormal = newGraphNode.colorNormal !== undefined
|
|
738
|
+
const hasColorTransformationStage = newGraphNode.colorTransformationStage !== undefined
|
|
739
|
+
if(!newGraphNode.isVirtual) {
|
|
740
|
+
if(globals.useTransformationStageColors) {
|
|
741
|
+
if(hasColorTransformationStage) {
|
|
742
|
+
newGraphNode.itemStyle.color = newGraphNode.colorTransformationStage
|
|
743
|
+
}
|
|
744
|
+
} else {
|
|
745
|
+
if(hasColorNormal) {
|
|
746
|
+
newGraphNode.itemStyle.color = newGraphNode.colorNormal
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
return newGraphNode;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function getGraphEdgeFromEdgeData(edgeIndex, year) {
|
|
755
|
+
const yearData = globals.yearToData.get(year);
|
|
756
|
+
const edgeIndexToData = yearData.get("edgeIndexToData");
|
|
757
|
+
const edgeData = edgeIndexToData.get(edgeIndex);
|
|
758
|
+
|
|
759
|
+
const flowId = edgeData.flow_id;
|
|
760
|
+
const sourceProcessId = edgeData.source_process_id;
|
|
761
|
+
const targetProcessId = edgeData.target_process_id;
|
|
762
|
+
const isUnitAbsoluteValue = edgeData.is_unit_absolute_value;
|
|
763
|
+
const value = edgeData.value;
|
|
764
|
+
const unit = edgeData.unit;
|
|
765
|
+
|
|
766
|
+
const newGraphEdge = {
|
|
767
|
+
id: flowId,
|
|
768
|
+
name: flowId,
|
|
769
|
+
source: sourceProcessId,
|
|
770
|
+
target: targetProcessId,
|
|
771
|
+
label: {
|
|
772
|
+
id: edgeData.flow_id,
|
|
773
|
+
show: true,
|
|
774
|
+
position: "middle",
|
|
775
|
+
formatter: (params) => {
|
|
776
|
+
// Format text for links
|
|
777
|
+
if (globals.useFlowTypeAsLabel) {
|
|
778
|
+
return isUnitAbsoluteValue ? "ABS" : "%";
|
|
779
|
+
} else {
|
|
780
|
+
return formatValue(edgeData.evaluated_value)
|
|
781
|
+
}
|
|
782
|
+
},
|
|
783
|
+
},
|
|
784
|
+
lineStyle: {
|
|
785
|
+
color: isUnitAbsoluteValue
|
|
786
|
+
? globals.edgeColors.absolute
|
|
787
|
+
: globals.edgeColors.relative,
|
|
788
|
+
// width can be used to change line width
|
|
789
|
+
},
|
|
790
|
+
|
|
791
|
+
// Custom data
|
|
792
|
+
text: isUnitAbsoluteValue ? "Absolute flow" : "Relative flow",
|
|
793
|
+
value: `${value} ${unit}`,
|
|
794
|
+
|
|
795
|
+
isVirtual: edgeData.is_virtual,
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
if (newGraphEdge.isVirtual) {
|
|
799
|
+
newGraphEdge.lineStyle.color = globals.virtualFlowColor;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
return newGraphEdge;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function buildGraphDataForYear(year, updateOptions = {}) {
|
|
806
|
+
const graphData = {
|
|
807
|
+
data: [],
|
|
808
|
+
links: [],
|
|
809
|
+
categories: [],
|
|
810
|
+
legendData: [],
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
const yearData = globals.yearToData.get(year);
|
|
814
|
+
|
|
815
|
+
const nodeIndexToData = yearData.get("nodeIndexToData");
|
|
816
|
+
for (const [nodeIndex, nodeData] of nodeIndexToData.entries()) {
|
|
817
|
+
const graphNode = getGraphNodeFromNodeData(year, nodeIndex);
|
|
818
|
+
if (globals.hideUnconnectedProcesses) {
|
|
819
|
+
const hasNoInflows = graphNode.numInflows === 0;
|
|
820
|
+
const hasNoOutflows = graphNode.numOutflows === 0;
|
|
821
|
+
if (hasNoInflows && hasNoOutflows) {
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
if (globals.useProcessIdAsLabel) {
|
|
827
|
+
graphNode.name = graphNode.id;
|
|
828
|
+
} else {
|
|
829
|
+
if (graphNode.label) {
|
|
830
|
+
graphNode.name = graphNode.label;
|
|
831
|
+
} else {
|
|
832
|
+
graphNode.name = `Missing label (${graphNode.id})`;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
graphData.data.push(graphNode);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Sort graphs in alphabetical order so legend is also in alphabetical order
|
|
840
|
+
graphData.data.sort((a, b) => a.name > b.name ? 1 : -1);
|
|
841
|
+
|
|
842
|
+
const edgeIndexToData = yearData.get("edgeIndexToData");
|
|
843
|
+
for (const [edgeIndex, edgeData] of edgeIndexToData.entries()) {
|
|
844
|
+
const graphEdge = getGraphEdgeFromEdgeData(edgeIndex, year);
|
|
845
|
+
graphData.links.push(graphEdge);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// Color nodes by categories
|
|
849
|
+
for (const node of graphData.data) {
|
|
850
|
+
const newCategory = {
|
|
851
|
+
name: node.id,
|
|
852
|
+
itemStyle: {}
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
const hasColorNormal = node.colorNormal !== undefined
|
|
856
|
+
const hasColorTransformationStage = node.colorTransformationStage !== undefined
|
|
857
|
+
if(!node.isVirtual) {
|
|
858
|
+
if(globals.useTransformationStageColors) {
|
|
859
|
+
if(hasColorTransformationStage) {
|
|
860
|
+
newCategory.itemStyle.color = node.colorTransformationStage
|
|
861
|
+
}
|
|
862
|
+
} else {
|
|
863
|
+
if(hasColorNormal) {
|
|
864
|
+
newCategory.itemStyle.color = node.colorNormal
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
graphData.categories.push(newCategory);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// Create legend from all visible nodes
|
|
873
|
+
const legendData = []
|
|
874
|
+
for (const node of graphData.data) {
|
|
875
|
+
const newEntry = {
|
|
876
|
+
name: node.id,
|
|
877
|
+
itemStyle: node.itemStyle,
|
|
878
|
+
tooltip: {
|
|
879
|
+
show: true,
|
|
880
|
+
formatter: getTooltipFormatter,
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
legendData.push(newEntry)
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// Build legend data - ECharts uses automatically node ID with this
|
|
887
|
+
graphData.legendData = legendData;
|
|
888
|
+
return graphData;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
function buildOption(graphData, updateOptions = {resetView: false}) {
|
|
892
|
+
const center = ["50%", "50%"];
|
|
893
|
+
if (!updateOptions.resetView) {
|
|
894
|
+
// Use previous center
|
|
895
|
+
const prevOption = chart.getOption();
|
|
896
|
+
const prevCenter = prevOption.series[0].center;
|
|
897
|
+
center[0] = prevCenter[0];
|
|
898
|
+
center[1] = prevCenter[1];
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
let layout = globals.freezeNodePositions ? "none" : "force";
|
|
902
|
+
if (updateOptions.resetView) {
|
|
903
|
+
layout = "force";
|
|
904
|
+
}
|
|
905
|
+
const option = {
|
|
906
|
+
options: [{
|
|
907
|
+
series: [{
|
|
908
|
+
name: "Process flows test",
|
|
909
|
+
// type: "graph",
|
|
910
|
+
// layout: layout,
|
|
911
|
+
data: graphData.data,
|
|
912
|
+
links: graphData.links,
|
|
913
|
+
categories: graphData.categories,
|
|
914
|
+
// center: center,
|
|
915
|
+
// zoom: zoom: 2,
|
|
916
|
+
// draggable: true,
|
|
917
|
+
// symbolSize: 40,
|
|
918
|
+
// symbol: "circle", // 'rect'
|
|
919
|
+
// label: {
|
|
920
|
+
// show: true, // node name to be shown in circle
|
|
921
|
+
// },
|
|
922
|
+
// edgeSymbol: ["circle", "arrow"], // for arrow from one to another
|
|
923
|
+
// edgeSymbolSize: [0, 15],
|
|
924
|
+
// emphasis: {
|
|
925
|
+
// focus: "adjacency",
|
|
926
|
+
// label: {
|
|
927
|
+
// show: true,
|
|
928
|
+
// },
|
|
929
|
+
// // disabled: true,
|
|
930
|
+
// },
|
|
931
|
+
// roam: true,
|
|
932
|
+
// force: {
|
|
933
|
+
// repulsion: [500, 1000, 2000],
|
|
934
|
+
// edgeLength: 50,
|
|
935
|
+
// },
|
|
936
|
+
// },
|
|
937
|
+
}]
|
|
938
|
+
}]
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// // graphData is year-specific data
|
|
942
|
+
// const option = {
|
|
943
|
+
// baseOption: {
|
|
944
|
+
// title: {
|
|
945
|
+
// text: "Process connection graph",
|
|
946
|
+
// subtext: `Year ${globals.currentYear}`,
|
|
947
|
+
// },
|
|
948
|
+
// tooltip: {
|
|
949
|
+
// formatter: getTooltipFormatter,
|
|
950
|
+
// },
|
|
951
|
+
// legend: [
|
|
952
|
+
// {
|
|
953
|
+
// type: "scroll",
|
|
954
|
+
// data: graphData.legendData,
|
|
955
|
+
// position: "left",
|
|
956
|
+
// orient: "vertical",
|
|
957
|
+
// right: 10,
|
|
958
|
+
// top: 50,
|
|
959
|
+
// height: "88%",
|
|
960
|
+
// },
|
|
961
|
+
// ],
|
|
962
|
+
// timeline: {
|
|
963
|
+
// show: true,
|
|
964
|
+
// type: "slider",
|
|
965
|
+
// axisType: "category",
|
|
966
|
+
// data: globals.years,
|
|
967
|
+
// left: "20px",
|
|
968
|
+
// right: "20px",
|
|
969
|
+
// },
|
|
970
|
+
// series: [
|
|
971
|
+
// {
|
|
972
|
+
// name: "Process flows",
|
|
973
|
+
// type: "graph",
|
|
974
|
+
// layout: layout,
|
|
975
|
+
// data: graphData.data,
|
|
976
|
+
// links: graphData.links,
|
|
977
|
+
// categories: graphData.categories,
|
|
978
|
+
// center: center,
|
|
979
|
+
// zoom: 2,
|
|
980
|
+
// draggable: true,
|
|
981
|
+
// symbolSize: 40,
|
|
982
|
+
// symbol: "circle", // 'rect'
|
|
983
|
+
// label: {
|
|
984
|
+
// show: true, // node name to be shown in circle
|
|
985
|
+
// },
|
|
986
|
+
// edgeSymbol: ["circle", "arrow"], // for arrow from one to another
|
|
987
|
+
// edgeSymbolSize: [0, 15],
|
|
988
|
+
// emphasis: {
|
|
989
|
+
// focus: "adjacency",
|
|
990
|
+
// label: {
|
|
991
|
+
// show: true,
|
|
992
|
+
// },
|
|
993
|
+
// // disabled: true,
|
|
994
|
+
// },
|
|
995
|
+
// roam: true,
|
|
996
|
+
// force: {
|
|
997
|
+
// repulsion: [500, 1000, 2000],
|
|
998
|
+
// edgeLength: 50,
|
|
999
|
+
// },
|
|
1000
|
+
// },
|
|
1001
|
+
// ],
|
|
1002
|
+
// },
|
|
1003
|
+
// options: [],
|
|
1004
|
+
// };
|
|
1005
|
+
|
|
1006
|
+
return option;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// Initialize data
|
|
1010
|
+
function initialize() {
|
|
1011
|
+
globals.years = Object.keys(globals.originalYearToData);
|
|
1012
|
+
globals.currentYear = globals.years[0];
|
|
1013
|
+
globals.currentYearIndex = 0
|
|
1014
|
+
|
|
1015
|
+
const yearToProcessIdToProcess = new Map();
|
|
1016
|
+
const yearToProcessIdToFlowIds = new Map();
|
|
1017
|
+
const yearToFlowIdToFlow = new Map();
|
|
1018
|
+
|
|
1019
|
+
// Create data mapping for each year
|
|
1020
|
+
const yearToData = new Map();
|
|
1021
|
+
for (const year of globals.years) {
|
|
1022
|
+
const yearData = new Map();
|
|
1023
|
+
const nodeData = {
|
|
1024
|
+
...globals.originalYearToData[year]["node_index_to_data"],
|
|
1025
|
+
};
|
|
1026
|
+
const edgeData = {
|
|
1027
|
+
...globals.originalYearToData[year]["edge_index_to_data"],
|
|
1028
|
+
};
|
|
1029
|
+
|
|
1030
|
+
// Year -> Process Id and Flow ID -> Process/Flow
|
|
1031
|
+
yearToProcessIdToProcess.set(year, new Map());
|
|
1032
|
+
yearToFlowIdToFlow.set(year, new Map());
|
|
1033
|
+
yearToProcessIdToFlowIds.set(year, new Map());
|
|
1034
|
+
|
|
1035
|
+
// Map node index to node data
|
|
1036
|
+
const nodeIdToNodeIndex = new Map();
|
|
1037
|
+
const nodeIndexToData = new Map();
|
|
1038
|
+
for (const key of Object.keys(nodeData)) {
|
|
1039
|
+
const nodeIndex = parseInt(key);
|
|
1040
|
+
const node = nodeData[nodeIndex];
|
|
1041
|
+
const nodeId = node.process_id;
|
|
1042
|
+
nodeIndexToData.set(nodeIndex, node);
|
|
1043
|
+
nodeIdToNodeIndex.set(nodeId, nodeIndex);
|
|
1044
|
+
|
|
1045
|
+
// Year -> Process ID -> Process
|
|
1046
|
+
const processIdToProcess = yearToProcessIdToProcess.get(year);
|
|
1047
|
+
processIdToProcess.set(nodeId, node);
|
|
1048
|
+
|
|
1049
|
+
// Year -> Process ID -> Flow IDs
|
|
1050
|
+
const processIdToFlowIds = yearToProcessIdToFlowIds.get(year);
|
|
1051
|
+
processIdToFlowIds.set(nodeId, {in: [], out: []});
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// Map edge index to edge data
|
|
1055
|
+
const edgeIndexToData = new Map();
|
|
1056
|
+
const edgeIdToData = new Map();
|
|
1057
|
+
for (const key of Object.keys(edgeData)) {
|
|
1058
|
+
const edgeIndex = parseInt(key);
|
|
1059
|
+
const edge = edgeData[edgeIndex];
|
|
1060
|
+
const edgeId = edge.flow_id;
|
|
1061
|
+
edgeIndexToData.set(edgeIndex, edge);
|
|
1062
|
+
edgeIdToData.set(edgeId, edge);
|
|
1063
|
+
|
|
1064
|
+
// Year -> Flow ID -> Flow
|
|
1065
|
+
const flowIdToFlow = yearToFlowIdToFlow.get(year);
|
|
1066
|
+
flowIdToFlow.set(edgeId, edge);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// Year -> Process ID -> Flow IDs
|
|
1070
|
+
for (const [flowId, flow] of yearToFlowIdToFlow.get(year).entries()) {
|
|
1071
|
+
const sourceProcessId = flow.source_process_id;
|
|
1072
|
+
const sourceProcessEntry = yearToProcessIdToFlowIds
|
|
1073
|
+
.get(year)
|
|
1074
|
+
.get(sourceProcessId);
|
|
1075
|
+
sourceProcessEntry.out.push(flowId);
|
|
1076
|
+
|
|
1077
|
+
const targetProcessId = flow.target_process_id;
|
|
1078
|
+
const targetProcessEntry = yearToProcessIdToFlowIds
|
|
1079
|
+
.get(year)
|
|
1080
|
+
.get(targetProcessId);
|
|
1081
|
+
targetProcessEntry.in.push(flowId);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
yearData.set("nodeIndexToData", nodeIndexToData);
|
|
1085
|
+
yearData.set("nodeIdToIndex", nodeIdToNodeIndex);
|
|
1086
|
+
yearData.set("edgeIndexToData", edgeIndexToData);
|
|
1087
|
+
yearData.set("edgeIdToData", edgeIdToData);
|
|
1088
|
+
yearData.set("nodeIdToPosition", new Map([]));
|
|
1089
|
+
yearToData.set(year, yearData);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// Build transformation stage name to color mapping
|
|
1093
|
+
for(const [k, v] of Object.entries(globals.scenarioData.transformation_stage_name_to_color)) {
|
|
1094
|
+
globals.transformationStageNameToColor.set(k, v)
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// Update scenario name
|
|
1098
|
+
globals.scenarioName = globals.scenarioData.scenario_name
|
|
1099
|
+
|
|
1100
|
+
globals.yearToData = yearToData;
|
|
1101
|
+
globals.yearToProcessIdToProcess = yearToProcessIdToProcess;
|
|
1102
|
+
globals.yearToFlowIdToFlow = yearToFlowIdToFlow;
|
|
1103
|
+
globals.yearToProcessIdToFlowIds = yearToProcessIdToFlowIds;
|
|
1104
|
+
|
|
1105
|
+
// Create option for ECharts
|
|
1106
|
+
const center = ["50%", "50%"];
|
|
1107
|
+
let layout = globals.freezeNodePositions ? "none" : "force";
|
|
1108
|
+
const option = {
|
|
1109
|
+
baseOption: {
|
|
1110
|
+
title: {
|
|
1111
|
+
text: `${globals.scenarioName}`,
|
|
1112
|
+
subtext: `Year ${globals.currentYear}`,
|
|
1113
|
+
textStyle: {
|
|
1114
|
+
color: "#000",
|
|
1115
|
+
fontSize: 20,
|
|
1116
|
+
fontWeight: 'bold',
|
|
1117
|
+
},
|
|
1118
|
+
subtextStyle: {
|
|
1119
|
+
color: "#000",
|
|
1120
|
+
fontSize: 16,
|
|
1121
|
+
fontWeight: 'bold',
|
|
1122
|
+
}
|
|
1123
|
+
},
|
|
1124
|
+
tooltip: {
|
|
1125
|
+
formatter: getTooltipFormatter,
|
|
1126
|
+
},
|
|
1127
|
+
legend: [
|
|
1128
|
+
{
|
|
1129
|
+
type: "scroll",
|
|
1130
|
+
data: [],
|
|
1131
|
+
position: "left",
|
|
1132
|
+
orient: "vertical",
|
|
1133
|
+
right: 10,
|
|
1134
|
+
top: 50,
|
|
1135
|
+
height: "88%",
|
|
1136
|
+
},
|
|
1137
|
+
],
|
|
1138
|
+
timeline: {
|
|
1139
|
+
show: true,
|
|
1140
|
+
type: "slider",
|
|
1141
|
+
currentIndex: 0,
|
|
1142
|
+
axisType: "category",
|
|
1143
|
+
data: [],
|
|
1144
|
+
left: "20px",
|
|
1145
|
+
right: "20px",
|
|
1146
|
+
},
|
|
1147
|
+
series: [
|
|
1148
|
+
{
|
|
1149
|
+
name: "Process flows",
|
|
1150
|
+
type: "graph",
|
|
1151
|
+
layout: "force",
|
|
1152
|
+
data: [],
|
|
1153
|
+
links: [],
|
|
1154
|
+
categories: [],
|
|
1155
|
+
center: center,
|
|
1156
|
+
zoom: 2,
|
|
1157
|
+
draggable: true,
|
|
1158
|
+
symbolSize: 40,
|
|
1159
|
+
symbol: "circle", // 'rect'
|
|
1160
|
+
label: {
|
|
1161
|
+
show: true, // node name to be shown in circle
|
|
1162
|
+
},
|
|
1163
|
+
edgeSymbol: ["circle", "arrow"], // for arrow from one to another
|
|
1164
|
+
edgeSymbolSize: [0, 15],
|
|
1165
|
+
emphasis: {
|
|
1166
|
+
focus: "adjacency",
|
|
1167
|
+
label: {
|
|
1168
|
+
show: true,
|
|
1169
|
+
},
|
|
1170
|
+
// disabled: true,
|
|
1171
|
+
},
|
|
1172
|
+
roam: true,
|
|
1173
|
+
force: {
|
|
1174
|
+
repulsion: [500, 1000, 2000],
|
|
1175
|
+
edgeLength: 50,
|
|
1176
|
+
},
|
|
1177
|
+
},
|
|
1178
|
+
],
|
|
1179
|
+
},
|
|
1180
|
+
options: [],
|
|
1181
|
+
};
|
|
1182
|
+
|
|
1183
|
+
// Build years and insert to options
|
|
1184
|
+
for (const year of globals.years) {
|
|
1185
|
+
const graphData = buildGraphDataForYear(year, {});
|
|
1186
|
+
const newSeries = {
|
|
1187
|
+
series: [
|
|
1188
|
+
{
|
|
1189
|
+
data: graphData.data,
|
|
1190
|
+
links: graphData.links,
|
|
1191
|
+
categories: graphData.categories,
|
|
1192
|
+
},
|
|
1193
|
+
],
|
|
1194
|
+
};
|
|
1195
|
+
option.options.push(newSeries);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
// Get first year data and activate it
|
|
1199
|
+
const currentYearData = buildGraphDataForYear(globals.currentYear);
|
|
1200
|
+
option.baseOption.timeline.currentIndex = 0
|
|
1201
|
+
option.baseOption.legend[0].data = currentYearData.legendData;
|
|
1202
|
+
option.baseOption.timeline.data = globals.years;
|
|
1203
|
+
|
|
1204
|
+
// Store reference to original option
|
|
1205
|
+
// This is needed when changed years
|
|
1206
|
+
globals.initialOption = option;
|
|
1207
|
+
chart.setOption(globals.initialOption);
|
|
1208
|
+
|
|
1209
|
+
// Build node colors by reading back the default node colors after rendering
|
|
1210
|
+
// first time and retrieve node colors
|
|
1211
|
+
const nodeInfos = []
|
|
1212
|
+
const model = chart.getModel()
|
|
1213
|
+
const series = model.getSeriesByIndex(0)
|
|
1214
|
+
const data = series.getData()
|
|
1215
|
+
data.each((index) => {
|
|
1216
|
+
const name = data.getName(index)
|
|
1217
|
+
const color = data.getItemVisual(index, "style").fill
|
|
1218
|
+
nodeInfos.push({ id: name, color: color })
|
|
1219
|
+
})
|
|
1220
|
+
|
|
1221
|
+
// Set colors for every year
|
|
1222
|
+
for(const year of globals.years) {
|
|
1223
|
+
const yearData = globals.yearToData.get(year);
|
|
1224
|
+
const nodeIdToIndex = yearData.get("nodeIdToIndex")
|
|
1225
|
+
const nodeIndexToData = yearData.get("nodeIndexToData")
|
|
1226
|
+
for(const entry of nodeInfos) {
|
|
1227
|
+
const nodeId = entry.id
|
|
1228
|
+
const nodeColor = entry.color
|
|
1229
|
+
const nodeIndex = nodeIdToIndex.get(nodeId)
|
|
1230
|
+
const nodeData = nodeIndexToData.get(nodeIndex)
|
|
1231
|
+
|
|
1232
|
+
// TODO: In year 1963 there is "Dissolving_pulp:Export" but that is not defined
|
|
1233
|
+
// in the original data?
|
|
1234
|
+
// Inject properties "color_normal" and "color_transformation_stage" to original node data
|
|
1235
|
+
if(nodeData == undefined) {
|
|
1236
|
+
console.log(nodeId)
|
|
1237
|
+
console.log(nodeIdToIndex.keys())
|
|
1238
|
+
console.log(year)
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
const transformationStageName = nodeData.transformation_stage
|
|
1242
|
+
nodeData["color_normal"] = nodeColor
|
|
1243
|
+
nodeData["color_transformation_stage"] = globals.transformationStageNameToColor.get(transformationStageName)
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
if(globals.useTransformationStageColors) {
|
|
1248
|
+
update({ resetView: false })
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
function update(updateOptions = {resetView: false}) {
|
|
1253
|
+
// // TODO: This is called from reset resetView and formatter for
|
|
1254
|
+
// const graphData = buildGraphDataForYear(globals.currentYear);
|
|
1255
|
+
// const option = buildOption(graphData, updateOptions);
|
|
1256
|
+
// chart.setOption(option);
|
|
1257
|
+
// // chart.setOption(option, { notMerge: true });
|
|
1258
|
+
// // chart.setOption(option, { replaceMerge: ["options"] });
|
|
1259
|
+
// globals.graphData = graphData;
|
|
1260
|
+
|
|
1261
|
+
// Update current year data used in globals.initialOption
|
|
1262
|
+
const graphData = buildGraphDataForYear(globals.currentYear);
|
|
1263
|
+
const option = globals.initialOption.options[globals.currentYearIndex]
|
|
1264
|
+
option.series[0].data = graphData.data
|
|
1265
|
+
option.series[0].links = graphData.links
|
|
1266
|
+
option.series[0].categories = graphData.categories
|
|
1267
|
+
chart.setOption(globals.initialOption)
|
|
1268
|
+
globals.graphData = graphData;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
function freezeNodePositions() {
|
|
1272
|
+
// Update current year nodes' position
|
|
1273
|
+
const yearData = getYearData(globals.currentYear);
|
|
1274
|
+
for (const [nodeIndex, nodeData] of yearData.data.entries()) {
|
|
1275
|
+
const nodePosition = calculateNodePosition(nodeData.id);
|
|
1276
|
+
setNodePosition(globals.currentYear, nodeData.id, nodePosition);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
const model = chart.getModel()
|
|
1280
|
+
const series = model.getSeriesByIndex(0)
|
|
1281
|
+
const coordSys = series.coordinateSystem
|
|
1282
|
+
const zoom = coordSys.getZoom()
|
|
1283
|
+
const center = coordSys.getCenter()
|
|
1284
|
+
globals.initialOption.baseOption.series[0].layout = "none";
|
|
1285
|
+
globals.initialOption.baseOption.series[0].zoom = zoom
|
|
1286
|
+
globals.initialOption.baseOption.series[0].center = center
|
|
1287
|
+
chart.setOption(globals.initialOption);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
function unfreezeNodePositions() {
|
|
1291
|
+
// Update current year nodes' position
|
|
1292
|
+
const yearData = getYearData(globals.currentYear);
|
|
1293
|
+
for (const [nodeIndex, nodeData] of yearData.data.entries()) {
|
|
1294
|
+
const nodePosition = calculateNodePosition(nodeData.id);
|
|
1295
|
+
setNodePosition(globals.currentYear, nodeData.id, nodePosition);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
const model = chart.getModel()
|
|
1299
|
+
const series = model.getSeriesByIndex(0)
|
|
1300
|
+
const coordSys = series.coordinateSystem
|
|
1301
|
+
const zoom = coordSys.getZoom()
|
|
1302
|
+
const center = coordSys.getCenter()
|
|
1303
|
+
globals.initialOption.baseOption.series[0].layout = "force";
|
|
1304
|
+
globals.initialOption.baseOption.series[0].zoom = zoom
|
|
1305
|
+
globals.initialOption.baseOption.series[0].center = center
|
|
1306
|
+
chart.setOption(globals.initialOption);
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
function getNodePosition(year, nodeId) {
|
|
1310
|
+
const yearIndex = getYearIndex(year);
|
|
1311
|
+
const yearData = globals.initialOption.options[yearIndex].series[0];
|
|
1312
|
+
for (const [nodeIndex, nodeData] of yearData.data.entries()) {
|
|
1313
|
+
if (nodeData.id == nodeId) {
|
|
1314
|
+
return {x: nodeData.x, y: nodeData.y};
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
console.log(`No node ${nodeId} at year ${year}`);
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
function setNodePosition(year, nodeId, nodePosition) {
|
|
1322
|
+
const yearIndex = getYearIndex(year);
|
|
1323
|
+
const yearData = globals.initialOption.options[yearIndex].series[0];
|
|
1324
|
+
for (const [nodeIndex, nodeData] of yearData.data.entries()) {
|
|
1325
|
+
if (nodeData.id == nodeId) {
|
|
1326
|
+
nodeData.x = nodePosition.x;
|
|
1327
|
+
nodeData.y = nodePosition.y;
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
// NOTE: Virtual flows might not be found for every year so no error here
|
|
1333
|
+
// console.log(`No node ${nodeId} at year ${year}`);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
function calculateNodePosition(nodeId) {
|
|
1337
|
+
const nodeIdToPosition = new Map();
|
|
1338
|
+
|
|
1339
|
+
const nodes = chart.getModel().getSeriesByIndex(0).getData();
|
|
1340
|
+
nodes.each(function (nodeIndex) {
|
|
1341
|
+
const nodeData = nodes.getRawDataItem(nodeIndex);
|
|
1342
|
+
const nodeLayout = nodes.getItemLayout(nodeIndex);
|
|
1343
|
+
const nodePosition = {x: nodeLayout[0], y: nodeLayout[1]};
|
|
1344
|
+
nodeIdToPosition.set(nodeData.id, nodePosition);
|
|
1345
|
+
});
|
|
1346
|
+
|
|
1347
|
+
if (!nodeIdToPosition.has(nodeId)) {
|
|
1348
|
+
// NOTE: Virtual flows might not be found for every year so
|
|
1349
|
+
// console.error(`No node ${nodeId} found in year ${globals.currentYear}!`);
|
|
1350
|
+
return {x: 0.0, y: 0.0};
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
const nodePosition = nodeIdToPosition.get(nodeId);
|
|
1354
|
+
return {x: nodePosition.x, y: nodePosition.y};
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
function changeCurrentYear(targetYear) {
|
|
1358
|
+
// Save current year nodes' position
|
|
1359
|
+
const currentYearData = getYearData(globals.currentYear);
|
|
1360
|
+
for (const [nodeIndex, nodeData] of currentYearData.data.entries()) {
|
|
1361
|
+
const nodePosition = calculateNodePosition(nodeData.id);
|
|
1362
|
+
setNodePosition(globals.currentYear, nodeData.id, nodePosition);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
const targetYearData = getYearData(targetYear);
|
|
1366
|
+
for (const [nodeIndex, nodeData] of currentYearData.data.entries()) {
|
|
1367
|
+
const nodePosition = getNodePosition(targetYear, nodeData.id);
|
|
1368
|
+
setNodePosition(targetYear, nodeData.id, nodePosition);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// Update year and chart
|
|
1372
|
+
const yearIndex = getYearIndex(targetYear);
|
|
1373
|
+
globals.currentYear = targetYear;
|
|
1374
|
+
globals.currentYearIndex = yearIndex
|
|
1375
|
+
globals.initialOption.baseOption.timeline.currentIndex = yearIndex;
|
|
1376
|
+
globals.initialOption.baseOption.title.subtext = `Year ${globals.currentYear}`;
|
|
1377
|
+
update({ resetView: false})
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
console.log("Initializing...");
|
|
1381
|
+
initialize();
|
|
1382
|
+
console.log("Done.");
|
|
1383
|
+
|
|
1384
|
+
// chart.dispatchAction({
|
|
1385
|
+
// type: 'highlight',
|
|
1386
|
+
// batch: [
|
|
1387
|
+
// { dataType: 'node', dataIndex: nodeDataIndex},
|
|
1388
|
+
// { dataType: 'edge', dataIndex: edgeDataIndex, notBlur: true},
|
|
1389
|
+
// ],
|
|
1390
|
+
// })
|
|
1391
|
+
// })
|