@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/index.mjs CHANGED
@@ -1,7 +1,19 @@
1
- import { ellipsis, keyToLabel, toPrecision, sum, isUndefined, unique, isEmpty } from '@hestia-earth/utils';
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, colors) => {
148
- const start = colors[index % colors.length];
149
- const end = colors[index % colors.length + 1];
150
- const gradientIndex = Math.ceil(index / colors.length);
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, colors) => {
154
- const color = colors[index % colors.length];
155
- const shade = Math.min(100, Math.max(10, Math.abs(100 - Math.ceil(index / colors.length) * 10)));
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 colors = palette[scheme];
160
- return index < colors.length ? colors[index] : scheme === 0 /* categorial */ ? getColorShade(index, colors) : getColorGradient(index, colors);
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, width, chart, data2, placement) => (isEmpty(data2) ? positionAtZero(chart) : placementX(x, chart, data2, placement)) + placementGap(placement),
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, width, chart, dataToPass, placement);
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 barHeight = height;
413
- const barTop = y - barHeight / 2;
414
- const barBottom = y + barHeight / 2;
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: barHeight };
404
+ return { barLeft, barRight, barTop, barBottom, thickness: barHeight2 };
418
405
  } else {
419
- const barWidth = width;
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 margin = 0.1;
667
- const padding2 = safeRange * margin;
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/indicator-breakdown.ts
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 processContributions = (record, nodes, impacts) => Object.entries(record || {}).flatMap(([index, impactContr]) => {
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
- ...processContributions(
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
- ...processContributions(contributions == null ? void 0 : contributions.impacts, impactAssessment.impacts || [], (impactAssessment == null ? void 0 : impactAssessment.endpoints) || [])
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((v) => v.value);
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
- export { ColorPalette, afterBarDrawPlugin, axisHoverPlugin, axisLabelCursor, backgroundHoverPlugin, barDataset, breakdownBarText, breakdownBars, breakdownScale, breakdownTooltip, categoryLabel, categoryValues, chartBreakdownLabel, chartLabel, chartLabelMaxLength, colorToRgba, defaultBarDatasets, defaultBarSettings, defaultHorizontalBarConfig, defaultHorizontalTooltip, defaultTicksFont, emissionCategories, emissionCategoriesOf, emissionCategoryValue, escape, getColor, groupDataByCategory, groupLogsById, hasNegativeContributions, hexToRgba, horizontalBarHeight, increaseScaleLimits, indicatorLogs, listColor, listColorContinuous, listColorWithAlpha, logToCsv, logsTotalValue, lollipopChartPlugin, namedContributors, nonZeroContributors, parseColor, parseContributions, sumValues, toLine, valueRatio, withOthers };
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 };