@hestia-earth/ui-components-charts 0.43.23 → 0.43.24

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