@hestia-earth/ui-components 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.
@@ -10,10 +10,9 @@ import { ReplaySubject, of, mergeMap, shareReplay, delay, map, catchError, disti
10
10
  import { HttpClient } from '@angular/common/http';
11
11
  import get from 'lodash.get';
12
12
  import { SCHEMA_VERSION, SchemaType, NodeType, TermTermType, productTermTermType, nestedSearchableKeys, SiteSiteType, EmissionMethodTier, isExpandable, isTypeNode, sortKeysByType, uniquenessFields, BlankNodesKey, impactAssessmentTermTermType, measurementTermTermType, emissionTermTermType, inputTermTermType, CycleFunctionalUnit, NonBlankNodesKey, jsonldPath, isTypeValid, typeToSchemaType, infrastructureTermTermType, managementTermTermType } from '@hestia-earth/schema';
13
- import { axisLabelCursor, afterBarDrawPlugin, lollipopChartPlugin, hasNegativeContributions, withOthers, barDataset, defaultBarDatasets, defaultBarSettings, categoryLabel, emissionCategories, categoryValues, emissionCategoriesOf, emissionCategoryValue, chartLabelMaxLength, defaultTicksFont, toLine, groupDataByCategory, backgroundHoverPlugin, axisHoverPlugin, increaseScaleLimits, defaultHorizontalTooltip, horizontalBarHeight, defaultHorizontalBarConfig, listColor, getColor, parseContributions as parseContributions$1, indicatorLogs, nonZeroContributors, logsTotalValue, namedContributors, breakdownBars, breakdownTooltip, breakdownBarText, breakdownScale, logToCsv as logToCsv$2 } from '@hestia-earth/ui-components-charts';
14
- export { ColorPalette, afterBarDrawPlugin, axisHoverPlugin, axisLabelCursor, backgroundHoverPlugin, chartLabelMaxLength, colorToRgba, defaultTicksFont, getColor, groupDataByCategory, hexToRgba, increaseScaleLimits, listColor, listColorContinuous, listColorWithAlpha, lollipopChartPlugin, parseColor, sumValues } from '@hestia-earth/ui-components-charts';
15
- import { select, selectAll } from 'd3-selection';
16
- import { isEmpty, unique, monthsBefore, toPrecision, isNumber, isUndefined, toComma, duration, keyToLabel, sum, min, max, mean, median, ellipsis as ellipsis$1, getPercentileValue, isEqual as isEqual$2, diffInDays } from '@hestia-earth/utils';
13
+ import { formatPrecision, axisLabelCursor, defaultChartSettings, exportAsSVG, downloadFile, hasNegativeContributions, withOthers, barDataset, defaultBarDatasets, defaultBarSettings, categoryLabel, emissionCategories, categoryValues, emissionCategoriesOf, emissionCategoryValue, contributionItemColor, contributionChartHeight, groupDataByCategory, contributionChartConfig, hasNegativeValues, contributionData, defaultContributionSettings, contributionCsv, dataAtIndex, datasetCenter, distributionChart, defaultLineSettings, defaultHorizontalTooltip, horizontalBarHeight, lollipopChartPlugin, backgroundHoverPlugin, afterBarDrawPlugin, axisHoverPlugin, defaultHorizontalBarConfig, chartLabelMaxLength, listColor, getColor, nodeBorderColours, nodeColours as nodeColours$1, hierarchyLegend, downloadSvg, downloadPng, hierarchyCsv, drawHierarchyChart, hierarchyTermTypes, hierarchyContributions, missingTermIds, hierarchyWarnings, hierarchyModels, hierarchyData, defaultIndicatorsModelId, parseContributions, indicatorLogs, nonZeroContributors, logsTotalValue, namedContributors, breakdownBars, breakdownTooltip, breakdownBarText, breakdownScale, logToCsv as logToCsv$1 } from '@hestia-earth/ui-components-charts';
14
+ export { ColorPalette, afterBarDrawPlugin, axisHoverPlugin, axisLabelCursor, backgroundHoverPlugin, chartLabelMaxLength, colorToRgba, convertToSvg, d3ellipse, d3wrap, defaultTicksFont, downloadFile, downloadPng, downloadSvg, exportAsSVG, getColor, groupDataByCategory, hexToRgba, increaseScaleLimits, listColor, listColorContinuous, listColorWithAlpha, lollipopChartPlugin, parseColor, registerChart, sumValues } from '@hestia-earth/ui-components-charts';
15
+ import { isEmpty, unique, monthsBefore, toPrecision, isNumber, isUndefined, duration, keyToLabel, sum, min, max, mean, median, isEqual as isEqual$2, diffInDays } from '@hestia-earth/utils';
17
16
  import { json2csv } from 'json-2-csv';
18
17
  import { propertyValue as propertyValue$1, emptyValue } from '@hestia-earth/utils/term';
19
18
  import defaultModelId from '@hestia-earth/glossary/resources/defaultModelId.json';
@@ -39,14 +38,11 @@ import { GoogleMap, MapMarker, MapPolygon } from '@angular/google-maps';
39
38
  import { Meta, DomSanitizer } from '@angular/platform-browser';
40
39
  import removeMd from 'remove-markdown';
41
40
  import orderBy from 'lodash.orderby';
42
- import { Chart, BarController, LineController, CategoryScale, LinearScale, PointElement, BarElement, LineElement, Title, Tooltip, Legend, TimeScale } from 'chart.js';
43
- import C2S from 'canvas-to-svg';
44
- import 'chartjs-adapter-date-fns';
45
- import annotationPlugin from 'chartjs-plugin-annotation';
46
41
  import merge$1 from 'lodash.merge';
42
+ import { Chart } from 'chart.js';
47
43
  import set from 'lodash.set';
48
44
  import uniqBy from 'lodash.uniqby';
49
- import { headersFromCsv, toCsv as toCsv$1, toJson, toCsvPivot, ErrorKeys } from '@hestia-earth/schema-convert';
45
+ import { headersFromCsv, toCsv, toJson, toCsvPivot, ErrorKeys } from '@hestia-earth/schema-convert';
50
46
  import { isCSVIncluded, isDefaultCSVSelected } from '@hestia-earth/json-schema/schema-utils';
51
47
  import { recommendedProperties, loadSchemas } from '@hestia-earth/json-schema';
52
48
  import { ScrollStrategyOptions, CdkOverlayOrigin, CdkConnectedOverlay } from '@angular/cdk/overlay';
@@ -63,10 +59,6 @@ import { createErrorFormatter, htmlRenderer, isMigrationError, isMissingProperty
63
59
  export { errorHasError, errorHasWarning, filterError, guidePageId, isMigrationError, isMissingOneOfError, isMissingPropertyError, missingNodeErrors } from '@hestia-earth/ui-components/file-errors';
64
60
  import { format, subDays, parseISO } from 'date-fns';
65
61
  import { enGB } from 'date-fns/locale';
66
- import { linkHorizontal } from 'd3-shape';
67
- import 'd3-transition';
68
- import { hierarchy, tree } from 'd3-hierarchy';
69
- import { easeElasticIn, easeElasticOut } from 'd3-ease';
70
62
 
71
63
  class HeAuthService {
72
64
  set token(token) {
@@ -263,20 +255,6 @@ const toSnakeCase = (value) => (value || '')
263
255
  .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
264
256
  .replace(/([A-Z])([A-Z][a-z])/g, '$1_$2')
265
257
  .toLowerCase();
266
- const downloadFile = (url, fileName) => {
267
- if (!url) {
268
- return;
269
- }
270
- const a = document.createElement('a');
271
- document.body.appendChild(a);
272
- a.setAttribute('style', 'display: none');
273
- a.href = url;
274
- a.download = fileName;
275
- a.click();
276
- a.remove();
277
- // Clean up the object URL after a short delay
278
- setTimeout(() => URL.revokeObjectURL(url), 100);
279
- };
280
258
  const dataVersionHeaderKey = 'X-DATA-VERSION';
281
259
  const dataVersionHeader = (dataVersion) => (dataVersion ? { [dataVersionHeaderKey]: dataVersion } : {});
282
260
 
@@ -560,51 +538,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
560
538
  }]
561
539
  }] });
562
540
 
563
- const d3ellipse = (selection, maxWidth, ellipsisStr = '...') => {
564
- selection.each(function () {
565
- const t = select(this);
566
- if (t.node().getComputedTextLength() > maxWidth) {
567
- const chars = t.text().split('');
568
- const line = [];
569
- do {
570
- line.push(chars.shift());
571
- t.text(line.join('') + ellipsisStr);
572
- } while (t.node().getComputedTextLength() <= maxWidth);
573
- }
574
- });
575
- };
576
- const shouldBreakLine = (overflows, isMultiWordLine, isOwnLineWord, isUnderLineLimit) => ((overflows && isMultiWordLine) || isOwnLineWord) && isUnderLineLimit;
577
- const d3wrap = (t, { maxWidth, maxLines, words, nHeight, lHeight, vcentre = false, ownLineWord = '' }) => {
578
- words = words.reverse();
579
- const y = t.attr('y');
580
- const x = t.attr('x');
581
- let lineNumber = 1;
582
- let word;
583
- let line = [];
584
- let tspan = t.text(null).append('tspan').attr('x', x).attr('y', y);
585
- while ((word = words.pop()) && lineNumber <= maxLines) {
586
- line.push(word);
587
- tspan.text(line.join(' '));
588
- // TODO: hyphenate words that are too long
589
- if (shouldBreakLine(tspan.node().getComputedTextLength() > maxWidth, line.length > 1, word === ownLineWord, lineNumber < maxLines)) {
590
- line.pop();
591
- tspan.text(line.join(' '));
592
- line = [word];
593
- tspan = t
594
- .append('tspan')
595
- .attr('x', x)
596
- .attr('y', y)
597
- .attr('dy', lineNumber++ * lHeight + 'px')
598
- .text(word);
599
- }
600
- }
601
- d3ellipse(tspan, maxWidth, '... ' + ownLineWord);
602
- if (vcentre) {
603
- const yOffset = nHeight / 2 - (t.selectAll('tspan').size() - 1) * (lHeight / 2);
604
- t.selectAll('tspan').attr('y', yOffset);
605
- }
606
- };
607
-
608
541
  const SUCCESS_CRITERION_MAX_DELTA_PERCENT = 5;
609
542
  const WARNING_CRITERION_MAX_DELTA_PERCENT = 20;
610
543
  var DeltaColour;
@@ -666,7 +599,7 @@ const parseMessage = (message = '') => message.split(',').reduce((prev, parts) =
666
599
  };
667
600
  }, {});
668
601
  const formatLine = ({ data: { message } }) => parseMessage(message);
669
- const logToCsv$1 = (lines) => json2csv((lines || []).map(formatLine).filter(data => !isEmpty(data)), {
602
+ const logToCsv = (lines) => json2csv((lines || []).map(formatLine).filter(data => !isEmpty(data)), {
670
603
  emptyFieldValue: ''
671
604
  });
672
605
 
@@ -1814,10 +1747,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
1814
1747
  args: [{ selector: 'he-blank-node-state-notice', changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (show()) {\n <div class=\"is-flex is-gap-8 is-align-items-center is-size-7 is-italic\">\n <span class=\"notice-data-unchaged\">\u2020 Data uploaded by the user</span>\n <span class=\"divider\"></span>\n <span class=\"notice-data-added\">* Data added by HESTIA</span>\n <span class=\"divider\"></span>\n <span class=\"notice-data-updated\">** Data updated by HESTIA</span>\n @if (showDeleted()) {\n <span class=\"divider\"></span>\n <span class=\"notice-data-deleted\">**** Data deleted by HESTIA</span>\n }\n </div>\n}\n", styles: [":host{display:block}@media screen and (max-width:767px){:host div{flex-direction:column;align-items:flex-start!important}:host div .divider{display:none}}.divider{width:1px;height:20px;background:#4a4a4a}\n"] }]
1815
1748
  }], propDecorators: { dataState: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataState", required: false }] }], showDeleted: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDeleted", required: false }] }] } });
1816
1749
 
1817
- const parsePrecision = (value, precision) => toPrecision(parseFloat(`${value}`), parseInt(`${precision}`, 10));
1818
- const transform = (value, precision = 3, enableComma = true) => typeof value !== 'boolean' && !isUndefined(value) && isNumber(value)
1819
- ? `${(enableComma ? toComma : v => v)(parsePrecision(value, precision))}`.replace(/\.00$/, '')
1820
- : value;
1750
+ const transform = formatPrecision;
1821
1751
  class PrecisionPipe {
1822
1752
  transform(value, precision = 3, enableComma = true) {
1823
1753
  return transform(value, precision, enableComma);
@@ -5624,163 +5554,6 @@ const axisLabelSettings = (tooltip, tooltipFn, clickFn) => {
5624
5554
  };
5625
5555
  };
5626
5556
 
5627
- const defaultChartWidth = 800;
5628
- const defaultChartHeight = 600;
5629
- const extractEssentialChartConfig = (config) => ({
5630
- type: config.type || 'bar',
5631
- data: config.data,
5632
- options: {
5633
- indexAxis: config.options?.indexAxis,
5634
- responsive: false,
5635
- maintainAspectRatio: false,
5636
- devicePixelRatio: 1,
5637
- animation: false,
5638
- animations: {
5639
- colors: false,
5640
- x: false,
5641
- y: false
5642
- },
5643
- transitions: {
5644
- active: {
5645
- animation: {
5646
- duration: 0
5647
- }
5648
- }
5649
- },
5650
- plugins: {
5651
- legend: { display: false },
5652
- title: config.options.plugins?.title || { display: false }
5653
- },
5654
- scales: config.options.scales
5655
- }
5656
- });
5657
- const stretchSvg = (svgString, width, height) => {
5658
- const parser = new DOMParser();
5659
- const doc = parser.parseFromString(svgString, 'image/svg+xml');
5660
- const svgElement = doc.documentElement;
5661
- // Add 100px padding to ViewBox for overflow labels
5662
- svgElement.setAttribute('viewBox', `0 0 ${width + 100} ${height}`);
5663
- svgElement.setAttribute('width', '100%');
5664
- svgElement.setAttribute('height', '100%');
5665
- svgElement.setAttribute('preserveAspectRatio', 'xMidYMid meet');
5666
- return svgElement.outerHTML;
5667
- };
5668
- const parseAfterDrawBarPlugins = (afterBarDrawConfig, units) => Array.isArray(afterBarDrawConfig)
5669
- ? afterBarDrawConfig.map(afterBarDrawPlugin)
5670
- : [
5671
- afterBarDrawPlugin({
5672
- textFn: ({ data }) => [toPrecision(Array.isArray(data) ? data[0] : data), units].filter(Boolean).join(' '),
5673
- emptyValueLabel: 'No data',
5674
- ...(afterBarDrawConfig || {})
5675
- })
5676
- ];
5677
- const convertToSvg = (config, metadata = {}) => new Promise((resolve, reject) => {
5678
- const width = metadata.width || defaultChartWidth;
5679
- const height = metadata.height || defaultChartHeight;
5680
- // 1. Create the SVG Context
5681
- const svgContext = new C2S(width, height);
5682
- svgContext.setTransform = () => { };
5683
- svgContext.resetTransform = () => { };
5684
- svgContext.roundRect = function (x, y, w, h) {
5685
- this.rect(x, y, w, h);
5686
- };
5687
- // 2. Mock the Canvas Element
5688
- const mockCanvas = document.createElement('canvas');
5689
- mockCanvas.width = width;
5690
- mockCanvas.height = height;
5691
- mockCanvas.style.width = `${width}px`;
5692
- mockCanvas.style.height = `${height}px`;
5693
- mockCanvas.getBoundingClientRect = () => ({
5694
- x: 0,
5695
- y: 0,
5696
- bottom: height,
5697
- height: height,
5698
- left: 0,
5699
- right: width,
5700
- top: 0,
5701
- width: width,
5702
- toJSON: () => { }
5703
- });
5704
- // Intercept getContext to return our SVG generator
5705
- mockCanvas.getContext = (type) => {
5706
- if (type === '2d') {
5707
- svgContext.canvas = mockCanvas;
5708
- return svgContext;
5709
- }
5710
- return null;
5711
- };
5712
- // 3. Prepare Config
5713
- const chartConfig = extractEssentialChartConfig(config);
5714
- // eslint-disable-next-line prefer-const
5715
- let chart;
5716
- chartConfig.options.animation = {
5717
- onComplete: () => {
5718
- try {
5719
- const svgString = svgContext.getSerializedSvg(true);
5720
- const finalSvg = stretchSvg(svgString, width, height);
5721
- chart.destroy();
5722
- resolve(finalSvg);
5723
- }
5724
- catch (e) {
5725
- reject(e);
5726
- }
5727
- }
5728
- };
5729
- // 4. Register Export-Specific Plugins
5730
- chartConfig.plugins = [
5731
- metadata.afterBarDrawConfig !== null && parseAfterDrawBarPlugins(metadata.afterBarDrawConfig, metadata.units),
5732
- metadata.lollipopConfig !== null && lollipopChartPlugin(metadata.lollipopConfig)
5733
- ]
5734
- .filter(Boolean)
5735
- .flat();
5736
- chart = new Chart(mockCanvas, chartConfig);
5737
- });
5738
- const exportAsSVG = async (config, metadata) => {
5739
- try {
5740
- const content = await convertToSvg(config, metadata);
5741
- const blob = new Blob([content], { type: 'image/svg+xml;charset=utf-8' });
5742
- const url = URL.createObjectURL(blob);
5743
- downloadFile(url, metadata.fileName || 'chart-export.svg');
5744
- }
5745
- catch (error) {
5746
- console.error('Failed to export SVG', error);
5747
- }
5748
- };
5749
- const svgToUrl = (svgElement) => {
5750
- const serializer = new XMLSerializer();
5751
- let svgString = serializer.serializeToString(svgElement);
5752
- if (!svgString.includes('xmlns')) {
5753
- svgString = svgString.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
5754
- }
5755
- const blob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' });
5756
- return URL.createObjectURL(blob);
5757
- };
5758
- const downloadSvg = (svgElement) => downloadFile(svgToUrl(svgElement), 'chart.svg');
5759
- const downloadPng = async (svgElement, { width, height } = {}) => {
5760
- const canvas = document.createElement('canvas');
5761
- const ctx = canvas.getContext('2d');
5762
- const img = new Image();
5763
- // Set canvas dimensions based on SVG size
5764
- // (You might want to fetch getBBox() or use explicitly set width/height)
5765
- const svgSize = svgElement.getBoundingClientRect();
5766
- canvas.width = width || svgSize.width;
5767
- canvas.height = height || svgSize.height;
5768
- img.onload = () => {
5769
- if (ctx) {
5770
- // ctx.fillStyle = 'white';
5771
- ctx.fillRect(0, 0, canvas.width, canvas.height);
5772
- ctx.drawImage(img, 0, 0);
5773
- const pngUrl = canvas.toDataURL('image/png');
5774
- downloadFile(pngUrl, 'chart.png');
5775
- }
5776
- };
5777
- img.src = svgToUrl(svgElement);
5778
- };
5779
-
5780
- const registerChart = (items = []) => () => {
5781
- Chart.register(BarController, LineController, CategoryScale, LinearScale, PointElement, BarElement, LineElement, Title, Tooltip, Legend, TimeScale, annotationPlugin, ...items);
5782
- };
5783
-
5784
5557
  class ChartConfigurationDirective {
5785
5558
  constructor() {
5786
5559
  this._elementRef = inject(ElementRef);
@@ -5882,17 +5655,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
5882
5655
  args: [{ selector: 'he-chart-export-button', changeDetection: ChangeDetectionStrategy.OnPush, imports: [HESvgIconComponent, NgbTooltip], template: "<button\n class=\"{{ buttonClass() }}\"\n type=\"button\"\n [ngbTooltip]=\"chart()?.exporting() ? '' : 'Download'\"\n placement=\"bottom\"\n (click)=\"open()\">\n <he-svg-icon name=\"download\" />\n</button>\n\n<ng-template #modal>\n <!-- the chart may sit in a popover closing on outside clicks: keep it open, or the chart is gone -->\n <div class=\"modal is-active\" (mousedown)=\"$event.stopPropagation()\">\n <div class=\"modal-background\"></div>\n <div class=\"modal-card\">\n <header class=\"modal-card-head\">\n <p class=\"modal-card-title\">Download Chart</p>\n <button class=\"delete is-small\" aria-label=\"close\" type=\"button\" (click)=\"close()\"></button>\n </header>\n <section class=\"modal-card-body\">\n <p class=\"has-text-secondary is-mb-2\">Download chart as:</p>\n <div class=\"is-flex is-flex-direction-column is-gap-4\">\n @for (format of exportFormats(); track format.extension) {\n <div class=\"is-flex is-gap-4\">\n <div class=\"field is-mb-0\">\n <input\n type=\"checkbox\"\n [id]=\"format.extension\"\n [checked]=\"exportFormat() === format.extension\"\n (change)=\"exportFormat.set(format.extension)\" />\n </div>\n <he-svg-icon name=\"image\" />\n <label class=\"has-text-grey-dark is-clickable\" [for]=\"format.extension\">\n {{ format.label }} (.{{ format.extension }})\n </label>\n </div>\n }\n </div>\n </section>\n <footer class=\"modal-card-foot\">\n <button class=\"button is-primary\" [disabled]=\"!exportFormat()\" (click)=\"download()\">\n @if (chart()?.exporting()) {\n <he-svg-icon name=\"loading\" animation=\"spin\" />\n } @else {\n <he-svg-icon name=\"download\" />\n }\n <span class=\"is-pl-2\">Download</span>\n </button>\n </footer>\n </div>\n </div>\n</ng-template>\n" }]
5883
5656
  }], propDecorators: { modal: [{ type: i0.ViewChild, args: ['modal', { isSignal: true }] }], buttonClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonClass", required: false }] }], chart: [{ type: i0.Input, args: [{ isSignal: true, alias: "chart", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], exportFormats: [{ type: i0.Input, args: [{ isSignal: true, alias: "exportFormats", required: false }] }], chartExportFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "chartExportFn", required: false }] }] } });
5884
5657
 
5885
- const defaultSettings$3 = Object.freeze({
5886
- options: {
5887
- responsive: true,
5888
- maintainAspectRatio: false,
5889
- plugins: {
5890
- legend: {
5891
- display: false
5892
- }
5893
- }
5894
- }
5895
- });
5896
5658
  class ChartComponent {
5897
5659
  constructor() {
5898
5660
  this.data = input(undefined, ...(ngDevMode ? [{ debugName: "data" }] : []));
@@ -5901,7 +5663,7 @@ class ChartComponent {
5901
5663
  this.exporting = signal(false, ...(ngDevMode ? [{ debugName: "exporting" }] : []));
5902
5664
  this.chartRef = viewChild('chartRef', ...(ngDevMode ? [{ debugName: "chartRef" }] : []));
5903
5665
  this.configuration = computed(() => ({
5904
- ...merge$1({}, defaultSettings$3, this.config()),
5666
+ ...merge$1({}, defaultChartSettings, this.config()),
5905
5667
  data: this.data()
5906
5668
  }), ...(ngDevMode ? [{ debugName: "configuration" }] : []));
5907
5669
  }
@@ -6076,81 +5838,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
6076
5838
  }]
6077
5839
  }] });
