@hestia-earth/ui-components-charts 0.43.23 → 0.43.25
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/dist/hierarchy-chart.css +45 -0
- package/dist/index.d.mts +668 -67
- package/dist/index.d.ts +668 -67
- package/dist/index.js +1511 -62
- package/dist/index.mjs +1462 -64
- package/package.json +12 -3
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
import { ellipsis, keyToLabel,
|
|
1
|
+
import { ellipsis, toComma, keyToLabel, isUndefined, isNumber, toPrecision, unique, sum, isEmpty, getPercentileValue } from '@hestia-earth/utils';
|
|
2
2
|
import Gradient from 'javascript-color-gradient';
|
|
3
3
|
import { ShadeGenerator } from 'shade-generator/dist/shadeGenerator';
|
|
4
|
+
import { select, selectAll } from 'd3-selection';
|
|
5
|
+
import merge from 'lodash.merge';
|
|
4
6
|
import emissionGroupings from '@hestia-earth/glossary/resources/emission-groupings.json';
|
|
7
|
+
import { Chart, BarController, LineController, CategoryScale, LinearScale, PointElement, BarElement, LineElement, Title, Tooltip, Legend, TimeScale } from 'chart.js';
|
|
8
|
+
import C2S from 'canvas-to-svg';
|
|
9
|
+
import { TermTermType, SchemaType } from '@hestia-earth/schema';
|
|
10
|
+
import defaultModelIds from '@hestia-earth/glossary/resources/defaultModelId.json';
|
|
11
|
+
import { linkHorizontal } from 'd3-shape';
|
|
12
|
+
import 'd3-transition';
|
|
13
|
+
import { hierarchy, tree } from 'd3-hierarchy';
|
|
14
|
+
import { easeElasticIn, easeElasticOut } from 'd3-ease';
|
|
15
|
+
import 'chartjs-adapter-date-fns';
|
|
16
|
+
import annotationPlugin from 'chartjs-plugin-annotation';
|
|
5
17
|
|
|
6
18
|
// src/charts/bar.ts
|
|
7
19
|
|
|
@@ -11,6 +23,17 @@ var defaultTicksFont = {
|
|
|
11
23
|
size: 13
|
|
12
24
|
};
|
|
13
25
|
var chartLabelMaxLength = 50;
|
|
26
|
+
var defaultChartSettings = Object.freeze({
|
|
27
|
+
options: {
|
|
28
|
+
responsive: true,
|
|
29
|
+
maintainAspectRatio: false,
|
|
30
|
+
plugins: {
|
|
31
|
+
legend: {
|
|
32
|
+
display: false
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
14
37
|
|
|
15
38
|
// src/charts/bar.ts
|
|
16
39
|
var grey = "#4a4a4a";
|
|
@@ -144,20 +167,20 @@ var palette = Object.freeze({
|
|
|
144
167
|
]
|
|
145
168
|
});
|
|
146
169
|
var getGradients = (...colours) => new Gradient().setColorGradient(...colours).setMidpoint(10).getColors();
|
|
147
|
-
var getColorGradient = (index,
|
|
148
|
-
const start =
|
|
149
|
-
const end =
|
|
150
|
-
const gradientIndex = Math.ceil(index /
|
|
170
|
+
var getColorGradient = (index, colors2) => {
|
|
171
|
+
const start = colors2[index % colors2.length];
|
|
172
|
+
const end = colors2[index % colors2.length + 1];
|
|
173
|
+
const gradientIndex = Math.ceil(index / colors2.length);
|
|
151
174
|
return getGradients(start, end)[gradientIndex];
|
|
152
175
|
};
|
|
153
|
-
var getColorShade = (index,
|
|
154
|
-
const color =
|
|
155
|
-
const shade = Math.min(100, Math.max(10, Math.abs(100 - Math.ceil(index /
|
|
176
|
+
var getColorShade = (index, colors2) => {
|
|
177
|
+
const color = colors2[index % colors2.length];
|
|
178
|
+
const shade = Math.min(100, Math.max(10, Math.abs(100 - Math.ceil(index / colors2.length) * 10)));
|
|
156
179
|
return ShadeGenerator.hue(color).shade(`${shade}`).hex();
|
|
157
180
|
};
|
|
158
181
|
var getColor = (index, scheme = 0 /* categorial */) => {
|
|
159
|
-
const
|
|
160
|
-
return index <
|
|
182
|
+
const colors2 = palette[scheme];
|
|
183
|
+
return index < colors2.length ? colors2[index] : scheme === 0 /* categorial */ ? getColorShade(index, colors2) : getColorGradient(index, colors2);
|
|
161
184
|
};
|
|
162
185
|
var listColor = (_v, index) => getColor(index);
|
|
163
186
|
var listColorContinuous = (_v, index) => getColor(index, 1 /* continuous */);
|
|
@@ -218,48 +241,12 @@ var groupDataByCategory = (data2, labels, category, { emissionCategoryValue: emi
|
|
|
218
241
|
// src/charts/csv.ts
|
|
219
242
|
var escape = (value) => `${value}`.includes(",") ? `"${value}"` : value;
|
|
220
243
|
var toLine = (values) => values.map(escape).join(",");
|
|
221
|
-
var data = emissionGroupings;
|
|
222
|
-
var categoryLabel = (key) => key ? keyToLabel(key.replace(/category/gi, "")) : "Unset";
|
|
223
|
-
var emissionCategories = Object.keys(data).filter((key) => key.toLowerCase().endsWith("category")).map((key) => ({ key, label: categoryLabel(key) }));
|
|
224
|
-
var categoryValues = (category) => data[category];
|
|
225
|
-
var emissionCategoriesOf = (emissionId) => Object.keys(data[emissionId]).filter((key) => !!data[emissionId][key]);
|
|
226
|
-
var emissionCategoryValue = (emissionId, category) => {
|
|
227
|
-
var _a;
|
|
228
|
-
return (_a = data[emissionId]) == null ? void 0 : _a[category];
|
|
229
|
-
};
|
|
230
|
-
var defaultHorizontalBarConfig = Object.freeze({
|
|
231
|
-
options: {
|
|
232
|
-
indexAxis: "y",
|
|
233
|
-
scales: {
|
|
234
|
-
x: {
|
|
235
|
-
grid: {
|
|
236
|
-
drawOnChartArea: false
|
|
237
|
-
},
|
|
238
|
-
ticks: {
|
|
239
|
-
callback: (value) => toPrecision(Number(value), 3)
|
|
240
|
-
}
|
|
241
|
-
},
|
|
242
|
-
y: {
|
|
243
|
-
grid: {
|
|
244
|
-
drawOnChartArea: false
|
|
245
|
-
},
|
|
246
|
-
ticks: {
|
|
247
|
-
crossAlign: "near"
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
var defaultHorizontalTooltip = ({ label, count, includedItems }) => (includedItems == null ? void 0 : includedItems.length) ? includedItems.map(defaultHorizontalTooltip).join("</br>") : `${label}: ${count}`;
|
|
254
|
-
var rowHeight = 40;
|
|
255
|
-
var padding = 60;
|
|
256
|
-
var horizontalBarHeight = (minHeight, maxHeight, nbRows) => Math.min(Math.max(nbRows * rowHeight + padding, minHeight), maxHeight);
|
|
257
244
|
var placementGap = (placement) => placement === "right" ? 8 : -8;
|
|
258
245
|
var placementX = (x, chart, data2, placement) => placement === "right" ? !Array.isArray(data2) && data2 < 0 ? positionAtZero(chart) : x : x;
|
|
259
246
|
var positionAtZero = (chart) => chart.scales.x.getPixelForValue(0);
|
|
260
247
|
var defaultBarDrawSettings = {
|
|
261
248
|
placement: "right",
|
|
262
|
-
xPosFn: (x, index,
|
|
249
|
+
xPosFn: (x, index, width2, chart, data2, placement) => (isEmpty(data2) ? positionAtZero(chart) : placementX(x, chart, data2, placement)) + placementGap(placement),
|
|
263
250
|
yPosFn: (y) => y + 3,
|
|
264
251
|
colorFn: (m, index, chart, data2) => isUndefined(data2) ? "#b5b5b5" : "#4a4a4a",
|
|
265
252
|
textFn: ({ data: data2 }) => `${data2}`,
|
|
@@ -298,7 +285,7 @@ var afterBarDrawPlugin = (settings) => ({
|
|
|
298
285
|
...defaultBarDrawSettings,
|
|
299
286
|
...settings != null ? settings : {}
|
|
300
287
|
};
|
|
301
|
-
const { ctx, width, height } = chart;
|
|
288
|
+
const { ctx, width: width2, height } = chart;
|
|
302
289
|
ctx.save();
|
|
303
290
|
const getStackItems = (index) => chart.data.datasets.map((dataset, dsIndex) => ({ dataset, meta: chart.getDatasetMeta(dsIndex) })).filter(({ meta }) => !meta.hidden).map(({ dataset, meta }) => ({
|
|
304
291
|
element: meta.data[index],
|
|
@@ -324,7 +311,7 @@ var afterBarDrawPlugin = (settings) => ({
|
|
|
324
311
|
var _a2, _b2;
|
|
325
312
|
return (_b2 = (_a2 = chart.data.labels) == null ? void 0 : _a2[index]) != null ? _b2 : "";
|
|
326
313
|
};
|
|
327
|
-
const getXPos = (anchorX, index, dataToPass) => xPosFn(anchorX, index,
|
|
314
|
+
const getXPos = (anchorX, index, dataToPass) => xPosFn(anchorX, index, width2, chart, dataToPass, placement);
|
|
328
315
|
const getYPos = (y, index, dataToPass) => yPosFn(y, index, height, chart, dataToPass, placement);
|
|
329
316
|
const getAnchorX = (minX, maxX) => placement === "left" ? minX : maxX;
|
|
330
317
|
const processIndex = (index) => {
|
|
@@ -407,16 +394,16 @@ var createHoverGradient = (ctx, chartArea, color) => {
|
|
|
407
394
|
return gradient;
|
|
408
395
|
};
|
|
409
396
|
var calculateBarBounds = (element, indexAxis = "y") => {
|
|
410
|
-
const { x, y, base, width, height } = element;
|
|
397
|
+
const { x, y, base, width: width2, height } = element;
|
|
411
398
|
if (indexAxis === "y") {
|
|
412
|
-
const
|
|
413
|
-
const barTop = y -
|
|
414
|
-
const barBottom = y +
|
|
399
|
+
const barHeight2 = height;
|
|
400
|
+
const barTop = y - barHeight2 / 2;
|
|
401
|
+
const barBottom = y + barHeight2 / 2;
|
|
415
402
|
const barLeft = Math.min(base, x);
|
|
416
403
|
const barRight = Math.max(base, x);
|
|
417
|
-
return { barLeft, barRight, barTop, barBottom, thickness:
|
|
404
|
+
return { barLeft, barRight, barTop, barBottom, thickness: barHeight2 };
|
|
418
405
|
} else {
|
|
419
|
-
const barWidth =
|
|
406
|
+
const barWidth = width2;
|
|
420
407
|
const barLeft = x - barWidth / 2;
|
|
421
408
|
const barRight = x + barWidth / 2;
|
|
422
409
|
const barTop = Math.min(base, y);
|
|
@@ -663,13 +650,1389 @@ var lollipopChartPlugin = (settings) => ({
|
|
|
663
650
|
var increaseScaleLimits = (scale, handleMinScaleZero = true) => {
|
|
664
651
|
const range = scale.max - scale.min;
|
|
665
652
|
const safeRange = range === 0 ? Math.abs(scale.max) || 1 : range;
|
|
666
|
-
const
|
|
667
|
-
const padding2 = safeRange *
|
|
653
|
+
const margin2 = 0.1;
|
|
654
|
+
const padding2 = safeRange * margin2;
|
|
668
655
|
scale.min -= handleMinScaleZero && scale.min === 0 ? 0 : padding2;
|
|
669
656
|
scale.max += padding2;
|
|
670
657
|
};
|
|
658
|
+
var parsePrecision = (value, precision = 3) => toPrecision(parseFloat(`${value}`), parseInt(`${precision}`, 10));
|
|
659
|
+
var formatPrecision = (value, precision = 3, enableComma = true) => typeof value !== "boolean" && !isUndefined(value) && isNumber(value) ? `${(enableComma ? toComma : (v) => v)(parsePrecision(value, precision))}`.replace(/\.00$/, "") : value;
|
|
660
|
+
|
|
661
|
+
// src/charts/contribution-chart.ts
|
|
662
|
+
var grey2 = "#4a4a4a";
|
|
663
|
+
var contributionBarHeight = 8;
|
|
664
|
+
var contributionHoverHeight = 10;
|
|
665
|
+
var defaultContributionSettings = Object.freeze({
|
|
666
|
+
type: "bar",
|
|
667
|
+
options: {
|
|
668
|
+
indexAxis: "y",
|
|
669
|
+
responsive: true,
|
|
670
|
+
plugins: {
|
|
671
|
+
tooltip: {
|
|
672
|
+
enabled: false
|
|
673
|
+
},
|
|
674
|
+
legend: {
|
|
675
|
+
display: false
|
|
676
|
+
}
|
|
677
|
+
},
|
|
678
|
+
scales: {
|
|
679
|
+
x: {
|
|
680
|
+
stacked: true,
|
|
681
|
+
display: true,
|
|
682
|
+
grid: {
|
|
683
|
+
display: true,
|
|
684
|
+
drawOnChartArea: true
|
|
685
|
+
},
|
|
686
|
+
ticks: {
|
|
687
|
+
font: defaultTicksFont,
|
|
688
|
+
callback: (label) => `${formatPrecision(+label)}`
|
|
689
|
+
}
|
|
690
|
+
},
|
|
691
|
+
y: {
|
|
692
|
+
stacked: true,
|
|
693
|
+
display: true,
|
|
694
|
+
position: "left",
|
|
695
|
+
grid: {
|
|
696
|
+
display: true,
|
|
697
|
+
drawOnChartArea: false,
|
|
698
|
+
drawTicks: true,
|
|
699
|
+
tickLength: 8,
|
|
700
|
+
offset: false
|
|
701
|
+
},
|
|
702
|
+
ticks: {
|
|
703
|
+
color: grey2,
|
|
704
|
+
padding: 4,
|
|
705
|
+
crossAlign: "center",
|
|
706
|
+
font: {
|
|
707
|
+
...defaultTicksFont,
|
|
708
|
+
weight: 400
|
|
709
|
+
},
|
|
710
|
+
callback: function(value) {
|
|
711
|
+
const label = this.getLabelForValue(value);
|
|
712
|
+
return ellipsis(label, chartLabelMaxLength);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
var contributionChartHeight = (minHeight, maxHeight, nbRows) => {
|
|
720
|
+
const rowHeight2 = contributionBarHeight + contributionHoverHeight * 2;
|
|
721
|
+
const padding2 = 60;
|
|
722
|
+
return Math.min(Math.max(nbRows * rowHeight2 + padding2, minHeight), maxHeight);
|
|
723
|
+
};
|
|
724
|
+
var datasetCenter = (chart, index) => {
|
|
725
|
+
const elements = chart.data.datasets.map((_, i) => {
|
|
726
|
+
const meta = chart.getDatasetMeta(i);
|
|
727
|
+
return meta.hidden ? null : meta.data[index];
|
|
728
|
+
}).filter((e) => !!e);
|
|
729
|
+
const right = Math.max(...elements.map((e) => e.x), ...elements.map((e) => e.base));
|
|
730
|
+
const left = Math.min(...elements.map((e) => e.x), ...elements.map((e) => e.base));
|
|
731
|
+
return (right + left) / 2;
|
|
732
|
+
};
|
|
733
|
+
var contributionItemColor = (item) => item.backgroundColor || item.borderColor || item.color;
|
|
734
|
+
var dataAtIndex = (data2, index, includeItems = true) => data2.map((item) => {
|
|
735
|
+
var _a;
|
|
736
|
+
return {
|
|
737
|
+
...item,
|
|
738
|
+
value: item.values[index],
|
|
739
|
+
includedItems: includeItems ? (_a = item.includedItems) == null ? void 0 : _a.map((included) => ({
|
|
740
|
+
...included,
|
|
741
|
+
value: included.values[index]
|
|
742
|
+
})) : []
|
|
743
|
+
};
|
|
744
|
+
}).filter((v) => !isUndefined(v.value)).sort((a, b) => Math.abs(b.value) - Math.abs(a.value));
|
|
745
|
+
var contributionCsv = (labels, data2) => [
|
|
746
|
+
toLine(["@id", ...data2.map((v) => v.label)]),
|
|
747
|
+
labels.map((label, index) => toLine([label, ...dataAtIndex(data2, index, false).map((v) => v.value)])).join("\n")
|
|
748
|
+
].join("\n");
|
|
749
|
+
var hasNegativeValues = (data2 = []) => data2.some((value) => value.values.some((v) => v < 0));
|
|
750
|
+
var contributionData = (data2, labels) => ({
|
|
751
|
+
datasets: data2.map(
|
|
752
|
+
(item) => ({
|
|
753
|
+
label: item.label,
|
|
754
|
+
data: item.values,
|
|
755
|
+
backgroundColor: item.backgroundColor || item.color,
|
|
756
|
+
borderColor: item.borderColor || item.color,
|
|
757
|
+
barThickness: contributionBarHeight,
|
|
758
|
+
maxBarThickness: contributionBarHeight,
|
|
759
|
+
includedItems: item.includedItems
|
|
760
|
+
})
|
|
761
|
+
),
|
|
762
|
+
labels
|
|
763
|
+
});
|
|
764
|
+
var contributionChartConfig = ({
|
|
765
|
+
onClick,
|
|
766
|
+
axis,
|
|
767
|
+
label,
|
|
768
|
+
hasNegativeValues: negative,
|
|
769
|
+
mobile
|
|
770
|
+
}) => ({
|
|
771
|
+
options: {
|
|
772
|
+
onClick,
|
|
773
|
+
plugins: { axisHover: axis },
|
|
774
|
+
scales: {
|
|
775
|
+
x: {
|
|
776
|
+
display: !mobile,
|
|
777
|
+
afterDataLimits: (scale) => increaseScaleLimits(scale),
|
|
778
|
+
title: {
|
|
779
|
+
display: !!label,
|
|
780
|
+
text: label
|
|
781
|
+
},
|
|
782
|
+
grid: {
|
|
783
|
+
lineWidth: (context) => context.tick.value === 0 && negative ? 1 : 0
|
|
784
|
+
}
|
|
785
|
+
},
|
|
786
|
+
y: {
|
|
787
|
+
display: !mobile
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
},
|
|
791
|
+
plugins: [
|
|
792
|
+
backgroundHoverPlugin({ threshold: contributionHoverHeight }),
|
|
793
|
+
afterBarDrawPlugin({
|
|
794
|
+
placement: "right",
|
|
795
|
+
textFn: ({ data: data2 }) => isUndefined(data2) ? "No data" : ""
|
|
796
|
+
}),
|
|
797
|
+
...axis ? [axisHoverPlugin] : []
|
|
798
|
+
]
|
|
799
|
+
});
|
|
800
|
+
var d3ellipse = (selection, maxWidth, ellipsisStr = "...") => {
|
|
801
|
+
selection.each(function() {
|
|
802
|
+
const t = select(this);
|
|
803
|
+
if (t.node().getComputedTextLength() > maxWidth) {
|
|
804
|
+
const chars = t.text().split("");
|
|
805
|
+
const line2 = [];
|
|
806
|
+
do {
|
|
807
|
+
line2.push(chars.shift());
|
|
808
|
+
t.text(line2.join("") + ellipsisStr);
|
|
809
|
+
} while (t.node().getComputedTextLength() <= maxWidth);
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
};
|
|
813
|
+
var shouldBreakLine = (overflows, isMultiWordLine, isOwnLineWord, isUnderLineLimit) => (overflows && isMultiWordLine || isOwnLineWord) && isUnderLineLimit;
|
|
814
|
+
var d3wrap = (t, {
|
|
815
|
+
maxWidth,
|
|
816
|
+
maxLines,
|
|
817
|
+
words,
|
|
818
|
+
nHeight,
|
|
819
|
+
lHeight,
|
|
820
|
+
vcentre = false,
|
|
821
|
+
ownLineWord = ""
|
|
822
|
+
}) => {
|
|
823
|
+
words = words.reverse();
|
|
824
|
+
const y = t.attr("y");
|
|
825
|
+
const x = t.attr("x");
|
|
826
|
+
let lineNumber = 1;
|
|
827
|
+
let word;
|
|
828
|
+
let line2 = [];
|
|
829
|
+
let tspan = t.text(null).append("tspan").attr("x", x).attr("y", y);
|
|
830
|
+
while ((word = words.pop()) && lineNumber <= maxLines) {
|
|
831
|
+
line2.push(word);
|
|
832
|
+
tspan.text(line2.join(" "));
|
|
833
|
+
if (shouldBreakLine(
|
|
834
|
+
tspan.node().getComputedTextLength() > maxWidth,
|
|
835
|
+
line2.length > 1,
|
|
836
|
+
word === ownLineWord,
|
|
837
|
+
lineNumber < maxLines
|
|
838
|
+
)) {
|
|
839
|
+
line2.pop();
|
|
840
|
+
tspan.text(line2.join(" "));
|
|
841
|
+
line2 = [word];
|
|
842
|
+
tspan = t.append("tspan").attr("x", x).attr("y", y).attr("dy", lineNumber++ * lHeight + "px").text(word);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
d3ellipse(tspan, maxWidth, "... " + ownLineWord);
|
|
846
|
+
if (vcentre) {
|
|
847
|
+
const yOffset = nHeight / 2 - (t.selectAll("tspan").size() - 1) * (lHeight / 2);
|
|
848
|
+
t.selectAll("tspan").attr("y", yOffset);
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
var colors = {
|
|
852
|
+
darkBlue: "rgba(25, 57, 87, 1)",
|
|
853
|
+
lightBlue: "rgba(132, 160, 220, 1)",
|
|
854
|
+
// `$warning` — marks the reported value, and is the one colour that reads against both a white
|
|
855
|
+
// page and the dark blue popover the parametric chart is shown in
|
|
856
|
+
orange: "rgba(255, 136, 27, 1)"
|
|
857
|
+
};
|
|
858
|
+
var opaqueColor = (color, opacity = 0.5) => color.replace(", 1)", `, ${opacity})`);
|
|
859
|
+
var defaultDistributionSettings = Object.freeze({
|
|
860
|
+
type: "line",
|
|
861
|
+
options: {
|
|
862
|
+
events: [],
|
|
863
|
+
responsive: true,
|
|
864
|
+
plugins: {
|
|
865
|
+
tooltip: {
|
|
866
|
+
enabled: false
|
|
867
|
+
},
|
|
868
|
+
legend: {
|
|
869
|
+
display: false,
|
|
870
|
+
align: "center"
|
|
871
|
+
}
|
|
872
|
+
},
|
|
873
|
+
scales: {
|
|
874
|
+
y: {
|
|
875
|
+
display: true,
|
|
876
|
+
position: "left",
|
|
877
|
+
grid: {
|
|
878
|
+
display: true
|
|
879
|
+
},
|
|
880
|
+
beginAtZero: true,
|
|
881
|
+
ticks: {
|
|
882
|
+
crossAlign: "center"
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
var joinXLabel = " to ";
|
|
889
|
+
var createDynamicHistogramData = (values, numberOfBins) => {
|
|
890
|
+
if (values.length === 0) {
|
|
891
|
+
return { labels: [], frequencies: [] };
|
|
892
|
+
}
|
|
893
|
+
const min = Math.min(...values);
|
|
894
|
+
const max = Math.max(...values);
|
|
895
|
+
const binSize = (max - min) / numberOfBins;
|
|
896
|
+
const frequencies = new Array(numberOfBins).fill(0);
|
|
897
|
+
const labels = [];
|
|
898
|
+
for (let i = 0; i < numberOfBins; i++) {
|
|
899
|
+
const binStart = min + i * binSize;
|
|
900
|
+
const binEnd = binStart + binSize;
|
|
901
|
+
labels.push([binStart, binEnd].join(joinXLabel));
|
|
902
|
+
values.forEach((point) => {
|
|
903
|
+
if (point >= binStart && point < binEnd) {
|
|
904
|
+
frequencies[i]++;
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
frequencies[numberOfBins - 1] += values.filter((p) => p === max).length;
|
|
909
|
+
return { labels, frequencies };
|
|
910
|
+
};
|
|
911
|
+
var curveSpread = 4;
|
|
912
|
+
var curvePoints = 120;
|
|
913
|
+
var curvePadding = 0.05;
|
|
914
|
+
var normalPdf = (x, mu, sd) => Math.exp(-0.5 * ((x - mu) / sd) ** 2) / (sd * Math.sqrt(2 * Math.PI));
|
|
915
|
+
var curveRange = (mu, sd, value, minX) => {
|
|
916
|
+
const values = [mu - curveSpread * sd, mu + curveSpread * sd, ...Number.isFinite(value) ? [value] : []];
|
|
917
|
+
const from = Math.min(...values);
|
|
918
|
+
const to = Math.max(...values);
|
|
919
|
+
const padding2 = (to - from) * curvePadding;
|
|
920
|
+
return { from: Number.isFinite(minX) ? Math.max(from - padding2, minX) : from - padding2, to: to + padding2 };
|
|
921
|
+
};
|
|
922
|
+
var createNormalCurve = (mu, sd, value, minX) => {
|
|
923
|
+
const { from, to } = curveRange(mu, sd, value, minX);
|
|
924
|
+
return Array.from({ length: curvePoints + 1 }, (_, index) => from + (to - from) * index / curvePoints).map((x) => ({
|
|
925
|
+
x,
|
|
926
|
+
y: normalPdf(x, mu, sd)
|
|
927
|
+
}));
|
|
928
|
+
};
|
|
929
|
+
var distributionAnnotations = (min, max, value, mean) => ({
|
|
930
|
+
...Number.isFinite(min) && Number.isFinite(max) ? {
|
|
931
|
+
interval: {
|
|
932
|
+
type: "box",
|
|
933
|
+
xMin: min,
|
|
934
|
+
xMax: max,
|
|
935
|
+
backgroundColor: opaqueColor(colors.lightBlue, 0.3),
|
|
936
|
+
borderWidth: 0,
|
|
937
|
+
drawTime: "beforeDatasetsDraw"
|
|
938
|
+
}
|
|
939
|
+
} : {},
|
|
940
|
+
...Number.isFinite(mean) ? {
|
|
941
|
+
mean: {
|
|
942
|
+
type: "line",
|
|
943
|
+
xMin: mean,
|
|
944
|
+
xMax: mean,
|
|
945
|
+
borderColor: colors.lightBlue,
|
|
946
|
+
borderWidth: 1,
|
|
947
|
+
borderDash: [4, 4],
|
|
948
|
+
label: {
|
|
949
|
+
display: true,
|
|
950
|
+
content: "average",
|
|
951
|
+
position: "start",
|
|
952
|
+
backgroundColor: colors.lightBlue,
|
|
953
|
+
color: colors.darkBlue,
|
|
954
|
+
font: { size: 9 }
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
} : {},
|
|
958
|
+
...Number.isFinite(value) ? {
|
|
959
|
+
value: {
|
|
960
|
+
type: "line",
|
|
961
|
+
xMin: value,
|
|
962
|
+
xMax: value,
|
|
963
|
+
borderColor: colors.orange,
|
|
964
|
+
borderWidth: 2,
|
|
965
|
+
label: {
|
|
966
|
+
display: true,
|
|
967
|
+
content: `${formatPrecision(value, 3, true)}`,
|
|
968
|
+
position: "start",
|
|
969
|
+
backgroundColor: colors.orange,
|
|
970
|
+
color: "#fff",
|
|
971
|
+
font: { size: 10 }
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
} : {}
|
|
975
|
+
});
|
|
976
|
+
var createSinglePointDataset = (labels, pointValue) => {
|
|
977
|
+
const pointData = new Array(labels.length).fill(null);
|
|
978
|
+
const binIndex = labels.findIndex((label) => {
|
|
979
|
+
const [minStr, maxStr] = label.split(joinXLabel);
|
|
980
|
+
const binMin = parseFloat(minStr);
|
|
981
|
+
const binMax = parseFloat(maxStr);
|
|
982
|
+
return pointValue >= binMin && pointValue < binMax;
|
|
983
|
+
});
|
|
984
|
+
if (binIndex !== -1) {
|
|
985
|
+
pointData[binIndex] = 1;
|
|
986
|
+
}
|
|
987
|
+
return pointData;
|
|
988
|
+
};
|
|
989
|
+
var histogramConfig = (label) => ({
|
|
990
|
+
options: {
|
|
991
|
+
scales: {
|
|
992
|
+
x: {
|
|
993
|
+
display: true,
|
|
994
|
+
grid: {
|
|
995
|
+
offset: true
|
|
996
|
+
},
|
|
997
|
+
min: 0,
|
|
998
|
+
ticks: {
|
|
999
|
+
callback: function(value) {
|
|
1000
|
+
var _a, _b, _c;
|
|
1001
|
+
const tickLabel = this.getLabelForValue(value);
|
|
1002
|
+
return (_c = (_b = (_a = tickLabel == null ? void 0 : tickLabel.split(joinXLabel)) == null ? void 0 : _a.map(parseFloat)) == null ? void 0 : _b.map((v) => formatPrecision(v, 3, true))) == null ? void 0 : _c.join("-");
|
|
1003
|
+
}
|
|
1004
|
+
},
|
|
1005
|
+
title: {
|
|
1006
|
+
display: true,
|
|
1007
|
+
text: label
|
|
1008
|
+
}
|
|
1009
|
+
},
|
|
1010
|
+
y: {
|
|
1011
|
+
ticks: {
|
|
1012
|
+
font: {
|
|
1013
|
+
family: defaultTicksFont.family,
|
|
1014
|
+
size: 10,
|
|
1015
|
+
weight: 400
|
|
1016
|
+
},
|
|
1017
|
+
color: "#4A4A4A"
|
|
1018
|
+
},
|
|
1019
|
+
title: {
|
|
1020
|
+
display: true,
|
|
1021
|
+
text: "Frequency of values"
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
});
|
|
1027
|
+
var parametricConfig = ({ label, intervalMin, intervalMax, value, mu }) => ({
|
|
1028
|
+
options: {
|
|
1029
|
+
plugins: {
|
|
1030
|
+
annotation: {
|
|
1031
|
+
annotations: distributionAnnotations(intervalMin, intervalMax, value, mu)
|
|
1032
|
+
}
|
|
1033
|
+
},
|
|
1034
|
+
scales: {
|
|
1035
|
+
x: {
|
|
1036
|
+
type: "linear",
|
|
1037
|
+
display: true,
|
|
1038
|
+
ticks: {
|
|
1039
|
+
font: {
|
|
1040
|
+
family: defaultTicksFont.family,
|
|
1041
|
+
size: 10,
|
|
1042
|
+
weight: 400
|
|
1043
|
+
},
|
|
1044
|
+
color: "#4A4A4A",
|
|
1045
|
+
callback: (tick) => `${formatPrecision(tick, 3, true)}`
|
|
1046
|
+
},
|
|
1047
|
+
title: {
|
|
1048
|
+
display: true,
|
|
1049
|
+
text: label
|
|
1050
|
+
}
|
|
1051
|
+
},
|
|
1052
|
+
y: {
|
|
1053
|
+
// the density itself carries no meaning for the reader, only the shape does
|
|
1054
|
+
ticks: {
|
|
1055
|
+
display: false
|
|
1056
|
+
},
|
|
1057
|
+
grid: {
|
|
1058
|
+
display: false
|
|
1059
|
+
},
|
|
1060
|
+
title: {
|
|
1061
|
+
display: false
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
var withinPercentile = (distribution = [], maxPercentile = 0) => {
|
|
1068
|
+
const limit = maxPercentile ? getPercentileValue(distribution, maxPercentile) : null;
|
|
1069
|
+
return limit ? distribution.filter((v) => v <= limit) : distribution;
|
|
1070
|
+
};
|
|
1071
|
+
var histogramDatasets = (frequencies) => [
|
|
1072
|
+
{
|
|
1073
|
+
label: "Distribution",
|
|
1074
|
+
data: frequencies,
|
|
1075
|
+
backgroundColor: opaqueColor(colors.lightBlue),
|
|
1076
|
+
borderColor: colors.lightBlue,
|
|
1077
|
+
borderWidth: 1,
|
|
1078
|
+
type: "bar"
|
|
1079
|
+
},
|
|
1080
|
+
{
|
|
1081
|
+
label: "Distribution",
|
|
1082
|
+
data: frequencies,
|
|
1083
|
+
backgroundColor: opaqueColor(colors.darkBlue),
|
|
1084
|
+
borderColor: colors.darkBlue,
|
|
1085
|
+
borderWidth: 1,
|
|
1086
|
+
fill: false,
|
|
1087
|
+
pointRadius: 0,
|
|
1088
|
+
type: "line"
|
|
1089
|
+
}
|
|
1090
|
+
];
|
|
1091
|
+
var valueDataset = (singlePoint) => (singlePoint == null ? void 0 : singlePoint.length) ? [
|
|
1092
|
+
{
|
|
1093
|
+
label: "Value",
|
|
1094
|
+
data: singlePoint,
|
|
1095
|
+
fill: false,
|
|
1096
|
+
borderColor: colors.darkBlue,
|
|
1097
|
+
backgroundColor: colors.darkBlue,
|
|
1098
|
+
pointRadius: 4,
|
|
1099
|
+
showLine: false,
|
|
1100
|
+
type: "line",
|
|
1101
|
+
tension: 0
|
|
1102
|
+
}
|
|
1103
|
+
] : [];
|
|
1104
|
+
var histogramData = ({ distribution, value, nbBins = 10, maxPercentile = 0 }) => {
|
|
1105
|
+
const grouped = createDynamicHistogramData(withinPercentile(distribution, maxPercentile), nbBins);
|
|
1106
|
+
return {
|
|
1107
|
+
datasets: [
|
|
1108
|
+
...distribution ? histogramDatasets(grouped.frequencies) : [],
|
|
1109
|
+
...valueDataset(createSinglePointDataset((grouped == null ? void 0 : grouped.labels) || [], value))
|
|
1110
|
+
],
|
|
1111
|
+
labels: grouped.labels
|
|
1112
|
+
};
|
|
1113
|
+
};
|
|
1114
|
+
var parametricData = ({ mu, sd, value, minX }) => ({
|
|
1115
|
+
datasets: [
|
|
1116
|
+
{
|
|
1117
|
+
label: "Distribution",
|
|
1118
|
+
data: createNormalCurve(mu, sd, value, minX),
|
|
1119
|
+
backgroundColor: opaqueColor(colors.lightBlue, 0.2),
|
|
1120
|
+
// light enough to read on a dark panel, dark enough to read on a white one
|
|
1121
|
+
borderColor: colors.lightBlue,
|
|
1122
|
+
borderWidth: 1,
|
|
1123
|
+
fill: true,
|
|
1124
|
+
pointRadius: 0,
|
|
1125
|
+
type: "line"
|
|
1126
|
+
}
|
|
1127
|
+
]
|
|
1128
|
+
});
|
|
1129
|
+
var isParametric = ({ mu, sd }) => Number.isFinite(mu) && Number.isFinite(sd) && sd > 0;
|
|
1130
|
+
var distributionChart = (inputs) => {
|
|
1131
|
+
var _a;
|
|
1132
|
+
const label = (_a = inputs.label) != null ? _a : "Distribution";
|
|
1133
|
+
return isParametric(inputs) ? {
|
|
1134
|
+
data: parametricData(inputs),
|
|
1135
|
+
config: merge({}, defaultDistributionSettings, parametricConfig({ ...inputs, label }))
|
|
1136
|
+
} : {
|
|
1137
|
+
data: histogramData(inputs),
|
|
1138
|
+
config: merge({}, defaultDistributionSettings, histogramConfig(label))
|
|
1139
|
+
};
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
// src/charts/download.ts
|
|
1143
|
+
var downloadFile = (url, fileName) => {
|
|
1144
|
+
if (!url) {
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
const a = document.createElement("a");
|
|
1148
|
+
document.body.appendChild(a);
|
|
1149
|
+
a.setAttribute("style", "display: none");
|
|
1150
|
+
a.href = url;
|
|
1151
|
+
a.download = fileName;
|
|
1152
|
+
a.click();
|
|
1153
|
+
a.remove();
|
|
1154
|
+
setTimeout(() => URL.revokeObjectURL(url), 100);
|
|
1155
|
+
};
|
|
1156
|
+
var data = emissionGroupings;
|
|
1157
|
+
var categoryLabel = (key) => key ? keyToLabel(key.replace(/category/gi, "")) : "Unset";
|
|
1158
|
+
var emissionCategories = Object.keys(data).filter((key) => key.toLowerCase().endsWith("category")).map((key) => ({ key, label: categoryLabel(key) }));
|
|
1159
|
+
var categoryValues = (category) => data[category];
|
|
1160
|
+
var emissionCategoriesOf = (emissionId) => Object.keys(data[emissionId]).filter((key) => !!data[emissionId][key]);
|
|
1161
|
+
var emissionCategoryValue = (emissionId, category) => {
|
|
1162
|
+
var _a;
|
|
1163
|
+
return (_a = data[emissionId]) == null ? void 0 : _a[category];
|
|
1164
|
+
};
|
|
1165
|
+
var defaultChartWidth = 800;
|
|
1166
|
+
var defaultChartHeight = 600;
|
|
1167
|
+
var extractEssentialChartConfig = (config) => {
|
|
1168
|
+
var _a, _b;
|
|
1169
|
+
return {
|
|
1170
|
+
type: config.type || "bar",
|
|
1171
|
+
data: config.data,
|
|
1172
|
+
options: {
|
|
1173
|
+
indexAxis: (_a = config.options) == null ? void 0 : _a.indexAxis,
|
|
1174
|
+
responsive: false,
|
|
1175
|
+
maintainAspectRatio: false,
|
|
1176
|
+
devicePixelRatio: 1,
|
|
1177
|
+
animation: false,
|
|
1178
|
+
animations: {
|
|
1179
|
+
colors: false,
|
|
1180
|
+
x: false,
|
|
1181
|
+
y: false
|
|
1182
|
+
},
|
|
1183
|
+
transitions: {
|
|
1184
|
+
active: {
|
|
1185
|
+
animation: {
|
|
1186
|
+
duration: 0
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
},
|
|
1190
|
+
plugins: {
|
|
1191
|
+
legend: { display: false },
|
|
1192
|
+
title: ((_b = config.options.plugins) == null ? void 0 : _b.title) || { display: false }
|
|
1193
|
+
},
|
|
1194
|
+
scales: config.options.scales
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
};
|
|
1198
|
+
var stretchSvg = (svgString, width2, height) => {
|
|
1199
|
+
const parser = new DOMParser();
|
|
1200
|
+
const doc = parser.parseFromString(svgString, "image/svg+xml");
|
|
1201
|
+
const svgElement = doc.documentElement;
|
|
1202
|
+
svgElement.setAttribute("viewBox", `0 0 ${width2 + 100} ${height}`);
|
|
1203
|
+
svgElement.setAttribute("width", "100%");
|
|
1204
|
+
svgElement.setAttribute("height", "100%");
|
|
1205
|
+
svgElement.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
|
1206
|
+
return svgElement.outerHTML;
|
|
1207
|
+
};
|
|
1208
|
+
var parseAfterDrawBarPlugins = (afterBarDrawConfig, units) => Array.isArray(afterBarDrawConfig) ? afterBarDrawConfig.map(afterBarDrawPlugin) : [
|
|
1209
|
+
afterBarDrawPlugin({
|
|
1210
|
+
textFn: ({ data: data2 }) => [toPrecision(Array.isArray(data2) ? data2[0] : data2), units].filter(Boolean).join(" "),
|
|
1211
|
+
emptyValueLabel: "No data",
|
|
1212
|
+
...afterBarDrawConfig || {}
|
|
1213
|
+
})
|
|
1214
|
+
];
|
|
1215
|
+
var convertToSvg = (config, metadata = {}) => new Promise((resolve, reject) => {
|
|
1216
|
+
const width2 = metadata.width || defaultChartWidth;
|
|
1217
|
+
const height = metadata.height || defaultChartHeight;
|
|
1218
|
+
const svgContext = new C2S(width2, height);
|
|
1219
|
+
svgContext.setTransform = () => {
|
|
1220
|
+
};
|
|
1221
|
+
svgContext.resetTransform = () => {
|
|
1222
|
+
};
|
|
1223
|
+
svgContext.roundRect = function(x, y, w, h) {
|
|
1224
|
+
this.rect(x, y, w, h);
|
|
1225
|
+
};
|
|
1226
|
+
const mockCanvas = document.createElement("canvas");
|
|
1227
|
+
mockCanvas.width = width2;
|
|
1228
|
+
mockCanvas.height = height;
|
|
1229
|
+
mockCanvas.style.width = `${width2}px`;
|
|
1230
|
+
mockCanvas.style.height = `${height}px`;
|
|
1231
|
+
mockCanvas.getBoundingClientRect = () => ({
|
|
1232
|
+
x: 0,
|
|
1233
|
+
y: 0,
|
|
1234
|
+
bottom: height,
|
|
1235
|
+
height,
|
|
1236
|
+
left: 0,
|
|
1237
|
+
right: width2,
|
|
1238
|
+
top: 0,
|
|
1239
|
+
width: width2,
|
|
1240
|
+
toJSON: () => {
|
|
1241
|
+
}
|
|
1242
|
+
});
|
|
1243
|
+
mockCanvas.getContext = (type) => {
|
|
1244
|
+
if (type === "2d") {
|
|
1245
|
+
svgContext.canvas = mockCanvas;
|
|
1246
|
+
return svgContext;
|
|
1247
|
+
}
|
|
1248
|
+
return null;
|
|
1249
|
+
};
|
|
1250
|
+
const chartConfig = extractEssentialChartConfig(config);
|
|
1251
|
+
let chart;
|
|
1252
|
+
chartConfig.options.animation = {
|
|
1253
|
+
onComplete: () => {
|
|
1254
|
+
try {
|
|
1255
|
+
const svgString = svgContext.getSerializedSvg(true);
|
|
1256
|
+
const finalSvg = stretchSvg(svgString, width2, height);
|
|
1257
|
+
chart.destroy();
|
|
1258
|
+
resolve(finalSvg);
|
|
1259
|
+
} catch (e) {
|
|
1260
|
+
reject(e);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
chartConfig.plugins = [
|
|
1265
|
+
metadata.afterBarDrawConfig !== null && parseAfterDrawBarPlugins(metadata.afterBarDrawConfig, metadata.units),
|
|
1266
|
+
metadata.lollipopConfig !== null && lollipopChartPlugin(metadata.lollipopConfig)
|
|
1267
|
+
].filter(Boolean).flat();
|
|
1268
|
+
chart = new Chart(mockCanvas, chartConfig);
|
|
1269
|
+
});
|
|
1270
|
+
var exportAsSVG = async (config, metadata) => {
|
|
1271
|
+
try {
|
|
1272
|
+
const content = await convertToSvg(config, metadata);
|
|
1273
|
+
const blob = new Blob([content], { type: "image/svg+xml;charset=utf-8" });
|
|
1274
|
+
const url = URL.createObjectURL(blob);
|
|
1275
|
+
downloadFile(url, metadata.fileName || "chart-export.svg");
|
|
1276
|
+
} catch (error) {
|
|
1277
|
+
console.error("Failed to export SVG", error);
|
|
1278
|
+
}
|
|
1279
|
+
};
|
|
1280
|
+
var svgToUrl = (svgElement) => {
|
|
1281
|
+
const serializer = new XMLSerializer();
|
|
1282
|
+
let svgString = serializer.serializeToString(svgElement);
|
|
1283
|
+
if (!svgString.includes("xmlns")) {
|
|
1284
|
+
svgString = svgString.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
|
|
1285
|
+
}
|
|
1286
|
+
const blob = new Blob([svgString], { type: "image/svg+xml;charset=utf-8" });
|
|
1287
|
+
return URL.createObjectURL(blob);
|
|
1288
|
+
};
|
|
1289
|
+
var downloadSvg = (svgElement) => downloadFile(svgToUrl(svgElement), "chart.svg");
|
|
1290
|
+
var downloadPng = async (svgElement, { width: width2, height } = {}) => {
|
|
1291
|
+
const canvas = document.createElement("canvas");
|
|
1292
|
+
const ctx = canvas.getContext("2d");
|
|
1293
|
+
const img = new Image();
|
|
1294
|
+
const svgSize = svgElement.getBoundingClientRect();
|
|
1295
|
+
canvas.width = width2 || svgSize.width;
|
|
1296
|
+
canvas.height = height || svgSize.height;
|
|
1297
|
+
img.onload = () => {
|
|
1298
|
+
if (ctx) {
|
|
1299
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
1300
|
+
ctx.drawImage(img, 0, 0);
|
|
1301
|
+
const pngUrl = canvas.toDataURL("image/png");
|
|
1302
|
+
downloadFile(pngUrl, "chart.png");
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
img.src = svgToUrl(svgElement);
|
|
1306
|
+
};
|
|
1307
|
+
var ChartNodeType = /* @__PURE__ */ ((ChartNodeType2) => {
|
|
1308
|
+
ChartNodeType2["midpoint"] = "midpoint";
|
|
1309
|
+
ChartNodeType2["category"] = "category";
|
|
1310
|
+
ChartNodeType2["emission"] = "emission";
|
|
1311
|
+
ChartNodeType2["endpoint"] = "endpoint";
|
|
1312
|
+
ChartNodeType2["input"] = "input";
|
|
1313
|
+
ChartNodeType2["operation"] = "operation";
|
|
1314
|
+
ChartNodeType2["root"] = "root";
|
|
1315
|
+
return ChartNodeType2;
|
|
1316
|
+
})(ChartNodeType || {});
|
|
1317
|
+
var nodeBorderColours = {
|
|
1318
|
+
["endpoint" /* endpoint */]: "#193957",
|
|
1319
|
+
["midpoint" /* midpoint */]: "#249DA5",
|
|
1320
|
+
["category" /* category */]: "#DC7033",
|
|
1321
|
+
["emission" /* emission */]: "#498F78",
|
|
1322
|
+
["input" /* input */]: "#FFC000",
|
|
1323
|
+
["operation" /* operation */]: "#D0CABA"
|
|
1324
|
+
};
|
|
1325
|
+
var nodeColours = {
|
|
1326
|
+
["endpoint" /* endpoint */]: "#DBE3EA",
|
|
1327
|
+
["midpoint" /* midpoint */]: "#D3EBED",
|
|
1328
|
+
["category" /* category */]: "#FFDEC0",
|
|
1329
|
+
["emission" /* emission */]: "#D5F3D8",
|
|
1330
|
+
["input" /* input */]: "#FFE8A3",
|
|
1331
|
+
["operation" /* operation */]: "#FBF4DF"
|
|
1332
|
+
};
|
|
1333
|
+
var legend = [
|
|
1334
|
+
{ type: "endpoint" /* endpoint */, text: "Endpoint / Single Score" },
|
|
1335
|
+
{ type: "midpoint" /* midpoint */, text: "Characterised Indicator" },
|
|
1336
|
+
{ type: "category" /* category */, text: "Category" },
|
|
1337
|
+
{ type: "emission" /* emission */, text: "Emission / Resource Use" },
|
|
1338
|
+
{ type: "input" /* input */, text: "Input" },
|
|
1339
|
+
{ type: "operation" /* operation */, text: "Operation" }
|
|
1340
|
+
];
|
|
1341
|
+
var getUnitsAndLabel = (terms, fallbackLabel = "unknown term") => {
|
|
1342
|
+
var _a, _b;
|
|
1343
|
+
const allTerms = (Array.isArray(terms) ? terms : [terms]).filter(Boolean);
|
|
1344
|
+
const term2 = allTerms == null ? void 0 : allTerms[0];
|
|
1345
|
+
const label = (_b = (_a = term2 == null ? void 0 : term2.name) != null ? _a : term2 == null ? void 0 : term2["@id"]) != null ? _b : fallbackLabel;
|
|
1346
|
+
const units = (term2 == null ? void 0 : term2.units) ? `(${term2 == null ? void 0 : term2.units})` : "";
|
|
1347
|
+
return {
|
|
1348
|
+
label: `${label.replace(units, "").trim()}${allTerms.length > 1 ? ` (and ${allTerms.length - 1} more)` : ""}`,
|
|
1349
|
+
terms: allTerms,
|
|
1350
|
+
units
|
|
1351
|
+
};
|
|
1352
|
+
};
|
|
1353
|
+
var typeMapping = {
|
|
1354
|
+
[TermTermType.endpointIndicator]: "endpoint" /* endpoint */,
|
|
1355
|
+
[TermTermType.characterisedIndicator]: "midpoint" /* midpoint */,
|
|
1356
|
+
[TermTermType.emission]: "emission" /* emission */,
|
|
1357
|
+
[TermTermType.resourceUse]: "emission" /* emission */,
|
|
1358
|
+
[TermTermType.operation]: "operation" /* operation */
|
|
1359
|
+
};
|
|
1360
|
+
var safeDivide = (numerator, denominator) => denominator !== 0 ? numerator / denominator : 0;
|
|
1361
|
+
var sumIndicators = (data2) => {
|
|
1362
|
+
const totalValues = data2.reduce(
|
|
1363
|
+
(acc, log) => {
|
|
1364
|
+
acc[log.indicator] = acc[log.indicator] || [];
|
|
1365
|
+
!isUndefined(log.weightedValue) && acc[log.indicator].push(log.weightedValue);
|
|
1366
|
+
if (log.category) {
|
|
1367
|
+
const categoryKey = `${log.indicator}-${log.category}`;
|
|
1368
|
+
acc[categoryKey] = acc[categoryKey] || [];
|
|
1369
|
+
!isUndefined(log.weightedValue) && acc[categoryKey].push(log.weightedValue);
|
|
1370
|
+
}
|
|
1371
|
+
return acc;
|
|
1372
|
+
},
|
|
1373
|
+
{}
|
|
1374
|
+
);
|
|
1375
|
+
return Object.fromEntries(
|
|
1376
|
+
Object.entries(totalValues).map(([key, values]) => [
|
|
1377
|
+
key,
|
|
1378
|
+
// if the total is positive, we ignore negative values
|
|
1379
|
+
sum(values) < 0 ? sum(values) : sum(values.filter((v) => v >= 0))
|
|
1380
|
+
])
|
|
1381
|
+
);
|
|
1382
|
+
};
|
|
1383
|
+
var groupIndicators = (data2, terms = {}, totals = {}) => data2.reduce(
|
|
1384
|
+
(acc, log) => {
|
|
1385
|
+
var _a, _b;
|
|
1386
|
+
const contributorType = typeMapping[(_a = terms[log.contributor]) == null ? void 0 : _a.termType] || "input" /* input */;
|
|
1387
|
+
const indicatorType = typeMapping[(_b = terms[log.indicator]) == null ? void 0 : _b.termType];
|
|
1388
|
+
const categoryKey = log.category ? `${log.indicator}-${log.category}` : void 0;
|
|
1389
|
+
const parentTotal = categoryKey ? totals[categoryKey] : totals[log.indicator];
|
|
1390
|
+
const contributorNode = {
|
|
1391
|
+
id: log.contributor,
|
|
1392
|
+
...getUnitsAndLabel(terms[log.contributor], log.contributor),
|
|
1393
|
+
type: contributorType,
|
|
1394
|
+
value: log.value,
|
|
1395
|
+
weightedValue: log.weightedValue,
|
|
1396
|
+
modelId: log.modelId,
|
|
1397
|
+
fraction: safeDivide(log.weightedValue, parentTotal),
|
|
1398
|
+
children: contributorType === "emission" /* emission */ ? log.inputs.map((i) => ({
|
|
1399
|
+
...i,
|
|
1400
|
+
...getUnitsAndLabel(terms[i.id], i.label),
|
|
1401
|
+
type: "input" /* input */,
|
|
1402
|
+
fraction: safeDivide(i.weightedValue, log.value),
|
|
1403
|
+
children: i.operations.map((o) => ({
|
|
1404
|
+
...o,
|
|
1405
|
+
...getUnitsAndLabel(terms[o.id], o.label),
|
|
1406
|
+
type: "operation" /* operation */,
|
|
1407
|
+
fraction: safeDivide(o.weightedValue, i.weightedValue)
|
|
1408
|
+
}))
|
|
1409
|
+
})) : []
|
|
1410
|
+
};
|
|
1411
|
+
acc[log.indicator] = acc[log.indicator] || {
|
|
1412
|
+
id: log.indicator,
|
|
1413
|
+
...getUnitsAndLabel(terms[log.indicator], log.indicator),
|
|
1414
|
+
modelId: log.modelId,
|
|
1415
|
+
type: indicatorType,
|
|
1416
|
+
value: totals[log.indicator],
|
|
1417
|
+
children: []
|
|
1418
|
+
};
|
|
1419
|
+
if (log.category) {
|
|
1420
|
+
const categoryId = `cat-${log.indicator}-${log.category}`;
|
|
1421
|
+
let categoryNode = acc[log.indicator].children.find((c) => c.id === categoryId);
|
|
1422
|
+
if (!categoryNode) {
|
|
1423
|
+
categoryNode = {
|
|
1424
|
+
id: categoryId,
|
|
1425
|
+
label: log.category,
|
|
1426
|
+
type: "category" /* category */,
|
|
1427
|
+
units: "",
|
|
1428
|
+
value: totals[categoryKey],
|
|
1429
|
+
weightedValue: totals[categoryKey],
|
|
1430
|
+
modelId: log.modelId,
|
|
1431
|
+
fraction: safeDivide(totals[categoryKey], totals[log.indicator]),
|
|
1432
|
+
children: []
|
|
1433
|
+
};
|
|
1434
|
+
acc[log.indicator].children.push(categoryNode);
|
|
1435
|
+
}
|
|
1436
|
+
categoryNode.children.push(contributorNode);
|
|
1437
|
+
} else {
|
|
1438
|
+
acc[log.indicator].children.push(contributorNode);
|
|
1439
|
+
}
|
|
1440
|
+
return acc;
|
|
1441
|
+
},
|
|
1442
|
+
{}
|
|
1443
|
+
);
|
|
1444
|
+
|
|
1445
|
+
// src/charts/hierarchy/contributions.ts
|
|
1446
|
+
var hierarchyTermTypes = [
|
|
1447
|
+
TermTermType.resourceUse,
|
|
1448
|
+
TermTermType.emission,
|
|
1449
|
+
TermTermType.characterisedIndicator,
|
|
1450
|
+
TermTermType.endpointIndicator,
|
|
1451
|
+
TermTermType.model
|
|
1452
|
+
];
|
|
1453
|
+
var defaultIndicatorsModelId = "defaultIndicatorsModelId";
|
|
1454
|
+
var defaultIndicatorsModelName = "Default Indicators (multi-model)";
|
|
1455
|
+
var defaultModelId = (indicatorId) => defaultModelIds[indicatorId];
|
|
1456
|
+
var hierarchyWarnings = (logs, terms) => [!logs.every((log) => terms[log.indicator] && terms[log.contributor]) && "missing-terms"].filter(Boolean);
|
|
1457
|
+
var operationOf = (eru, modelId) => eru.operation ? [
|
|
1458
|
+
{
|
|
1459
|
+
id: eru.operation["@id"],
|
|
1460
|
+
...getUnitsAndLabel(eru.operation),
|
|
1461
|
+
modelId,
|
|
1462
|
+
weightedValue: eru.value
|
|
1463
|
+
}
|
|
1464
|
+
] : [];
|
|
1465
|
+
var inputsOf = (impactAssessment, contributor, modelId) => Object.values(
|
|
1466
|
+
(impactAssessment.emissionsResourceUse || []).filter((eru) => {
|
|
1467
|
+
var _a;
|
|
1468
|
+
return eru.term["@id"] === contributor && ((_a = eru.inputs) == null ? void 0 : _a.length);
|
|
1469
|
+
}).reduce(
|
|
1470
|
+
(acc, eru) => {
|
|
1471
|
+
var _a;
|
|
1472
|
+
const inputId = eru.inputs.map((i) => i["@id"]).join(";");
|
|
1473
|
+
const operations = operationOf(eru, modelId);
|
|
1474
|
+
return {
|
|
1475
|
+
...acc,
|
|
1476
|
+
[inputId]: acc[inputId] ? {
|
|
1477
|
+
...acc[inputId],
|
|
1478
|
+
weightedValue: acc[inputId].weightedValue + eru.value,
|
|
1479
|
+
modelId,
|
|
1480
|
+
operations: [...acc[inputId].operations, ...operations]
|
|
1481
|
+
} : {
|
|
1482
|
+
id: inputId,
|
|
1483
|
+
...!((_a = eru.inputs) == null ? void 0 : _a.length) ? { label: "Input group (under development)" } : getUnitsAndLabel(eru.inputs),
|
|
1484
|
+
modelId,
|
|
1485
|
+
weightedValue: eru.value,
|
|
1486
|
+
operations
|
|
1487
|
+
}
|
|
1488
|
+
};
|
|
1489
|
+
},
|
|
1490
|
+
{}
|
|
1491
|
+
)
|
|
1492
|
+
);
|
|
1493
|
+
var parseLogValues = (impactAssessment, values) => {
|
|
1494
|
+
const log = values[0];
|
|
1495
|
+
const weightedValue = values.reduce((total, curr) => total + curr.weightedValue, 0);
|
|
1496
|
+
const value = values.reduce((total, curr) => total + curr.value, 0);
|
|
1497
|
+
return {
|
|
1498
|
+
...log,
|
|
1499
|
+
weightedValue,
|
|
1500
|
+
value,
|
|
1501
|
+
inputs: inputsOf(impactAssessment, log.contributor, log.modelId)
|
|
1502
|
+
};
|
|
1503
|
+
};
|
|
1504
|
+
var processContributions = (record, nodes) => Object.entries(record || {}).flatMap(([index, byIndicator]) => {
|
|
1505
|
+
const indicator = nodes[+index];
|
|
1506
|
+
return indicator ? Object.entries(byIndicator).flatMap(
|
|
1507
|
+
([impactTermId, modelContr]) => Object.entries(modelContr).map(([modelId, weightedValue]) => {
|
|
1508
|
+
const contributor = indicator.term["@id"];
|
|
1509
|
+
const value = indicator.value || 0;
|
|
1510
|
+
const coefficient = value !== 0 ? weightedValue / value : 0;
|
|
1511
|
+
return coefficient > 0 ? { indicator: impactTermId, contributor, coefficient, modelId, weightedValue, value } : null;
|
|
1512
|
+
})
|
|
1513
|
+
) : null;
|
|
1514
|
+
});
|
|
1515
|
+
var hierarchyContributions = (impactAssessment, stored) => {
|
|
1516
|
+
const logs = [
|
|
1517
|
+
...processContributions(stored == null ? void 0 : stored.emissionsResourceUse, impactAssessment.emissionsResourceUse || []),
|
|
1518
|
+
...processContributions(stored == null ? void 0 : stored.impacts, impactAssessment.impacts || [])
|
|
1519
|
+
].filter(Boolean);
|
|
1520
|
+
const grouped = logs.reduce(
|
|
1521
|
+
(acc, log) => {
|
|
1522
|
+
const key = [log.indicator, log.contributor, log.modelId].join("/");
|
|
1523
|
+
return { ...acc, [key]: [...acc[key] || [], log] };
|
|
1524
|
+
},
|
|
1525
|
+
{}
|
|
1526
|
+
);
|
|
1527
|
+
return Object.values(grouped).map((values) => parseLogValues(impactAssessment, values));
|
|
1528
|
+
};
|
|
1529
|
+
var missingTermIds = (logs, terms = {}) => unique(logs.flatMap((l) => {
|
|
1530
|
+
var _a;
|
|
1531
|
+
return [l.contributor, l.indicator, ...((_a = l.inputs) != null ? _a : []).map((i) => i.id)];
|
|
1532
|
+
}).filter((id) => !terms[id]));
|
|
1533
|
+
var hierarchyModels = (logs, terms) => [
|
|
1534
|
+
...logs.some((log) => log.modelId === defaultModelId(log.indicator)) ? [{ "@id": defaultIndicatorsModelId, name: defaultIndicatorsModelName }] : [],
|
|
1535
|
+
...unique(logs.map(({ modelId }) => terms[modelId] || { "@id": modelId }))
|
|
1536
|
+
];
|
|
1537
|
+
var forModel = (log, modelId) => log.modelId === (modelId === defaultIndicatorsModelId ? defaultModelId(log.indicator) : modelId);
|
|
1538
|
+
var withCategory = (log, terms, category) => {
|
|
1539
|
+
var _a;
|
|
1540
|
+
const included = category && [TermTermType.emission, TermTermType.resourceUse].includes((_a = terms[log.contributor]) == null ? void 0 : _a.termType);
|
|
1541
|
+
const label = category ? categoryLabel(category) : void 0;
|
|
1542
|
+
return included ? {
|
|
1543
|
+
...log,
|
|
1544
|
+
category: emissionCategoryValue(log.contributor, category) || `Not available for ${label}`,
|
|
1545
|
+
categoryLabel: label
|
|
1546
|
+
} : log;
|
|
1547
|
+
};
|
|
1548
|
+
var hierarchyData = (logs, terms, modelId, category) => logs.filter((log) => forModel(log, modelId)).map((log) => withCategory(log, terms, category));
|
|
671
1549
|
|
|
672
|
-
// src/charts/
|
|
1550
|
+
// src/charts/hierarchy/data.ts
|
|
1551
|
+
var nonLCAIndicatorsId = "nonLCAIndicators";
|
|
1552
|
+
var childLabel = (indicator, child) => {
|
|
1553
|
+
const prefixRemoved = indicator.type === "endpoint" /* endpoint */ && child.label.split(", ")[1];
|
|
1554
|
+
return prefixRemoved ? "[...]" + prefixRemoved.charAt(0).toUpperCase() + prefixRemoved.slice(1) : child.label;
|
|
1555
|
+
};
|
|
1556
|
+
var linkIndicators = (indicators) => {
|
|
1557
|
+
const all = Object.values(indicators);
|
|
1558
|
+
return all.map((indicator) => ({
|
|
1559
|
+
...indicator,
|
|
1560
|
+
children: indicator.children.map(
|
|
1561
|
+
(child) => {
|
|
1562
|
+
var _a, _b;
|
|
1563
|
+
return child.type === "midpoint" /* midpoint */ ? {
|
|
1564
|
+
...child,
|
|
1565
|
+
label: childLabel(indicator, child),
|
|
1566
|
+
modelId: ((_a = indicators[child.id]) == null ? void 0 : _a.modelId) || child.modelId,
|
|
1567
|
+
children: (_b = indicators[child.id]) == null ? void 0 : _b.children
|
|
1568
|
+
} : child;
|
|
1569
|
+
}
|
|
1570
|
+
)
|
|
1571
|
+
})).filter((indicator) => !all.some((parent) => parent.children.some((child) => child.id === indicator.id)));
|
|
1572
|
+
};
|
|
1573
|
+
var otherIndicators = (children) => children.length ? {
|
|
1574
|
+
id: nonLCAIndicatorsId,
|
|
1575
|
+
label: "Other indicators",
|
|
1576
|
+
term: "n/a",
|
|
1577
|
+
modelId: "n/a",
|
|
1578
|
+
units: "",
|
|
1579
|
+
value: null,
|
|
1580
|
+
weightedValue: null,
|
|
1581
|
+
type: "endpoint" /* endpoint */,
|
|
1582
|
+
fraction: null,
|
|
1583
|
+
children
|
|
1584
|
+
} : null;
|
|
1585
|
+
var hierarchyChildren = (data2, terms) => {
|
|
1586
|
+
const linked = linkIndicators(groupIndicators(data2, terms, sumIndicators(data2)));
|
|
1587
|
+
const endpoints = linked.filter((indicator) => indicator.type === "endpoint" /* endpoint */);
|
|
1588
|
+
const midpoints = linked.filter((indicator) => indicator.type === "midpoint" /* midpoint */);
|
|
1589
|
+
return endpoints.length ? [...endpoints, otherIndicators(midpoints)].filter(Boolean) : linked;
|
|
1590
|
+
};
|
|
1591
|
+
var hierarchyLegend = (data2, includedTypes) => {
|
|
1592
|
+
var _a;
|
|
1593
|
+
const shown = legend.filter(({ type }) => includedTypes.includes(type));
|
|
1594
|
+
const categoryLabel2 = (_a = data2.find((d) => d.categoryLabel)) == null ? void 0 : _a.categoryLabel;
|
|
1595
|
+
return categoryLabel2 ? shown.map((l) => l.type === "category" /* category */ ? { ...l, text: categoryLabel2 } : l) : shown;
|
|
1596
|
+
};
|
|
1597
|
+
var extraInputKeys = ["input", "input value"];
|
|
1598
|
+
var extraOperationKeys = ["operation", "operation value"];
|
|
1599
|
+
var dataToCsv = (keys, terms) => (data2) => {
|
|
1600
|
+
var _a;
|
|
1601
|
+
return [
|
|
1602
|
+
// line for the data
|
|
1603
|
+
toLine([
|
|
1604
|
+
...keys.map((k) => {
|
|
1605
|
+
var _a2;
|
|
1606
|
+
return ((_a2 = terms[data2[k]]) == null ? void 0 : _a2.name) || data2[k] || "";
|
|
1607
|
+
}),
|
|
1608
|
+
...extraInputKeys.map(() => ""),
|
|
1609
|
+
...extraOperationKeys.map(() => "")
|
|
1610
|
+
]),
|
|
1611
|
+
// lines for each inputs and operations
|
|
1612
|
+
...((_a = data2.inputs) != null ? _a : []).flatMap((input) => {
|
|
1613
|
+
var _a2;
|
|
1614
|
+
return [
|
|
1615
|
+
toLine([...keys.map(() => ""), input.label, input.weightedValue, ...extraOperationKeys.map(() => "")]),
|
|
1616
|
+
...((_a2 = input.operations) != null ? _a2 : []).map(
|
|
1617
|
+
(operation) => toLine([...keys.map(() => ""), ...extraInputKeys.map(() => ""), operation.label, operation.weightedValue])
|
|
1618
|
+
)
|
|
1619
|
+
];
|
|
1620
|
+
})
|
|
1621
|
+
];
|
|
1622
|
+
};
|
|
1623
|
+
var hierarchyCsv = (data2, terms) => {
|
|
1624
|
+
const keys = Object.keys(data2[0]).filter((k) => k !== "inputs");
|
|
1625
|
+
return [toLine([...keys, ...extraInputKeys, ...extraOperationKeys]), ...data2.flatMap(dataToCsv(keys, terms))].join(
|
|
1626
|
+
"\n"
|
|
1627
|
+
);
|
|
1628
|
+
};
|
|
1629
|
+
var width = 800;
|
|
1630
|
+
var margin = { top: 10, right: 20, bottom: 10, left: 20 };
|
|
1631
|
+
var fontSize = Math.floor(width / 100);
|
|
1632
|
+
var nodePadding = fontSize / 2;
|
|
1633
|
+
var lineHeight = fontSize * 1.2;
|
|
1634
|
+
var barHeight = lineHeight;
|
|
1635
|
+
var barRadius = 4;
|
|
1636
|
+
var nodeHeight = nodePadding * 2 + 2 * lineHeight + barHeight;
|
|
1637
|
+
var nodeWidth = nodeHeight * 2 * 1.618;
|
|
1638
|
+
var nodeContentWidth = nodeWidth - nodePadding * 2;
|
|
1639
|
+
var layerSpacing = (width - margin.left - margin.right - nodeWidth * 5) / 4;
|
|
1640
|
+
var nodeMargin = nodePadding * 0.75;
|
|
1641
|
+
var nodeSpacingFactor = (nodeHeight + nodeMargin * 2) / nodeHeight;
|
|
1642
|
+
var nodeDuration = 600;
|
|
1643
|
+
var linkDuration = 200;
|
|
1644
|
+
var groupDelay = nodeDuration - 100;
|
|
1645
|
+
var greyedOpacity = 0.2;
|
|
1646
|
+
var groupOverlap = lineHeight - nodePadding - nodeMargin;
|
|
1647
|
+
var groupNodeSpace = nodeHeight - 2 * groupOverlap;
|
|
1648
|
+
var collapseChildren = (node) => {
|
|
1649
|
+
node._children = node.children || node._children;
|
|
1650
|
+
node.children = null;
|
|
1651
|
+
return node;
|
|
1652
|
+
};
|
|
1653
|
+
var restoreChildren = (node) => {
|
|
1654
|
+
node.children = node._children;
|
|
1655
|
+
node._children = null;
|
|
1656
|
+
return node;
|
|
1657
|
+
};
|
|
1658
|
+
var childrenOf = (node) => (node == null ? void 0 : node.children) || (node == null ? void 0 : node._children) || [];
|
|
1659
|
+
var enterStagger = (parent, child) => Math.max(childrenOf(parent).indexOf(child), 0) * 10;
|
|
1660
|
+
var exitStagger = (parent, child) => {
|
|
1661
|
+
const children = childrenOf(parent);
|
|
1662
|
+
const index = children.indexOf(child);
|
|
1663
|
+
return index < 0 ? 0 : (children.length - index - 1) * 10;
|
|
1664
|
+
};
|
|
1665
|
+
var wrap = (selection, maxWidth = nodeContentWidth) => {
|
|
1666
|
+
selection.each(function(d) {
|
|
1667
|
+
const t = select(this);
|
|
1668
|
+
const units = ellipsis(d.data.units, 20);
|
|
1669
|
+
d3wrap(t, {
|
|
1670
|
+
maxWidth,
|
|
1671
|
+
maxLines: d.data.showBar ? 2 : 3,
|
|
1672
|
+
vcentre: !d.data.showBar,
|
|
1673
|
+
words: [...d.data.label.split(/\s+/), units],
|
|
1674
|
+
ownLineWord: units,
|
|
1675
|
+
nHeight: nodeHeight,
|
|
1676
|
+
lHeight: lineHeight
|
|
1677
|
+
});
|
|
1678
|
+
});
|
|
1679
|
+
};
|
|
1680
|
+
var setNodeSize = (selection) => selection.attr("width", nodeWidth).attr("height", nodeHeight);
|
|
1681
|
+
var formatBar = (selection) => selection.attr("height", barHeight).attr("rx", `${barRadius}`).attr("ry", `${barRadius}`).attr("y", nodeHeight - nodePadding - barHeight).attr("x", nodePadding);
|
|
1682
|
+
var formatBarPath = (data2) => {
|
|
1683
|
+
const x = nodePadding;
|
|
1684
|
+
const y = nodeHeight - nodePadding - barHeight;
|
|
1685
|
+
const h = barHeight;
|
|
1686
|
+
const w = fractionWidth(data2);
|
|
1687
|
+
const isFull = data2.fraction >= 0.99;
|
|
1688
|
+
const safeW = data2.fraction < 0.01 ? 0 : Math.max(w, barRadius);
|
|
1689
|
+
const safeH = Math.max(h, 2 * barRadius);
|
|
1690
|
+
return isFull ? `
|
|
1691
|
+
M ${x + safeW - barRadius}, ${y}
|
|
1692
|
+
a ${barRadius},${barRadius} 0 0 1 ${barRadius},${barRadius} v ${safeH - 2 * barRadius}
|
|
1693
|
+
a ${barRadius},${barRadius} 0 0 1 -${barRadius},${barRadius} h -${safeW - 2 * barRadius}
|
|
1694
|
+
a ${barRadius},${barRadius} 0 0 1 -${barRadius},-${barRadius} v -${safeH - 2 * barRadius}
|
|
1695
|
+
a ${barRadius},${barRadius} 0 0 1 ${barRadius},-${barRadius}
|
|
1696
|
+
z
|
|
1697
|
+
` : `
|
|
1698
|
+
M ${x + safeW}, ${y}
|
|
1699
|
+
v ${safeH}
|
|
1700
|
+
h -${safeW - barRadius}
|
|
1701
|
+
a ${barRadius},${barRadius} 0 0 1 -${barRadius},-${barRadius}
|
|
1702
|
+
v -${safeH - 2 * barRadius}
|
|
1703
|
+
a ${barRadius},${barRadius} 0 0 1 ${barRadius},-${barRadius}
|
|
1704
|
+
z
|
|
1705
|
+
`;
|
|
1706
|
+
};
|
|
1707
|
+
var groupButtonWidth = nodeWidth * 2 / 7;
|
|
1708
|
+
var groupButtonHeight = nodePadding * 2 + lineHeight + 3;
|
|
1709
|
+
var setGroupButtonXY = (selection) => selection.attr("width", groupButtonWidth).attr("height", (d) => d._groupOpen ? lineHeight + nodePadding * 2 : groupButtonHeight).attr("x", (d) => d._groupOpen ? nodeWidth * 2.5 / 7 : nodeWidth / 2 - groupButtonWidth / 2).attr(
|
|
1710
|
+
"y",
|
|
1711
|
+
(d) => d._groupOpen ? groupOverlap + (groupNodeSpace - (lineHeight + nodePadding * 2)) / 2 : nodePadding + lineHeight + 6
|
|
1712
|
+
);
|
|
1713
|
+
var formatNumber = (value) => isNumber(value) ? toPrecision(value, 3) : "N/A";
|
|
1714
|
+
var isTypeWithoutValue = (nodeDatum) => ["input" /* input */, "operation" /* operation */].includes(nodeDatum.data.type);
|
|
1715
|
+
var getWeightedValueText = (nodeDatum) => {
|
|
1716
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
1717
|
+
return `${formatNumber(nodeDatum.data.weightedValue)}${((_a = nodeDatum.parent) == null ? void 0 : _a.data.value) === null ? "" : ` of ${formatNumber(((_b = nodeDatum.parent) == null ? void 0 : _b.data.value) || ((_c = nodeDatum.parent) == null ? void 0 : _c.data.weightedValue) || 0)}`} ${isNumber((_d = nodeDatum.parent) == null ? void 0 : _d.data.value) ? (_f = (_e = nodeDatum.parent) == null ? void 0 : _e.data) == null ? void 0 : _f.units : (_i = (_h = (_g = nodeDatum.parent) == null ? void 0 : _g.parent) == null ? void 0 : _h.data) == null ? void 0 : _i.units}`;
|
|
1718
|
+
};
|
|
1719
|
+
var generateTipData = (nodeDatum, { baseUrl, schemaBaseUrl }) => {
|
|
1720
|
+
var _a;
|
|
1721
|
+
return {
|
|
1722
|
+
hasWeightedValue: nodeDatum.data.weightedValue,
|
|
1723
|
+
weightedValueText: getWeightedValueText(nodeDatum),
|
|
1724
|
+
valueKeyHref: `${schemaBaseUrl()}/${SchemaType.Indicator}#value`,
|
|
1725
|
+
modelDocsHref: nodeDatum.data.modelDocs,
|
|
1726
|
+
valueValueText: isNumber(nodeDatum.data.value) ? `${formatNumber(nodeDatum.data.value)} ${nodeDatum.data.units}` : isTypeWithoutValue(nodeDatum) ? "" : "no contribution",
|
|
1727
|
+
hasValue: nodeDatum.data.value,
|
|
1728
|
+
modelKeyHref: `${schemaBaseUrl()}/${SchemaType.Indicator}#methodModel`,
|
|
1729
|
+
modelValue: nodeDatum.data.modelName,
|
|
1730
|
+
modelValueHref: `${baseUrl()}/term/${nodeDatum.data.modelId}`,
|
|
1731
|
+
termKeyHref: `${schemaBaseUrl()}/${SchemaType.Indicator}#term`,
|
|
1732
|
+
terms: (_a = nodeDatum.data.terms) == null ? void 0 : _a.map((term2) => ({
|
|
1733
|
+
text: term2.name || term2["@id"],
|
|
1734
|
+
href: `${baseUrl()}/term/${term2["@id"]}`
|
|
1735
|
+
}))
|
|
1736
|
+
};
|
|
1737
|
+
};
|
|
1738
|
+
var addTooltip = (selection, { tooltip, urls }) => {
|
|
1739
|
+
let hoveringTip = false;
|
|
1740
|
+
let hoveringNode = false;
|
|
1741
|
+
let lastNodeId;
|
|
1742
|
+
const closeTip = () => {
|
|
1743
|
+
tooltip.close();
|
|
1744
|
+
selectAll(".tip-target").classed("tip-target", false);
|
|
1745
|
+
lastNodeId = null;
|
|
1746
|
+
};
|
|
1747
|
+
const startCloseTip = () => {
|
|
1748
|
+
setTimeout(() => {
|
|
1749
|
+
if (!hoveringTip && !hoveringNode) {
|
|
1750
|
+
closeTip();
|
|
1751
|
+
}
|
|
1752
|
+
}, 100);
|
|
1753
|
+
};
|
|
1754
|
+
selection.on("click", closeTip).on("pointerenter", (event, nodeDatum) => {
|
|
1755
|
+
hoveringNode = true;
|
|
1756
|
+
if (lastNodeId === nodeDatum.data.id) {
|
|
1757
|
+
return;
|
|
1758
|
+
}
|
|
1759
|
+
closeTip();
|
|
1760
|
+
lastNodeId = nodeDatum.data.id;
|
|
1761
|
+
select(event.target).classed("tip-target", true);
|
|
1762
|
+
tooltip.open(generateTipData(nodeDatum, urls), event.target);
|
|
1763
|
+
select(tooltip.element()).on("pointerenter", () => {
|
|
1764
|
+
hoveringTip = true;
|
|
1765
|
+
}).on("pointerleave", () => {
|
|
1766
|
+
hoveringTip = false;
|
|
1767
|
+
startCloseTip();
|
|
1768
|
+
});
|
|
1769
|
+
}).on("pointerleave", () => {
|
|
1770
|
+
hoveringNode = false;
|
|
1771
|
+
startCloseTip();
|
|
1772
|
+
});
|
|
1773
|
+
return selection;
|
|
1774
|
+
};
|
|
1775
|
+
var mergedNodes = (selection, options) => {
|
|
1776
|
+
selection.selectAll(".node-box").classed("node-openable", (d) => !!d.height).classed("node-open", (d) => d._open).call(setNodeSize).attr("stroke", ({ data: data2 }) => nodeBorderColours[data2.type]).attr("fill", (d) => nodeColours[d.data.type]).attr("stroke-width", "2").attr("rx", "3px").attr("ry", "3px").style("user-select", typeof window !== "undefined" && window.innerWidth < 768 ? "none" : null);
|
|
1777
|
+
selection.filter((d) => !d.data.group && d.data.id !== nonLCAIndicatorsId).call(addTooltip, options);
|
|
1778
|
+
selection.selectAll(".node-label").attr("y", (d) => d.data.showBar ? lineHeight / 2 + nodePadding / 2 : 0).attr("x", nodeWidth / 2).attr("font-weight", "bold").attr("fill", "#4a4a4a").attr("text-anchor", "middle").attr("alignment-baseline", "central");
|
|
1779
|
+
const groups = selection.filter((d) => d.data.group);
|
|
1780
|
+
groups.selectAll(".group-border").attr("width", nodeWidth + nodePadding * 2).attr("x", -nodePadding).attr("y", nodeHeight - (lineHeight + nodePadding * 2)).attr("opacity", (d) => d._greyed && d._groupOpen ? greyedOpacity : d._groupOpen ? 1 : 0).attr(
|
|
1781
|
+
"height",
|
|
1782
|
+
(d) => d._groupOpen ? Math.abs(d.parent._groupChildren.length - d.parent.children.length) * (nodeHeight * nodeSpacingFactor) + nodePadding + (lineHeight + nodePadding * 2) / 2 : 0
|
|
1783
|
+
).attr("fill", "none").attr("stroke-dasharray", "2, 5").attr("stroke", "#b5b5b5").attr("stroke-width", "1");
|
|
1784
|
+
groups.selectAll(".group-label").attr("width", nodeWidth).attr("y", nodeHeight / 3).attr("x", nodeWidth / 2).text((d) => d.data.label).attr("opacity", (d) => d._groupOpen ? 0 : d._greyed ? greyedOpacity : 1).attr("fill", "#4a4a4a").attr("text-anchor", "middle").attr("alignment-baseline", "central").attr("font-weight", "bold");
|
|
1785
|
+
groups.selectAll(".group-button-border").attr("width", nodeWidth).attr("x", 0).attr("y", 0).attr("height", (d) => d._groupOpen ? "0" : "45").attr("fill", "none").attr("stroke-dasharray", "2, 5").attr("stroke", "#b5b5b5").attr("stroke-width", "1");
|
|
1786
|
+
groups.selectAll(".group-button").call(setGroupButtonXY).style("pointer-events", (d) => d._greyed ? "none" : "auto").style("stroke-opacity", (d) => d._greyed ? greyedOpacity : 1).style("display", "none").attr("fill", (d) => nodeColours[d.data.type]).attr("rx", "3px").attr("ry", "3px");
|
|
1787
|
+
groups.selectAll(".group-button-text").text((d) => d._groupOpen ? "Hide" : "Show").style("display", "none").attr("x", (d) => d._groupOpen ? nodeWidth / 2 : nodeWidth / 2).attr("y", (d) => d._groupOpen ? nodeMargin + lineHeight + lineHeight / 2 : nodeHeight / 3 + groupButtonHeight - 4).attr("font-size", () => `${fontSize * 1.3}px`).attr("fill", "#4a4a4a").attr("text-anchor", "middle").attr("alignment-baseline", "central");
|
|
1788
|
+
selection.selectAll(".sibling-mask").remove();
|
|
1789
|
+
selection.filter((d) => d._greyed && !d.data.group).append("rect").classed("node-openable", (d) => !!d.height).classed("sibling-mask mask-openable", true).attr("fill", "rgba(250, 250, 250)").attr("stroke", "rgba(250, 250, 250)").attr("fill-opacity", "0.8").attr("stroke-opacity", "0.9").attr("stroke-width", "2").attr("rx", "3px").attr("ry", "3px").call(setNodeSize);
|
|
1790
|
+
selection.filter((d) => d._greyed && d.data.group).append("rect").classed("sibling-mask", true).attr("fill", "rgba(250, 250, 250)").attr("stroke", "rgba(250, 250, 250)").attr("fill-opacity", "0.8").attr("stroke-opacity", "0.9").attr("stroke-width", "2").attr("rx", "3px").attr("ry", "3px").call(setGroupButtonXY);
|
|
1791
|
+
return selection;
|
|
1792
|
+
};
|
|
1793
|
+
var fractionCutoff = 0.7;
|
|
1794
|
+
var fractionWidth = (data2) => Math.min(Math.abs(data2.fraction), 1) * nodeContentWidth;
|
|
1795
|
+
var enterNodes = (selection) => {
|
|
1796
|
+
const nodes = selection.filter((d) => d.data.type !== "root" /* root */ && !d.data.group);
|
|
1797
|
+
nodes.append("rect").classed("node-box", true);
|
|
1798
|
+
nodes.append("text").classed("node-label", true).text(({ data: data2 }) => data2.label + " " + data2.units).attr("opacity", 0);
|
|
1799
|
+
setTimeout(() => {
|
|
1800
|
+
const labels = nodes.selectAll(".node-label").call(wrap);
|
|
1801
|
+
labels.attr("opacity", 1);
|
|
1802
|
+
});
|
|
1803
|
+
const groups = selection.filter((d) => d.data.group);
|
|
1804
|
+
groups.append("rect").attr("class", "group-border");
|
|
1805
|
+
groups.append("text").attr("class", "group-label");
|
|
1806
|
+
groups.append("rect").attr("class", "group-button-border");
|
|
1807
|
+
groups.append("rect").attr("class", "group-button");
|
|
1808
|
+
groups.append("text").attr("class", "group-button-text");
|
|
1809
|
+
const withBar = nodes.filter((d) => d.data.showBar);
|
|
1810
|
+
withBar.filter((d) => d.data.fraction > 0).append("rect").classed("bar-background", true).attr("width", nodeContentWidth).attr("fill", "white").call(formatBar);
|
|
1811
|
+
withBar.append("path").classed("bar-foreground", true).attr("d", (d) => formatBarPath(d.data)).attr("fill", (d) => nodeBorderColours[d.data.type]);
|
|
1812
|
+
withBar.append("text").classed("bar-label", true).text(
|
|
1813
|
+
({ data: data2 }) => data2.fraction === 0 ? "no contribution" : `${Math.round(100 * data2.fraction)}% ${Math.round(100 * data2.fraction) === 0 ? " (rounded)" : ""}`
|
|
1814
|
+
).attr("text-anchor", (d) => d.data.fraction === 0 ? "middle" : d.data.fraction > fractionCutoff ? "end" : "start").attr(
|
|
1815
|
+
"x",
|
|
1816
|
+
(d) => d.data.fraction === 0 ? nodeWidth / 2 : fractionWidth(d.data) + nodePadding + (d.data.fraction > fractionCutoff ? -4 : 4)
|
|
1817
|
+
).attr("y", nodeHeight - nodePadding - barHeight / 2).attr("fill", "#4a4a4a").attr("alignment-baseline", "central");
|
|
1818
|
+
return selection;
|
|
1819
|
+
};
|
|
1820
|
+
var mergedLinks = (selection, { togglingGroup, switchingSelection }) => selection.style("stroke", (d) => d.target._open && nodeBorderColours[d.source.data.type] || null).attr("stroke-dasharray", (d) => d.target.data.group ? "2,5" : null).attr("stroke-width", (d) => d.target._open ? 2 : d.target._groupOpen ? 0 : 1).attr("stroke-opacity", (d) => d.target._greyed ? 0.1 : 0.6).transition().duration(linkDuration).delay((d) => {
|
|
1821
|
+
var _a;
|
|
1822
|
+
const groupClosing = togglingGroup && !((_a = childrenOf(d.target.parent).find((n) => n.data.group)) == null ? void 0 : _a._groupOpen);
|
|
1823
|
+
return groupClosing ? groupDelay : (switchingSelection ? nodeDuration : 0) + enterStagger(d.source, d.target);
|
|
1824
|
+
}).attr("opacity", 1).attr("d", (d) => {
|
|
1825
|
+
const targetDy = d.target._groupOpen ? nodePadding + lineHeight / 2 + nodeMargin * 2 : nodeHeight / 2;
|
|
1826
|
+
return linkHorizontal()({
|
|
1827
|
+
source: [d.source.y + nodeWidth, d.source.x + nodeHeight / 2],
|
|
1828
|
+
target: [d.target.y, d.target.x + targetDy]
|
|
1829
|
+
});
|
|
1830
|
+
});
|
|
1831
|
+
var groupByPercentage = (node) => ["endpoint" /* endpoint */, "midpoint" /* midpoint */, "emission" /* emission */].includes(node.data.type);
|
|
1832
|
+
var groupPercentage = (node, percentage = 0.02) => {
|
|
1833
|
+
var _a, _b, _c;
|
|
1834
|
+
const grouped = (_a = node._children) == null ? void 0 : _a.filter((child) => child.data.fraction < percentage);
|
|
1835
|
+
if (((_b = node._children) == null ? void 0 : _b.length) > 6 && (grouped == null ? void 0 : grouped.length) > 1) {
|
|
1836
|
+
const type = node._children[0].data.type;
|
|
1837
|
+
const groupNode = {
|
|
1838
|
+
...hierarchy({
|
|
1839
|
+
id: `${type}-group`,
|
|
1840
|
+
label: `${grouped.length} more under ${Math.round(percentage * 100)}%.`,
|
|
1841
|
+
weightedValue: null,
|
|
1842
|
+
value: null,
|
|
1843
|
+
type,
|
|
1844
|
+
group: true
|
|
1845
|
+
}),
|
|
1846
|
+
depth: node.depth + 1,
|
|
1847
|
+
height: 1,
|
|
1848
|
+
parent: node
|
|
1849
|
+
};
|
|
1850
|
+
const notGrouped = (_c = node._children) == null ? void 0 : _c.filter((child) => child.data.fraction >= percentage);
|
|
1851
|
+
node._children.splice(notGrouped.length, 0, groupNode);
|
|
1852
|
+
notGrouped.push(groupNode);
|
|
1853
|
+
node._groupChildren = node._children;
|
|
1854
|
+
node._children = notGrouped;
|
|
1855
|
+
}
|
|
1856
|
+
};
|
|
1857
|
+
var showBar = (node) => {
|
|
1858
|
+
var _a, _b;
|
|
1859
|
+
return [
|
|
1860
|
+
((_a = node.parent) == null ? void 0 : _a.data.type) !== "root" /* root */,
|
|
1861
|
+
((_b = node.parent) == null ? void 0 : _b.data.id) !== nonLCAIndicatorsId,
|
|
1862
|
+
!node.data.group,
|
|
1863
|
+
!isNaN(node.data.fraction)
|
|
1864
|
+
].every(Boolean);
|
|
1865
|
+
};
|
|
1866
|
+
var totalWidthFor = (numLayers) => {
|
|
1867
|
+
const chartWidth = numLayers * nodeWidth + (numLayers - 1) * layerSpacing;
|
|
1868
|
+
return { chartWidth, totalWidth: Math.max(width, chartWidth + margin.left + margin.right) };
|
|
1869
|
+
};
|
|
1870
|
+
var hierarchyOf = (children) => hierarchy({
|
|
1871
|
+
id: "dummy-root",
|
|
1872
|
+
label: "",
|
|
1873
|
+
terms: [],
|
|
1874
|
+
modelId: "",
|
|
1875
|
+
value: null,
|
|
1876
|
+
weightedValue: null,
|
|
1877
|
+
units: "",
|
|
1878
|
+
type: "root" /* root */,
|
|
1879
|
+
children
|
|
1880
|
+
});
|
|
1881
|
+
var prepareRoot = (root, terms) => root.sort((a, b) => (b.data.fraction || -Infinity) - (a.data.fraction || -Infinity)).eachAfter((node) => {
|
|
1882
|
+
var _a, _b;
|
|
1883
|
+
if (node.data.fraction === 0) {
|
|
1884
|
+
node.children = null;
|
|
1885
|
+
node.height = 0;
|
|
1886
|
+
}
|
|
1887
|
+
node.data.type !== "root" /* root */ && collapseChildren(node);
|
|
1888
|
+
groupByPercentage(node) && groupPercentage(node);
|
|
1889
|
+
node.data.showBar = showBar(node);
|
|
1890
|
+
node.data.modelName = (_a = terms == null ? void 0 : terms[node.data.modelId]) == null ? void 0 : _a.name;
|
|
1891
|
+
node.data.modelDocs = (_b = terms == null ? void 0 : terms[node.data.modelId]) == null ? void 0 : _b.docs;
|
|
1892
|
+
});
|
|
1893
|
+
var drawHierarchyChart = (svgElement, { data: data2, terms, tooltip, urls }) => {
|
|
1894
|
+
const tree$1 = hierarchyOf(hierarchyChildren(data2, terms));
|
|
1895
|
+
const includedTypes = unique(tree$1.descendants().map((n) => n.data.type));
|
|
1896
|
+
const root = prepareRoot(tree$1, terms);
|
|
1897
|
+
const numLayers = hierarchyLegend(data2, includedTypes).length;
|
|
1898
|
+
const { chartWidth, totalWidth } = totalWidthFor(numLayers);
|
|
1899
|
+
const svg = select(svgElement);
|
|
1900
|
+
svg.selectAll("*").remove();
|
|
1901
|
+
const innerHeight = (root.children.length - 1) * nodeHeight * nodeSpacingFactor + nodeHeight;
|
|
1902
|
+
const yMin = (nodeHeight - innerHeight) / 2 - margin.top;
|
|
1903
|
+
const yTotal = innerHeight + margin.top + margin.bottom;
|
|
1904
|
+
svg.attr("viewBox", [-20, yMin, totalWidth, yTotal].join(" ")).attr("font-size", `${fontSize}px`);
|
|
1905
|
+
let node = svg.append("g").selectAll("g");
|
|
1906
|
+
let link = svg.append("g").selectAll("path");
|
|
1907
|
+
const updateChart = ({ togglingGroup = false, switchingSelection = false } = {}) => {
|
|
1908
|
+
const treeLayout = tree().nodeSize([nodeHeight, nodeWidth]).separation(
|
|
1909
|
+
(...neighbours) => neighbours.some((n) => n.data.group && n._groupOpen) ? 1 - groupOverlap / nodeHeight : nodeSpacingFactor
|
|
1910
|
+
);
|
|
1911
|
+
const tree2 = treeLayout(root);
|
|
1912
|
+
const links = tree2.links().filter((d) => d.source.data.type !== "root" /* root */) || [];
|
|
1913
|
+
const nodes = tree2.descendants().reverse();
|
|
1914
|
+
const offset = Math.max(0, (totalWidth - margin.left - margin.right - chartWidth) / 2);
|
|
1915
|
+
nodes.forEach((d) => {
|
|
1916
|
+
d.y = offset + (d.depth - 1) * (nodeWidth + layerSpacing);
|
|
1917
|
+
});
|
|
1918
|
+
const minimiseHeight = ({ previousTanslateY, previousYTotal }, depth) => {
|
|
1919
|
+
var _a, _b, _c;
|
|
1920
|
+
const maxYTotal = Math.max(previousYTotal, yTotal);
|
|
1921
|
+
const parent = tree2.find((n) => n.depth === depth - 1 && n._open);
|
|
1922
|
+
(_a = parent == null ? void 0 : parent.children) == null ? void 0 : _a.forEach((n) => n.x += previousTanslateY);
|
|
1923
|
+
const { min: minX, max: maxX } = ((parent == null ? void 0 : parent.children) || []).reduce(
|
|
1924
|
+
({ min, max }, n) => ({
|
|
1925
|
+
min: n.x < min ? n.x : min,
|
|
1926
|
+
max: n.x > max ? n.x : max
|
|
1927
|
+
}),
|
|
1928
|
+
{ min: parent == null ? void 0 : parent.x, max: parent == null ? void 0 : parent.x }
|
|
1929
|
+
);
|
|
1930
|
+
const { yMin: childrenYMin, yTotal: childrenYTotal } = ((_b = parent == null ? void 0 : parent.children) == null ? void 0 : _b.length) ? { yMin: minX - margin.top, yTotal: maxX - minX + margin.bottom + margin.top + nodeHeight } : { yMin: null, yTotal: null };
|
|
1931
|
+
const overflowAbove = yMin - childrenYMin;
|
|
1932
|
+
const overflowBelow = childrenYTotal + childrenYMin - (maxYTotal + yMin);
|
|
1933
|
+
const translateY = overflowAbove > 0 ? overflowAbove : overflowBelow > 0 ? Math.max(-overflowBelow, overflowAbove) : 0;
|
|
1934
|
+
(_c = parent == null ? void 0 : parent.children) == null ? void 0 : _c.forEach((n) => n.x += translateY);
|
|
1935
|
+
return {
|
|
1936
|
+
previousYTotal: Math.max(childrenYTotal, previousYTotal),
|
|
1937
|
+
previousTanslateY: translateY + previousTanslateY
|
|
1938
|
+
};
|
|
1939
|
+
};
|
|
1940
|
+
const depths = Array.from({ length: numLayers }, (_, i) => i + 2);
|
|
1941
|
+
const { previousYTotal: newHeight } = depths.reduce(minimiseHeight, {
|
|
1942
|
+
previousTanslateY: 0,
|
|
1943
|
+
previousYTotal: yTotal
|
|
1944
|
+
});
|
|
1945
|
+
svg.transition().duration(300).attr("viewBox", [-20, yMin, totalWidth, newHeight].join(" "));
|
|
1946
|
+
node = node.data(
|
|
1947
|
+
nodes.filter((d) => d.data.type !== "root" /* root */),
|
|
1948
|
+
(d) => {
|
|
1949
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1950
|
+
return `${(_c = (_b = (_a = d.parent) == null ? void 0 : _a.parent) == null ? void 0 : _b.parent) == null ? void 0 : _c.data.id}-${(_e = (_d = d.parent) == null ? void 0 : _d.parent) == null ? void 0 : _e.data.id}-${(_f = d.parent) == null ? void 0 : _f.data.id}-${d.data.id}`;
|
|
1951
|
+
}
|
|
1952
|
+
).join(
|
|
1953
|
+
(enter) => enter.append("g").call(enterNodes).attr("opacity", 0).attr("transform", (d) => "translate(" + (togglingGroup ? d.y : d.y - nodeWidth / 2) + "," + d.x + ")"),
|
|
1954
|
+
(update) => update,
|
|
1955
|
+
(exit) => {
|
|
1956
|
+
tooltip.close();
|
|
1957
|
+
return exit.style("pointer-events", "none").transition().duration(nodeDuration / 2).delay((d) => exitStagger(d.parent, d)).ease(easeElasticIn.amplitude(0.5).period(1)).attr("transform", (d) => "translate(" + (togglingGroup ? d.y : d.y - nodeWidth / 2) + "," + d.x + ")").attr("opacity", 0).remove().on("end", () => tooltip.close());
|
|
1958
|
+
}
|
|
1959
|
+
);
|
|
1960
|
+
node.call(mergedNodes, { tooltip, urls }).transition().delay((d) => {
|
|
1961
|
+
var _a;
|
|
1962
|
+
const groupClosing = togglingGroup && !((_a = childrenOf(d.parent).find((n) => n.data.group)) == null ? void 0 : _a._groupOpen);
|
|
1963
|
+
return groupClosing ? groupDelay : (switchingSelection ? nodeDuration : 0) + enterStagger(d.parent, d);
|
|
1964
|
+
}).duration(nodeDuration).ease(easeElasticOut.amplitude(0.5).period(1)).attr("transform", (d) => `translate(${d.y},${d.x})`).attr("opacity", 1);
|
|
1965
|
+
node.selectAll(".node-openable").on("click", handleNodeClick);
|
|
1966
|
+
node.selectAll(".group-button").on("click", handleGroupButtonClick);
|
|
1967
|
+
link = link.data(
|
|
1968
|
+
links,
|
|
1969
|
+
(d) => {
|
|
1970
|
+
var _a, _b, _c;
|
|
1971
|
+
return `${(_b = (_a = d.source.parent) == null ? void 0 : _a.parent) == null ? void 0 : _b.data.id}-${(_c = d.source.parent) == null ? void 0 : _c.data.id}-${d.source.data.id}-${d.target.data.id}`;
|
|
1972
|
+
}
|
|
1973
|
+
).join(
|
|
1974
|
+
(enter) => enter.append("path").classed("link-path", true).attr("opacity", 0).attr("fill", "none").attr("stroke", "#b5b5b5"),
|
|
1975
|
+
(update) => update,
|
|
1976
|
+
(exit) => exit.transition().duration(linkDuration / 2).delay((d) => exitStagger(d.source, d.target)).attr("opacity", 0).remove()
|
|
1977
|
+
).call(mergedLinks, { togglingGroup, switchingSelection });
|
|
1978
|
+
};
|
|
1979
|
+
const handleGroupButtonClick = (_event, d) => {
|
|
1980
|
+
if (d._greyed) {
|
|
1981
|
+
return;
|
|
1982
|
+
}
|
|
1983
|
+
d._groupOpen = !d._groupOpen;
|
|
1984
|
+
const children = d.parent._groupChildren;
|
|
1985
|
+
d.parent._groupChildren = d.parent.children;
|
|
1986
|
+
d.parent.children = children;
|
|
1987
|
+
updateChart({ togglingGroup: true });
|
|
1988
|
+
};
|
|
1989
|
+
const handleNodeClick = (_event, d) => {
|
|
1990
|
+
d._open = !d._open;
|
|
1991
|
+
const switchingSelection = "_greyed" in d && d._greyed;
|
|
1992
|
+
childrenOf(d.parent).forEach((child) => {
|
|
1993
|
+
child._greyed = false;
|
|
1994
|
+
if (child !== d) {
|
|
1995
|
+
child._open = false;
|
|
1996
|
+
collapseChildren(child);
|
|
1997
|
+
child._greyed = d._open;
|
|
1998
|
+
}
|
|
1999
|
+
});
|
|
2000
|
+
d.children ? collapseChildren(d) : restoreChildren(d);
|
|
2001
|
+
updateChart({ switchingSelection });
|
|
2002
|
+
};
|
|
2003
|
+
handleNodeClick(
|
|
2004
|
+
null,
|
|
2005
|
+
root.find((n) => n.depth === 1)
|
|
2006
|
+
);
|
|
2007
|
+
return { includedTypes };
|
|
2008
|
+
};
|
|
2009
|
+
var defaultHorizontalBarConfig = Object.freeze({
|
|
2010
|
+
options: {
|
|
2011
|
+
indexAxis: "y",
|
|
2012
|
+
scales: {
|
|
2013
|
+
x: {
|
|
2014
|
+
grid: {
|
|
2015
|
+
drawOnChartArea: false
|
|
2016
|
+
},
|
|
2017
|
+
ticks: {
|
|
2018
|
+
callback: (value) => toPrecision(Number(value), 3)
|
|
2019
|
+
}
|
|
2020
|
+
},
|
|
2021
|
+
y: {
|
|
2022
|
+
grid: {
|
|
2023
|
+
drawOnChartArea: false
|
|
2024
|
+
},
|
|
2025
|
+
ticks: {
|
|
2026
|
+
crossAlign: "near"
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
});
|
|
2032
|
+
var defaultHorizontalTooltip = ({ label, count, includedItems }) => (includedItems == null ? void 0 : includedItems.length) ? includedItems.map(defaultHorizontalTooltip).join("</br>") : `${label}: ${count}`;
|
|
2033
|
+
var rowHeight = 40;
|
|
2034
|
+
var padding = 60;
|
|
2035
|
+
var horizontalBarHeight = (minHeight, maxHeight, nbRows) => Math.min(Math.max(nbRows * rowHeight + padding, minHeight), maxHeight);
|
|
673
2036
|
var logsTotalValue = (logs, includeNegativeValues = true) => sum((includeNegativeValues ? logs : logs.filter((curr) => curr.value >= 0)).map((v) => v.value));
|
|
674
2037
|
var valueRatio = (value, total) => total === 0 ? 0 : toPrecision(value * 100 / Math.abs(total), 2);
|
|
675
2038
|
var chartLabel = (value, total) => {
|
|
@@ -694,7 +2057,7 @@ var logToCsv = (logs, impact) => [
|
|
|
694
2057
|
}
|
|
695
2058
|
)
|
|
696
2059
|
].join("\n");
|
|
697
|
-
var
|
|
2060
|
+
var processContributions2 = (record, nodes, impacts) => Object.entries(record || {}).flatMap(([index, impactContr]) => {
|
|
698
2061
|
const indicator = nodes[+index];
|
|
699
2062
|
return indicator ? Object.entries(impactContr).flatMap(([impactTermId, modelContr]) => {
|
|
700
2063
|
const impact = impacts == null ? void 0 : impacts.find((v) => v.term["@id"] === impactTermId);
|
|
@@ -714,12 +2077,12 @@ var processContributions = (record, nodes, impacts) => Object.entries(record ||
|
|
|
714
2077
|
}) : null;
|
|
715
2078
|
});
|
|
716
2079
|
var parseContributions = (impactAssessment, contributions) => [
|
|
717
|
-
...
|
|
2080
|
+
...processContributions2(
|
|
718
2081
|
contributions == null ? void 0 : contributions.emissionsResourceUse,
|
|
719
2082
|
impactAssessment.emissionsResourceUse || [],
|
|
720
2083
|
(impactAssessment == null ? void 0 : impactAssessment.impacts) || []
|
|
721
2084
|
),
|
|
722
|
-
...
|
|
2085
|
+
...processContributions2(contributions == null ? void 0 : contributions.impacts, impactAssessment.impacts || [], (impactAssessment == null ? void 0 : impactAssessment.endpoints) || [])
|
|
723
2086
|
].filter(Boolean);
|
|
724
2087
|
var groupLogsById = (logs) => Object.values(
|
|
725
2088
|
logs.reduce(
|
|
@@ -763,8 +2126,8 @@ var breakdownBars = (values, category) => {
|
|
|
763
2126
|
};
|
|
764
2127
|
var breakdownBarText = (displayValue, total, maximumValues) => displayValue ? { textFn: ({ label, data: data2 }, index) => index === maximumValues ? `${label} at ${data2}` : `${data2}` } : { textFn: ({ data: data2 }) => `${valueRatio(data2, total)}%` };
|
|
765
2128
|
var breakdownTooltip = (item, includedItems, logs, total, maximumValues) => !isUndefined(item == null ? void 0 : item.count) ? (includedItems == null ? void 0 : includedItems.length) ? chartBreakdownLabel(logs, total, maximumValues) : `${item.label}: ${chartLabel(item.count, total)}` : "";
|
|
766
|
-
var breakdownScale = (values) => {
|
|
767
|
-
const numbers = values.map((
|
|
2129
|
+
var breakdownScale = (values, category) => {
|
|
2130
|
+
const numbers = breakdownBars(values, category).map(({ count }) => count);
|
|
768
2131
|
return {
|
|
769
2132
|
options: {
|
|
770
2133
|
scales: {
|
|
@@ -778,4 +2141,39 @@ var breakdownScale = (values) => {
|
|
|
778
2141
|
};
|
|
779
2142
|
};
|
|
780
2143
|
|
|
781
|
-
|
|
2144
|
+
// src/charts/line.ts
|
|
2145
|
+
var defaultLineSettings = Object.freeze({
|
|
2146
|
+
type: "line",
|
|
2147
|
+
options: {
|
|
2148
|
+
scales: {
|
|
2149
|
+
x: {
|
|
2150
|
+
type: "time",
|
|
2151
|
+
time: {
|
|
2152
|
+
unit: "day"
|
|
2153
|
+
},
|
|
2154
|
+
ticks: {
|
|
2155
|
+
font: defaultTicksFont
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
});
|
|
2161
|
+
var registerChart = (items = []) => () => {
|
|
2162
|
+
Chart.register(
|
|
2163
|
+
BarController,
|
|
2164
|
+
LineController,
|
|
2165
|
+
CategoryScale,
|
|
2166
|
+
LinearScale,
|
|
2167
|
+
PointElement,
|
|
2168
|
+
BarElement,
|
|
2169
|
+
LineElement,
|
|
2170
|
+
Title,
|
|
2171
|
+
Tooltip,
|
|
2172
|
+
Legend,
|
|
2173
|
+
TimeScale,
|
|
2174
|
+
annotationPlugin,
|
|
2175
|
+
...items
|
|
2176
|
+
);
|
|
2177
|
+
};
|
|
2178
|
+
|
|
2179
|
+
export { ChartNodeType, ColorPalette, afterBarDrawPlugin, axisHoverPlugin, axisLabelCursor, backgroundHoverPlugin, barDataset, breakdownBarText, breakdownBars, breakdownScale, breakdownTooltip, categoryLabel, categoryValues, chartBreakdownLabel, chartLabel, chartLabelMaxLength, colorToRgba, contributionBarHeight, contributionChartConfig, contributionChartHeight, contributionCsv, contributionData, contributionHoverHeight, contributionItemColor, convertToSvg, d3ellipse, d3wrap, dataAtIndex, datasetCenter, defaultBarDatasets, defaultBarSettings, defaultChartSettings, defaultContributionSettings, defaultDistributionSettings, defaultHorizontalBarConfig, defaultHorizontalTooltip, defaultIndicatorsModelId, defaultIndicatorsModelName, defaultLineSettings, defaultModelId, defaultTicksFont, distributionChart, downloadFile, downloadPng, downloadSvg, drawHierarchyChart, emissionCategories, emissionCategoriesOf, emissionCategoryValue, escape, exportAsSVG, formatPrecision, getColor, getUnitsAndLabel, groupDataByCategory, groupIndicators, groupLogsById, hasNegativeContributions, hasNegativeValues, hexToRgba, hierarchyChildren, hierarchyContributions, hierarchyCsv, hierarchyData, hierarchyLegend, hierarchyModels, hierarchyTermTypes, hierarchyWarnings, horizontalBarHeight, increaseScaleLimits, indicatorLogs, isParametric, legend, linkIndicators, listColor, listColorContinuous, listColorWithAlpha, logToCsv, logsTotalValue, lollipopChartPlugin, missingTermIds, namedContributors, nodeBorderColours, nodeColours, nonLCAIndicatorsId, nonZeroContributors, parseColor, parseContributions, registerChart, sumIndicators, sumValues, toLine, valueRatio, withOthers };
|