@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,
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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({},
|
|
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:${
|
|
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:${
|
|
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() ||
|
|
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
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
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
|
-
|
|
6314
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
:
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
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({},
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
15416
|
-
|
|
15417
|
-
|
|
15418
|
-
|
|
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.
|
|
16007
|
-
svg: () => this.
|
|
16008
|
-
csv: () => downloadFile(`data:text/html;charset=utf-8,${encodeURIComponent(
|
|
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.
|
|
14752
|
+
untracked(() => this.draw());
|
|
16014
14753
|
}
|
|
16015
14754
|
});
|
|
16016
14755
|
}
|
|
16017
|
-
|
|
14756
|
+
draw() {
|
|
16018
14757
|
try {
|
|
16019
|
-
this.
|
|
16020
|
-
|
|
16021
|
-
|
|
16022
|
-
|
|
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(
|
|
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 =>
|
|
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(() =>
|
|
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)) ? [] :
|
|
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.
|
|
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
|
|
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$
|
|
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,
|
|
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
|