6078
5840
 
6079
- const grey = '#4a4a4a';
6080
- const barheight = 8; // 8px
6081
- const hoverHeight = 10; // 10px
6082
- const defaultSettings$2 = Object.freeze({
6083
- type: 'bar',
6084
- options: {
6085
- indexAxis: 'y',
6086
- responsive: true,
6087
- plugins: {
6088
- tooltip: {
6089
- enabled: false
6090
- },
6091
- legend: {
6092
- display: false
6093
- }
6094
- },
6095
- scales: {
6096
- x: {
6097
- stacked: true,
6098
- display: true,
6099
- grid: {
6100
- display: true,
6101
- drawOnChartArea: true
6102
- },
6103
- ticks: {
6104
- font: defaultTicksFont,
6105
- callback: label => `${transform(+label)}`
6106
- }
6107
- },
6108
- y: {
6109
- stacked: true,
6110
- display: true,
6111
- position: 'left',
6112
- grid: {
6113
- display: true,
6114
- drawOnChartArea: false,
6115
- drawTicks: true,
6116
- tickLength: 8,
6117
- offset: false
6118
- },
6119
- ticks: {
6120
- color: grey,
6121
- padding: 4,
6122
- crossAlign: 'center',
6123
- font: {
6124
- ...defaultTicksFont,
6125
- weight: 400
6126
- },
6127
- callback: function (value) {
6128
- const label = this.getLabelForValue(value);
6129
- return ellipsis$1(label, chartLabelMaxLength);
6130
- }
6131
- }
6132
- }
6133
- }
6134
- }
6135
- });
6136
- const chartHeight = (minHeight, maxHeight, nbRows) => {
6137
- const rowHeight = barheight + hoverHeight * 2; // Pixels per bar (includes bar + gap)
6138
- const padding = 60; // Extra pixels for x-axis labels, legend, etc.
6139
- const height = nbRows * rowHeight + padding;
6140
- return Math.min(Math.max(height, minHeight), maxHeight);
6141
- };
6142
- const datasetCenter = (chart, index) => {
6143
- const elements = chart.data.datasets
6144
- .map((_, i) => {
6145
- const meta = chart.getDatasetMeta(i);
6146
- return meta.hidden ? null : meta.data[index];
6147
- })
6148
- .filter(e => !!e);
6149
- const right = Math.max(...elements.map(e => e.x), ...elements.map(e => e.base));
6150
- const left = Math.min(...elements.map(e => e.x), ...elements.map(e => e.base));
6151
- return (right + left) / 2;
6152
- };
6153
- const itemColor = (item) => item.backgroundColor || item.borderColor || item.color;
6154
5841
  const tooltipFn = ({ label, selectedLabel, units, values }) => `
6155
5842
  <div class="is-flex is-flex-direction-column is-overflow-y-auto" style="max-height:400px">
6156
5843
  <div class="is-hidden-tablet is-p-1 has-text-centered has-text-secondary has-text-weight-bold is-size-7 with-border-secondary-accent" style="border-bottom-width:1px">${label}</div>
@@ -6162,7 +5849,7 @@ const tooltipFn = ({ label, selectedLabel, units, values }) => `
6162
5849
  .map(value => `
6163
5850
  <div class="is-flex is-flex-direction-column is-align-self-stretch w-100">
6164
5851
  <div class="is-flex is-align-items-center is-gap-4 is-p-1 has-text-grey-dark is-size-7" style="height:21px">
6165
- <div class="is-flex-shrink-0 is-radius-3" style="width:12px;height:12px;background:${itemColor(value)}"></div>
5852
+ <div class="is-flex-shrink-0 is-radius-3" style="width:12px;height:12px;background:${contributionItemColor(value)}"></div>
6166
5853
  <div class="is-flex-grow-1">
6167
5854
  <span class="has-text-ellipsis is-ellipsis-1 ${value.label === selectedLabel ? 'has-text-weight-bold' : ''}">${value.label}</span>
6168
5855
  </div>
@@ -6170,7 +5857,7 @@ const tooltipFn = ({ label, selectedLabel, units, values }) => `
6170
5857
  ${((value.label === selectedLabel ? value.includedItems : []) || [])
6171
5858
  .map(included => `
6172
5859
  <div class="is-flex is-align-items-center is-gap-4 is-p-1 is-pl-4 has-text-grey-dark is-size-7" style="height:21px">
6173
- <div class="is-flex-shrink-0 is-radius-3" style="width:8px;height:8px;background:${itemColor(included)}"></div>
5860
+ <div class="is-flex-shrink-0 is-radius-3" style="width:8px;height:8px;background:${contributionItemColor(included)}"></div>
6174
5861
  <div class="is-flex-grow-1">
6175
5862
  <span class="has-text-ellipsis is-ellipsis-1">${included.label}</span>
6176
5863
  </div>
@@ -6208,23 +5895,6 @@ const tooltipFn = ({ label, selectedLabel, units, values }) => `
6208
5895
  </div>
6209
5896
  </div>
6210
5897
  `.trim();
6211
- const dataAtIndex = (data, index, includeItems = true) => data
6212
- .map(item => ({
6213
- ...item,
6214
- value: item.values[index],
6215
- includedItems: includeItems
6216
- ? item.includedItems?.map(included => ({
6217
- ...included,
6218
- value: included.values[index]
6219
- }))
6220
- : []
6221
- }))
6222
- .filter(v => !isUndefined(v.value))
6223
- .sort((a, b) => Math.abs(b.value) - Math.abs(a.value));
6224
- const logToCsv = (labels, data) => [
6225
- toLine(['@id', ...data.map(v => v.label)]),
6226
- labels.map((label, index) => toLine([label, ...dataAtIndex(data, index, false).map(v => v.value)])).join('\n')
6227
- ].join('\n');
6228
5898
  class ContributionChartComponent {
6229
5899
  constructor() {
6230
5900
  this.emissionCategoryService = inject(CyclesEmissionsCategoryService);
@@ -6276,53 +5946,18 @@ class ContributionChartComponent {
6276
5946
  this.axisEnabled = computed(() => !!this.axisTooltipFn() || !!this.axisLabelClickFn(), ...(ngDevMode ? [{ debugName: "axisEnabled" }] : []));
6277
5947
  this.axisTooltipContent = computed(() => (data) => this.axisTooltipFn()?.(data.label, data.index) ?? '', ...(ngDevMode ? [{ debugName: "axisTooltipContent" }] : []));
6278
5948
  this.exporting = computed(() => this.chart()?.exporting(), ...(ngDevMode ? [{ debugName: "exporting" }] : []));
6279
- this.chartHeight = computed(() => this.height() || chartHeight(this.minHeight(), this.maxHeight(), this.labels()?.length ?? 0), ...(ngDevMode ? [{ debugName: "chartHeight" }] : []));
6280
- this.hasNegativeContributions = computed(() => this.displayData()?.some(value => value.values.some(v => v < 0)), ...(ngDevMode ? [{ debugName: "hasNegativeContributions" }] : []));
5949
+ this.chartHeight = computed(() => this.height() || contributionChartHeight(this.minHeight(), this.maxHeight(), this.labels()?.length ?? 0), ...(ngDevMode ? [{ debugName: "chartHeight" }] : []));
6281
5950
  this.displayData = computed(() => groupDataByCategory(this.data(), this.labels(), this.category(), this.emissionCategoryService), ...(ngDevMode ? [{ debugName: "displayData" }] : []));
6282
- this.defaultConfig = computed(() => ({
6283
- options: {
6284
- onClick: (event, activeElements, chart) => this.onItemClick(event, activeElements, chart),
6285
- plugins: { axisHover: this.axisSettings },
6286
- scales: {
6287
- x: {
6288
- display: !this.responsiveService.isMobile(),
6289
- afterDataLimits: scale => increaseScaleLimits(scale),
6290
- title: {
6291
- display: !!this.label(),
6292
- text: this.label()
6293
- },
6294
- grid: {
6295
- lineWidth: context => (context.tick.value === 0 && this.hasNegativeContributions() ? 1 : 0)
6296
- }
6297
- },
6298
- y: {
6299
- display: !this.responsiveService.isMobile()
6300
- }
6301
- }
6302
- },
6303
- plugins: [
6304
- backgroundHoverPlugin({ threshold: hoverHeight }),
6305
- afterBarDrawPlugin({
6306
- placement: 'right',
6307
- textFn: ({ data }) => (isUndefined(data) ? 'No data' : '')
6308
- }),
6309
- ...(this.axisEnabled() ? [axisHoverPlugin] : [])
6310
- ]
5951
+ this.defaultConfig = computed(() => contributionChartConfig({
5952
+ onClick: (event, activeElements, chart) => this.onItemClick(event, activeElements, chart),
5953
+ axis: this.axisEnabled() ? this.axisSettings : undefined,
5954
+ label: this.label(),
5955
+ hasNegativeValues: hasNegativeValues(this.displayData()),
5956
+ mobile: this.responsiveService.isMobile()
6311
5957
  }), ...(ngDevMode ? [{ debugName: "defaultConfig" }] : []));
6312
- this.dataConfig = computed(() => ({
6313
- datasets: this.displayData().map(item => ({
6314
- label: item.label,
6315
- data: item.values,
6316
- backgroundColor: item.backgroundColor || item.color,
6317
- borderColor: item.borderColor || item.color,
6318
- barThickness: barheight,
6319
- maxBarThickness: barheight,
6320
- includedItems: item.includedItems
6321
- })),
6322
- labels: this.labels()
6323
- }), ...(ngDevMode ? [{ debugName: "dataConfig" }] : []));
6324
- this.configuration = computed(() => merge$1({}, defaultSettings$2, this.defaultConfig(), this.config()), ...(ngDevMode ? [{ debugName: "configuration" }] : []));
6325
- this.csvContent = computed(() => `data:text/html;charset=utf-8,${encodeURIComponent(logToCsv(this.labels(), this.displayData()))}`, ...(ngDevMode ? [{ debugName: "csvContent" }] : []));
5958
+ this.dataConfig = computed(() => contributionData(this.displayData(), this.labels()), ...(ngDevMode ? [{ debugName: "dataConfig" }] : []));
5959
+ this.configuration = computed(() => merge$1({}, defaultContributionSettings, this.defaultConfig(), this.config()), ...(ngDevMode ? [{ debugName: "configuration" }] : []));
5960
+ this.csvContent = computed(() => `data:text/html;charset=utf-8,${encodeURIComponent(contributionCsv(this.labels(), this.displayData()))}`, ...(ngDevMode ? [{ debugName: "csvContent" }] : []));
6326
5961
  }
6327
5962
  onItemClick(event, activeElements, chart) {
6328
5963
  const index = activeElements?.[0]?.index;
@@ -6359,182 +5994,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
6359
5994
  args: [{ selector: 'he-contribution-chart', exportAs: 'contributionChart', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ChartComponent, ChartTooltipComponent, BarChartLegendComponent], template: "<div class=\"is-relative\" [class.chart-area-border]=\"showLegend()\">\n <he-chart\n [data]=\"dataConfig()\"\n [config]=\"configuration()\"\n [showExportButton]=\"showExportButton()\"\n [style.height.px]=\"chartHeight()\">\n <he-chart-tooltip [tooltipFn]=\"tooltipFn\" />\n @if (axisEnabled()) {\n <he-chart-tooltip #axisTooltip=\"chartTooltip\" [tooltipFn]=\"axisTooltipContent()\" />\n }\n </he-chart>\n\n @if (!data()?.length) {\n <div class=\"is-absolute w-100 h-100 | no-data\">\n <div class=\"is-flex is-justify-content-center is-align-items-center w-100 h-100\">\n <span class=\"is-size-7\">No contribution available.</span>\n </div>\n </div>\n }\n</div>\n\n@if (showLegend()) {\n <he-bar-chart-legend [data]=\"displayData()\" />\n}\n", styles: [":host{display:block}he-chart{height:400px}he-bar-chart-legend ::ng-deep .breakdown-legend--color{border-radius:3px}.no-data{top:0;background-color:#0000004d}\n"] }]
6360
5995
  }], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], showExportButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showExportButton", required: false }] }], showLegend: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLegend", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], axisTooltipFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "axisTooltipFn", required: false }] }], axisLabelClickFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "axisLabelClickFn", required: false }] }], minHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "minHeight", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], category: [{ type: i0.Input, args: [{ isSignal: true, alias: "category", required: false }] }], chart: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ChartComponent), { isSignal: true }] }], tooltip: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ChartTooltipComponent), { isSignal: true }] }], axisTooltip: [{ type: i0.ViewChild, args: ['axisTooltip', { isSignal: true }] }] } });
6361
5996
 
6362
- const colors = {
6363
- darkBlue: 'rgba(25, 57, 87, 1)',
6364
- lightBlue: 'rgba(132, 160, 220, 1)',
6365
- // `$warning` — marks the reported value, and is the one colour that reads against both a white
6366
- // page and the dark blue popover the parametric chart is shown in
6367
- orange: 'rgba(255, 136, 27, 1)'
6368
- };
6369
- const opaqueColor = (color, opacity = 0.5) => color.replace(', 1)', `, ${opacity})`);
6370
- const defaultSettings$1 = Object.freeze({
6371
- type: 'line',
6372
- options: {
6373
- events: [],
6374
- responsive: true,
6375
- plugins: {
6376
- tooltip: {
6377
- enabled: false
6378
- },
6379
- legend: {
6380
- display: false,
6381
- align: 'center'
6382
- }
6383
- },
6384
- scales: {
6385
- y: {
6386
- display: true,
6387
- position: 'left',
6388
- grid: {
6389
- display: true
6390
- },
6391
- beginAtZero: true,
6392
- ticks: {
6393
- crossAlign: 'center'
6394
- }
6395
- }
6396
- }
6397
- }
6398
- });
6399
- const joinXLabel = ' to ';
6400
- const createDynamicHistogramData = (values, numberOfBins) => {
6401
- if (values.length === 0) {
6402
- return { labels: [], frequencies: [] };
6403
- }
6404
- // Find the minimum and maximum values in the dataset
6405
- const min = Math.min(...values);
6406
- const max = Math.max(...values);
6407
- // Calculate the size of each bin
6408
- const binSize = (max - min) / numberOfBins;
6409
- // Create an array to hold the bin frequencies, initialized to zero
6410
- const frequencies = new Array(numberOfBins).fill(0);
6411
- // Create labels for the chart based on the bin ranges
6412
- const labels = [];
6413
- for (let i = 0; i < numberOfBins; i++) {
6414
- const binStart = min + i * binSize;
6415
- const binEnd = binStart + binSize;
6416
- labels.push([binStart, binEnd].join(joinXLabel));
6417
- // Count data points for the current bin
6418
- values.forEach(point => {
6419
- if (point >= binStart && point < binEnd) {
6420
- frequencies[i]++;
6421
- }
6422
- });
6423
- }
6424
- // Ensure the maximum value is included in the last bin
6425
- frequencies[numberOfBins - 1] += values.filter(p => p === max).length;
6426
- return { labels, frequencies };
6427
- };
6428
- /**
6429
- * Parametric mode: how far either side of the mean the curve is drawn, in standard deviations.
6430
- * Four covers ~99.99% of the density, so the tails are visually flat by the time it stops.
6431
- */
6432
- const curveSpread = 4;
6433
- const curvePoints = 120;
6434
- /** Fraction of the drawn range kept clear at each end, so a marker on the edge is not clipped. */
6435
- const curvePadding = 0.05;
6436
- const normalPdf = (x, mu, sd) => Math.exp(-0.5 * ((x - mu) / sd) ** 2) / (sd * Math.sqrt(2 * Math.PI));
6437
- /**
6438
- * The x range to draw. The reported value is included when it is known, because the whole point of
6439
- * the chart is to show how far outside the interval it sits — clipping it to the curve would hide
6440
- * exactly what the reader came to see.
6441
- */
6442
- const curveRange = (mu, sd, value, minX) => {
6443
- const values = [mu - curveSpread * sd, mu + curveSpread * sd, ...(Number.isFinite(value) ? [value] : [])];
6444
- const from = Math.min(...values);
6445
- const to = Math.max(...values);
6446
- const padding = (to - from) * curvePadding;
6447
- // a fitted normal has mass either side of the mean, but the quantity it describes may not:
6448
- // drawing a physical amount below its floor invites the reader to think that is possible
6449
- return { from: Number.isFinite(minX) ? Math.max(from - padding, minX) : from - padding, to: to + padding };
6450
- };
6451
- /** Samples the normal PDF, as `{x, y}` points for a linear x axis. */
6452
- const createNormalCurve = (mu, sd, value, minX) => {
6453
- const { from, to } = curveRange(mu, sd, value, minX);
6454
- return Array.from({ length: curvePoints + 1 }, (_, index) => from + ((to - from) * index) / curvePoints).map(x => ({
6455
- x,
6456
- y: normalPdf(x, mu, sd)
6457
- }));
6458
- };
6459
- /**
6460
- * The shaded confidence interval, the mean, and the vertical marker at the reported value.
6461
- *
6462
- * The mean earns its line: the y axis is a probability density, whose absolute value means nothing
6463
- * to a reader, so without it there is no way to tell what the peak of the curve represents.
6464
- */
6465
- const distributionAnnotations = (min, max, value, mean) => ({
6466
- ...(Number.isFinite(min) && Number.isFinite(max)
6467
- ? {
6468
- interval: {
6469
- type: 'box',
6470
- xMin: min,
6471
- xMax: max,
6472
- backgroundColor: opaqueColor(colors.lightBlue, 0.3),
6473
- borderWidth: 0,
6474
- drawTime: 'beforeDatasetsDraw'
6475
- }
6476
- }
6477
- : {}),
6478
- ...(Number.isFinite(mean)
6479
- ? {
6480
- mean: {
6481
- type: 'line',
6482
- xMin: mean,
6483
- xMax: mean,
6484
- borderColor: colors.lightBlue,
6485
- borderWidth: 1,
6486
- borderDash: [4, 4],
6487
- label: {
6488
- display: true,
6489
- content: 'average',
6490
- position: 'start',
6491
- backgroundColor: colors.lightBlue,
6492
- color: colors.darkBlue,
6493
- font: { size: 9 }
6494
- }
6495
- }
6496
- }
6497
- : {}),
6498
- ...(Number.isFinite(value)
6499
- ? {
6500
- value: {
6501
- type: 'line',
6502
- xMin: value,
6503
- xMax: value,
6504
- borderColor: colors.orange,
6505
- borderWidth: 2,
6506
- label: {
6507
- display: true,
6508
- content: `${transform(value, 3, true)}`,
6509
- position: 'start',
6510
- backgroundColor: colors.orange,
6511
- color: '#fff',
6512
- font: { size: 10 }
6513
- }
6514
- }
6515
- }
6516
- : {})
6517
- });
6518
- const createSinglePointDataset = (labels, pointValue) => {
6519
- // Create an array with the same length as the labels, filled with nulls.
6520
- const pointData = new Array(labels.length).fill(null);
6521
- // Find the index of the bin that contains the pointValue.
6522
- // We parse the bin range from the label string (e.g., '900-1000').
6523
- const binIndex = labels.findIndex(label => {
6524
- // Split the label to get the bin's min and max values
6525
- const [minStr, maxStr] = label.split(joinXLabel);
6526
- const binMin = parseFloat(minStr);
6527
- const binMax = parseFloat(maxStr);
6528
- return pointValue >= binMin && pointValue < binMax;
6529
- });
6530
- // If a valid bin is found, place the point value in the array.
6531
- if (binIndex !== -1) {
6532
- // We'll set the single point's Y-value to its own value here,
6533
- // but you could also set it to a frequency or a custom value.
6534
- pointData[binIndex] = 1;
6535
- }
6536
- return pointData;
6537
- };
5997
+ /** A distribution as a histogram, or as a normal curve; drawn from `@hestia-earth/ui-components-charts`. */
6538
5998
  class DistributionChartComponent {
6539
5999
  constructor() {
6540
6000
  /**
@@ -6581,159 +6041,20 @@ class DistributionChartComponent {
6581
6041
  * for a physical amount. Left unset the curve is drawn symmetrically about `mu`.
6582
6042
  */
6583
6043
  this.minX = input(...(ngDevMode ? [undefined, { debugName: "minX" }] : []));
6584
- /**
6585
- * `sd` must be strictly positive: a zero or negative deviation makes the PDF a division by zero.
6586
- */
6587
- this.parametric = computed(() => Number.isFinite(this.mu()) && Number.isFinite(this.sd()) && this.sd() > 0, ...(ngDevMode ? [{ debugName: "parametric" }] : []));
6588
- this.curve = computed(() => createNormalCurve(this.mu(), this.sd(), this.value(), this.minX()), ...(ngDevMode ? [{ debugName: "curve" }] : []));
6589
- this.maxPercentileValue = computed(() => this.maxPercentile() ? getPercentileValue(this.distribution(), this.maxPercentile()) : null, ...(ngDevMode ? [{ debugName: "maxPercentileValue" }] : []));
6590
- this.groupedData = computed(() => createDynamicHistogramData(this.maxPercentileValue()
6591
- ? (this.distribution() ?? []).filter(v => v <= this.maxPercentileValue())
6592
- : (this.distribution() ?? []), this.nbBins()), ...(ngDevMode ? [{ debugName: "groupedData" }] : []));
6593
- this.singlePoint = computed(() => createSinglePointDataset(this.groupedData()?.labels || [], this.value()), ...(ngDevMode ? [{ debugName: "singlePoint" }] : []));
6594
- this.defaultConfig = computed(() => ({
6595
- options: {
6596
- scales: {
6597
- x: {
6598
- display: true,
6599
- grid: {
6600
- offset: true
6601
- },
6602
- min: 0,
6603
- ticks: {
6604
- callback: function (value) {
6605
- const label = this.getLabelForValue(value);
6606
- return (label
6607
- ?.split(joinXLabel)
6608
- ?.map(parseFloat)
6609
- ?.map(v => transform(v, 3, true))
6610
- // .map(v => v.toExponential())
6611
- ?.join('-'));
6612
- }
6613
- },
6614
- title: {
6615
- display: true,
6616
- text: this.label()
6617
- }
6618
- },
6619
- y: {
6620
- ticks: {
6621
- font: {
6622
- family: defaultTicksFont.family,
6623
- size: 10,
6624
- weight: 400
6625
- },
6626
- color: '#4A4A4A'
6627
- },
6628
- title: {
6629
- display: true,
6630
- text: 'Frequency of values'
6631
- }
6632
- }
6633
- }
6634
- }
6635
- }), ...(ngDevMode ? [{ debugName: "defaultConfig" }] : []));
6636
- /**
6637
- * The x axis is `linear` here, not the category axis the histogram uses: the curve is `{x, y}`
6638
- * points, and the annotations position the interval and the marker at real x values.
6639
- */
6640
- this.parametricConfig = computed(() => ({
6641
- options: {
6642
- plugins: {
6643
- annotation: {
6644
- annotations: distributionAnnotations(this.intervalMin(), this.intervalMax(), this.value(), this.mu())
6645
- }
6646
- },
6647
- scales: {
6648
- x: {
6649
- type: 'linear',
6650
- display: true,
6651
- ticks: {
6652
- font: {
6653
- family: defaultTicksFont.family,
6654
- size: 10,
6655
- weight: 400
6656
- },
6657
- color: '#4A4A4A',
6658
- callback: value => `${transform(value, 3, true)}`
6659
- },
6660
- title: {
6661
- display: true,
6662
- text: this.label()
6663
- }
6664
- },
6665
- y: {
6666
- // the density itself carries no meaning for the reader, only the shape does
6667
- ticks: {
6668
- display: false
6669
- },
6670
- grid: {
6671
- display: false
6672
- },
6673
- title: {
6674
- display: false
6675
- }
6676
- }
6677
- }
6678
- }
6679
- }), ...(ngDevMode ? [{ debugName: "parametricConfig" }] : []));
6680
- this.histogramData = computed(() => ({
6681
- datasets: [
6682
- this.distribution()
6683
- ? [
6684
- {
6685
- label: 'Distribution',
6686
- data: this.groupedData().frequencies,
6687
- backgroundColor: opaqueColor(colors.lightBlue),
6688
- borderColor: colors.lightBlue,
6689
- borderWidth: 1,
6690
- type: 'bar'
6691
- },
6692
- {
6693
- label: 'Distribution',
6694
- data: this.groupedData().frequencies,
6695
- backgroundColor: opaqueColor(colors.darkBlue),
6696
- borderColor: colors.darkBlue,
6697
- borderWidth: 1,
6698
- fill: false,
6699
- pointRadius: 0,
6700
- type: 'line'
6701
- }
6702
- ]
6703
- : [],
6704
- this.singlePoint()?.length
6705
- ? {
6706
- label: 'Value',
6707
- data: this.singlePoint(),
6708
- fill: false,
6709
- borderColor: colors.darkBlue,
6710
- backgroundColor: colors.darkBlue,
6711
- pointRadius: 4,
6712
- showLine: false,
6713
- type: 'line',
6714
- tension: 0
6715
- }
6716
- : []
6717
- ].flat(),
6718
- labels: this.groupedData().labels
6719
- }), ...(ngDevMode ? [{ debugName: "histogramData" }] : []));
6720
- this.parametricData = computed(() => ({
6721
- datasets: [
6722
- {
6723
- label: 'Distribution',
6724
- data: this.curve(),
6725
- backgroundColor: opaqueColor(colors.lightBlue, 0.2),
6726
- // light enough to read on a dark panel, dark enough to read on a white one
6727
- borderColor: colors.lightBlue,
6728
- borderWidth: 1,
6729
- fill: true,
6730
- pointRadius: 0,
6731
- type: 'line'
6732
- }
6733
- ]
6734
- }), ...(ngDevMode ? [{ debugName: "parametricData" }] : []));
6735
- this.dataConfig = computed(() => this.parametric() ? this.parametricData() : this.histogramData(), ...(ngDevMode ? [{ debugName: "dataConfig" }] : []));
6736
- this.configuration = computed(() => merge$1({}, defaultSettings$1, this.parametric() ? this.parametricConfig() : this.defaultConfig(), this.config()), ...(ngDevMode ? [{ debugName: "configuration" }] : []));
6044
+ this.chart = computed(() => distributionChart({
6045
+ distribution: this.distribution(),
6046
+ value: this.value(),
6047
+ label: this.label(),
6048
+ nbBins: this.nbBins(),
6049
+ maxPercentile: this.maxPercentile(),
6050
+ mu: this.mu(),
6051
+ sd: this.sd(),
6052
+ intervalMin: this.intervalMin(),
6053
+ intervalMax: this.intervalMax(),
6054
+ minX: this.minX()
6055
+ }), ...(ngDevMode ? [{ debugName: "chart" }] : []));
6056
+ this.dataConfig = computed(() => this.chart().data, ...(ngDevMode ? [{ debugName: "dataConfig" }] : []));
6057
+ this.configuration = computed(() => merge$1({}, this.chart().config, this.config()), ...(ngDevMode ? [{ debugName: "configuration" }] : []));
6737
6058
  }
6738
6059
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DistributionChartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6739
6060
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: DistributionChartComponent, isStandalone: true, selector: "he-distribution-chart", inputs: { distribution: { classPropertyName: "distribution", publicName: "distribution", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, nbBins: { classPropertyName: "nbBins", publicName: "nbBins", isSignal: true, isRequired: false, transformFunction: null }, maxPercentile: { classPropertyName: "maxPercentile", publicName: "maxPercentile", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, mu: { classPropertyName: "mu", publicName: "mu", isSignal: true, isRequired: false, transformFunction: null }, sd: { classPropertyName: "sd", publicName: "sd", isSignal: true, isRequired: false, transformFunction: null }, intervalMin: { classPropertyName: "intervalMin", publicName: "intervalMin", isSignal: true, isRequired: false, transformFunction: null }, intervalMax: { classPropertyName: "intervalMax", publicName: "intervalMax", isSignal: true, isRequired: false, transformFunction: null }, minX: { classPropertyName: "minX", publicName: "minX", isSignal: true, isRequired: false, transformFunction: null } }, exportAs: ["distributionChart"], ngImport: i0, template: "<he-chart [data]=\"dataConfig()\" [config]=\"configuration()\" />\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "component", type: ChartComponent, selector: "he-chart", inputs: ["data", "config", "showExportButton"], exportAs: ["chart"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
@@ -6743,22 +6064,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
6743
6064
  args: [{ selector: 'he-distribution-chart', exportAs: 'distributionChart', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ChartComponent], template: "<he-chart [data]=\"dataConfig()\" [config]=\"configuration()\" />\n", styles: [":host{display:block}\n"] }]
6744
6065
  }], propDecorators: { distribution: [{ type: i0.Input, args: [{ isSignal: true, alias: "distribution", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], nbBins: [{ type: i0.Input, args: [{ isSignal: true, alias: "nbBins", required: false }] }], maxPercentile: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxPercentile", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], mu: [{ type: i0.Input, args: [{ isSignal: true, alias: "mu", required: false }] }], sd: [{ type: i0.Input, args: [{ isSignal: true, alias: "sd", required: false }] }], intervalMin: [{ type: i0.Input, args: [{ isSignal: true, alias: "intervalMin", required: false }] }], intervalMax: [{ type: i0.Input, args: [{ isSignal: true, alias: "intervalMax", required: false }] }], minX: [{ type: i0.Input, args: [{ isSignal: true, alias: "minX", required: false }] }] } });
6745
6066
 
6746
- const defaultSettings = Object.freeze({
6747
- type: 'line',
6748
- options: {
6749
- scales: {
6750
- x: {
6751
- type: 'time',
6752
- time: {
6753
- unit: 'day'
6754
- },
6755
- ticks: {
6756
- font: defaultTicksFont
6757
- }
6758
- }
6759
- }
6760
- }
6761
- });
6762
6067
  class LineChartComponent {
6763
6068
  constructor() {
6764
6069
  this.datasets = input([], ...(ngDevMode ? [{ debugName: "datasets" }] : []));
@@ -6769,7 +6074,7 @@ class LineChartComponent {
6769
6074
  this.dataConfig = computed(() => ({
6770
6075
  datasets: this.datasets()
6771
6076
  }), ...(ngDevMode ? [{ debugName: "dataConfig" }] : []));
6772
- this.configuration = computed(() => merge$1({}, defaultSettings, this.config()), ...(ngDevMode ? [{ debugName: "configuration" }] : []));
6077
+ this.configuration = computed(() => merge$1({}, defaultLineSettings, this.config()), ...(ngDevMode ? [{ debugName: "configuration" }] : []));
6773
6078
  }
6774
6079
  exportAsSvg(config = {}) {
6775
6080
  return this.chart().exportAsSvg(config);
@@ -8425,9 +7730,9 @@ class NodeCsvExportConfirmComponent {
8425
7730
  this.included = computed(() => this.includedNodes()
8426
7731
  .filter(({ included }) => included)
8427
7732
  .map(({ node }) => node), ...(ngDevMode ? [{ debugName: "included" }] : []));
8428
- this.csvData = computed(() => toCsv$1(this.nodes() || [], { includeExising: !this.isUpload() }), ...(ngDevMode ? [{ debugName: "csvData" }] : []));
7733
+ this.csvData = computed(() => toCsv(this.nodes() || [], { includeExising: !this.isUpload() }), ...(ngDevMode ? [{ debugName: "csvData" }] : []));
8429
7734
  this.csvContent = computed(() => this.headers().length
8430
- ? this.domSanitizer.bypassSecurityTrustResourceUrl(`data:text/html;charset=utf-8,${encodeURIComponent(toCsv$1(this.included(), { includeExising: !this.isUpload(), selectedHeaders: this.headers() }))}`)
7735
+ ? this.domSanitizer.bypassSecurityTrustResourceUrl(`data:text/html;charset=utf-8,${encodeURIComponent(toCsv(this.included(), { includeExising: !this.isUpload(), selectedHeaders: this.headers() }))}`)
8431
7736
  : undefined, ...(ngDevMode ? [{ debugName: "csvContent" }] : []));
8432
7737
  this.downloadFilename = computed(() => fileToExt(this.filename(), this.extension()), ...(ngDevMode ? [{ debugName: "downloadFilename" }] : []));
8433
7738
  }
@@ -12635,7 +11940,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
12635
11940
  }]
12636
11941
  }] });
12637
11942
 
12638
- const nodeColours$1 = {
11943
+ const nodeColours = {
12639
11944
  [NodeType.Cycle]: '#ecf1ff',
12640
11945
  [NodeType.Site]: '#d5f3d8',
12641
11946
  [NodeType.ImpactAssessment]: '#e7e0cb',
@@ -12912,7 +12217,7 @@ class NodeCsvPreviewComponent {
12912
12217
  ? this.schemas()
12913
12218
  ? toCsvPivot(this.schemas(), this.nodes())
12914
12219
  : ''
12915
- : toCsv$1(this.nodes(), this.csvOptions()), ...(ngDevMode ? [{ debugName: "_csv" }] : []));
12220
+ : toCsv(this.nodes(), this.csvOptions()), ...(ngDevMode ? [{ debugName: "_csv" }] : []));
12916
12221
  this.csv = computed(() => this.domSanitizer.bypassSecurityTrustResourceUrl(`data:text/html;charset=utf-8,${encodeURIComponent(this._csv())}`), ...(ngDevMode ? [{ debugName: "csv" }] : []));
12917
12222
  this.parsedCsv = computed(() => parse(this._csv()).data, ...(ngDevMode ? [{ debugName: "parsedCsv" }] : []));
12918
12223
  this.headers = computed(() => this.parsedCsv()[0], ...(ngDevMode ? [{ debugName: "headers" }] : []));
@@ -13032,7 +12337,7 @@ class NodeLogsFileComponent {
13032
12337
  ?.filter(({ level }) => levels.indexOf(level) >= levels.indexOf(this.selectedLevel()))
13033
12338
  ?.filter(({ code }) => (!this.modelFilter() || code.toLocaleLowerCase().includes(`model=${this.modelFilter().toLowerCase()}`)) &&
13034
12339
  (!this.termFilter() || code.toLocaleLowerCase().includes(`term=${this.termFilter().toLowerCase()}`))), ...(ngDevMode ? [{ debugName: "nodeLogLines" }] : []));
13035
- this.csvContent = computed(() => this.domSanitizer.bypassSecurityTrustResourceUrl(`data:text/html;charset=utf-8,${encodeURIComponent(logToCsv$1(this.allLogs()))}`), ...(ngDevMode ? [{ debugName: "csvContent" }] : []));
12340
+ this.csvContent = computed(() => this.domSanitizer.bypassSecurityTrustResourceUrl(`data:text/html;charset=utf-8,${encodeURIComponent(logToCsv(this.allLogs()))}`), ...(ngDevMode ? [{ debugName: "csvContent" }] : []));
13036
12341
  this.csvFilename = computed(() => fileToExt(this.node()?.['@id'] ?? '', 'csv'), ...(ngDevMode ? [{ debugName: "csvFilename" }] : []));
13037
12342
  }
13038
12343
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeLogsFileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
@@ -15181,7 +14486,7 @@ const valueToNodes = (value, type) => Array.isArray(value)
15181
14486
  const nodesFromError = (error, type) => error ? valueToNodes(error.node, type) || valueToNodes(error.value, error.schema) || [] : [];
15182
14487
  const errorCsv = nodes => {
15183
14488
  try {
15184
- return toCsv$1(nodes, { includeExising: true });
14489
+ return toCsv(nodes, { includeExising: true });
15185
14490
  }
15186
14491
  catch {
15187
14492
  return '';
@@ -15412,528 +14717,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
15412
14717
  */
15413
14718
  const impactAssessmentProduct = (impactAssessment) => impactAssessment?.product?.term || impactAssessment?.term;
15414
14719
 
15415
- /* eslint-disable complexity */
15416
- var ChartNodeType;
15417
- (function (ChartNodeType) {
15418
- ChartNodeType["midpoint"] = "midpoint";
15419
- ChartNodeType["category"] = "category";
15420
- ChartNodeType["emission"] = "emission";
15421
- ChartNodeType["endpoint"] = "endpoint";
15422
- ChartNodeType["input"] = "input";
15423
- ChartNodeType["operation"] = "operation";
15424
- ChartNodeType["root"] = "root";
15425
- })(ChartNodeType || (ChartNodeType = {}));
15426
- const nodeBorderColours = {
15427
- [ChartNodeType.endpoint]: '#193957',
15428
- [ChartNodeType.midpoint]: '#249DA5',
15429
- [ChartNodeType.category]: '#DC7033',
15430
- [ChartNodeType.emission]: '#498F78',
15431
- [ChartNodeType.input]: '#FFC000',
15432
- [ChartNodeType.operation]: '#D0CABA'
15433
- };
15434
- const nodeColours = {
15435
- [ChartNodeType.endpoint]: '#DBE3EA',
15436
- [ChartNodeType.midpoint]: '#D3EBED',
15437
- [ChartNodeType.category]: '#FFDEC0',
15438
- [ChartNodeType.emission]: '#D5F3D8',
15439
- [ChartNodeType.input]: '#FFE8A3',
15440
- [ChartNodeType.operation]: '#FBF4DF'
15441
- };
15442
- const legend = [
15443
- { type: ChartNodeType.endpoint, text: 'Endpoint / Single Score' },
15444
- { type: ChartNodeType.midpoint, text: 'Characterised Indicator' },
15445
- { type: ChartNodeType.category, text: 'Category' },
15446
- { type: ChartNodeType.emission, text: 'Emission / Resource Use' },
15447
- { type: ChartNodeType.input, text: 'Input' },
15448
- { type: ChartNodeType.operation, text: 'Operation' }
15449
- ];
15450
- /* eslint-disable-next-line complexity */
15451
- const getUnitsAndLabel = (terms, fallbackLabel = 'unknown term') => {
15452
- const allTerms = (Array.isArray(terms) ? terms : [terms]).filter(Boolean);
15453
- const term = allTerms?.[0];
15454
- const label = term?.name ?? term?.['@id'] ?? fallbackLabel;
15455
- const units = term?.units ? `(${term?.units})` : '';
15456
- return {
15457
- label: `${label.replace(units, '').trim()}${allTerms.length > 1 ? ` (and ${allTerms.length - 1} more)` : ''}`,
15458
- terms: allTerms,
15459
- units
15460
- };
15461
- };
15462
- const typeMapping = {
15463
- [TermTermType.endpointIndicator]: ChartNodeType.endpoint,
15464
- [TermTermType.characterisedIndicator]: ChartNodeType.midpoint,
15465
- [TermTermType.emission]: ChartNodeType.emission,
15466
- [TermTermType.resourceUse]: ChartNodeType.emission,
15467
- [TermTermType.operation]: ChartNodeType.operation
15468
- };
15469
- const safeDivide = (numerator, denominator) => (denominator !== 0 ? numerator / denominator : 0);
15470
- const sumIndicators = (data) => {
15471
- const totalValues = data.reduce((acc, log) => {
15472
- acc[log.indicator] = acc[log.indicator] || [];
15473
- !isUndefined(log.weightedValue) && acc[log.indicator].push(log.weightedValue);
15474
- if (log.category) {
15475
- const categoryKey = `${log.indicator}-${log.category}`;
15476
- acc[categoryKey] = acc[categoryKey] || [];
15477
- !isUndefined(log.weightedValue) && acc[categoryKey].push(log.weightedValue);
15478
- }
15479
- return acc;
15480
- }, {});
15481
- return Object.fromEntries(Object.entries(totalValues).map(([key, values]) => [
15482
- key,
15483
- // if the total is positive, we ignore negative values
15484
- sum(values) < 0 ? sum(values) : sum(values.filter(v => v >= 0))
15485
- ]));
15486
- };
15487
- const groupIndicators = (data, terms = {}, totals = {}) => data.reduce((acc, log) => {
15488
- const contributorType = typeMapping[terms[log.contributor]?.termType] || ChartNodeType.input;
15489
- const indicatorType = typeMapping[terms[log.indicator]?.termType];
15490
- const categoryKey = log.category ? `${log.indicator}-${log.category}` : undefined;
15491
- const parentTotal = categoryKey ? totals[categoryKey] : totals[log.indicator];
15492
- const contributorNode = {
15493
- id: log.contributor,
15494
- ...getUnitsAndLabel(terms[log.contributor], log.contributor),
15495
- type: contributorType,
15496
- value: log.value,
15497
- weightedValue: log.weightedValue,
15498
- modelId: log.modelId,
15499
- fraction: safeDivide(log.weightedValue, parentTotal),
15500
- children: contributorType === ChartNodeType.emission
15501
- ? log.inputs.map(i => ({
15502
- ...i,
15503
- ...getUnitsAndLabel(terms[i.id], i.label),
15504
- type: ChartNodeType.input,
15505
- fraction: safeDivide(i.weightedValue, log.value),
15506
- children: i.operations.map(o => ({
15507
- ...o,
15508
- ...getUnitsAndLabel(terms[o.id], o.label),
15509
- type: ChartNodeType.operation,
15510
- fraction: safeDivide(o.weightedValue, i.weightedValue)
15511
- }))
15512
- }))
15513
- : []
15514
- };
15515
- acc[log.indicator] = acc[log.indicator] || {
15516
- id: log.indicator,
15517
- ...getUnitsAndLabel(terms[log.indicator], log.indicator),
15518
- modelId: log.modelId,
15519
- type: indicatorType,
15520
- value: totals[log.indicator],
15521
- children: []
15522
- };
15523
- if (log.category) {
15524
- const categoryId = `cat-${log.indicator}-${log.category}`;
15525
- let categoryNode = acc[log.indicator].children.find(c => c.id === categoryId);
15526
- if (!categoryNode) {
15527
- categoryNode = {
15528
- id: categoryId,
15529
- label: log.category,
15530
- type: ChartNodeType.category,
15531
- units: '',
15532
- value: totals[categoryKey],
15533
- weightedValue: totals[categoryKey],
15534
- modelId: log.modelId,
15535
- fraction: safeDivide(totals[categoryKey], totals[log.indicator]),
15536
- children: []
15537
- };
15538
- acc[log.indicator].children.push(categoryNode);
15539
- }
15540
- categoryNode.children.push(contributorNode);
15541
- }
15542
- else {
15543
- acc[log.indicator].children.push(contributorNode);
15544
- }
15545
- return acc;
15546
- }, {});
15547
-
15548
- const width = 800;
15549
- const margin = { top: 10, right: 20, bottom: 10, left: 20 };
15550
- const fontSize = Math.floor(width / 100);
15551
- const nodePadding = fontSize / 2;
15552
- const lineHeight = fontSize * 1.2;
15553
- const barHeight = lineHeight;
15554
- const barRadius = 4;
15555
- const nodeHeight = nodePadding * 2 + 2 * lineHeight + barHeight;
15556
- const nodeWidth = nodeHeight * 2 * 1.618;
15557
- const nodeContentWidth = nodeWidth - nodePadding * 2;
15558
- const layerSpacing = (width - margin.left - margin.right - nodeWidth * 5) / 4;
15559
- const nodeMargin = nodePadding * 0.75;
15560
- const nodeSpacingFactor = (nodeHeight + nodeMargin * 2) / nodeHeight;
15561
- const nodeDuration = 600;
15562
- const linkDuration = 200;
15563
- const groupDelay = nodeDuration - 100;
15564
- const nonLCAIndicatorsId = 'nonLCAIndicators';
15565
- const greyedOpacity = 0.2;
15566
- const groupOverlap = lineHeight - nodePadding - nodeMargin;
15567
- const groupNodeSpace = nodeHeight - 2 * groupOverlap;
15568
- const collapseChildren = (node) => {
15569
- node._children = node.children || node._children;
15570
- node.children = null;
15571
- return node;
15572
- };
15573
- const restoreChildren = (node) => {
15574
- node.children = node._children;
15575
- node._children = null;
15576
- return node;
15577
- };
15578
- // children currently laid out, falling back to the collapsed ones while a node is closing
15579
- const childrenOf = (node) => (node?.children || node?._children || []);
15580
- // Stagger the children so they animate one after the other: in order when they appear, in reverse
15581
- // when they disappear. A child that is not in the list anymore has no known position: it is not
15582
- // delayed, which also keeps the delay finite (a `NaN` delay would never run, nor ever be removed).
15583
- const enterStagger = (parent, child) => Math.max(childrenOf(parent).indexOf(child), 0) * 10;
15584
- const exitStagger = (parent, child) => {
15585
- const children = childrenOf(parent);
15586
- const index = children.indexOf(child);
15587
- return index < 0 ? 0 : (children.length - index - 1) * 10;
15588
- };
15589
- const wrap = (selection, maxWidth = nodeContentWidth) => {
15590
- selection.each(function (d) {
15591
- const t = select(this);
15592
- const units = ellipsis$1(d.data.units, 20);
15593
- d3wrap(t, {
15594
- maxWidth,
15595
- maxLines: d.data.showBar ? 2 : 3,
15596
- vcentre: !d.data.showBar,
15597
- words: [...d.data.label.split(/\s+/), units],
15598
- ownLineWord: units,
15599
- nHeight: nodeHeight,
15600
- lHeight: lineHeight
15601
- });
15602
- });
15603
- };
15604
- const setNodeSize = selection => selection.attr('width', nodeWidth).attr('height', nodeHeight);
15605
- const formatBar = (selection) => selection
15606
- .attr('height', barHeight)
15607
- .attr('rx', `${barRadius}`)
15608
- .attr('ry', `${barRadius}`)
15609
- .attr('y', nodeHeight - nodePadding - barHeight)
15610
- .attr('x', nodePadding);
15611
- const formatBarPath = (data) => {
15612
- const x = nodePadding;
15613
- const y = nodeHeight - nodePadding - barHeight;
15614
- const h = barHeight;
15615
- const w = fractionWidth(data);
15616
- const isFull = data.fraction >= 0.99;
15617
- // Prevent visual glitches if the bar is narrower than the radius
15618
- const safeW = data.fraction < 0.01 ? 0 : Math.max(w, barRadius);
15619
- const safeH = Math.max(h, 2 * barRadius);
15620
- return isFull
15621
- ? `
15622
- M ${x + safeW - barRadius}, ${y}
15623
- a ${barRadius},${barRadius} 0 0 1 ${barRadius},${barRadius} v ${safeH - 2 * barRadius}
15624
- a ${barRadius},${barRadius} 0 0 1 -${barRadius},${barRadius} h -${safeW - 2 * barRadius}
15625
- a ${barRadius},${barRadius} 0 0 1 -${barRadius},-${barRadius} v -${safeH - 2 * barRadius}
15626
- a ${barRadius},${barRadius} 0 0 1 ${barRadius},-${barRadius}
15627
- z
15628
- `
15629
- : `
15630
- M ${x + safeW}, ${y}
15631
- v ${safeH}
15632
- h -${safeW - barRadius}
15633
- a ${barRadius},${barRadius} 0 0 1 -${barRadius},-${barRadius}
15634
- v -${safeH - 2 * barRadius}
15635
- a ${barRadius},${barRadius} 0 0 1 ${barRadius},-${barRadius}
15636
- z
15637
- `;
15638
- };
15639
- const groupButtonWidth = (nodeWidth * 2) / 7;
15640
- const groupButtonHeight = nodePadding * 2 + lineHeight + 3;
15641
- const setGroupButtonXY = (selection) => selection
15642
- .attr('width', groupButtonWidth)
15643
- .attr('height', d => (d._groupOpen ? lineHeight + nodePadding * 2 : groupButtonHeight))
15644
- .attr('x', d => (d._groupOpen ? (nodeWidth * 2.5) / 7 : nodeWidth / 2 - groupButtonWidth / 2))
15645
- .attr('y', d => d._groupOpen ? groupOverlap + (groupNodeSpace - (lineHeight + nodePadding * 2)) / 2 : nodePadding + lineHeight + 6);
15646
- const formatNumber = (value) => (isNumber(value) ? toPrecision(value, 3) : 'N/A');
15647
- const isTypeWithoutValue = (nodeDatum) => [ChartNodeType.input, ChartNodeType.operation].includes(nodeDatum.data.type);
15648
- const getWeightedValueText = (nodeDatum) => `${formatNumber(nodeDatum.data.weightedValue)}${nodeDatum.parent?.data.value === null
15649
- ? ''
15650
- : ` of ${formatNumber(nodeDatum.parent?.data.value || nodeDatum.parent?.data.weightedValue || 0)}`} ${isNumber(nodeDatum.parent?.data.value) ? nodeDatum.parent?.data?.units : nodeDatum.parent?.parent?.data?.units}`;
15651
- const generateTipData = (nodeDatum) => ({
15652
- hasWeightedValue: nodeDatum.data.weightedValue,
15653
- weightedValueText: getWeightedValueText(nodeDatum),
15654
- valueKeyHref: `${schemaBaseUrl()}/${SchemaType.Indicator}#value`,
15655
- modelDocsHref: nodeDatum.data.modelDocs,
15656
- valueValueText: isNumber(nodeDatum.data.value)
15657
- ? `${formatNumber(nodeDatum.data.value)} ${nodeDatum.data.units}`
15658
- : isTypeWithoutValue(nodeDatum)
15659
- ? ''
15660
- : 'no contribution',
15661
- hasValue: nodeDatum.data.value,
15662
- modelKeyHref: `${schemaBaseUrl()}/${SchemaType.Indicator}#methodModel`,
15663
- modelValue: nodeDatum.data.modelName,
15664
- modelValueHref: `${baseUrl()}/term/${nodeDatum.data.modelId}`,
15665
- termKeyHref: `${schemaBaseUrl()}/${SchemaType.Indicator}#term`,
15666
- terms: nodeDatum.data.terms?.map(term => ({
15667
- text: term.name || term['@id'],
15668
- href: `${baseUrl()}/term/${term['@id']}`
15669
- }))
15670
- });
15671
- const addTooltip = (selection, { tooltipOperator }) => {
15672
- let hoveringTip = false;
15673
- let hoveringNode = false;
15674
- let lastNodeId;
15675
- const closeTip = () => {
15676
- tooltipOperator.close(false);
15677
- selectAll('.tip-target').classed('tip-target', false);
15678
- lastNodeId = null;
15679
- };
15680
- const startCloseTip = () => {
15681
- setTimeout(() => {
15682
- if (!hoveringTip && !hoveringNode) {
15683
- closeTip();
15684
- }
15685
- }, 100);
15686
- };
15687
- selection
15688
- .on('click', closeTip)
15689
- .on('pointerenter', (event, nodeDatum) => {
15690
- hoveringNode = true;
15691
- if (lastNodeId === nodeDatum.data.id) {
15692
- return;
15693
- }
15694
- closeTip();
15695
- lastNodeId = nodeDatum.data.id;
15696
- select(event.target).classed('tip-target', true);
15697
- tooltipOperator.open({ tipData: generateTipData(nodeDatum) });
15698
- select('.driver-chart-tooltip')
15699
- .on('pointerenter', () => {
15700
- hoveringTip = true;
15701
- })
15702
- .on('pointerleave', () => {
15703
- hoveringTip = false;
15704
- startCloseTip();
15705
- });
15706
- })
15707
- .on('pointerleave', () => {
15708
- hoveringNode = false;
15709
- startCloseTip();
15710
- });
15711
- return selection;
15712
- };
15713
- const mergedNodes = (selection, { tooltipOperator }) => {
15714
- selection
15715
- .selectAll('.node-box')
15716
- .classed('node-openable', d => !!d.height)
15717
- .classed('node-open', d => d._open)
15718
- .call(setNodeSize)
15719
- .attr('stroke', ({ data }) => nodeBorderColours[data.type])
15720
- .attr('fill', d => nodeColours[d.data.type])
15721
- .attr('stroke-width', '2')
15722
- .attr('rx', '3px')
15723
- .attr('ry', '3px')
15724
- .style('user-select', typeof window !== 'undefined' && window.innerWidth < 768 ? 'none' : null);
15725
- selection.filter(d => !d.data.group && d.data.id !== nonLCAIndicatorsId).call(addTooltip, { tooltipOperator });
15726
- selection
15727
- .selectAll('.node-label')
15728
- .attr('y', d => (d.data.showBar ? lineHeight / 2 + nodePadding / 2 : 0))
15729
- .attr('x', nodeWidth / 2)
15730
- .attr('font-weight', 'bold')
15731
- .attr('fill', '#4a4a4a')
15732
- .attr('text-anchor', 'middle')
15733
- .attr('alignment-baseline', 'central');
15734
- const groups = selection.filter(d => d.data.group);
15735
- groups
15736
- .selectAll('.group-border')
15737
- .attr('width', nodeWidth + nodePadding * 2)
15738
- .attr('x', -nodePadding)
15739
- .attr('y', nodeHeight - (lineHeight + nodePadding * 2))
15740
- .attr('opacity', d => (d._greyed && d._groupOpen ? greyedOpacity : d._groupOpen ? 1 : 0))
15741
- .attr('height', d => d._groupOpen
15742
- ? Math.abs(d.parent._groupChildren.length - d.parent.children.length) * (nodeHeight * nodeSpacingFactor) +
15743
- nodePadding +
15744
- (lineHeight + nodePadding * 2) / 2
15745
- : 0)
15746
- .attr('fill', 'none')
15747
- .attr('stroke-dasharray', '2, 5')
15748
- .attr('stroke', '#b5b5b5')
15749
- .attr('stroke-width', '1');
15750
- groups
15751
- .selectAll('.group-label')
15752
- .attr('width', nodeWidth)
15753
- .attr('y', nodeHeight / 3)
15754
- .attr('x', nodeWidth / 2)
15755
- .text(d => d.data.label)
15756
- .attr('opacity', d => (d._groupOpen ? 0 : d._greyed ? greyedOpacity : 1))
15757
- .attr('fill', '#4a4a4a')
15758
- .attr('text-anchor', 'middle')
15759
- .attr('alignment-baseline', 'central')
15760
- .attr('font-weight', 'bold');
15761
- groups
15762
- .selectAll('.group-button-border')
15763
- .attr('width', nodeWidth)
15764
- .attr('x', 0)
15765
- .attr('y', 0)
15766
- .attr('height', d => (d._groupOpen ? '0' : '45'))
15767
- .attr('fill', 'none')
15768
- .attr('stroke-dasharray', '2, 5')
15769
- .attr('stroke', '#b5b5b5')
15770
- .attr('stroke-width', '1');
15771
- groups
15772
- .selectAll('.group-button')
15773
- .call(setGroupButtonXY)
15774
- .style('pointer-events', d => (d._greyed ? 'none' : 'auto'))
15775
- .style('stroke-opacity', d => (d._greyed ? greyedOpacity : 1))
15776
- .style('display', 'none') // do not show on export
15777
- .attr('fill', d => nodeColours[d.data.type])
15778
- .attr('rx', '3px')
15779
- .attr('ry', '3px');
15780
- groups
15781
- .selectAll('.group-button-text')
15782
- .text(d => (d._groupOpen ? 'Hide' : 'Show'))
15783
- .style('display', 'none') // do not show on export
15784
- .attr('x', d => (d._groupOpen ? nodeWidth / 2 : nodeWidth / 2))
15785
- .attr('y', d => (d._groupOpen ? nodeMargin + lineHeight + lineHeight / 2 : nodeHeight / 3 + groupButtonHeight - 4))
15786
- .attr('font-size', () => `${fontSize * 1.3}px`)
15787
- .attr('fill', '#4a4a4a')
15788
- .attr('text-anchor', 'middle')
15789
- .attr('alignment-baseline', 'central');
15790
- selection.selectAll('.sibling-mask').remove();
15791
- selection
15792
- .filter(d => d._greyed && !d.data.group)
15793
- .append('rect')
15794
- .classed('node-openable', d => !!d.height)
15795
- .classed('sibling-mask mask-openable', true)
15796
- .attr('fill', 'rgba(250, 250, 250)')
15797
- .attr('stroke', 'rgba(250, 250, 250)')
15798
- .attr('fill-opacity', '0.8')
15799
- .attr('stroke-opacity', '0.9')
15800
- .attr('stroke-width', '2')
15801
- .attr('rx', '3px')
15802
- .attr('ry', '3px')
15803
- .call(setNodeSize);
15804
- selection
15805
- .filter(d => d._greyed && d.data.group)
15806
- .append('rect')
15807
- .classed('sibling-mask', true)
15808
- .attr('fill', 'rgba(250, 250, 250)')
15809
- .attr('stroke', 'rgba(250, 250, 250)')
15810
- .attr('fill-opacity', '0.8')
15811
- .attr('stroke-opacity', '0.9')
15812
- .attr('stroke-width', '2')
15813
- .attr('rx', '3px')
15814
- .attr('ry', '3px')
15815
- .call(setGroupButtonXY);
15816
- return selection;
15817
- };
15818
- // more than 70%
15819
- const fractionCutoff = 0.7;
15820
- const fractionWidth = (data) => Math.min(Math.abs(data.fraction), 1) * nodeContentWidth;
15821
- const enterNodes = (selection) => {
15822
- const nodes = selection.filter(d => d.data.type !== ChartNodeType.root && !d.data.group);
15823
- nodes.append('rect').classed('node-box', true);
15824
- nodes
15825
- .append('text')
15826
- .classed('node-label', true)
15827
- .text(({ data }) => data.label + ' ' + data.units)
15828
- .attr('opacity', 0);
15829
- // Text elements must be rendered before we can calculate wrapping
15830
- setTimeout(() => {
15831
- const labels = nodes.selectAll('.node-label').call(wrap);
15832
- labels.attr('opacity', 1);
15833
- });
15834
- const groups = selection.filter(d => d.data.group);
15835
- groups.append('rect').attr('class', 'group-border');
15836
- groups.append('text').attr('class', 'group-label');
15837
- groups.append('rect').attr('class', 'group-button-border');
15838
- groups.append('rect').attr('class', 'group-button');
15839
- groups.append('text').attr('class', 'group-button-text');
15840
- const withBar = nodes.filter(d => d.data.showBar);
15841
- withBar
15842
- .filter(d => d.data.fraction > 0)
15843
- .append('rect')
15844
- .classed('bar-background', true)
15845
- .attr('width', nodeContentWidth)
15846
- .attr('fill', 'white')
15847
- .call(formatBar);
15848
- withBar
15849
- .append('path')
15850
- .classed('bar-foreground', true)
15851
- .attr('d', d => formatBarPath(d.data))
15852
- .attr('fill', d => nodeBorderColours[d.data.type]);
15853
- withBar
15854
- .append('text')
15855
- .classed('bar-label', true)
15856
- .text(({ data }) => data.fraction === 0
15857
- ? 'no contribution'
15858
- : `${Math.round(100 * data.fraction)}% ${Math.round(100 * data.fraction) === 0 ? ' (rounded)' : ''}`)
15859
- .attr('text-anchor', d => (d.data.fraction === 0 ? 'middle' : d.data.fraction > fractionCutoff ? 'end' : 'start'))
15860
- .attr('x', d => d.data.fraction === 0
15861
- ? nodeWidth / 2
15862
- : fractionWidth(d.data) + nodePadding + (d.data.fraction > fractionCutoff ? -4 : 4))
15863
- .attr('y', nodeHeight - nodePadding - barHeight / 2)
15864
- .attr('fill', '#4a4a4a')
15865
- .attr('alignment-baseline', 'central');
15866
- return selection;
15867
- };
15868
- const mergedLinks = (selection, { togglingGroup, switchingSelection }) => selection
15869
- .style('stroke', d => (d.target._open && nodeBorderColours[d.source.data.type]) || null)
15870
- .attr('stroke-dasharray', d => (d.target.data.group ? '2,5' : null))
15871
- .attr('stroke-width', d => (d.target._open ? 2 : d.target._groupOpen ? 0 : 1))
15872
- .attr('stroke-opacity', d => (d.target._greyed ? 0.1 : 0.6))
15873
- .transition()
15874
- .duration(linkDuration)
15875
- .delay(d => {
15876
- const groupClosing = togglingGroup && !childrenOf(d.target.parent).find(n => n.data.group)?._groupOpen;
15877
- return groupClosing ? groupDelay : (switchingSelection ? nodeDuration : 0) + enterStagger(d.source, d.target);
15878
- })
15879
- .attr('opacity', 1)
15880
- .attr('d', d => {
15881
- const targetDy = d.target._groupOpen ? nodePadding + lineHeight / 2 + nodeMargin * 2 : nodeHeight / 2;
15882
- return linkHorizontal()({
15883
- source: [d.source.y + nodeWidth, d.source.x + nodeHeight / 2],
15884
- target: [d.target.y, d.target.x + targetDy]
15885
- });
15886
- });
15887
- const groupByPercentage = node => [ChartNodeType.endpoint, ChartNodeType.midpoint, ChartNodeType.emission].includes(node.data.type);
15888
- const groupPercentage = (node, percentage = 0.02) => {
15889
- const grouped = node._children?.filter(child => child.data.fraction < percentage);
15890
- if (node._children?.length > 6 && grouped?.length > 1) {
15891
- const type = node._children[0].data.type;
15892
- const groupNode = {
15893
- ...hierarchy({
15894
- id: `${type}-group`,
15895
- label: `${grouped.length} more under ${Math.round(percentage * 100)}%.`,
15896
- weightedValue: null,
15897
- value: null,
15898
- type,
15899
- group: true
15900
- }),
15901
- depth: node.depth + 1,
15902
- height: 1,
15903
- parent: node
15904
- };
15905
- const notGrouped = node._children?.filter(child => child.data.fraction >= percentage);
15906
- node._children.splice(notGrouped.length, 0, groupNode);
15907
- notGrouped.push(groupNode);
15908
- node._groupChildren = node._children;
15909
- node._children = notGrouped;
15910
- }
15911
- };
15912
- const showBar = (node) => [
15913
- node.parent?.data.type !== ChartNodeType.root,
15914
- node.parent?.data.id !== nonLCAIndicatorsId,
15915
- !node.data.group,
15916
- !isNaN(node.data.fraction)
15917
- ].every(Boolean);
15918
- const extraInputKeys = ['input', 'input value'];
15919
- const extraOperationKeys = ['operation', 'operation value'];
15920
- const dataToCsv = (keys, terms) => (data) => [
15921
- // line for the data
15922
- toLine([
15923
- ...keys.map(k => terms[data[k]]?.name || data[k] || ''),
15924
- ...extraInputKeys.map(() => ''),
15925
- ...extraOperationKeys.map(() => '')
15926
- ]),
15927
- // lines for each inputs and operations
15928
- ...(data.inputs ?? []).flatMap(input => [
15929
- toLine([...keys.map(() => ''), input.label, input.weightedValue, ...extraOperationKeys.map(() => '')]),
15930
- ...(input.operations ?? []).map(operation => toLine([...keys.map(() => ''), ...extraInputKeys.map(() => ''), operation.label, operation.weightedValue]))
15931
- ])
15932
- ];
15933
- const toCsv = (data, terms) => {
15934
- const keys = Object.keys(data[0]).filter(k => k !== 'inputs');
15935
- return [toLine([...keys, ...extraInputKeys, ...extraOperationKeys]), ...data.flatMap(dataToCsv(keys, terms))].join('\n');
15936
- };
14720
+ /**
14721
+ * The driver chart, drawn with d3 by `@hestia-earth/ui-components-charts`; this component gives it the
14722
+ * platform's popover, legend and export.
14723
+ */
15937
14724
  class HierarchyChartComponent {
15938
14725
  constructor() {
15939
14726
  this.chartRef = viewChild.required('chart');
@@ -15942,58 +14729,10 @@ class HierarchyChartComponent {
15942
14729
  this.terms = input({}, ...(ngDevMode ? [{ debugName: "terms" }] : []));
15943
14730
  this.chartError = output();
15944
14731
  this.nodeBorderColours = nodeBorderColours;
15945
- this.nodeColours = nodeColours;
14732
+ this.nodeColours = nodeColours$1;
15946
14733
  this.expandDownload = signal(false, ...(ngDevMode ? [{ debugName: "expandDownload" }] : []));
15947
14734
  this.includedTypes = signal([], ...(ngDevMode ? [{ debugName: "includedTypes" }] : []));
15948
- this.legend = computed(() => {
15949
- const includedTypes = this.includedTypes();
15950
- const defaultLegend = legend.filter(({ type }) => includedTypes.includes(type));
15951
- const categoryLabel = this.data().find(d => d.categoryLabel)?.categoryLabel;
15952
- return categoryLabel
15953
- ? defaultLegend.map(l => (l.type === ChartNodeType.category ? { ...l, text: categoryLabel } : l))
15954
- : defaultLegend;
15955
- }, ...(ngDevMode ? [{ debugName: "legend" }] : []));
15956
- this.totalsPerIndicator = computed(() => sumIndicators(this.data()), ...(ngDevMode ? [{ debugName: "totalsPerIndicator" }] : []));
15957
- this.indicators = computed(() => groupIndicators(this.data(), this.terms(), this.totalsPerIndicator()), ...(ngDevMode ? [{ debugName: "indicators" }] : []));
15958
- this.indicatorArray = computed(() => Object.values(this.indicators()), ...(ngDevMode ? [{ debugName: "indicatorArray" }] : []));
15959
- this.linkedIndicators = computed(() => this.indicatorArray()
15960
- // link endpoints to midpoints and midpoints to any child midpoints
15961
- .map(indicator => ({
15962
- ...indicator,
15963
- children: indicator.children.map(child => {
15964
- const prefixRemoved = indicator.type === ChartNodeType.endpoint && child.label.split(', ')[1];
15965
- const childIndicator = this.indicators()[child.id];
15966
- return child.type === ChartNodeType.midpoint
15967
- ? {
15968
- ...child,
15969
- label: prefixRemoved
15970
- ? '[...]' + prefixRemoved.charAt(0).toUpperCase() + prefixRemoved.slice(1)
15971
- : child.label,
15972
- modelId: childIndicator?.modelId || child.modelId,
15973
- children: childIndicator?.children
15974
- }
15975
- : child;
15976
- })
15977
- }))
15978
- // remove duplicated indicators
15979
- .filter(indicator => !this.indicatorArray().some(parent => parent.children.some(child => child.id === indicator.id))), ...(ngDevMode ? [{ debugName: "linkedIndicators" }] : []));
15980
- this.nonLCAIndicators = computed(() => this.linkedIndicators().filter(indicator => indicator.type === ChartNodeType.midpoint), ...(ngDevMode ? [{ debugName: "nonLCAIndicators" }] : []));
15981
- this.otherEndpoint = computed(() => this.nonLCAIndicators().length
15982
- ? {
15983
- id: nonLCAIndicatorsId,
15984
- label: 'Other indicators',
15985
- term: 'n/a',
15986
- modelId: 'n/a',
15987
- units: '',
15988
- value: null,
15989
- weightedValue: null,
15990
- type: ChartNodeType.endpoint,
15991
- fraction: null,
15992
- children: this.nonLCAIndicators()
15993
- }
15994
- : null, ...(ngDevMode ? [{ debugName: "otherEndpoint" }] : []));
15995
- this.endpoints = computed(() => this.linkedIndicators().filter(indicator => indicator.type === ChartNodeType.endpoint), ...(ngDevMode ? [{ debugName: "endpoints" }] : []));
15996
- this.chartChildren = computed(() => this.endpoints().length ? [...this.endpoints(), this.otherEndpoint()].filter(Boolean) : this.linkedIndicators(), ...(ngDevMode ? [{ debugName: "chartChildren" }] : []));
14735
+ this.legend = computed(() => hierarchyLegend(this.data(), this.includedTypes()), ...(ngDevMode ? [{ debugName: "legend" }] : []));
15997
14736
  this.exportFormats = [
15998
14737
  ...exportFormats,
15999
14738
  {
@@ -16003,201 +14742,35 @@ class HierarchyChartComponent {
16003
14742
  ];
16004
14743
  this.chartExportFn = async (format) => {
16005
14744
  return {
16006
- png: () => this.downloadPng(),
16007
- svg: () => this.downloadSvg(),
16008
- csv: () => downloadFile(`data:text/html;charset=utf-8,${encodeURIComponent(toCsv(this.data(), this.terms()))}`, 'chart.csv')
14745
+ png: () => downloadPng(this.chartRef().nativeElement),
14746
+ svg: () => downloadSvg(this.chartRef().nativeElement),
14747
+ csv: () => downloadFile(`data:text/html;charset=utf-8,${encodeURIComponent(hierarchyCsv(this.data(), this.terms()))}`, 'chart.csv')
16009
14748
  }[format]();
16010
14749
  };
16011
14750
  effect(() => {
16012
14751
  if ([!isEmpty(this.data()), !isEmpty(this.terms()), this.chartRef()?.nativeElement].every(Boolean)) {
16013
- untracked(() => this.init());
14752
+ untracked(() => this.draw());
16014
14753
  }
16015
14754
  });
16016
14755
  }
16017
- init() {
14756
+ draw() {
16018
14757
  try {
16019
- this.root = null;
16020
- this.node = null;
16021
- this.link = null;
16022
- this.initChart();
14758
+ const { includedTypes } = drawHierarchyChart(this.chartRef().nativeElement, {
14759
+ data: this.data(),
14760
+ terms: this.terms(),
14761
+ tooltip: {
14762
+ open: tipData => this.tooltipOperator().open({ tipData }),
14763
+ close: () => this.tooltipOperator().close(false),
14764
+ element: () => document.querySelector('.driver-chart-tooltip')
14765
+ },
14766
+ urls: { baseUrl, schemaBaseUrl }
14767
+ });
14768
+ this.includedTypes.set(includedTypes);
16023
14769
  }
16024
14770
  catch (error) {
16025
14771
  this.chartError.emit(error);
16026
14772
  }
16027
14773
  }
16028
- initChart() {
16029
- const root = hierarchy({
16030
- id: 'dummy-root',
16031
- label: '',
16032
- terms: [],
16033
- modelId: '',
16034
- value: null,
16035
- weightedValue: null,
16036
- units: '',
16037
- type: ChartNodeType.root,
16038
- children: this.chartChildren()
16039
- });
16040
- this.includedTypes.set(unique(root.descendants().map(n => n.data.type)));
16041
- this.root = root
16042
- // treat `0` as -infinity so they appear all the way to the bottom
16043
- .sort((a, b) => (b.data.fraction || -Infinity) - (a.data.fraction || -Infinity))
16044
- .eachAfter(node => {
16045
- if (node.data.fraction === 0) {
16046
- node.children = null;
16047
- node.height = 0;
16048
- }
16049
- node.data.type !== ChartNodeType.root && collapseChildren(node);
16050
- groupByPercentage(node) && groupPercentage(node);
16051
- node.data.showBar = showBar(node);
16052
- node.data.modelName = this.terms()?.[node.data.modelId]?.name;
16053
- node.data.modelDocs = this.terms()?.[node.data.modelId]?.docs;
16054
- });
16055
- this.renderChart();
16056
- }
16057
- renderChart() {
16058
- this.svg = select(this.chartRef()?.nativeElement);
16059
- this.svg.selectAll('*').remove();
16060
- // root node is always positioned at ⟨0, 0⟩
16061
- const innerHeight = (this.root.children.length - 1) * nodeHeight * nodeSpacingFactor + nodeHeight;
16062
- this.yMin = (nodeHeight - innerHeight) / 2 - margin.top;
16063
- this.yTotal = innerHeight + margin.top + margin.bottom;
16064
- const numLayers = this.legend().length;
16065
- const chartWidth = numLayers * nodeWidth + (numLayers - 1) * layerSpacing;
16066
- const totalWidth = Math.max(width, chartWidth + margin.left + margin.right);
16067
- this.svg
16068
- .attr('viewBox', [-margin.left, this.yMin, totalWidth, this.yTotal].join(' '))
16069
- .attr('font-size', `${fontSize}px`);
16070
- this.node = this.svg.append('g').selectAll('g');
16071
- this.link = this.svg.append('g').selectAll('path');
16072
- this.handleNodeClick(null, this.root.find(node => node.depth === 1));
16073
- }
16074
- // flip x/y to go left-right instead of top-bottom
16075
- updateChart({ togglingGroup = false, switchingSelection = false } = {}) {
16076
- const root = this.root;
16077
- const treeLayout = tree()
16078
- .nodeSize([nodeHeight, nodeWidth])
16079
- .separation((...neighbours) => neighbours.some(n => n.data.group && n._groupOpen) ? 1 - groupOverlap / nodeHeight : nodeSpacingFactor);
16080
- const tree$1 = treeLayout(root);
16081
- const links = tree$1.links().filter(d => d.source.data.type !== ChartNodeType.root) || [];
16082
- const nodes = tree$1.descendants().reverse();
16083
- const numLayers = this.legend().length;
16084
- const chartWidth = numLayers * nodeWidth + (numLayers - 1) * layerSpacing;
16085
- const totalWidth = Math.max(width, chartWidth + margin.left + margin.right);
16086
- const offset = Math.max(0, (totalWidth - margin.left - margin.right - chartWidth) / 2);
16087
- nodes.forEach(d => {
16088
- d.y = offset + (d.depth - 1) * (nodeWidth + layerSpacing);
16089
- });
16090
- // By default the tree expands symmetrically upwards and downwards from the selected node.
16091
- // We move children down to prevent expansion upwards or up to minimise expansion downwards
16092
- const minimiseHeight = ({ previousTanslateY, previousYTotal }, depth) => {
16093
- const maxYTotal = Math.max(previousYTotal, this.yTotal);
16094
- const parent = tree$1.find(node => node.depth === depth - 1 && node._open);
16095
- parent?.children?.forEach(node => (node.x += previousTanslateY));
16096
- const { min: minX, max: maxX } = (parent?.children || []).reduce(({ min, max }, n) => ({
16097
- min: n.x < min ? n.x : min,
16098
- max: n.x > max ? n.x : max
16099
- }), { min: parent?.x, max: parent?.x });
16100
- const { yMin, yTotal } = parent?.children?.length
16101
- ? { yMin: minX - margin.top, yTotal: maxX - minX + margin.bottom + margin.top + nodeHeight }
16102
- : { yMin: null, yTotal: null };
16103
- const overflowAbove = this.yMin - yMin; // positive when overflows above
16104
- const overflowBelow = yTotal + yMin - (maxYTotal + this.yMin); // positive when overflows below
16105
- // - translateY when overflows above: positive
16106
- // - translateY when overflows below: negative by smallest of underflow above or overflow below
16107
- // - translateY when no overflow: 0
16108
- const translateY = overflowAbove > 0 ? overflowAbove : overflowBelow > 0 ? Math.max(-overflowBelow, overflowAbove) : 0;
16109
- parent?.children?.forEach(node => (node.x += translateY));
16110
- return {
16111
- previousYTotal: Math.max(yTotal, previousYTotal),
16112
- previousTanslateY: translateY + previousTanslateY
16113
- };
16114
- };
16115
- const depths = Array.from({ length: numLayers }, (_, i) => i + 2);
16116
- const { previousYTotal: newHeight } = depths.reduce(minimiseHeight, {
16117
- previousTanslateY: 0,
16118
- previousYTotal: this.yTotal
16119
- });
16120
- this.svg.transition().duration(300).attr('viewBox', [-margin.left, this.yMin, totalWidth, newHeight].join(' '));
16121
- this.node = this.node
16122
- // the root is a dummy node: it is never rendered, and has no parent to animate against
16123
- .data(nodes.filter(d => d.data.type !== ChartNodeType.root), d => `${d.parent?.parent?.parent?.data.id}-${d.parent?.parent?.data.id}-${d.parent?.data.id}-${d.data.id}`)
16124
- .join(enter => enter
16125
- .append('g')
16126
- .call(enterNodes)
16127
- .attr('opacity', 0)
16128
- .attr('transform', d => 'translate(' + (togglingGroup ? d.y : d.y - nodeWidth / 2) + ',' + d.x + ')'), update => update, exit => {
16129
- this.tooltipOperator().close(false);
16130
- return (exit
16131
- .style('pointer-events', 'none')
16132
- .transition()
16133
- .duration(nodeDuration / 2)
16134
- .delay(d => exitStagger(d.parent, d))
16135
- .ease(easeElasticIn.amplitude(0.5).period(1))
16136
- .attr('transform', d => 'translate(' + (togglingGroup ? d.y : d.y - nodeWidth / 2) + ',' + d.x + ')')
16137
- .attr('opacity', 0)
16138
- .remove()
16139
- // in case a tooltip was opened on a transitioning element
16140
- .on('end', () => this.tooltipOperator().close(false)));
16141
- });
16142
- this.node
16143
- .call(mergedNodes, { tooltipOperator: this.tooltipOperator() })
16144
- .transition()
16145
- .delay(d => {
16146
- const groupClosing = togglingGroup && !childrenOf(d.parent).find(n => n.data.group)?._groupOpen;
16147
- return groupClosing ? groupDelay : (switchingSelection ? nodeDuration : 0) + enterStagger(d.parent, d);
16148
- })
16149
- .duration(nodeDuration)
16150
- .ease(easeElasticOut.amplitude(0.5).period(1))
16151
- .attr('transform', d => `translate(${d.y},${d.x})`)
16152
- .attr('opacity', 1);
16153
- this.node.selectAll('.node-openable').on('click', this.handleNodeClick.bind(this));
16154
- this.node.selectAll('.group-button').on('click', this.handleGroupButtonClick.bind(this));
16155
- this.link = this.link
16156
- .data(links, d => `${d.source.parent?.parent?.data.id}-${d.source.parent?.data.id}-${d.source.data.id}-${d.target.data.id}`)
16157
- .join(enter => enter
16158
- .append('path')
16159
- .classed('link-path', true)
16160
- .attr('opacity', 0)
16161
- .attr('fill', 'none')
16162
- .attr('stroke', '#b5b5b5'), update => update, exit => exit
16163
- .transition()
16164
- .duration(linkDuration / 2)
16165
- .delay(d => exitStagger(d.source, d.target))
16166
- .attr('opacity', 0)
16167
- .remove())
16168
- .call(mergedLinks, { togglingGroup, switchingSelection });
16169
- }
16170
- handleGroupButtonClick(_event, d) {
16171
- if (d._greyed) {
16172
- return;
16173
- }
16174
- d._groupOpen = !d._groupOpen;
16175
- const children = d.parent._groupChildren;
16176
- d.parent._groupChildren = d.parent.children;
16177
- d.parent.children = children;
16178
- this.updateChart({ togglingGroup: true });
16179
- }
16180
- handleNodeClick(_event, d) {
16181
- d._open = !d._open;
16182
- const switchingSelection = '_greyed' in d && d._greyed;
16183
- // the node can still be clicked while it animates out, by which time its parent is collapsed
16184
- childrenOf(d.parent).forEach(child => {
16185
- child._greyed = false;
16186
- if (child !== d) {
16187
- child._open = false;
16188
- collapseChildren(child);
16189
- child._greyed = d._open;
16190
- }
16191
- });
16192
- d.children ? collapseChildren(d) : restoreChildren(d);
16193
- this.updateChart({ switchingSelection });
16194
- }
16195
- downloadSvg() {
16196
- return downloadSvg(this.chartRef().nativeElement);
16197
- }
16198
- downloadPng() {
16199
- return downloadPng(this.chartRef().nativeElement);
16200
- }
16201
14774
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: HierarchyChartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
16202
14775
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: HierarchyChartComponent, isStandalone: true, selector: "he-hierarchy-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, terms: { classPropertyName: "terms", publicName: "terms", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { chartError: "chartError" }, viewQueries: [{ propertyName: "chartRef", first: true, predicate: ["chart"], descendants: true, isSignal: true }, { propertyName: "tooltipOperator", first: true, predicate: ["t"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"chart-area-border\">\n <div\n class=\"is-flex is-justify-content-center is-align-items-center is-flex-wrap-wrap is-gap-16 chart-area-border is-legend\">\n @for (legendItem of legend(); track legendItem.type) {\n <div class=\"is-flex is-align-items-center is-gap-8 | is-legend-item\">\n <span\n class=\"is-inline-block-tablet is-align-middle | key-colour\"\n [style.backgroundColor]=\"nodeColours[legendItem.type]\"\n [style.borderColor]=\"nodeBorderColours[legendItem.type]\"></span>\n <span class=\"is-size-7\">{{ legendItem.text }}</span>\n </div>\n }\n </div>\n\n <div class=\"chart-container is-relative w-100 is-mt-2\">\n <svg class=\"w-100 h-100\" #chart></svg>\n\n <span\n class=\"is-hidden\"\n [ngbPopover]=\"tipContent\"\n triggers=\"manual\"\n placement=\"right left auto\"\n container=\"body\"\n #t=\"ngbPopover\"\n positionTarget=\".tip-target\"\n popoverClass=\"is-narrow driver-chart-tooltip\"\n [animation]=\"true\"></span>\n\n <div class=\"is-absolute | chart-controls\">\n <div class=\"is-flex is-flex-direction-column is-align-items-flex-end is-gap-4\">\n <he-chart-export-button\n buttonClass=\"button is-small\"\n [chartExportFn]=\"chartExportFn\"\n [exportFormats]=\"exportFormats\" />\n </div>\n </div>\n </div>\n</div>\n\n<ng-template #tipContent let-tipData=\"tipData\">\n @if (tipData.hasWeightedValue) {\n <p>contribution: {{ tipData.weightedValueText }}</p>\n }\n @if (tipData.valueValueText) {\n <p>\n <a class=\"is-dark\" target=\"_blank\" [href]=\"tipData.valueKeyHref\">value</a>\n <span class=\"is-pr-1\">:</span>\n <span>{{ tipData.valueValueText }}</span>\n @if (tipData.hasValue) {\n <a class=\"is-dark is-ml-2\" [href]=\"tipData.modelDocsHref\" target=\"_blank\">\n <span>Docs</span>\n <he-svg-icon name=\"external-link\" class=\"ml-2\" />\n </a>\n }\n </p>\n }\n @if (tipData.modelValue) {\n <p>\n <a class=\"is-dark\" target=\"_blank\" [href]=\"tipData.modelKeyHref\">methodModel</a>\n <span class=\"is-pr-1\">:</span>\n <a class=\"is-dark\" target=\"_blank\" [href]=\"tipData.modelValueHref\">{{ tipData.modelValue }}</a>\n </p>\n }\n @if (tipData.terms?.length) {\n <p>\n <a class=\"is-dark\" target=\"_blank\" [href]=\"tipData.termKeyHref\">terms</a>\n <span class=\"is-pr-1\">:</span>\n @for (term of tipData.terms; track term.text; let lastTerm = $last) {\n <a class=\"is-dark\" target=\"_blank\" [href]=\"term.href\">{{ term.text }}</a>\n @if (!lastTerm) {\n <span class=\"is-px-1\">;</span>\n }\n }\n </p>\n }\n</ng-template>\n", styles: [".chart-controls{top:0;right:0}.chart-controls .button,.chart-controls he-chart-export-button ::ng-deep>.button{width:36px;height:36px}.key-colour{border-radius:3px;border:1px solid transparent;height:20px;width:20px}svg{pointer-events:none}:host ::ng-deep .node-box{fill-opacity:.3;stroke-opacity:.3;pointer-events:auto}:host ::ng-deep .node-openable:not(.mask-openable){cursor:pointer;stroke-opacity:1;fill-opacity:1}:host ::ng-deep .group-button,:host ::ng-deep .group-button-text{display:inline!important;cursor:pointer}:host ::ng-deep .node-label tspan{alignment-baseline:inherit}:host ::ng-deep .sibling-mask{pointer-events:none}:host ::ng-deep .mask-openable:hover{fill:none;stroke:none}:host ::ng-deep .mask-openable{cursor:pointer;pointer-events:all}\n"], dependencies: [{ kind: "directive", type: NgbPopover, selector: "[ngbPopover]", inputs: ["animation", "autoClose", "ngbPopover", "popoverTitle", "placement", "popperOptions", "triggers", "positionTarget", "container", "disablePopover", "popoverClass", "popoverContext", "openDelay", "closeDelay"], outputs: ["shown", "hidden"], exportAs: ["ngbPopover"] }, { kind: "component", type: HESvgIconComponent, selector: "he-svg-icon", inputs: ["name", "size", "animation"] }, { kind: "component", type: ChartExportButtonComponent, selector: "he-chart-export-button", inputs: ["buttonClass", "chart", "config", "exportFormats", "chartExportFn"] }] }); }
16203
14776
  }
@@ -16206,13 +14779,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
16206
14779
  args: [{ selector: 'he-hierarchy-chart', imports: [NgbPopover, HESvgIconComponent, ChartExportButtonComponent], template: "<div class=\"chart-area-border\">\n <div\n class=\"is-flex is-justify-content-center is-align-items-center is-flex-wrap-wrap is-gap-16 chart-area-border is-legend\">\n @for (legendItem of legend(); track legendItem.type) {\n <div class=\"is-flex is-align-items-center is-gap-8 | is-legend-item\">\n <span\n class=\"is-inline-block-tablet is-align-middle | key-colour\"\n [style.backgroundColor]=\"nodeColours[legendItem.type]\"\n [style.borderColor]=\"nodeBorderColours[legendItem.type]\"></span>\n <span class=\"is-size-7\">{{ legendItem.text }}</span>\n </div>\n }\n </div>\n\n <div class=\"chart-container is-relative w-100 is-mt-2\">\n <svg class=\"w-100 h-100\" #chart></svg>\n\n <span\n class=\"is-hidden\"\n [ngbPopover]=\"tipContent\"\n triggers=\"manual\"\n placement=\"right left auto\"\n container=\"body\"\n #t=\"ngbPopover\"\n positionTarget=\".tip-target\"\n popoverClass=\"is-narrow driver-chart-tooltip\"\n [animation]=\"true\"></span>\n\n <div class=\"is-absolute | chart-controls\">\n <div class=\"is-flex is-flex-direction-column is-align-items-flex-end is-gap-4\">\n <he-chart-export-button\n buttonClass=\"button is-small\"\n [chartExportFn]=\"chartExportFn\"\n [exportFormats]=\"exportFormats\" />\n </div>\n </div>\n </div>\n</div>\n\n<ng-template #tipContent let-tipData=\"tipData\">\n @if (tipData.hasWeightedValue) {\n <p>contribution: {{ tipData.weightedValueText }}</p>\n }\n @if (tipData.valueValueText) {\n <p>\n <a class=\"is-dark\" target=\"_blank\" [href]=\"tipData.valueKeyHref\">value</a>\n <span class=\"is-pr-1\">:</span>\n <span>{{ tipData.valueValueText }}</span>\n @if (tipData.hasValue) {\n <a class=\"is-dark is-ml-2\" [href]=\"tipData.modelDocsHref\" target=\"_blank\">\n <span>Docs</span>\n <he-svg-icon name=\"external-link\" class=\"ml-2\" />\n </a>\n }\n </p>\n }\n @if (tipData.modelValue) {\n <p>\n <a class=\"is-dark\" target=\"_blank\" [href]=\"tipData.modelKeyHref\">methodModel</a>\n <span class=\"is-pr-1\">:</span>\n <a class=\"is-dark\" target=\"_blank\" [href]=\"tipData.modelValueHref\">{{ tipData.modelValue }}</a>\n </p>\n }\n @if (tipData.terms?.length) {\n <p>\n <a class=\"is-dark\" target=\"_blank\" [href]=\"tipData.termKeyHref\">terms</a>\n <span class=\"is-pr-1\">:</span>\n @for (term of tipData.terms; track term.text; let lastTerm = $last) {\n <a class=\"is-dark\" target=\"_blank\" [href]=\"term.href\">{{ term.text }}</a>\n @if (!lastTerm) {\n <span class=\"is-px-1\">;</span>\n }\n }\n </p>\n }\n</ng-template>\n", styles: [".chart-controls{top:0;right:0}.chart-controls .button,.chart-controls he-chart-export-button ::ng-deep>.button{width:36px;height:36px}.key-colour{border-radius:3px;border:1px solid transparent;height:20px;width:20px}svg{pointer-events:none}:host ::ng-deep .node-box{fill-opacity:.3;stroke-opacity:.3;pointer-events:auto}:host ::ng-deep .node-openable:not(.mask-openable){cursor:pointer;stroke-opacity:1;fill-opacity:1}:host ::ng-deep .group-button,:host ::ng-deep .group-button-text{display:inline!important;cursor:pointer}:host ::ng-deep .node-label tspan{alignment-baseline:inherit}:host ::ng-deep .sibling-mask{pointer-events:none}:host ::ng-deep .mask-openable:hover{fill:none;stroke:none}:host ::ng-deep .mask-openable{cursor:pointer;pointer-events:all}\n"] }]
16207
14780
  }], ctorParameters: () => [], propDecorators: { chartRef: [{ type: i0.ViewChild, args: ['chart', { isSignal: true }] }], tooltipOperator: [{ type: i0.ViewChild, args: ['t', { isSignal: true }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], terms: [{ type: i0.Input, args: [{ isSignal: true, alias: "terms", required: false }] }], chartError: [{ type: i0.Output, args: ["chartError"] }] } });
16208
14781
 
16209
- const filterTermTypes$1 = [
16210
- TermTermType.resourceUse,
16211
- TermTermType.emission,
16212
- TermTermType.characterisedIndicator,
16213
- TermTermType.endpointIndicator,
16214
- TermTermType.model
16215
- ];
16216
14782
  const searchParams = (should) => ({
16217
14783
  fields: ['@type', '@id', 'name', 'units', 'termType'],
16218
14784
  limit: 9999, // do not use scroll
@@ -16224,89 +14790,13 @@ const searchParams = (should) => ({
16224
14790
  }
16225
14791
  }
16226
14792
  });
16227
- const defaultIndicatorsModelId = 'defaultIndicatorsModelId';
16228
- const defaultIndicatorsModelName = 'Default Indicators (multi-model)';
16229
- const warnings = (logs, terms) => [!logs.every(log => terms[log.indicator] && terms[log.contributor]) && 'missing-terms'].filter(Boolean);
16230
- const parseLogValues = (impactAssessment, values) => {
16231
- const log = values[0];
16232
- const { weightedTotal, total } = values.reduce((prev, curr) => ({
16233
- weightedTotal: prev.weightedTotal + curr.weightedValue,
16234
- total: prev.total + curr.value
16235
- }), { weightedTotal: 0, total: 0 });
16236
- const inputsObj = (impactAssessment.emissionsResourceUse || [])
16237
- .filter(eru => eru.term['@id'] === log.contributor && eru.inputs?.length)
16238
- .reduce((acc, eru) => {
16239
- const inputId = eru.inputs.map(i => i['@id']).join(';');
16240
- const currentOperations = eru.operation
16241
- ? [
16242
- {
16243
- id: eru.operation['@id'],
16244
- ...getUnitsAndLabel(eru.operation),
16245
- modelId: log.modelId,
16246
- weightedValue: eru.value
16247
- }
16248
- ]
16249
- : [];
16250
- acc[inputId] = acc[inputId]
16251
- ? {
16252
- ...acc[inputId],
16253
- weightedValue: acc[inputId].weightedValue + eru.value,
16254
- modelId: log.modelId,
16255
- operations: [...acc[inputId].operations, ...currentOperations]
16256
- }
16257
- : {
16258
- id: inputId,
16259
- ...(!eru.inputs?.length ? { label: 'Input group (under development)' } : getUnitsAndLabel(eru.inputs)),
16260
- modelId: log.modelId,
16261
- weightedValue: eru.value,
16262
- operations: [...currentOperations]
16263
- };
16264
- return acc;
16265
- }, {});
16266
- const inputs = Object.values(inputsObj);
16267
- return { ...log, weightedValue: weightedTotal, value: total, inputs };
16268
- };
16269
- const processContributions = (record, nodes) => Object.entries(record || {}).flatMap(([index, contributions]) => {
16270
- const indicator = nodes[+index];
16271
- return indicator
16272
- ? Object.entries(contributions).flatMap(([impactTermId, modelContr]) => Object.entries(modelContr).map(([modelId, weightedValue]) => {
16273
- const contributor = indicator.term['@id'];
16274
- const value = indicator.value || 0;
16275
- const coefficient = value !== 0 ? weightedValue / value : 0;
16276
- // only get contributions with positive coefficient
16277
- return coefficient > 0
16278
- ? {
16279
- indicator: impactTermId,
16280
- contributor,
16281
- coefficient,
16282
- modelId,
16283
- weightedValue,
16284
- value
16285
- }
16286
- : null;
16287
- }))
16288
- : null;
16289
- });
16290
- const parseContributions = (impactAssessment, contributions) => {
16291
- const allLogs = [
16292
- ...processContributions(contributions.emissionsResourceUse, impactAssessment.emissionsResourceUse || []),
16293
- ...processContributions(contributions.impacts, impactAssessment.impacts || [])
16294
- ].filter(Boolean);
16295
- const grouped = allLogs.reduce((acc, log) => {
16296
- const key = [log.indicator, log.contributor, log.modelId].join('/');
16297
- acc[key] = acc[key] || [];
16298
- acc[key].push(log);
16299
- return acc;
16300
- }, {});
16301
- return Object.values(grouped).map(logs => parseLogValues(impactAssessment, logs));
16302
- };
16303
14793
  class ImpactAssessmentsGraphComponent {
16304
14794
  constructor() {
16305
14795
  this.searchService = inject(HeSearchService);
16306
14796
  this.nodeService = inject(HeNodeService);
16307
14797
  this.emissionCategoryService = inject(CyclesEmissionsCategoryService);
16308
14798
  this.initialTerms = toSignal(this.searchService
16309
- .search$(searchParams(filterTermTypes$1.map(termType => matchTermType(termType))))
14799
+ .search$(searchParams(hierarchyTermTypes.map(termType => matchTermType(termType))))
16310
14800
  .pipe(mergeMap(({ results }) => from(results).pipe(map(term => {
16311
14801
  const modelDocs = term.termType === TermTermType.model ? findMatchingModel({ model: term['@id'] }) : null;
16312
14802
  return {
@@ -16329,16 +14819,14 @@ class ImpactAssessmentsGraphComponent {
16329
14819
  }),
16330
14820
  stream: ({ params: { selectedImpactAssessment, isRecalculated } }) => !selectedImpactAssessment || !isRecalculated
16331
14821
  ? of([])
16332
- : this.nodeService.getContributions$(selectedImpactAssessment).pipe(map(contributions => parseContributions(selectedImpactAssessment, contributions)), catchError(err => {
14822
+ : this.nodeService.getContributions$(selectedImpactAssessment).pipe(map(contributions => hierarchyContributions(selectedImpactAssessment, contributions)), catchError(err => {
16333
14823
  console.error(err);
16334
14824
  this.error.set(err.toString());
16335
14825
  return of([]);
16336
14826
  }))
16337
14827
  });
16338
14828
  this.nodeLogs = computed(() => this.nodeLogsResource.value() ?? [], ...(ngDevMode ? [{ debugName: "nodeLogs" }] : []));
16339
- this.logTermIds = computed(() => unique(this.nodeLogs()
16340
- .flatMap(l => [l.contributor, l.indicator, ...l.inputs.map(i => i.id)])
16341
- .filter(termId => !this.initialTerms()?.[termId])), ...(ngDevMode ? [{ debugName: "logTermIds" }] : []));
14829
+ this.logTermIds = computed(() => missingTermIds(this.nodeLogs(), this.initialTerms()), ...(ngDevMode ? [{ debugName: "logTermIds" }] : []));
16342
14830
  this.allTermsResource = rxResource({
16343
14831
  params: () => ({
16344
14832
  logTermIds: this.logTermIds(),
@@ -16355,40 +14843,14 @@ class ImpactAssessmentsGraphComponent {
16355
14843
  });
16356
14844
  this.allTerms = computed(() => this.allTermsResource.value() ?? {}, ...(ngDevMode ? [{ debugName: "allTerms" }] : []));
16357
14845
  this.noData = computed(() => !this.nodeLogs()?.length, ...(ngDevMode ? [{ debugName: "noData" }] : []));
16358
- this.warnings = computed(() => [this.nodeLogs(), this.allTerms()].some(v => isEmpty(v)) ? [] : warnings(this.nodeLogs(), this.allTerms()), ...(ngDevMode ? [{ debugName: "warnings" }] : []));
14846
+ this.warnings = computed(() => [this.nodeLogs(), this.allTerms()].some(v => isEmpty(v)) ? [] : hierarchyWarnings(this.nodeLogs(), this.allTerms()), ...(ngDevMode ? [{ debugName: "warnings" }] : []));
16359
14847
  this.showWarnings = signal(false, ...(ngDevMode ? [{ debugName: "showWarnings" }] : []));
16360
14848
  this.error = signal(undefined, ...(ngDevMode ? [{ debugName: "error" }] : []));
16361
14849
  this.selectedModelId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedModelId" }] : []));
16362
- this.hasDefaultModelLogs = computed(() => this.nodeLogs()?.some(log => log.modelId === getDefaultModelId(log.indicator)), ...(ngDevMode ? [{ debugName: "hasDefaultModelLogs" }] : []));
16363
- this.models = computed(() => [
16364
- ...(this.hasDefaultModelLogs() ? [{ '@id': defaultIndicatorsModelId, name: defaultIndicatorsModelName }] : []),
16365
- ...unique(this.nodeLogs().map(({ modelId }) => this.allTerms()[modelId] || { '@id': modelId }))
16366
- ], ...(ngDevMode ? [{ debugName: "models" }] : []));
14850
+ this.models = computed(() => hierarchyModels(this.nodeLogs(), this.allTerms()), ...(ngDevMode ? [{ debugName: "models" }] : []));
16367
14851
  this.categories = this.emissionCategoryService.categories;
16368
14852
  this.selectedCategory = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedCategory" }] : []));
16369
- this.chartData = computed(() => {
16370
- const category = this.selectedCategory();
16371
- const categoryLabel = category ? this.emissionCategoryService.categoryLabel(category) : undefined;
16372
- return this.nodeLogs()
16373
- .filter(log => log.modelId ===
16374
- (this.selectedModelId() === defaultIndicatorsModelId
16375
- ? getDefaultModelId(log.indicator)
16376
- : this.selectedModelId()))
16377
- .map(log => {
16378
- const includeCategory = category &&
16379
- [TermTermType.emission, TermTermType.resourceUse].includes(this.allTerms()[log.contributor]?.termType);
16380
- const categoryValue = includeCategory
16381
- ? this.emissionCategoryService.emissionCategoryValue(log.contributor, category)
16382
- : null;
16383
- return includeCategory
16384
- ? {
16385
- ...log,
16386
- category: categoryValue || `Not available for ${categoryLabel}`,
16387
- categoryLabel
16388
- }
16389
- : log;
16390
- });
16391
- }, ...(ngDevMode ? [{ debugName: "chartData" }] : []));
14853
+ this.chartData = computed(() => hierarchyData(this.nodeLogs(), this.allTerms(), this.selectedModelId(), this.selectedCategory()), ...(ngDevMode ? [{ debugName: "chartData" }] : []));
16392
14854
  this.loading = computed(() => [this.nodeLogsResource.isLoading(), this.allTermsResource.isLoading()].some(Boolean), ...(ngDevMode ? [{ debugName: "loading" }] : []));
16393
14855
  this.showChart = computed(() => [!isEmpty(this.chartData()), !isEmpty(this.allTerms()), !this.loading(), !this.error(), !this.showWarnings()].every(Boolean), ...(ngDevMode ? [{ debugName: "showChart" }] : []));
16394
14856
  effect(() => {
@@ -16430,7 +14892,7 @@ class ImpactAssessmentsIndicatorBreakdownChartComponent {
16430
14892
  }),
16431
14893
  stream: ({ params: { impactAssessment } }) => this.nodeService
16432
14894
  .getContributions$(impactAssessment)
16433
- .pipe(map(contributions => parseContributions$1(impactAssessment, contributions)))
14895
+ .pipe(map(contributions => parseContributions(impactAssessment, contributions)))
16434
14896
  });
16435
14897
  this.logs = computed(() => indicatorLogs(this.logsResource.value() ?? [], this.selectedTermId(), this.selectedMethodId()), ...(ngDevMode ? [{ debugName: "logs" }] : []));
16436
14898
  this.nonZeroLogs = computed(() => nonZeroContributors(this.logs()), ...(ngDevMode ? [{ debugName: "nonZeroLogs" }] : []));
@@ -16477,7 +14939,7 @@ class ImpactAssessmentsIndicatorBreakdownChartComponent {
16477
14939
  label: 'Data'
16478
14940
  }
16479
14941
  ];
16480
- this.csvContent = computed(() => `data:text/html;charset=utf-8,${encodeURIComponent(logToCsv$2(this.logs(), this.impactAssessment()))}`, ...(ngDevMode ? [{ debugName: "csvContent" }] : []));
14942
+ this.csvContent = computed(() => `data:text/html;charset=utf-8,${encodeURIComponent(logToCsv$1(this.logs(), this.impactAssessment()))}`, ...(ngDevMode ? [{ debugName: "csvContent" }] : []));
16481
14943
  this.chartExportFn = async (format, chart) => {
16482
14944
  return {
16483
14945
  png: () => chart.exportAsPng(),
@@ -17514,5 +15976,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
17514
15976
  * Generated bundle index. Do not edit.
17515
15977
  */
17516
15978
 
17517
- export { ARRAY_DELIMITER, ApplyPurePipe, BarChartComponent, BibliographiesSearchConfirmComponent, BlankNodeStateComponent, BlankNodeStateNoticeComponent, BlankNodeValueDeltaComponent, CapitalizePipe, ChartComponent, ChartConfigurationDirective, ChartExportButtonComponent, ChartTooltipComponent, ClickOutsideDirective, ClipboardComponent, CollapsibleBlockComponent, CollapsibleBoxComponent, CollapsibleBoxStyle, CompoundDirective, CompoundPipe, ContributionChartComponent, ControlValueAccessor, CycleNodesKeyGroup, CyclesCompletenessComponent, CyclesEmissionsCategoryService, CyclesEmissionsChartComponent, CyclesFunctionalUnitMeasureComponent, CyclesMetadataComponent, CyclesNodesComponent, CyclesNodesTimelineComponent, CyclesResultComponent, DataTableComponent, DefaultPipe, DeltaColour, DistributionChartComponent, DrawerContainerComponent, DurationPipe, EllipsisPipe, EngineModelsLinkComponent, EngineModelsLookupInfoComponent, EngineModelsStageComponent, EngineModelsStageDeepComponent, EngineModelsStageDeepService, EngineModelsVersionInfoComponent, EngineModelsVersionLinkComponent, EngineOrchestratorEditComponent, EngineRequirementsFormComponent, FileSizePipe, FileUploadErrorKeys, FilesDragDropDirective, FilesDropZoneComponent, FilesErrorSummaryComponent, FilesFormComponent, FilesFormEditableComponent, FilesUploadErrorsComponent, FilterAccordionComponent, FormulaBlockComponent, GUIDE_ENABLED, GetPipe, GlossaryMigrationFormat, GuideOverlayComponent, HE_API_BASE_URL, HE_CALCULATIONS_BASE_URL, HE_MAP_LOADED, HE_REGION_GEOJSON_BASE_URL, HeAuthService, HeCommonService, HeEngineService, HeGlossaryService, HeMendeleyService, HeNodeCsvService, HeNodeService, HeNodeStoreService, HeSchemaService, HeSearchService, HeToastService, HorizontalBarChartComponent, HorizontalButtonsGroupComponent, ImpactAssessmentsGraphComponent, ImpactAssessmentsIndicatorBreakdownChartComponent, ImpactAssessmentsIndicatorsChartComponent, ImpactAssessmentsProductsComponent, IsArrayPipe, IsObjectPipe, IssueConfirmComponent, KeyToLabelPipe, Level, LineChartComponent, LinkKeyValueComponent, LogStatus, LongPressDirective, MAX_RESULTS, MapsDrawingComponent, MapsDrawingConfirmComponent, MaxPipe, MeanPipe, MedianPipe, MendeleySearchResult, MinPipe, MobileShellComponent, NavigationMenuComponent, NoExtPipe, NodeAggregatedComponent, NodeAggregatedFormulasComponent, NodeAggregatedInfoComponent, NodeAggregatedQualityScoreComponent, NodeAggregationLogsComponent, NodeCsvExportConfirmComponent, NodeCsvPreviewComponent, NodeCsvSelectHeadersComponent, NodeIconComponent, NodeJLogModelsComponent, NodeJsonldComponent, NodeJsonldSchemaComponent, NodeKeyState, NodeLinkComponent, NodeLogsFileComponent, NodeLogsModelsComponent, NodeLogsTimeComponent, NodeMissingLookupFactorsComponent, NodeQualityScore, NodeRecommendationsComponent, NodeSelectComponent, NodeValueDetailsComponent, PipelineStagesProgressComponent, PluralizePipe, PopoverComponent, PopoverConfirmComponent, PrecisionPipe, RelatedNodeResult, RemoveMarkdownPipe, RepeatPipe, Repository, ResizedDirective, ResizedEvent, ResponsiveService, SchemaInfoComponent, SchemaVersionLinkComponent, SearchExtendComponent, ShelfDialogComponent, ShellComponent, SiteNodesKeyGroup, SitesManagementChartComponent, SitesMapsComponent, SitesNodesComponent, SkeletonTextComponent, SocialTagsComponent, SortByPipe, SortSelectComponent, SumPipe, TagsInputDirective, Template, TermsPropertyContentComponent, TermsSubClassOfContentComponent, TermsUnitsDescriptionComponent, ThousandSuffixesPipe, ThousandsPipe, TimesPipe, ToastComponent, UncapitalizePipe, addPolygonToFeature, aggregatedSitePrimaryProduct, allCountriesQuery, allGroups, allOptions, availableProperties, axisLabelSettings, backgroundModelParams, baseApiUrl, baseUrl, bottom, buildSummary, bytesSize, calculateCycleDuration, calculateCycleDurationEnabled, calculateCycleStartDate, calculateCycleStartDateEnabled, capitalize, changelogUrl, clustererImage, code, compoundToHtml, computeKeys, computeTerms, contactUsEmail, contactUsLink, convertToSvg, coordinatesToPoint, copyObject, countGroupVisibleNodes, countriesQuery, createMarker, cropsQuery, d3ellipse, d3wrap, dataPathLabel, dataPathToKey, dataVersionHeader, dataVersionHeaderKey, defaultFeature, defaultLabel, defaultSuggestionType, definitionToSchemaType, distinctUntilChangedDeep, downloadFile, downloadPng, downloadSvg, ellipsis, engineGitBaseUrl, engineGitUrl, errorText, errorsLabel, evaluateSuccess, exportAsSVG, exportFormats, externalLink, externalNodeLink, fillColor, fillStyle, filterBlankNode$1 as filterBlankNode, filterParams, findBackgroundModel, findConfigModels, findMatchingModel, findModels, findNodeModel, findOrchestratorModel, findProperty, findPropertyById, findTermModels, flatFilterData, flatFilterNode, formatCustomErrorMessage, formatDate, formatError, formatPropertyError, formatter$1 as formatter, getDatesBetween, gitBranch, gitHome, glossaryBaseUrl, glossaryLink, groupBlankNodesByTermIdentity, groupChanged, groupJLogByField, groupJLogByTerm, groupLogsByTerm, groupNodesByTerm, groupdLogsByKey, grouppedKeys, grouppedValueKeys, groupsLogsByFields, guideModelUrl, guideNamespace, handleAPIError, handleGuideEvent, hasError, hasValidationError, hasWarning, highestMethodTier, ignoreKeys$2 as ignoreKeys, impactAssessmentProduct, initialFilterState, injectResizeEvent$, inputGroupsTermTypes, isAddPropertyEnabled, isChrome, isDateBetween, isEqual, isExternal, isIdenticalNodeValue, isKeyClosedVisible, isKeyHidden, isMaxStage, isMethodModelAllowed, isNonNodeModelKey, isSchemaIri, isScrolledBelow, isState, isTermTypeAllowed, isValidKey, jLogModelCount, keyToDataPath, levels, loadMapApi, locationQuery, logToCsv$1 as logToCsv, logValueArray, logsKey, lookupUrl, mapFilterData, mapsUrl, markerIcon, markerPie, matchAggregatedQuery, matchAggregatedValidatedQuery, matchBoolPrefixQuery, matchCountry, matchExactQuery, matchGlobalRegion, matchId, matchNameNormalized, matchNestedKey, matchPhrasePrefixQuery, matchPhraseQuery, matchPrimaryProductQuery, matchQuery, matchRegex, matchRegion, matchTermType, matchType, maxAreaSize, measurementValue, mergeDataWithHeaders, methodTierOrder, migrationErrorMessage, migrationsUrl, modelCount, modelKeyParams, modelParams, models, multiMatchQuery, nestedProperty, nestingEnabled, nestingTypeEnabled, noValue, nodeAvailableProperties, nodeById, nodeChartLabels, nodeColours$1 as nodeColours, nodeDataState, nodeDataStates, nodeDataVersion, nodeId, nodeIdWithoutDataVersion, nodeIds, nodeLink, nodeLinkEnabled, nodeLinkTypeEnabled, nodeLogsUrl, nodeQualityScoreColor, nodeQualityScoreLevel, nodeQualityScoreMaxDefault, nodeQualityScoreOrder, nodeRequestId, nodeSecondaryColours, nodeToAggregationFilename, nodeType, nodeTypeDataState, nodeTypeIcon, nodeTypeIconSchema, nodeUrl, nodeUrlParams, nodeVersion, nodeVersionKey, nodesByState, nodesByType, numberGte, openNodeUrl, openUrl, optionsFromGroup, parentKey, parentProperty, parseData, parseDataPath, parseLines, parseMessage, parseNewValue, pluralize, pointToCoordinates, polygonBounds, polygonToCoordinates, polygonToMap, polygonsFromFeature, populateWithTrackIdsFilterData, postGuideEvent, primaryProduct, productsQuery, propertyError, propertyId, recursiveProperties, refToSchemaType, refreshPropertyKeys, regionsQuery, registerChart, repeat, reportIssueLink, reportIssueUrl, safeJSONParse, safeJSONStringify, schemaBaseUrl, schemaDataBaseUrl, schemaLink, schemaRequiredProperties, schemaTypeToDefaultValue, scrollToEl, scrollTop, searchFilterData, searchableTypes, siblingProperty, simplifyContributions, singleProperty, siteTooBig, siteTypeToColor, siteTypeToIcon, sortProperties, sortedDates, strokeColor, strokeStyle, subValueKeys, suggestMatchQuery, suggestQuery, suggestTerms, takeAfterViewInit, termLocation, termLocationName, termProperties, termSearch, termTypeLabel, toSnakeCase, toTextParts, toThousands, typeToNewProperty, typeaheadFocus, uncapitalize, uniqueDatesBetween, updateProperties, valueLink, valueToString, valueTypeToDefault, valueValue, waitFor, wildcardQuery };
15979
+ export { ARRAY_DELIMITER, ApplyPurePipe, BarChartComponent, BibliographiesSearchConfirmComponent, BlankNodeStateComponent, BlankNodeStateNoticeComponent, BlankNodeValueDeltaComponent, CapitalizePipe, ChartComponent, ChartConfigurationDirective, ChartExportButtonComponent, ChartTooltipComponent, ClickOutsideDirective, ClipboardComponent, CollapsibleBlockComponent, CollapsibleBoxComponent, CollapsibleBoxStyle, CompoundDirective, CompoundPipe, ContributionChartComponent, ControlValueAccessor, CycleNodesKeyGroup, CyclesCompletenessComponent, CyclesEmissionsCategoryService, CyclesEmissionsChartComponent, CyclesFunctionalUnitMeasureComponent, CyclesMetadataComponent, CyclesNodesComponent, CyclesNodesTimelineComponent, CyclesResultComponent, DataTableComponent, DefaultPipe, DeltaColour, DistributionChartComponent, DrawerContainerComponent, DurationPipe, EllipsisPipe, EngineModelsLinkComponent, EngineModelsLookupInfoComponent, EngineModelsStageComponent, EngineModelsStageDeepComponent, EngineModelsStageDeepService, EngineModelsVersionInfoComponent, EngineModelsVersionLinkComponent, EngineOrchestratorEditComponent, EngineRequirementsFormComponent, FileSizePipe, FileUploadErrorKeys, FilesDragDropDirective, FilesDropZoneComponent, FilesErrorSummaryComponent, FilesFormComponent, FilesFormEditableComponent, FilesUploadErrorsComponent, FilterAccordionComponent, FormulaBlockComponent, GUIDE_ENABLED, GetPipe, GlossaryMigrationFormat, GuideOverlayComponent, HE_API_BASE_URL, HE_CALCULATIONS_BASE_URL, HE_MAP_LOADED, HE_REGION_GEOJSON_BASE_URL, HeAuthService, HeCommonService, HeEngineService, HeGlossaryService, HeMendeleyService, HeNodeCsvService, HeNodeService, HeNodeStoreService, HeSchemaService, HeSearchService, HeToastService, HorizontalBarChartComponent, HorizontalButtonsGroupComponent, ImpactAssessmentsGraphComponent, ImpactAssessmentsIndicatorBreakdownChartComponent, ImpactAssessmentsIndicatorsChartComponent, ImpactAssessmentsProductsComponent, IsArrayPipe, IsObjectPipe, IssueConfirmComponent, KeyToLabelPipe, Level, LineChartComponent, LinkKeyValueComponent, LogStatus, LongPressDirective, MAX_RESULTS, MapsDrawingComponent, MapsDrawingConfirmComponent, MaxPipe, MeanPipe, MedianPipe, MendeleySearchResult, MinPipe, MobileShellComponent, NavigationMenuComponent, NoExtPipe, NodeAggregatedComponent, NodeAggregatedFormulasComponent, NodeAggregatedInfoComponent, NodeAggregatedQualityScoreComponent, NodeAggregationLogsComponent, NodeCsvExportConfirmComponent, NodeCsvPreviewComponent, NodeCsvSelectHeadersComponent, NodeIconComponent, NodeJLogModelsComponent, NodeJsonldComponent, NodeJsonldSchemaComponent, NodeKeyState, NodeLinkComponent, NodeLogsFileComponent, NodeLogsModelsComponent, NodeLogsTimeComponent, NodeMissingLookupFactorsComponent, NodeQualityScore, NodeRecommendationsComponent, NodeSelectComponent, NodeValueDetailsComponent, PipelineStagesProgressComponent, PluralizePipe, PopoverComponent, PopoverConfirmComponent, PrecisionPipe, RelatedNodeResult, RemoveMarkdownPipe, RepeatPipe, Repository, ResizedDirective, ResizedEvent, ResponsiveService, SchemaInfoComponent, SchemaVersionLinkComponent, SearchExtendComponent, ShelfDialogComponent, ShellComponent, SiteNodesKeyGroup, SitesManagementChartComponent, SitesMapsComponent, SitesNodesComponent, SkeletonTextComponent, SocialTagsComponent, SortByPipe, SortSelectComponent, SumPipe, TagsInputDirective, Template, TermsPropertyContentComponent, TermsSubClassOfContentComponent, TermsUnitsDescriptionComponent, ThousandSuffixesPipe, ThousandsPipe, TimesPipe, ToastComponent, UncapitalizePipe, addPolygonToFeature, aggregatedSitePrimaryProduct, allCountriesQuery, allGroups, allOptions, availableProperties, axisLabelSettings, backgroundModelParams, baseApiUrl, baseUrl, bottom, buildSummary, bytesSize, calculateCycleDuration, calculateCycleDurationEnabled, calculateCycleStartDate, calculateCycleStartDateEnabled, capitalize, changelogUrl, clustererImage, code, compoundToHtml, computeKeys, computeTerms, contactUsEmail, contactUsLink, coordinatesToPoint, copyObject, countGroupVisibleNodes, countriesQuery, createMarker, cropsQuery, dataPathLabel, dataPathToKey, dataVersionHeader, dataVersionHeaderKey, defaultFeature, defaultLabel, defaultSuggestionType, definitionToSchemaType, distinctUntilChangedDeep, ellipsis, engineGitBaseUrl, engineGitUrl, errorText, errorsLabel, evaluateSuccess, exportFormats, externalLink, externalNodeLink, fillColor, fillStyle, filterBlankNode$1 as filterBlankNode, filterParams, findBackgroundModel, findConfigModels, findMatchingModel, findModels, findNodeModel, findOrchestratorModel, findProperty, findPropertyById, findTermModels, flatFilterData, flatFilterNode, formatCustomErrorMessage, formatDate, formatError, formatPropertyError, formatter$1 as formatter, getDatesBetween, gitBranch, gitHome, glossaryBaseUrl, glossaryLink, groupBlankNodesByTermIdentity, groupChanged, groupJLogByField, groupJLogByTerm, groupLogsByTerm, groupNodesByTerm, groupdLogsByKey, grouppedKeys, grouppedValueKeys, groupsLogsByFields, guideModelUrl, guideNamespace, handleAPIError, handleGuideEvent, hasError, hasValidationError, hasWarning, highestMethodTier, ignoreKeys$2 as ignoreKeys, impactAssessmentProduct, initialFilterState, injectResizeEvent$, inputGroupsTermTypes, isAddPropertyEnabled, isChrome, isDateBetween, isEqual, isExternal, isIdenticalNodeValue, isKeyClosedVisible, isKeyHidden, isMaxStage, isMethodModelAllowed, isNonNodeModelKey, isSchemaIri, isScrolledBelow, isState, isTermTypeAllowed, isValidKey, jLogModelCount, keyToDataPath, levels, loadMapApi, locationQuery, logToCsv, logValueArray, logsKey, lookupUrl, mapFilterData, mapsUrl, markerIcon, markerPie, matchAggregatedQuery, matchAggregatedValidatedQuery, matchBoolPrefixQuery, matchCountry, matchExactQuery, matchGlobalRegion, matchId, matchNameNormalized, matchNestedKey, matchPhrasePrefixQuery, matchPhraseQuery, matchPrimaryProductQuery, matchQuery, matchRegex, matchRegion, matchTermType, matchType, maxAreaSize, measurementValue, mergeDataWithHeaders, methodTierOrder, migrationErrorMessage, migrationsUrl, modelCount, modelKeyParams, modelParams, models, multiMatchQuery, nestedProperty, nestingEnabled, nestingTypeEnabled, noValue, nodeAvailableProperties, nodeById, nodeChartLabels, nodeColours, nodeDataState, nodeDataStates, nodeDataVersion, nodeId, nodeIdWithoutDataVersion, nodeIds, nodeLink, nodeLinkEnabled, nodeLinkTypeEnabled, nodeLogsUrl, nodeQualityScoreColor, nodeQualityScoreLevel, nodeQualityScoreMaxDefault, nodeQualityScoreOrder, nodeRequestId, nodeSecondaryColours, nodeToAggregationFilename, nodeType, nodeTypeDataState, nodeTypeIcon, nodeTypeIconSchema, nodeUrl, nodeUrlParams, nodeVersion, nodeVersionKey, nodesByState, nodesByType, numberGte, openNodeUrl, openUrl, optionsFromGroup, parentKey, parentProperty, parseData, parseDataPath, parseLines, parseMessage, parseNewValue, pluralize, pointToCoordinates, polygonBounds, polygonToCoordinates, polygonToMap, polygonsFromFeature, populateWithTrackIdsFilterData, postGuideEvent, primaryProduct, productsQuery, propertyError, propertyId, recursiveProperties, refToSchemaType, refreshPropertyKeys, regionsQuery, repeat, reportIssueLink, reportIssueUrl, safeJSONParse, safeJSONStringify, schemaBaseUrl, schemaDataBaseUrl, schemaLink, schemaRequiredProperties, schemaTypeToDefaultValue, scrollToEl, scrollTop, searchFilterData, searchableTypes, siblingProperty, simplifyContributions, singleProperty, siteTooBig, siteTypeToColor, siteTypeToIcon, sortProperties, sortedDates, strokeColor, strokeStyle, subValueKeys, suggestMatchQuery, suggestQuery, suggestTerms, takeAfterViewInit, termLocation, termLocationName, termProperties, termSearch, termTypeLabel, toSnakeCase, toTextParts, toThousands, typeToNewProperty, typeaheadFocus, uncapitalize, uniqueDatesBetween, updateProperties, valueLink, valueToString, valueTypeToDefault, valueValue, waitFor, wildcardQuery };
17518
15980
  //# sourceMappingURL=hestia-earth-ui-components.mjs.map