@porscheinformatik/clr-addons 19.17.0 → 19.18.1

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.
@@ -1,14 +1,14 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { Component, NgModule, Injectable, EventEmitter, Output, Input, Directive, ViewChild, ContentChildren, TemplateRef, ViewChildren, HostBinding, ElementRef, Renderer2, forwardRef, Inject, ContentChild, Optional, input, ChangeDetectionStrategy, signal, computed, SkipSelf, inject, model, contentChild, linkedSignal, DestroyRef, contentChildren, effect, output, viewChild, InjectionToken, isSignal, HostListener, LOCALE_ID, Self, Host, ChangeDetectorRef, NgZone, Injector, untracked, runInInjectionContext } from '@angular/core';
3
3
  import * as i1 from '@angular/common';
4
- import { CommonModule, DOCUMENT, NgComponentOutlet, NgTemplateOutlet, getLocaleDateFormat, FormatWidth, DecimalPipe, NgForOf, NgClass } from '@angular/common';
5
- import { ClarityIcons, arrowIcon, angleIcon, timesIcon, trashIcon, plusCircleIcon, exclamationCircleIcon, searchIcon, ellipsisVerticalIcon, filterGridCircleIcon, filterGridIcon, pencilIcon, historyIcon as historyIcon$1, treeViewIcon, organizationIcon, calendarIcon, checkCircleIcon, windowCloseIcon, exclamationTriangleIcon, infoStandardIcon, downloadIcon, copyToClipboardIcon, successStandardIcon, angleDoubleIcon } from '@cds/core/icon';
4
+ import { CommonModule, DOCUMENT, NgComponentOutlet, NgTemplateOutlet, getLocaleDateFormat, FormatWidth, NgForOf, NgClass } from '@angular/common';
5
+ import { ClarityIcons, arrowIcon, angleIcon, timesIcon, trashIcon, plusCircleIcon, exclamationCircleIcon, searchIcon, ellipsisVerticalIcon, filterGridCircleIcon, filterGridIcon, pencilIcon, historyIcon as historyIcon$1, treeViewIcon, organizationIcon, calendarIcon, checkCircleIcon, windowCloseIcon, exclamationTriangleIcon, infoStandardIcon, copyToClipboardIcon, successStandardIcon, angleDoubleIcon } from '@cds/core/icon';
6
6
  import * as i1$1 from '@clr/angular';
7
- import { ClarityModule, ClrFormsModule, ClrDropdown, ClrIconModule, ClrForm, ClrAlert, ClrAlignment, ClrSide, ClrAxis, ClrPopoverToggleService, ClrPopoverEventsService, ClrPopoverPositionService, ClrPopoverHostDirective, ClrCommonStringsService, ClrDropdownModule, ClrDatagrid, ClrDatagridColumn, ClrDatagridSortOrder, DatagridPropertyComparator, ClrDatagridPagination, ClrDatagridFilter, ClrDateContainer, ClrLabel, ClrControlHelper, ClrControlError, ClrControlSuccess, ClrDatepickerModule, ClrSignpostContent, ClrSignpostModule, ClrAlertModule, ClrStartDateInput, ClrEndDateInput, ClrTooltipModule } from '@clr/angular';
7
+ import { ClarityModule, ClrFormsModule, ClrDropdown, ClrIconModule, ClrForm, ClrAlert, ClrAlignment, ClrSide, ClrAxis, ClrPopoverToggleService, ClrPopoverEventsService, ClrPopoverPositionService, ClrPopoverHostDirective, ClrCommonStringsService, ClrDropdownModule, ClrDatagrid, ClrDatagridColumn, ClrDatagridSortOrder, DatagridPropertyComparator, ClrDatagridPagination, ClrDatagridFilter, ClrDateContainer, ClrLabel, ClrControlHelper, ClrControlError, ClrControlSuccess, ClrDatepickerModule, ClrSignpostContent, ClrSignpostModule, ClrTooltipModule } from '@clr/angular';
8
8
  import * as i3$1 from '@angular/forms';
9
9
  import { FormsModule, NG_VALIDATORS, NgControl, NG_VALUE_ACCESSOR, ReactiveFormsModule, FormControl, NgModel } from '@angular/forms';
10
10
  import { Subject, BehaviorSubject, timer as timer$1, asyncScheduler, interval, map as map$1, distinctUntilChanged, shareReplay, combineLatest, skip, auditTime, fromEvent, take as take$1, merge, debounceTime as debounceTime$1, tap, of, switchMap, filter as filter$2, ReplaySubject, takeUntil as takeUntil$1, delay } from 'rxjs';
11
- import { takeUntil, take, observeOn, filter as filter$1, debounceTime, map as map$2, distinctUntilChanged as distinctUntilChanged$1 } from 'rxjs/operators';
11
+ import { takeUntil, take, observeOn, filter as filter$1, debounceTime } from 'rxjs/operators';
12
12
  import * as i3 from '@angular/router';
13
13
  import { RouterModule } from '@angular/router';
14
14
  import { trigger, transition, style, animate, state } from '@angular/animations';
@@ -16,8 +16,6 @@ import { takeUntilDestroyed, toObservable, outputFromObservable, toSignal } from
16
16
  import * as i1$2 from '@angular/common/http';
17
17
  import '@cds/core/icon/register.js';
18
18
  import { CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop';
19
- import { format, select, scaleBand, scaleLinear, max, axisBottom, axisLeft, group, sum, scalePoint, line, curveMonotoneX, area, axisRight, arc, pie, color } from 'd3';
20
- import { NgxSkeletonLoaderComponent } from 'ngx-skeleton-loader';
21
19
  import { CdkCopyToClipboard } from '@angular/cdk/clipboard';
22
20
  import { provideAnimations } from '@angular/platform-browser/animations';
23
21
 
@@ -15603,2485 +15601,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImpo
15603
15601
  * The full license information can be found in LICENSE in the root directory of this project.
15604
15602
  */
15605
15603
 
15606
- /*
15607
- * Copyright (c) 2018-2026 Porsche Informatik. All Rights Reserved.
15608
- * This software is released under MIT license.
15609
- * The full license information can be found in LICENSE in the root directory of this project.
15610
- */
15611
-
15612
- const TOO_MANY_ITEMS_MESSAGE = 'too many items';
15613
- const TOO_MANY_ITEMS_GROUPED_MESSAGE = 'too many items';
15614
- const TOO_MANY_ITEMS_ALERT_TYPE = 'warning';
15615
- const NO_ITEMS_MESSAGE = 'no items';
15616
- const NO_ITEMS_ALERT_TYPE = 'info';
15617
-
15618
- /**
15619
- * Converts a chart color value to a CSS-compatible string.
15620
- *
15621
- * Supports:
15622
- * - Hex values: '#e57200' → returned as-is
15623
- * - CSS custom props: '--cds-global-color-lavender-1000' → wrapped in var(...)
15624
- * - Any other string: 'rgb(...)' / 'hsl(...)' → returned as-is
15625
- */
15626
- function toChartColor(color) {
15627
- if (!color) {
15628
- return '';
15629
- }
15630
- return color.startsWith('--') ? `var(${color})` : color;
15631
- }
15632
-
15633
- class TextRenderer {
15634
- constructor() {
15635
- this.cachedFont = '';
15636
- this.cachedEllipsisWidth = 0;
15637
- this.canvas = document.createElement('canvas');
15638
- const ctx = this.canvas.getContext('2d');
15639
- if (!ctx) {
15640
- throw new Error('2D context error');
15641
- }
15642
- this.context = ctx;
15643
- }
15644
- render(text, availableHeight, availableWidth, fontSize = '12px', fontFamily = 'Arial') {
15645
- const font = `${fontSize} ${fontFamily}`;
15646
- if (this.cachedFont !== font) {
15647
- this.context.font = font;
15648
- this.cachedEllipsisWidth = this.context.measureText('...').width;
15649
- this.cachedFont = font;
15650
- }
15651
- else {
15652
- this.context.font = font;
15653
- }
15654
- const textMetrics = this.context.measureText(text);
15655
- const textWidth = textMetrics.width;
15656
- const textHeight = Math.abs(textMetrics.actualBoundingBoxAscent) + Math.abs(textMetrics.actualBoundingBoxDescent);
15657
- if (textWidth <= availableWidth && textHeight <= availableHeight) {
15658
- return text;
15659
- }
15660
- let remainingWidth = availableWidth - this.cachedEllipsisWidth;
15661
- let truncatedText = '';
15662
- for (const char of text) {
15663
- const charWidth = this.context.measureText(char).width;
15664
- if (remainingWidth - charWidth >= 0) {
15665
- truncatedText += char;
15666
- remainingWidth -= charWidth;
15667
- }
15668
- else {
15669
- break;
15670
- }
15671
- }
15672
- truncatedText = truncatedText.trim();
15673
- if (truncatedText.length === 0) {
15674
- return '';
15675
- }
15676
- return truncatedText + '...';
15677
- }
15678
- }
15679
-
15680
- const percentage = (value, total) => {
15681
- if (total <= 0) {
15682
- return 0;
15683
- }
15684
- return Math.min(100, Math.max(0, Math.round((value / total) * 100)));
15685
- };
15686
- const d3percentFormat = (f => (d) => `${f(d)}%`)(format('.1f'));
15687
-
15688
- /*
15689
- * Copyright (c) 2018-2026 Porsche Informatik. All Rights Reserved.
15690
- * This software is released under MIT license.
15691
- * The full license information can be found in LICENSE in the root directory of this project.
15692
- */
15693
- /**
15694
- * Abstract base class shared by all chart components.
15695
- * ...
15696
- */
15697
- class ChartBase {
15698
- constructor() {
15699
- // ── Common Input ───────────────────────────────────────────────────────────
15700
- /** Whether the chart is in loading state (shows skeleton). */
15701
- this.loading = input(false);
15702
- // ── View references ────────────────────────────────────────────────────────
15703
- /** Reference to the `<svg #chart>` element in the component template. */
15704
- this.chartRef = viewChild('chart');
15705
- /** Reference to the `<div #container>` host element in the component template. */
15706
- this.containerRef = viewChild('container');
15707
- /** Exposes the raw SVG element for the chart-export button. */
15708
- this.svgElement = computed(() => this.chartRef()?.nativeElement);
15709
- // ── Tooltip State ──────────────────────────────────────────────────────────
15710
- this.selectedItem = signal(undefined);
15711
- this.tooltipPosition = signal(undefined);
15712
- }
15713
- // ── Lifecycle ──────────────────────────────────────────────────────────────
15714
- /** Schedules the first render after the view is ready. */
15715
- ngAfterViewInit() {
15716
- requestAnimationFrame(() => this.updateChart());
15717
- }
15718
- // ── Common Implementations ─────────────────────────────────────────────────
15719
- /** Returns the pixel dimensions of the container div. */
15720
- getContainerDimensions() {
15721
- const el = this.containerRef().nativeElement;
15722
- return { width: el.clientWidth, height: el.clientHeight };
15723
- }
15724
- /**
15725
- * Clears the active tooltip. Override in subclasses that have additional
15726
- * selection state
15727
- */
15728
- resetTooltip() {
15729
- if (this.selectedItem() || this.tooltipPosition()) {
15730
- this.selectedItem.set(undefined);
15731
- this.tooltipPosition.set(undefined);
15732
- }
15733
- }
15734
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartBase, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
15735
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "19.2.18", type: ChartBase, isStandalone: true, inputs: { loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "chartRef", first: true, predicate: ["chart"], descendants: true, isSignal: true }, { propertyName: "containerRef", first: true, predicate: ["container"], descendants: true, isSignal: true }], ngImport: i0 }); }
15736
- }
15737
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartBase, decorators: [{
15738
- type: Directive
15739
- }] });
15740
-
15741
- class ChartAlertOverlayComponent {
15742
- constructor() {
15743
- this.alertMessage = input.required();
15744
- this.alertType = input('info');
15745
- }
15746
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartAlertOverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
15747
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.18", type: ChartAlertOverlayComponent, isStandalone: true, selector: "cng-chart-alert-overlay", inputs: { alertMessage: { classPropertyName: "alertMessage", publicName: "alertMessage", isSignal: true, isRequired: true, transformFunction: null }, alertType: { classPropertyName: "alertType", publicName: "alertType", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
15748
- <clr-alert [clrAlertSizeSmall]="true" [clrAlertClosable]="false" [clrAlertType]="alertType()">
15749
- <clr-alert-item>
15750
- <span class="alert-text">
15751
- {{ alertMessage() }}
15752
- </span>
15753
- </clr-alert-item>
15754
- </clr-alert>
15755
- <div class="overlay"></div>
15756
- `, isInline: true, styles: [":host{position:absolute;top:0;left:0;right:0;pointer-events:none;z-index:1;clr-alert{pointer-events:auto;::ng-deep .alert{margin-bottom:0}}.overlay{height:2rem;background:linear-gradient(to bottom,#fff,#fff0)}}\n"], dependencies: [{ kind: "ngmodule", type: ClrAlertModule }, { kind: "component", type: i1$1.ClrAlert, selector: "clr-alert", inputs: ["clrAlertSizeSmall", "clrAlertClosable", "clrAlertAppLevel", "clrCloseButtonAriaLabel", "clrAlertLightweight", "clrAlertType", "clrAlertIcon", "clrAlertClosed"], outputs: ["clrAlertClosedChange"] }, { kind: "component", type: i1$1.ClrAlertItem, selector: "clr-alert-item" }, { kind: "directive", type: i1$1.ClrAlertText, selector: ".alert-text" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
15757
- }
15758
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartAlertOverlayComponent, decorators: [{
15759
- type: Component,
15760
- args: [{ selector: 'cng-chart-alert-overlay', template: `
15761
- <clr-alert [clrAlertSizeSmall]="true" [clrAlertClosable]="false" [clrAlertType]="alertType()">
15762
- <clr-alert-item>
15763
- <span class="alert-text">
15764
- {{ alertMessage() }}
15765
- </span>
15766
- </clr-alert-item>
15767
- </clr-alert>
15768
- <div class="overlay"></div>
15769
- `, changeDetection: ChangeDetectionStrategy.OnPush, imports: [ClrAlertModule], styles: [":host{position:absolute;top:0;left:0;right:0;pointer-events:none;z-index:1;clr-alert{pointer-events:auto;::ng-deep .alert{margin-bottom:0}}.overlay{height:2rem;background:linear-gradient(to bottom,#fff,#fff0)}}\n"] }]
15770
- }] });
15771
-
15772
- class ChartExportService {
15773
- exportSvg(svgEl, filename) {
15774
- const clone = this.cloneWithDimensions(svgEl);
15775
- const svgStr = new XMLSerializer().serializeToString(clone);
15776
- this.download(new Blob([svgStr], { type: 'image/svg+xml;charset=utf-8' }), `${filename}.svg`);
15777
- }
15778
- exportPng(svgEl, filename) {
15779
- this.toCanvas(svgEl).then(canvas => canvas.toBlob(blob => this.download(blob, `${filename}.png`), 'image/png'));
15780
- }
15781
- toCanvas(svgEl) {
15782
- return new Promise(resolve => {
15783
- const w = svgEl.clientWidth || Number(svgEl.getAttribute('width')) || 800;
15784
- const h = svgEl.clientHeight || Number(svgEl.getAttribute('height')) || 600;
15785
- const clone = this.cloneWithDimensions(svgEl, w, h);
15786
- const url = URL.createObjectURL(new Blob([new XMLSerializer().serializeToString(clone)], { type: 'image/svg+xml;charset=utf-8' }));
15787
- const img = new Image();
15788
- img.onload = () => {
15789
- const scale = 2; // 2× for retina quality
15790
- const canvas = document.createElement('canvas');
15791
- canvas.width = w * scale;
15792
- canvas.height = h * scale;
15793
- const ctx = canvas.getContext('2d');
15794
- ctx.scale(scale, scale);
15795
- ctx.fillStyle = '#ffffff';
15796
- ctx.fillRect(0, 0, w, h);
15797
- ctx.drawImage(img, 0, 0, w, h);
15798
- URL.revokeObjectURL(url);
15799
- resolve(canvas);
15800
- };
15801
- img.src = url;
15802
- });
15803
- }
15804
- cloneWithDimensions(svgEl, w, h) {
15805
- const clone = svgEl.cloneNode(true);
15806
- const width = w ?? svgEl.clientWidth ?? Number(svgEl.getAttribute('width')) ?? 800;
15807
- const height = h ?? svgEl.clientHeight ?? Number(svgEl.getAttribute('height')) ?? 600;
15808
- clone.setAttribute('width', String(width));
15809
- clone.setAttribute('height', String(height));
15810
- clone.style.background = '#ffffff';
15811
- return clone;
15812
- }
15813
- download(blob, filename) {
15814
- const url = URL.createObjectURL(blob);
15815
- const a = document.createElement('a');
15816
- a.href = url;
15817
- a.download = filename;
15818
- a.click();
15819
- setTimeout(() => URL.revokeObjectURL(url), 100);
15820
- }
15821
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartExportService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
15822
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartExportService, providedIn: 'root' }); }
15823
- }
15824
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartExportService, decorators: [{
15825
- type: Injectable,
15826
- args: [{ providedIn: 'root' }]
15827
- }] });
15828
-
15829
- ClarityIcons.addIcons(downloadIcon);
15830
- class ChartExportButtonComponent {
15831
- constructor() {
15832
- this.svgRef = input(undefined);
15833
- this.filename = input('chart');
15834
- this.exportService = inject(ChartExportService);
15835
- }
15836
- export(format) {
15837
- const svg = this.svgRef();
15838
- if (!svg) {
15839
- return;
15840
- }
15841
- switch (format) {
15842
- case 'svg':
15843
- this.exportService.exportSvg(svg, this.filename());
15844
- break;
15845
- case 'png':
15846
- this.exportService.exportPng(svg, this.filename());
15847
- break;
15848
- }
15849
- }
15850
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartExportButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
15851
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.18", type: ChartExportButtonComponent, isStandalone: true, selector: "cng-chart-export-button", inputs: { svgRef: { classPropertyName: "svgRef", publicName: "svgRef", isSignal: true, isRequired: false, transformFunction: null }, filename: { classPropertyName: "filename", publicName: "filename", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
15852
- <clr-dropdown>
15853
- <button class="btn btn-sm btn-icon btn-link export-trigger" clrDropdownTrigger title="Export chart">
15854
- <cds-icon shape="download" size="16"></cds-icon>
15855
- </button>
15856
- <clr-dropdown-menu *clrIfOpen clrPosition="bottom-left">
15857
- <button clrDropdownItem (click)="export('svg')">SVG</button>
15858
- <button clrDropdownItem (click)="export('png')">PNG</button>
15859
- </clr-dropdown-menu>
15860
- </clr-dropdown>
15861
- `, isInline: true, styles: [":host{position:absolute;bottom:-15px;left:-5px;z-index:10;opacity:1;pointer-events:auto}\n"], dependencies: [{ kind: "ngmodule", type: ClrDropdownModule }, { kind: "component", type: i1$1.ClrDropdown, selector: "clr-dropdown", inputs: ["clrCloseMenuOnItemClick"] }, { kind: "component", type: i1$1.ClrDropdownMenu, selector: "clr-dropdown-menu", inputs: ["clrPosition"] }, { kind: "directive", type: i1$1.ClrDropdownTrigger, selector: "[clrDropdownTrigger],[clrDropdownToggle]" }, { kind: "directive", type: i1$1.ClrDropdownItem, selector: "[clrDropdownItem]", inputs: ["clrDisabled", "id"] }, { kind: "directive", type: i1$1.ClrIfOpen, selector: "[clrIfOpen]", inputs: ["clrIfOpen"], outputs: ["clrIfOpenChange"] }, { kind: "directive", type: i1$1.CdsIconCustomTag, selector: "cds-icon" }, { kind: "ngmodule", type: ClrIconModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
15862
- }
15863
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartExportButtonComponent, decorators: [{
15864
- type: Component,
15865
- args: [{ selector: 'cng-chart-export-button', template: `
15866
- <clr-dropdown>
15867
- <button class="btn btn-sm btn-icon btn-link export-trigger" clrDropdownTrigger title="Export chart">
15868
- <cds-icon shape="download" size="16"></cds-icon>
15869
- </button>
15870
- <clr-dropdown-menu *clrIfOpen clrPosition="bottom-left">
15871
- <button clrDropdownItem (click)="export('svg')">SVG</button>
15872
- <button clrDropdownItem (click)="export('png')">PNG</button>
15873
- </clr-dropdown-menu>
15874
- </clr-dropdown>
15875
- `, imports: [ClrDropdownModule, ClrIconModule], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{position:absolute;bottom:-15px;left:-5px;z-index:10;opacity:1;pointer-events:auto}\n"] }]
15876
- }] });
15877
-
15878
- class ChartLegendComponent {
15879
- constructor() {
15880
- this.items = input.required();
15881
- this.toChartColor = toChartColor;
15882
- }
15883
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartLegendComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
15884
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: ChartLegendComponent, isStandalone: true, selector: "cng-chart-legend", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
15885
- <div class="chart-legend">
15886
- @for (item of items(); track item.label) {
15887
- <div class="legend-item">
15888
- <span class="legend-color-square" [style.background-color]="toChartColor(item.color)"></span>
15889
- <span class="legend-label">{{ item.label }}</span>
15890
- </div>
15891
- }
15892
- </div>
15893
- `, isInline: true, styles: [":host{display:block}.chart-legend{display:flex;flex-wrap:wrap;gap:.25rem 1rem;padding:.5rem 0 .25rem;font-size:11px;color:var(--clr-color-neutral-600, #666)}.legend-item{display:flex;align-items:center;gap:.35rem}.legend-color-square{width:10px;height:10px;border-radius:2px;flex-shrink:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
15894
- }
15895
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartLegendComponent, decorators: [{
15896
- type: Component,
15897
- args: [{ selector: 'cng-chart-legend', template: `
15898
- <div class="chart-legend">
15899
- @for (item of items(); track item.label) {
15900
- <div class="legend-item">
15901
- <span class="legend-color-square" [style.background-color]="toChartColor(item.color)"></span>
15902
- <span class="legend-label">{{ item.label }}</span>
15903
- </div>
15904
- }
15905
- </div>
15906
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}.chart-legend{display:flex;flex-wrap:wrap;gap:.25rem 1rem;padding:.5rem 0 .25rem;font-size:11px;color:var(--clr-color-neutral-600, #666)}.legend-item{display:flex;align-items:center;gap:.35rem}.legend-color-square{width:10px;height:10px;border-radius:2px;flex-shrink:0}\n"] }]
15907
- }] });
15908
-
15909
- class ChartSkeletonComponent {
15910
- constructor() {
15911
- this.skeletonType = input('loading');
15912
- this.orientation = input.required();
15913
- this.animationStyle = computed(() => (this.skeletonType() === 'loading' ? 'pulse' : undefined));
15914
- }
15915
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartSkeletonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
15916
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: ChartSkeletonComponent, isStandalone: true, selector: "cng-bar-chart-skeleton", inputs: { skeletonType: { classPropertyName: "skeletonType", publicName: "skeletonType", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"chart-skeleton\" [class.placeholder-skeleton]=\"skeletonType() === 'placeholder'\">\n <div\n class=\"skeleton-bars\"\n [class.horizontal]=\"orientation() === 'horizontal'\"\n [class.vertical]=\"orientation() === 'vertical'\"\n >\n @for (_item of [1, 2, 3, 4, 5, 6]; track $index) {\n <div class=\"skeleton-bar bar-{{ $index + 1 }}\">\n <ngx-skeleton-loader\n [appearance]=\"'circle'\"\n [count]=\"1\"\n [animation]=\"animationStyle()\"\n [theme]=\"{\n width: '100%',\n height: '100%',\n 'border-radius': 'var(--clr-base-border-radius-xxs)',\n }\"\n />\n </div>\n }\n </div>\n</div>\n", styles: [".chart-skeleton{width:100%;height:100%}.placeholder-skeleton ::ng-deep .skeleton-loader{cursor:default}.skeleton-bars{display:flex;gap:var(--clr-base-gap-l);width:100%;height:100%;padding:var(--cds-global-layout-space-xs) var(--cds-global-layout-space-xl)}.skeleton-bars.vertical{flex-direction:row;align-items:flex-end;justify-content:center}.skeleton-bars.vertical .skeleton-bar{width:var(--cds-global-layout-space-md)}.skeleton-bars.vertical .skeleton-bar.bar-1{height:60%}.skeleton-bars.vertical .skeleton-bar.bar-2{height:95%}.skeleton-bars.vertical .skeleton-bar.bar-3{height:40%}.skeleton-bars.vertical .skeleton-bar.bar-4{height:75%}.skeleton-bars.vertical .skeleton-bar.bar-5{height:50%}.skeleton-bars.vertical .skeleton-bar.bar-6{height:25%}.skeleton-bars.horizontal{flex-direction:column;justify-content:center}.skeleton-bars.horizontal .skeleton-bar{height:var(--cds-global-layout-space-md)}.skeleton-bars.horizontal .skeleton-bar.bar-1{width:60%}.skeleton-bars.horizontal .skeleton-bar.bar-2{width:95%}.skeleton-bars.horizontal .skeleton-bar.bar-3{width:40%}.skeleton-bars.horizontal .skeleton-bar.bar-4{width:75%}.skeleton-bars.horizontal .skeleton-bar.bar-5{width:50%}.skeleton-bars.horizontal .skeleton-bar.bar-6{width:25%}\n"], dependencies: [{ kind: "component", type: NgxSkeletonLoaderComponent, selector: "ngx-skeleton-loader", inputs: ["count", "loadingText", "appearance", "animation", "ariaLabel", "theme"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
15917
- }
15918
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartSkeletonComponent, decorators: [{
15919
- type: Component,
15920
- args: [{ selector: 'cng-bar-chart-skeleton', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgxSkeletonLoaderComponent], template: "<div class=\"chart-skeleton\" [class.placeholder-skeleton]=\"skeletonType() === 'placeholder'\">\n <div\n class=\"skeleton-bars\"\n [class.horizontal]=\"orientation() === 'horizontal'\"\n [class.vertical]=\"orientation() === 'vertical'\"\n >\n @for (_item of [1, 2, 3, 4, 5, 6]; track $index) {\n <div class=\"skeleton-bar bar-{{ $index + 1 }}\">\n <ngx-skeleton-loader\n [appearance]=\"'circle'\"\n [count]=\"1\"\n [animation]=\"animationStyle()\"\n [theme]=\"{\n width: '100%',\n height: '100%',\n 'border-radius': 'var(--clr-base-border-radius-xxs)',\n }\"\n />\n </div>\n }\n </div>\n</div>\n", styles: [".chart-skeleton{width:100%;height:100%}.placeholder-skeleton ::ng-deep .skeleton-loader{cursor:default}.skeleton-bars{display:flex;gap:var(--clr-base-gap-l);width:100%;height:100%;padding:var(--cds-global-layout-space-xs) var(--cds-global-layout-space-xl)}.skeleton-bars.vertical{flex-direction:row;align-items:flex-end;justify-content:center}.skeleton-bars.vertical .skeleton-bar{width:var(--cds-global-layout-space-md)}.skeleton-bars.vertical .skeleton-bar.bar-1{height:60%}.skeleton-bars.vertical .skeleton-bar.bar-2{height:95%}.skeleton-bars.vertical .skeleton-bar.bar-3{height:40%}.skeleton-bars.vertical .skeleton-bar.bar-4{height:75%}.skeleton-bars.vertical .skeleton-bar.bar-5{height:50%}.skeleton-bars.vertical .skeleton-bar.bar-6{height:25%}.skeleton-bars.horizontal{flex-direction:column;justify-content:center}.skeleton-bars.horizontal .skeleton-bar{height:var(--cds-global-layout-space-md)}.skeleton-bars.horizontal .skeleton-bar.bar-1{width:60%}.skeleton-bars.horizontal .skeleton-bar.bar-2{width:95%}.skeleton-bars.horizontal .skeleton-bar.bar-3{width:40%}.skeleton-bars.horizontal .skeleton-bar.bar-4{width:75%}.skeleton-bars.horizontal .skeleton-bar.bar-5{width:50%}.skeleton-bars.horizontal .skeleton-bar.bar-6{width:25%}\n"] }]
15921
- }] });
15922
-
15923
- class ChartTooltipComponent {
15924
- constructor() {
15925
- this.tooltipPosition = input.required();
15926
- this.tooltipOrientation = input('top');
15927
- this.squareColor = input.required();
15928
- this.tooltipClickable = input(true);
15929
- this.tooltipClosed = output();
15930
- this.tooltipHeaderClicked = output();
15931
- this.toChartColor = toChartColor;
15932
- }
15933
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartTooltipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
15934
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: ChartTooltipComponent, isStandalone: true, selector: "cng-chart-tooltip", inputs: { tooltipPosition: { classPropertyName: "tooltipPosition", publicName: "tooltipPosition", isSignal: true, isRequired: true, transformFunction: null }, tooltipOrientation: { classPropertyName: "tooltipOrientation", publicName: "tooltipOrientation", isSignal: true, isRequired: false, transformFunction: null }, squareColor: { classPropertyName: "squareColor", publicName: "squareColor", isSignal: true, isRequired: true, transformFunction: null }, tooltipClickable: { classPropertyName: "tooltipClickable", publicName: "tooltipClickable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { tooltipClosed: "tooltipClosed", tooltipHeaderClicked: "tooltipHeaderClicked" }, ngImport: i0, template: "<div\n class=\"tooltip-container\"\n [class.orientation-bottom]=\"tooltipOrientation() === 'bottom'\"\n (click)=\"$event.stopPropagation()\"\n [style.left.px]=\"tooltipPosition().x\"\n [style.top.px]=\"tooltipPosition().y\"\n>\n <div class=\"tooltip-header\">\n <div class=\"color-label-wrapper\">\n @if (squareColor()) {\n <div class=\"color-square\" [style.background-color]=\"toChartColor(squareColor())\"></div>\n }\n <h5\n [class.has-more-info]=\"tooltipClickable()\"\n (click)=\"$event.stopPropagation(); tooltipClickable() && tooltipHeaderClicked.emit()\"\n >\n <ng-content select=\"cng-title\" />\n </h5>\n </div>\n <cds-icon shape=\"times\" class=\"tooltip-close-btn\" (click)=\"$event.stopPropagation(); tooltipClosed.emit()\" />\n </div>\n <div class=\"tooltip-content\">\n <ng-content />\n </div>\n <hr />\n <div class=\"tooltip-footer\"><ng-content select=\"cng-footer\" /></div>\n</div>\n", styles: [".tooltip-container{position:absolute;background:var(--clr-color-neutral-0, #fff);border:1px solid var(--clr-color-neutral-400, #ccc);border-radius:4px;box-shadow:0 2px 8px #00000026;padding:12px 16px;z-index:1000;min-width:150px;transform:translate(-50%,-100%) translateY(-10px)}.tooltip-container.orientation-bottom{transform:translate(-50%) translateY(10px)}.tooltip-header{display:flex;flex-direction:row;justify-content:space-between;gap:8px}.tooltip-header h5{margin:0;font-size:14px;font-weight:600;color:var(--clr-color-neutral-1000, #000);flex-grow:1;max-width:200px}.tooltip-close-btn{cursor:pointer;color:var(--clr-close-color)}.tooltip-close-btn:hover,.tooltip-close-btn:focus{color:var(--clr-close-color-hover)}.color-label-wrapper{display:flex;flex-direction:row;gap:8px}.tooltip-content{font-size:12px;color:var(--clr-color-neutral-700, #565656)}hr:has(+.tooltip-footer:empty){display:none}hr{margin:6px -16px}\n"], dependencies: [{ kind: "ngmodule", type: ClrIconModule }, { kind: "directive", type: i1$1.CdsIconCustomTag, selector: "cds-icon" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
15935
- }
15936
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChartTooltipComponent, decorators: [{
15937
- type: Component,
15938
- args: [{ selector: 'cng-chart-tooltip', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ClrIconModule], template: "<div\n class=\"tooltip-container\"\n [class.orientation-bottom]=\"tooltipOrientation() === 'bottom'\"\n (click)=\"$event.stopPropagation()\"\n [style.left.px]=\"tooltipPosition().x\"\n [style.top.px]=\"tooltipPosition().y\"\n>\n <div class=\"tooltip-header\">\n <div class=\"color-label-wrapper\">\n @if (squareColor()) {\n <div class=\"color-square\" [style.background-color]=\"toChartColor(squareColor())\"></div>\n }\n <h5\n [class.has-more-info]=\"tooltipClickable()\"\n (click)=\"$event.stopPropagation(); tooltipClickable() && tooltipHeaderClicked.emit()\"\n >\n <ng-content select=\"cng-title\" />\n </h5>\n </div>\n <cds-icon shape=\"times\" class=\"tooltip-close-btn\" (click)=\"$event.stopPropagation(); tooltipClosed.emit()\" />\n </div>\n <div class=\"tooltip-content\">\n <ng-content />\n </div>\n <hr />\n <div class=\"tooltip-footer\"><ng-content select=\"cng-footer\" /></div>\n</div>\n", styles: [".tooltip-container{position:absolute;background:var(--clr-color-neutral-0, #fff);border:1px solid var(--clr-color-neutral-400, #ccc);border-radius:4px;box-shadow:0 2px 8px #00000026;padding:12px 16px;z-index:1000;min-width:150px;transform:translate(-50%,-100%) translateY(-10px)}.tooltip-container.orientation-bottom{transform:translate(-50%) translateY(10px)}.tooltip-header{display:flex;flex-direction:row;justify-content:space-between;gap:8px}.tooltip-header h5{margin:0;font-size:14px;font-weight:600;color:var(--clr-color-neutral-1000, #000);flex-grow:1;max-width:200px}.tooltip-close-btn{cursor:pointer;color:var(--clr-close-color)}.tooltip-close-btn:hover,.tooltip-close-btn:focus{color:var(--clr-close-color-hover)}.color-label-wrapper{display:flex;flex-direction:row;gap:8px}.tooltip-content{font-size:12px;color:var(--clr-color-neutral-700, #565656)}hr:has(+.tooltip-footer:empty){display:none}hr{margin:6px -16px}\n"] }]
15939
- }] });
15940
-
15941
- // Clarity actually has an "outside-click" directive, but they don't export it 🤷
15942
- class OutsideClickDirective {
15943
- constructor() {
15944
- this.outsideClick = output({ alias: 'cngOutsideClick' });
15945
- this.renderer = inject(Renderer2);
15946
- this.destroyRef = inject(DestroyRef);
15947
- }
15948
- ngAfterViewInit() {
15949
- const listener = this.renderer.listen('document', 'click', () => this.outsideClick.emit());
15950
- this.destroyRef.onDestroy(listener);
15951
- }
15952
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: OutsideClickDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
15953
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.18", type: OutsideClickDirective, isStandalone: true, selector: "[cngOutsideClick]", outputs: { outsideClick: "cngOutsideClick" }, ngImport: i0 }); }
15954
- }
15955
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: OutsideClickDirective, decorators: [{
15956
- type: Directive,
15957
- args: [{
15958
- selector: '[cngOutsideClick]',
15959
- }]
15960
- }] });
15961
-
15962
- /**
15963
- * Enum for the Screen size in pixels, defined by clarity here https://vmware.github.io/clarity/documentation/v0.12/grid
15964
- * We consider screens above 1200px to be a desktop, between 576px and 1199px to be a tablet and below 576px to be a phone.
15965
- */
15966
- var ScreenWidth;
15967
- (function (ScreenWidth) {
15968
- ScreenWidth[ScreenWidth["EXTRA_LARGE"] = 1200] = "EXTRA_LARGE";
15969
- ScreenWidth[ScreenWidth["LARGE"] = 992] = "LARGE";
15970
- ScreenWidth[ScreenWidth["MEDIUM"] = 768] = "MEDIUM";
15971
- ScreenWidth[ScreenWidth["SMALL"] = 576] = "SMALL";
15972
- ScreenWidth[ScreenWidth["EXTRA_SMALL"] = 575] = "EXTRA_SMALL";
15973
- })(ScreenWidth || (ScreenWidth = {}));
15974
- var ScreenOrientation;
15975
- (function (ScreenOrientation) {
15976
- ScreenOrientation["PORTRAIT"] = "PORTRAIT";
15977
- ScreenOrientation["LANDSCAPE"] = "LANDSCAPE";
15978
- })(ScreenOrientation || (ScreenOrientation = {}));
15979
- const DATEPICKER_ENABLE_BREAKPOINT = 768;
15980
- const MOBILE_USERAGENT_REGEX = /Mobi/i;
15981
- class ScreenStateService {
15982
- constructor(_document) {
15983
- this._document = _document;
15984
- this.isUserAgentMobile = false;
15985
- this.screenWidthSubject = new ReplaySubject(1);
15986
- this.screenOrientationSubject = new ReplaySubject(1);
15987
- window.addEventListener('resize', () => {
15988
- this.onResize();
15989
- });
15990
- window.addEventListener('orientationchange', () => {
15991
- this.onOrientationChange();
15992
- });
15993
- this.determineMobileUserAgent();
15994
- // Execute them once for initial values
15995
- this.onResize();
15996
- this.onOrientationChange();
15997
- }
15998
- getScreenWidthChanged() {
15999
- return this.screenWidthSubject;
16000
- }
16001
- getMobileStateChanged() {
16002
- return this.getScreenWidthChanged().pipe(map$2(screenWidth => screenWidth < DATEPICKER_ENABLE_BREAKPOINT && this.isUserAgentMobile), distinctUntilChanged$1());
16003
- }
16004
- getScreenOrientationChanged() {
16005
- return this.screenOrientationSubject;
16006
- }
16007
- onResize() {
16008
- const windowWidth = window.innerWidth;
16009
- this.screenWidthSubject.next(windowWidth);
16010
- }
16011
- onOrientationChange() {
16012
- const prevOrientation = this.screenOrientation;
16013
- const orientation = window.orientation;
16014
- // Probably not always correct since this depends on the devices default orientation.
16015
- // Better way to identify this would probably be comparing height & width of the screen.
16016
- if (orientation === -90 || orientation === 90) {
16017
- this.screenOrientation = ScreenOrientation.LANDSCAPE;
16018
- }
16019
- else {
16020
- this.screenOrientation = ScreenOrientation.PORTRAIT;
16021
- }
16022
- if (this.screenOrientation !== prevOrientation) {
16023
- this.screenOrientationSubject.next(this.screenOrientation);
16024
- }
16025
- }
16026
- determineMobileUserAgent() {
16027
- if (this._document) {
16028
- this.isUserAgentMobile = MOBILE_USERAGENT_REGEX.test(this._document.defaultView.navigator.userAgent);
16029
- }
16030
- }
16031
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ScreenStateService, deps: [{ token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable }); }
16032
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ScreenStateService, providedIn: 'root' }); }
16033
- }
16034
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ScreenStateService, decorators: [{
16035
- type: Injectable,
16036
- args: [{ providedIn: 'root' }]
16037
- }], ctorParameters: () => [{ type: Document, decorators: [{
16038
- type: Inject,
16039
- args: [DOCUMENT]
16040
- }] }] });
16041
-
16042
- class WindowResizeDirective {
16043
- constructor() {
16044
- this.debounce = input(250);
16045
- this.includeFirst = input(false);
16046
- this.windowResize = output({ alias: 'cngWindowResize' });
16047
- this.screenStateService = inject(ScreenStateService);
16048
- this.destroyRef = inject(DestroyRef);
16049
- }
16050
- ngOnInit() {
16051
- const skipCount = this.includeFirst() ? 0 : 1;
16052
- this.screenStateService
16053
- .getScreenWidthChanged()
16054
- .pipe(takeUntilDestroyed(this.destroyRef), skip(skipCount), debounceTime(this.debounce()))
16055
- .subscribe(width => this.windowResize.emit(width));
16056
- }
16057
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: WindowResizeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
16058
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.18", type: WindowResizeDirective, isStandalone: true, selector: "[cngWindowResize]", inputs: { debounce: { classPropertyName: "debounce", publicName: "debounce", isSignal: true, isRequired: false, transformFunction: null }, includeFirst: { classPropertyName: "includeFirst", publicName: "includeFirst", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { windowResize: "cngWindowResize" }, ngImport: i0 }); }
16059
- }
16060
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: WindowResizeDirective, decorators: [{
16061
- type: Directive,
16062
- args: [{
16063
- selector: '[cngWindowResize]',
16064
- }]
16065
- }] });
16066
-
16067
- class BarChartComponent extends ChartBase {
16068
- static { this.HORIZONTAL_BAR_MIN_HEIGHT_PX = 25; }
16069
- constructor() {
16070
- super();
16071
- this.data = input.required();
16072
- this.stackLabels = input(undefined);
16073
- this.orientation = input.required();
16074
- this.tooltipOrientation = input('top');
16075
- this.barSizePx = input(15);
16076
- this.barAreaSizePx = input(40);
16077
- this.tooltipPercentOfTotal = input('of total');
16078
- this.tooltipPercentOf = input('of');
16079
- this.noItemsMessage = input(NO_ITEMS_MESSAGE);
16080
- this.tooManyItemsMessage = input(TOO_MANY_ITEMS_MESSAGE);
16081
- this.tooManyItemsGroupedMessage = input(TOO_MANY_ITEMS_GROUPED_MESSAGE);
16082
- this.showLegend = input(true);
16083
- this.showExportButton = input(false);
16084
- this.exportFilename = input('bar-chart');
16085
- /** Optional label rendered below the X axis. */
16086
- this.xAxisLabel = input('');
16087
- /** Optional label rendered rotated to the left of the Y axis. */
16088
- this.yAxisLabel = input('');
16089
- this.valueClicked = output();
16090
- this.textRenderer = new TextRenderer();
16091
- this.MARGIN = { top: 10, right: 20, bottom: 30, left: 65 };
16092
- this.toChartColor = toChartColor;
16093
- this.total = computed(() => this.data().reduce((acc, v) => acc + v.value, 0));
16094
- this.totalByStack = computed(() => {
16095
- const totals = {};
16096
- for (const d of this.slicedDataPoints()) {
16097
- totals[d.stackKey] = (totals[d.stackKey] ?? 0) + d.value;
16098
- }
16099
- return totals;
16100
- });
16101
- this.keysByStack = computed(() => {
16102
- const keys = {};
16103
- for (const d of this.slicedDataPoints()) {
16104
- if (!keys[d.stackKey]) {
16105
- keys[d.stackKey] = [];
16106
- }
16107
- keys[d.stackKey].push(d.key);
16108
- }
16109
- return keys;
16110
- });
16111
- // Count the total amount of bars by either the stacKey (if stacked) or the key (if not stacked)
16112
- this.totalBarCount = computed(() => new Set(this.data()
16113
- .filter(d => d.value > 0)
16114
- .map(d => d.stackKey ?? d.key)).size);
16115
- this.showingBarCount = computed(() => new Set(this.slicedDataPoints()
16116
- .filter(d => d.value > 0)
16117
- .map(d => d.stackKey)).size);
16118
- this.alertMessageAndType = computed(() => {
16119
- if (this.loading()) {
16120
- return undefined;
16121
- }
16122
- if (!this.showingBarCount()) {
16123
- return [this.noItemsMessage(), NO_ITEMS_ALERT_TYPE];
16124
- }
16125
- else if (this.totalBarCount() !== this.showingBarCount()) {
16126
- return [
16127
- this.stackLabels() ? this.tooManyItemsGroupedMessage() : this.tooManyItemsMessage(),
16128
- TOO_MANY_ITEMS_ALERT_TYPE,
16129
- ];
16130
- }
16131
- return undefined;
16132
- });
16133
- this.legendItems = computed(() => {
16134
- if (!this.showLegend() || !this.data()?.length) {
16135
- return [];
16136
- }
16137
- if (this.stackLabels()?.length) {
16138
- // Stacked: one legend entry per distinct label (= each layer in the stack)
16139
- const seen = new Set();
16140
- const items = [];
16141
- for (const item of this.data()) {
16142
- const label = item.fullLabel ?? item.label;
16143
- if (!seen.has(label)) {
16144
- seen.add(label);
16145
- items.push({ label, color: item.color });
16146
- }
16147
- }
16148
- return items;
16149
- }
16150
- // Non-stacked: one entry per bar
16151
- return this.data().map(item => ({ label: item.fullLabel ?? item.label, color: item.color }));
16152
- });
16153
- /** Computed values used by the tooltip to avoid inline logic in the template. */
16154
- this.tooltipKey = computed(() => {
16155
- const item = this.selectedItem();
16156
- if (!item) {
16157
- return undefined;
16158
- }
16159
- return this.stackLabels()?.length ? this.keysByStack()[item.stackKey] ?? [] : [item.key];
16160
- });
16161
- this.tooltipLabel = computed(() => {
16162
- const item = this.selectedItem();
16163
- if (!item) {
16164
- return undefined;
16165
- }
16166
- return this.stackLabels()?.length ? item.stackKey : item.fullLabel ?? item.label;
16167
- });
16168
- this.tooltipValue = computed(() => {
16169
- const item = this.selectedItem();
16170
- if (!item) {
16171
- return undefined;
16172
- }
16173
- return this.stackLabels()?.length ? this.totalByStack()[item.stackKey] ?? 0 : item.value;
16174
- });
16175
- /** Slices belonging to the currently selected stack – used by the tooltip @for loop. */
16176
- this.selectedStackSlices = computed(() => {
16177
- const item = this.selectedItem();
16178
- if (!item || !this.stackLabels()?.length) {
16179
- return [];
16180
- }
16181
- return this.slicedDataPoints().filter(d => d.stackKey === item.stackKey);
16182
- });
16183
- this.maxAmountOfItems = signal(undefined);
16184
- this.slicedDataPoints = signal([]);
16185
- this.barSelection = null;
16186
- this.labelSelection = null;
16187
- }
16188
- ngOnChanges(_changes) {
16189
- if (!this.svg) {
16190
- return;
16191
- }
16192
- requestAnimationFrame(() => this.updateChart());
16193
- }
16194
- ngAfterViewInit() {
16195
- this.createChart();
16196
- super.ngAfterViewInit(); // schedules the initial requestAnimationFrame(() => updateChart())
16197
- }
16198
- createChart() {
16199
- const element = this.chartRef().nativeElement;
16200
- this.svg = select(element);
16201
- }
16202
- updateChart() {
16203
- this.svg.selectAll('*').remove();
16204
- if (this.loading()) {
16205
- this.slicedDataPoints.set([]);
16206
- return;
16207
- }
16208
- const { width: containerWidth, height: containerHeight } = this.getContainerDimensions();
16209
- const extraBottom = this.xAxisLabel() ? 16 : 0;
16210
- const extraLeft = this.yAxisLabel() ? 16 : 0;
16211
- const leftMargin = (this.orientation() === 'horizontal' ? this.MARGIN.left : 30) + extraLeft;
16212
- const width = containerWidth - leftMargin - this.MARGIN.right;
16213
- const height = containerHeight - this.MARGIN.top - (this.MARGIN.bottom + extraBottom);
16214
- this.maxAmountOfItems.set(this.getMaxAmountOfItems(width, height));
16215
- if (!this.data()?.length) {
16216
- this.slicedDataPoints.set([]);
16217
- return;
16218
- }
16219
- const stacks = this.stackItems(this.data());
16220
- const slicedStacks = stacks.slice(0, this.maxAmountOfItems());
16221
- const flatStacks = slicedStacks.flat();
16222
- this.slicedDataPoints.set(flatStacks);
16223
- const slicedStackKeys = new Set(slicedStacks.map(stack => stack[0].stackKey));
16224
- const labels = this.stackLabels()?.length
16225
- ? this.stackLabels()
16226
- .filter(label => slicedStackKeys.has(label))
16227
- .map(label => ({ stackKey: label, label }))
16228
- : flatStacks.map(d => ({ stackKey: d.stackKey, label: d.label }));
16229
- const g = this.svg
16230
- .attr('width', width)
16231
- .attr('height', height)
16232
- .append('g')
16233
- .attr('transform', `translate(${leftMargin},${this.MARGIN.top})`);
16234
- if (this.orientation() === 'vertical') {
16235
- this.createVerticalChart(g, labels, width, height, this.xAxisLabel(), this.yAxisLabel(), leftMargin);
16236
- }
16237
- else {
16238
- this.createHorizontalChart(g, labels, width, height, this.xAxisLabel(), this.yAxisLabel(), leftMargin);
16239
- }
16240
- this.styleGridLines(g);
16241
- }
16242
- createVerticalChart(g, labels, width, height, xAxisLabel, yAxisLabel, leftMargin) {
16243
- const keys = labels.map(d => d.stackKey);
16244
- const x = scaleBand().domain(keys).range([0, width]);
16245
- const y = scaleLinear()
16246
- .domain([0, max(this.slicedDataPoints(), (d) => d.stackValue1) || 0])
16247
- .nice()
16248
- .range([height, 0]);
16249
- // X Axis
16250
- const labelSelection = g
16251
- .append('g')
16252
- .attr('transform', `translate(0,${height})`)
16253
- .call(axisBottom(x))
16254
- .selectAll('text');
16255
- this.labelSelection = labelSelection
16256
- .data(labels)
16257
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
16258
- .call(this.addTextCommonInfo)
16259
- .style('cursor', 'pointer')
16260
- .call(this.addTextAndTitle.bind(this), x.bandwidth())
16261
- .call(this.addTextHoverHandlers.bind(this))
16262
- .call(this.addTextClickHandler.bind(this));
16263
- // Y Axis
16264
- const tickValues = y.ticks(5).filter((tick) => Number.isInteger(tick));
16265
- const yAxis = g
16266
- .append('g')
16267
- .call(axisLeft(y).tickValues(tickValues).tickSize(-width).tickFormat(format('~s')))
16268
- .selectAll('text');
16269
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
16270
- yAxis.call(this.addTextCommonInfo);
16271
- this.createBarSelectionGroups(g);
16272
- // Add highlight rect (behind)
16273
- this.addVerticalBarRectangle(x, y, 2)
16274
- .attr('class', 'bar-highlight')
16275
- .attr('fill', '#fff')
16276
- .style('stroke', (d) => toChartColor(d.color))
16277
- .attr('stroke-width', 2)
16278
- .style('opacity', 0); // hidden by default;
16279
- // Add main bar rect (on top)
16280
- this.addVerticalBarRectangle(x, y)
16281
- .attr('class', 'bar')
16282
- .style('fill', (d) => toChartColor(d.color));
16283
- this.appendAxisLabel(g, xAxisLabel, width / 2, height + 40);
16284
- this.appendAxisLabel(g, yAxisLabel, -height / 2, -(leftMargin - 8), 'rotate(-90)');
16285
- }
16286
- createHorizontalChart(g, labels, width, height, xAxisLabel, yAxisLabel, leftMargin) {
16287
- const x = scaleLinear()
16288
- .domain([0, max(this.slicedDataPoints(), (d) => d.stackValue1) || 0])
16289
- .nice()
16290
- .range([0, width]);
16291
- const keys = labels.map(d => d.stackKey);
16292
- const y = scaleBand().domain(keys).range([0, height]);
16293
- // X Axis
16294
- const tickValues = x.ticks(5).filter((tick) => Number.isInteger(tick));
16295
- const xAxis = g
16296
- .append('g')
16297
- .attr('transform', `translate(0,${height})`)
16298
- .call(axisBottom(x).tickValues(tickValues).tickSize(-height).tickFormat(format('~s')))
16299
- .selectAll('text');
16300
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
16301
- xAxis.call(this.addTextCommonInfo);
16302
- // Y Axis
16303
- const labelSelection = g
16304
- .append('g')
16305
- .attr('transform', `translate(0,0)`)
16306
- .call(axisLeft(y))
16307
- .selectAll('text');
16308
- this.labelSelection = labelSelection
16309
- .data(labels)
16310
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
16311
- .call(this.addTextCommonInfo)
16312
- .style('cursor', 'pointer')
16313
- .call(this.addTextAndTitle.bind(this), this.MARGIN.left - 10)
16314
- .call(this.addTextHoverHandlers.bind(this))
16315
- .call(this.addTextClickHandler.bind(this));
16316
- this.createBarSelectionGroups(g);
16317
- // Add highlight rect (behind)
16318
- this.addHorizontalBarRectangle(x, y, 2)
16319
- .attr('class', 'bar-highlight')
16320
- .attr('fill', '#fff')
16321
- .style('stroke', (d) => toChartColor(d.color))
16322
- .attr('stroke-width', 2)
16323
- .style('opacity', 0); // hidden by default;
16324
- // Add main bar rect (on top)
16325
- this.addHorizontalBarRectangle(x, y)
16326
- .attr('class', 'bar')
16327
- .style('fill', (d) => toChartColor(d.color));
16328
- // X axis description label (value axis at bottom)
16329
- this.appendAxisLabel(g, xAxisLabel, width / 2, height + 40);
16330
- // Y axis description label: centred in the extra band reserved for it
16331
- this.appendAxisLabel(g, yAxisLabel, -height / 2, -((this.MARGIN.left + leftMargin) / 2), 'rotate(-90)');
16332
- }
16333
- createBarSelectionGroups(g) {
16334
- this.barSelection = g
16335
- .selectAll('.bar-group')
16336
- .data(this.slicedDataPoints(), (d) => d.key)
16337
- .join('g')
16338
- .attr('class', 'bar-group')
16339
- .style('cursor', 'pointer')
16340
- .on('mouseover', (_e, d) => this.setHoverStylesByStackKey(d.stackKey, d.key, true))
16341
- .on('mouseout', (_e, d) => this.setHoverStylesByStackKey(d.stackKey, d.key, false))
16342
- .call(this.addBarClickHandler.bind(this));
16343
- }
16344
- addTextCommonInfo(g) {
16345
- g.style('font-size', '11px').style('fill', 'var(--clr-color-neutral-600, #666)');
16346
- }
16347
- addTextHoverHandlers(g) {
16348
- g.on('mouseover', (_e, d) => this.setHoverStylesByStackKey(d.stackKey, undefined, true)) //
16349
- .on('mouseout', (_e, d) => this.setHoverStylesByStackKey(d.stackKey, undefined, false));
16350
- }
16351
- setHoverStylesByStackKey(stackKey, key, isHover) {
16352
- // When hovering a stack section, we also need to highlight the label, but not the other sections of the same stack.
16353
- // But when hovering a label, we need to highlight all sections of the stack.
16354
- const barGroup = this.barSelection.filter((d) => d.stackKey === stackKey && (key == null || d.key === key));
16355
- barGroup.select('.bar').style('mix-blend-mode', isHover ? 'multiply' : 'unset');
16356
- barGroup.select('.bar-highlight').style('opacity', isHover ? '1' : '0');
16357
- this.labelSelection
16358
- .filter((d) => d.stackKey === stackKey)
16359
- .style('font-weight', isHover ? 'bold' : 'unset');
16360
- }
16361
- addBarClickHandler(g) {
16362
- g.on('click', (event, d) => {
16363
- event.stopPropagation();
16364
- this.openTooltipByKey(d.key);
16365
- });
16366
- }
16367
- addTextClickHandler(g) {
16368
- // no click handler for labels in stacked charts
16369
- if (this.stackLabels()?.length) {
16370
- return;
16371
- }
16372
- g.on('click', (event, d) => {
16373
- event.stopPropagation();
16374
- // Note: In non-stacked charts, the stackKey is equal to the key, so this works - but the logic is confusing [VU3REQ-4790]
16375
- this.openTooltipByKey(d.stackKey);
16376
- });
16377
- }
16378
- openTooltipByKey(key) {
16379
- const index = this.slicedDataPoints().findIndex(item => item.key === key);
16380
- const rect = this.barSelection
16381
- .filter((_d, i) => i === index)
16382
- // we actually want the bar, not the group (because if value is 0, then the group is not positioned correctly
16383
- .node()
16384
- .children[0].getBoundingClientRect();
16385
- const container = this.chartRef().nativeElement.getBoundingClientRect();
16386
- this.tooltipPosition.set({
16387
- x: rect.left - container.left + rect.width / 2,
16388
- y: (this.tooltipOrientation() === 'top' ? rect.top : rect.bottom) - container.top,
16389
- });
16390
- this.selectedItem.set(this.slicedDataPoints()[index]);
16391
- }
16392
- addTextAndTitle(g, availableWidth) {
16393
- g.each((d, index, nodes) => {
16394
- const target = select(nodes[index]);
16395
- target.text(this.textRenderer.render(d.label, 15, availableWidth, target.style('font-size'), target.style('font-family')));
16396
- target.append('title').text(d.label);
16397
- });
16398
- }
16399
- styleGridLines(g) {
16400
- g.selectAll('.tick line').style('stroke', 'var(--clr-color-neutral-200, #e8e8e8)');
16401
- }
16402
- addVerticalBarRectangle(x, y, extraSize = 0) {
16403
- return this.barSelection
16404
- .append('rect')
16405
- .attr('x', (d) => {
16406
- return (x(d.stackKey) || 0) + (x.bandwidth() - this.barSizePx()) / 2 + extraSize;
16407
- })
16408
- .attr('y', (d) => y(d.stackValue1))
16409
- .attr('width', this.barSizePx() - 2 * extraSize)
16410
- .attr('height', (d) => y(d.stackValue0) - y(d.stackValue1))
16411
- .attr('rx', 1)
16412
- .attr('ry', 1);
16413
- }
16414
- addHorizontalBarRectangle(x, y, extraSize = 0) {
16415
- return this.barSelection
16416
- .append('rect')
16417
- .attr('x', (d) => x(d.stackValue0))
16418
- .attr('y', (d) => {
16419
- return (y(d.stackKey) || 0) + (y.bandwidth() - this.barSizePx()) / 2 + extraSize;
16420
- })
16421
- .attr('width', (d) => x(d.stackValue1) - x(d.stackValue0))
16422
- .attr('height', this.barSizePx() - 2 * extraSize)
16423
- .attr('rx', 1)
16424
- .attr('ry', 1);
16425
- }
16426
- /** Appends a shared-style axis description label to the chart group. */
16427
- appendAxisLabel(g, text, x, y, transform) {
16428
- if (!text) {
16429
- return;
16430
- }
16431
- const el = g
16432
- .append('text')
16433
- .attr('text-anchor', 'middle')
16434
- .style('font-size', '12px')
16435
- .style('fill', 'var(--clr-color-neutral-600, #666)')
16436
- .text(text)
16437
- .attr('x', x)
16438
- .attr('y', y);
16439
- if (transform) {
16440
- el.attr('transform', transform);
16441
- }
16442
- }
16443
- stackItems(data) {
16444
- let groups;
16445
- if (this.stackLabels()?.length) {
16446
- groups = Array.from(group(data, (d) => d.stackKey ?? d.key));
16447
- }
16448
- else {
16449
- groups = data.map(item => [item.key, [item]]);
16450
- }
16451
- return this.mapItems(groups);
16452
- }
16453
- mapItems(groups) {
16454
- const result = [];
16455
- for (const [_, items] of groups) {
16456
- let stackValue = 0;
16457
- const stackSum = sum(items, (d) => d.value);
16458
- result.push(items.map(item => {
16459
- const stackValue0 = stackValue;
16460
- stackValue += item.value;
16461
- return {
16462
- ...item,
16463
- stackValue0,
16464
- stackValue1: stackValue,
16465
- stackKey: item.stackKey ?? item.key,
16466
- percentageOfStack: stackSum === 0 ? 0 : (100 * item.value) / stackSum,
16467
- };
16468
- }));
16469
- }
16470
- return result;
16471
- }
16472
- getMaxAmountOfItems(width, height) {
16473
- if (this.orientation() === 'vertical') {
16474
- return Math.floor(width / this.barAreaSizePx());
16475
- }
16476
- return Math.floor(height / BarChartComponent.HORIZONTAL_BAR_MIN_HEIGHT_PX);
16477
- }
16478
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: BarChartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
16479
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: BarChartComponent, isStandalone: false, selector: "clr-bar-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null }, stackLabels: { classPropertyName: "stackLabels", publicName: "stackLabels", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: true, transformFunction: null }, tooltipOrientation: { classPropertyName: "tooltipOrientation", publicName: "tooltipOrientation", isSignal: true, isRequired: false, transformFunction: null }, barSizePx: { classPropertyName: "barSizePx", publicName: "barSizePx", isSignal: true, isRequired: false, transformFunction: null }, barAreaSizePx: { classPropertyName: "barAreaSizePx", publicName: "barAreaSizePx", isSignal: true, isRequired: false, transformFunction: null }, tooltipPercentOfTotal: { classPropertyName: "tooltipPercentOfTotal", publicName: "tooltipPercentOfTotal", isSignal: true, isRequired: false, transformFunction: null }, tooltipPercentOf: { classPropertyName: "tooltipPercentOf", publicName: "tooltipPercentOf", isSignal: true, isRequired: false, transformFunction: null }, noItemsMessage: { classPropertyName: "noItemsMessage", publicName: "noItemsMessage", isSignal: true, isRequired: false, transformFunction: null }, tooManyItemsMessage: { classPropertyName: "tooManyItemsMessage", publicName: "tooManyItemsMessage", isSignal: true, isRequired: false, transformFunction: null }, tooManyItemsGroupedMessage: { classPropertyName: "tooManyItemsGroupedMessage", publicName: "tooManyItemsGroupedMessage", isSignal: true, isRequired: false, transformFunction: null }, showLegend: { classPropertyName: "showLegend", publicName: "showLegend", isSignal: true, isRequired: false, transformFunction: null }, showExportButton: { classPropertyName: "showExportButton", publicName: "showExportButton", isSignal: true, isRequired: false, transformFunction: null }, exportFilename: { classPropertyName: "exportFilename", publicName: "exportFilename", isSignal: true, isRequired: false, transformFunction: null }, xAxisLabel: { classPropertyName: "xAxisLabel", publicName: "xAxisLabel", isSignal: true, isRequired: false, transformFunction: null }, yAxisLabel: { classPropertyName: "yAxisLabel", publicName: "yAxisLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueClicked: "valueClicked" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"chart-container\">\n <div class=\"chart-area\" #container (cngWindowResize)=\"updateChart()\" [class.pt-3]=\"alertMessageAndType()\">\n <svg [class.d-none]=\"loading() || !data()?.length\" #chart></svg>\n\n @if (tooltipPosition()) {\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n [tooltipOrientation]=\"tooltipOrientation()\"\n [squareColor]=\"!stackLabels() ? selectedItem()?.color : undefined\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"valueClicked.emit({ key: tooltipKey(), label: tooltipLabel(), value: tooltipValue() })\"\n >\n <ng-container ngProjectAs=\"cng-title\"> ({{ tooltipValue() }}) {{ tooltipLabel() }}</ng-container>\n\n <p class=\"mt-0\">\n {{ (100 * tooltipValue()) / total() | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n </p>\n\n @if (stackLabels()) { @let stackKey = selectedItem().stackKey; @for (slice of selectedStackSlices(); track\n slice.key) {\n <div class=\"mt-0-5 d-flex\">\n <div class=\"color-square mr-0-5\" [style.background-color]=\"toChartColor(slice.color)\"></div>\n <strong>{{ slice.fullLabel || slice.label }}:&nbsp;</strong>\n <span\n class=\"has-more-info\"\n (click)=\"\n valueClicked.emit({\n key: [slice.key],\n label: slice.fullLabel || slice.label,\n value: slice.value,\n })\n \"\n >\n {{ slice.value }}\n </span>\n </div>\n <p class=\"mt-0-25 percentage-info\">\n {{ (100 * slice.value) / total() | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n {{ slice.percentageOfStack | number : '1.0-2' }}% {{ tooltipPercentOf() }} \"{{ stackKey }}\"\n </p>\n } }\n </cng-chart-tooltip>\n } @if (loading() || !data()?.length) {\n <cng-bar-chart-skeleton [orientation]=\"orientation()\" [skeletonType]=\"loading() ? 'loading' : 'placeholder'\" />\n } @if (alertMessageAndType()) {\n <cng-chart-alert-overlay [alertType]=\"alertMessageAndType()[1]\" [alertMessage]=\"alertMessageAndType()[0]\" />\n }\n </div>\n\n @if (showLegend() && !loading() && legendItems().length) {\n <cng-chart-legend [items]=\"legendItems()\" />\n } @if (showExportButton() && !loading() && data()?.length) {\n <cng-chart-export-button class=\"chart-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}:host ::ng-deep .domain{display:none}.chart-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative}.chart-area{flex:1;min-height:0;position:relative}svg{height:100%;width:100%}cng-bar-chart-skeleton{height:100%}.percentage-info{margin-left:15px}\n"], dependencies: [{ kind: "component", type: ChartAlertOverlayComponent, selector: "cng-chart-alert-overlay", inputs: ["alertMessage", "alertType"] }, { kind: "component", type: ChartExportButtonComponent, selector: "cng-chart-export-button", inputs: ["svgRef", "filename"] }, { kind: "component", type: ChartLegendComponent, selector: "cng-chart-legend", inputs: ["items"] }, { kind: "component", type: ChartSkeletonComponent, selector: "cng-bar-chart-skeleton", inputs: ["skeletonType", "orientation"] }, { kind: "component", type: ChartTooltipComponent, selector: "cng-chart-tooltip", inputs: ["tooltipPosition", "tooltipOrientation", "squareColor", "tooltipClickable"], outputs: ["tooltipClosed", "tooltipHeaderClicked"] }, { kind: "directive", type: OutsideClickDirective, selector: "[cngOutsideClick]", outputs: ["cngOutsideClick"] }, { kind: "directive", type: WindowResizeDirective, selector: "[cngWindowResize]", inputs: ["debounce", "includeFirst"], outputs: ["cngWindowResize"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
16480
- }
16481
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: BarChartComponent, decorators: [{
16482
- type: Component,
16483
- args: [{ selector: 'clr-bar-chart', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<div class=\"chart-container\">\n <div class=\"chart-area\" #container (cngWindowResize)=\"updateChart()\" [class.pt-3]=\"alertMessageAndType()\">\n <svg [class.d-none]=\"loading() || !data()?.length\" #chart></svg>\n\n @if (tooltipPosition()) {\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n [tooltipOrientation]=\"tooltipOrientation()\"\n [squareColor]=\"!stackLabels() ? selectedItem()?.color : undefined\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"valueClicked.emit({ key: tooltipKey(), label: tooltipLabel(), value: tooltipValue() })\"\n >\n <ng-container ngProjectAs=\"cng-title\"> ({{ tooltipValue() }}) {{ tooltipLabel() }}</ng-container>\n\n <p class=\"mt-0\">\n {{ (100 * tooltipValue()) / total() | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n </p>\n\n @if (stackLabels()) { @let stackKey = selectedItem().stackKey; @for (slice of selectedStackSlices(); track\n slice.key) {\n <div class=\"mt-0-5 d-flex\">\n <div class=\"color-square mr-0-5\" [style.background-color]=\"toChartColor(slice.color)\"></div>\n <strong>{{ slice.fullLabel || slice.label }}:&nbsp;</strong>\n <span\n class=\"has-more-info\"\n (click)=\"\n valueClicked.emit({\n key: [slice.key],\n label: slice.fullLabel || slice.label,\n value: slice.value,\n })\n \"\n >\n {{ slice.value }}\n </span>\n </div>\n <p class=\"mt-0-25 percentage-info\">\n {{ (100 * slice.value) / total() | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n {{ slice.percentageOfStack | number : '1.0-2' }}% {{ tooltipPercentOf() }} \"{{ stackKey }}\"\n </p>\n } }\n </cng-chart-tooltip>\n } @if (loading() || !data()?.length) {\n <cng-bar-chart-skeleton [orientation]=\"orientation()\" [skeletonType]=\"loading() ? 'loading' : 'placeholder'\" />\n } @if (alertMessageAndType()) {\n <cng-chart-alert-overlay [alertType]=\"alertMessageAndType()[1]\" [alertMessage]=\"alertMessageAndType()[0]\" />\n }\n </div>\n\n @if (showLegend() && !loading() && legendItems().length) {\n <cng-chart-legend [items]=\"legendItems()\" />\n } @if (showExportButton() && !loading() && data()?.length) {\n <cng-chart-export-button class=\"chart-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}:host ::ng-deep .domain{display:none}.chart-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative}.chart-area{flex:1;min-height:0;position:relative}svg{height:100%;width:100%}cng-bar-chart-skeleton{height:100%}.percentage-info{margin-left:15px}\n"] }]
16484
- }], ctorParameters: () => [] });
16485
-
16486
- /*
16487
- * Copyright (c) 2018-2026 Porsche Informatik. All Rights Reserved.
16488
- * This software is released under MIT license.
16489
- * The full license information can be found in LICENSE in the root directory of this project.
16490
- */
16491
- /**
16492
- * Renders interactive dot circles for a single XY series onto the given D3 group.
16493
- *
16494
- * - Default radius: 4px, hover radius: 6px.
16495
- * - Calls `onClick` with the clicked element, data point and series when a dot is clicked.
16496
- *
16497
- */
16498
- function renderDots(g, series, x, y, onClick) {
16499
- g.selectAll(`.dot-${series.key}`)
16500
- .data(series.data, (d) => d.x)
16501
- .join('circle')
16502
- .attr('class', `dot dot-${series.key}`)
16503
- .attr('cx', (d) => x(d.x) ?? 0)
16504
- .attr('cy', (d) => y(d.value))
16505
- .attr('r', 4)
16506
- .style('fill', toChartColor(series.color))
16507
- .attr('stroke', '#fff')
16508
- .attr('stroke-width', 2)
16509
- .style('cursor', 'pointer')
16510
- .on('mouseover', (e) => {
16511
- select(e.currentTarget).attr('r', 6);
16512
- })
16513
- .on('mouseout', (e) => {
16514
- select(e.currentTarget).attr('r', 4);
16515
- })
16516
- .on('click', (event, d) => {
16517
- event.stopPropagation();
16518
- onClick(event.currentTarget, d, series);
16519
- });
16520
- }
16521
-
16522
- /*
16523
- * Copyright (c) 2018-2026 Porsche Informatik. All Rights Reserved.
16524
- * This software is released under MIT license.
16525
- * The full license information can be found in LICENSE in the root directory of this project.
16526
- */
16527
- /**
16528
- * Draws the X and Y axes (with integer-only Y ticks and grid lines) plus
16529
- * optional description labels onto the given D3 group.
16530
- *
16531
- * @param g The D3 group element to append into.
16532
- * @param x Any D3 axis scale keyed on strings (`ScalePoint` or `ScaleBand`).
16533
- * @param y The linear Y scale.
16534
- * @param opts Axis configuration options.
16535
- */
16536
- function drawXYAxes(g, x, y, opts) {
16537
- const { xLabelMap, width, height, xAxisLabel, yAxisLabel, effectiveLeft } = opts;
16538
- // X axis
16539
- g.append('g')
16540
- .attr('transform', `translate(0,${height})`)
16541
- .call(axisBottom(x).tickFormat((d) => xLabelMap.get(d) ?? d))
16542
- .selectAll('text')
16543
- .style('font-size', '11px')
16544
- .style('fill', 'var(--clr-color-neutral-600, #666)');
16545
- // Y axis
16546
- const tickValues = y.ticks(5).filter((t) => Number.isInteger(t));
16547
- g.append('g')
16548
- .call(axisLeft(y).tickValues(tickValues).tickSize(-width).tickFormat(format('~s')))
16549
- .selectAll('text')
16550
- .style('font-size', '11px')
16551
- .style('fill', 'var(--clr-color-neutral-600, #666)');
16552
- // Optional X axis description label
16553
- if (xAxisLabel) {
16554
- g.append('text')
16555
- .attr('x', width / 2)
16556
- .attr('y', height + 40)
16557
- .attr('text-anchor', 'middle')
16558
- .style('font-size', '12px')
16559
- .style('fill', 'var(--clr-color-neutral-600, #666)')
16560
- .text(xAxisLabel);
16561
- }
16562
- // Optional Y axis description label (rotated, centred in the left-margin band)
16563
- if (yAxisLabel) {
16564
- g.append('text')
16565
- .attr('transform', 'rotate(-90)')
16566
- .attr('x', -height / 2)
16567
- .attr('y', -(effectiveLeft / 2))
16568
- .attr('text-anchor', 'middle')
16569
- .style('font-size', '12px')
16570
- .style('fill', 'var(--clr-color-neutral-600, #666)')
16571
- .text(yAxisLabel);
16572
- }
16573
- }
16574
- /**
16575
- * Styles all D3 grid tick lines inside `g` with the neutral chart grid color.
16576
- */
16577
- function styleGridLines(g) {
16578
- g.selectAll('.tick line').style('stroke', 'var(--clr-color-neutral-200, #e8e8e8)');
16579
- }
16580
-
16581
- class LineChartComponent extends ChartBase {
16582
- constructor() {
16583
- super(...arguments);
16584
- this.series = input.required();
16585
- this.tooltipOrientation = input('top');
16586
- this.showArea = input(false);
16587
- this.showLegend = input(true);
16588
- this.showValues = input(false);
16589
- this.showExportButton = input(false);
16590
- this.exportFilename = input('line-chart');
16591
- this.noItemsMessage = input(NO_ITEMS_MESSAGE);
16592
- this.tooltipPercentOfTotal = input('of total');
16593
- /** Optional label rendered below the X axis. */
16594
- this.xAxisLabel = input('');
16595
- /** Optional label rendered rotated to the left of the Y axis. */
16596
- this.yAxisLabel = input('');
16597
- this.valueClicked = output();
16598
- this.MARGIN = { top: 20, right: 30, bottom: 30, left: 65 };
16599
- this.hasData = computed(() => this.series().some(s => s.data.length > 0));
16600
- this.total = computed(() => this.series().reduce((acc, s) => acc + s.data.reduce((a, d) => a + d.value, 0), 0));
16601
- this.alertMessageAndType = computed(() => {
16602
- if (this.loading()) {
16603
- return undefined;
16604
- }
16605
- if (!this.hasData()) {
16606
- return [this.noItemsMessage(), NO_ITEMS_ALERT_TYPE];
16607
- }
16608
- return undefined;
16609
- });
16610
- this.legendItems = computed(() => {
16611
- if (!this.showLegend()) {
16612
- return [];
16613
- }
16614
- return this.series().map(s => ({ label: s.label, color: s.color }));
16615
- });
16616
- }
16617
- ngOnChanges(_changes) {
16618
- if (!this.svg) {
16619
- return;
16620
- }
16621
- requestAnimationFrame(() => this.updateChart());
16622
- }
16623
- ngAfterViewInit() {
16624
- this.svg = select(this.chartRef().nativeElement);
16625
- super.ngAfterViewInit();
16626
- }
16627
- updateChart() {
16628
- this.svg.selectAll('*').remove();
16629
- if (this.loading()) {
16630
- return;
16631
- }
16632
- if (!this.hasData()) {
16633
- return;
16634
- }
16635
- const { width: containerWidth, height: containerHeight } = this.getContainerDimensions();
16636
- const extraBottom = this.xAxisLabel() ? 16 : 0;
16637
- const extraLeft = this.yAxisLabel() ? 16 : 0;
16638
- const extraTop = this.showValues() ? 14 : 0;
16639
- const effectiveLeft = this.MARGIN.left + extraLeft;
16640
- const width = containerWidth - effectiveLeft - this.MARGIN.right;
16641
- const height = containerHeight - (this.MARGIN.top + extraTop) - (this.MARGIN.bottom + extraBottom);
16642
- // Collect all unique X keys in the order they appear across all series
16643
- const xKeys = [...new Set(this.series().flatMap(s => s.data.map(d => d.x)))];
16644
- // Build a display-label map
16645
- const xLabelMap = new Map();
16646
- this.series().forEach(s => s.data.forEach(d => xLabelMap.set(d.x, d.xLabel ?? d.x)));
16647
- const x = scalePoint().domain(xKeys).range([0, width]).padding(0.5);
16648
- const maxY = max(this.series().flatMap(s => s.data.map(d => d.value))) ?? 0;
16649
- const y = scaleLinear().domain([0, maxY]).nice().range([height, 0]);
16650
- const g = this.svg
16651
- .attr('width', width)
16652
- .attr('height', height)
16653
- .append('g')
16654
- .attr('transform', `translate(${effectiveLeft},${this.MARGIN.top + extraTop})`);
16655
- drawXYAxes(g, x, y, {
16656
- xLabelMap,
16657
- width,
16658
- height,
16659
- xAxisLabel: this.xAxisLabel(),
16660
- yAxisLabel: this.yAxisLabel(),
16661
- effectiveLeft,
16662
- });
16663
- styleGridLines(g);
16664
- this.drawSeries(g, x, y);
16665
- }
16666
- drawSeries(g, x, y) {
16667
- const lineGenerator = line()
16668
- .x((d) => x(d.x) ?? 0)
16669
- .y((d) => y(d.value))
16670
- .curve(curveMonotoneX);
16671
- for (const series of this.series()) {
16672
- if (!series.data.length) {
16673
- continue;
16674
- }
16675
- // Optional area fill
16676
- if (this.showArea()) {
16677
- g.append('path')
16678
- .datum(series.data)
16679
- .attr('class', `area area-${series.key}`)
16680
- .style('fill', toChartColor(series.color))
16681
- .attr('fill-opacity', 0.1)
16682
- .attr('d', lineGenerator);
16683
- }
16684
- // Line path
16685
- g.append('path')
16686
- .datum(series.data)
16687
- .attr('class', `line line-${series.key}`)
16688
- .attr('fill', 'none')
16689
- .style('stroke', toChartColor(series.color))
16690
- .attr('stroke-width', 2)
16691
- .attr('stroke-linejoin', 'round')
16692
- .attr('stroke-linecap', 'round')
16693
- .attr('d', lineGenerator);
16694
- // Dots
16695
- renderDots(g, series, x, y, (el, point, s) => this.openTooltip(el, point, s));
16696
- // Value labels above each dot
16697
- if (this.showValues()) {
16698
- g.selectAll(`.value-label-${series.key}`)
16699
- .data(series.data, (d) => d.x)
16700
- .join('text')
16701
- .attr('class', `value-label value-label-${series.key}`)
16702
- .attr('x', (d) => x(d.x) ?? 0)
16703
- .attr('y', (d) => y(d.value) - 9)
16704
- .attr('text-anchor', 'middle')
16705
- .style('font-size', '11px')
16706
- .style('font-weight', '600')
16707
- .style('fill', toChartColor(series.color))
16708
- .attr('stroke', '#fff')
16709
- .attr('stroke-width', 3)
16710
- .attr('paint-order', 'stroke fill')
16711
- .style('pointer-events', 'none')
16712
- .text((d) => format('~s')(d.value));
16713
- }
16714
- }
16715
- }
16716
- openTooltip(el, point, series) {
16717
- const rect = el.getBoundingClientRect();
16718
- const container = this.chartRef().nativeElement.getBoundingClientRect();
16719
- this.tooltipPosition.set({
16720
- x: rect.left - container.left + rect.width / 2,
16721
- y: (this.tooltipOrientation() === 'top' ? rect.top : rect.bottom) - container.top,
16722
- });
16723
- this.selectedItem.set({ ...point, seriesKey: series.key, seriesLabel: series.label, color: series.color });
16724
- }
16725
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: LineChartComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
16726
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: LineChartComponent, isStandalone: false, selector: "clr-line-chart", inputs: { series: { classPropertyName: "series", publicName: "series", isSignal: true, isRequired: true, transformFunction: null }, tooltipOrientation: { classPropertyName: "tooltipOrientation", publicName: "tooltipOrientation", isSignal: true, isRequired: false, transformFunction: null }, showArea: { classPropertyName: "showArea", publicName: "showArea", isSignal: true, isRequired: false, transformFunction: null }, showLegend: { classPropertyName: "showLegend", publicName: "showLegend", isSignal: true, isRequired: false, transformFunction: null }, showValues: { classPropertyName: "showValues", publicName: "showValues", isSignal: true, isRequired: false, transformFunction: null }, showExportButton: { classPropertyName: "showExportButton", publicName: "showExportButton", isSignal: true, isRequired: false, transformFunction: null }, exportFilename: { classPropertyName: "exportFilename", publicName: "exportFilename", isSignal: true, isRequired: false, transformFunction: null }, noItemsMessage: { classPropertyName: "noItemsMessage", publicName: "noItemsMessage", isSignal: true, isRequired: false, transformFunction: null }, tooltipPercentOfTotal: { classPropertyName: "tooltipPercentOfTotal", publicName: "tooltipPercentOfTotal", isSignal: true, isRequired: false, transformFunction: null }, xAxisLabel: { classPropertyName: "xAxisLabel", publicName: "xAxisLabel", isSignal: true, isRequired: false, transformFunction: null }, yAxisLabel: { classPropertyName: "yAxisLabel", publicName: "yAxisLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueClicked: "valueClicked" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"chart-container\">\n <div class=\"chart-area\" #container (cngWindowResize)=\"updateChart()\" [class.pt-3]=\"alertMessageAndType()\">\n <svg [class.d-none]=\"loading() || !hasData()\" #chart></svg>\n\n @if (tooltipPosition()) {\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n [tooltipOrientation]=\"tooltipOrientation()\"\n [squareColor]=\"selectedItem()?.color\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"\n valueClicked.emit({\n seriesKey: selectedItem().seriesKey,\n seriesLabel: selectedItem().seriesLabel,\n x: selectedItem().x,\n xLabel: selectedItem().xLabel,\n value: selectedItem().value,\n })\n \"\n >\n <ng-container ngProjectAs=\"cng-title\">\n ({{ selectedItem().value }}) {{ selectedItem().xLabel || selectedItem().x }}\n </ng-container>\n\n <p class=\"mt-0\">\n <strong>{{ selectedItem().seriesLabel }}:</strong>\n {{ selectedItem().value }}\n </p>\n <p class=\"mt-0\">\n {{ (100 * selectedItem().value) / total() | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n </p>\n </cng-chart-tooltip>\n } @if (loading() || !hasData()) {\n <cng-bar-chart-skeleton orientation=\"vertical\" [skeletonType]=\"loading() ? 'loading' : 'placeholder'\" />\n } @if (alertMessageAndType()) {\n <cng-chart-alert-overlay [alertType]=\"alertMessageAndType()[1]\" [alertMessage]=\"alertMessageAndType()[0]\" />\n }\n </div>\n\n @if (showLegend() && !loading() && legendItems().length) {\n <cng-chart-legend [items]=\"legendItems()\" />\n } @if (showExportButton() && !loading() && hasData()) {\n <cng-chart-export-button class=\"chart-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}:host ::ng-deep .domain{display:none}.chart-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative}.chart-area{flex:1;min-height:0;position:relative}svg{height:100%;width:100%}cng-bar-chart-skeleton{height:100%}.line,.area{pointer-events:none}.dot{transition:r .1s ease}\n"], dependencies: [{ kind: "component", type: ChartAlertOverlayComponent, selector: "cng-chart-alert-overlay", inputs: ["alertMessage", "alertType"] }, { kind: "component", type: ChartExportButtonComponent, selector: "cng-chart-export-button", inputs: ["svgRef", "filename"] }, { kind: "component", type: ChartLegendComponent, selector: "cng-chart-legend", inputs: ["items"] }, { kind: "component", type: ChartSkeletonComponent, selector: "cng-bar-chart-skeleton", inputs: ["skeletonType", "orientation"] }, { kind: "component", type: ChartTooltipComponent, selector: "cng-chart-tooltip", inputs: ["tooltipPosition", "tooltipOrientation", "squareColor", "tooltipClickable"], outputs: ["tooltipClosed", "tooltipHeaderClicked"] }, { kind: "directive", type: OutsideClickDirective, selector: "[cngOutsideClick]", outputs: ["cngOutsideClick"] }, { kind: "directive", type: WindowResizeDirective, selector: "[cngWindowResize]", inputs: ["debounce", "includeFirst"], outputs: ["cngWindowResize"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
16727
- }
16728
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: LineChartComponent, decorators: [{
16729
- type: Component,
16730
- args: [{ selector: 'clr-line-chart', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"chart-container\">\n <div class=\"chart-area\" #container (cngWindowResize)=\"updateChart()\" [class.pt-3]=\"alertMessageAndType()\">\n <svg [class.d-none]=\"loading() || !hasData()\" #chart></svg>\n\n @if (tooltipPosition()) {\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n [tooltipOrientation]=\"tooltipOrientation()\"\n [squareColor]=\"selectedItem()?.color\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"\n valueClicked.emit({\n seriesKey: selectedItem().seriesKey,\n seriesLabel: selectedItem().seriesLabel,\n x: selectedItem().x,\n xLabel: selectedItem().xLabel,\n value: selectedItem().value,\n })\n \"\n >\n <ng-container ngProjectAs=\"cng-title\">\n ({{ selectedItem().value }}) {{ selectedItem().xLabel || selectedItem().x }}\n </ng-container>\n\n <p class=\"mt-0\">\n <strong>{{ selectedItem().seriesLabel }}:</strong>\n {{ selectedItem().value }}\n </p>\n <p class=\"mt-0\">\n {{ (100 * selectedItem().value) / total() | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n </p>\n </cng-chart-tooltip>\n } @if (loading() || !hasData()) {\n <cng-bar-chart-skeleton orientation=\"vertical\" [skeletonType]=\"loading() ? 'loading' : 'placeholder'\" />\n } @if (alertMessageAndType()) {\n <cng-chart-alert-overlay [alertType]=\"alertMessageAndType()[1]\" [alertMessage]=\"alertMessageAndType()[0]\" />\n }\n </div>\n\n @if (showLegend() && !loading() && legendItems().length) {\n <cng-chart-legend [items]=\"legendItems()\" />\n } @if (showExportButton() && !loading() && hasData()) {\n <cng-chart-export-button class=\"chart-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}:host ::ng-deep .domain{display:none}.chart-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative}.chart-area{flex:1;min-height:0;position:relative}svg{height:100%;width:100%}cng-bar-chart-skeleton{height:100%}.line,.area{pointer-events:none}.dot{transition:r .1s ease}\n"] }]
16731
- }] });
16732
-
16733
- class AreaChartComponent extends ChartBase {
16734
- constructor() {
16735
- super(...arguments);
16736
- this.series = input.required();
16737
- this.tooltipOrientation = input('top');
16738
- this.showLegend = input(true);
16739
- this.showExportButton = input(false);
16740
- this.exportFilename = input('area-chart');
16741
- /** Area fill opacity (0–1). Default: 0.2. */
16742
- this.areaOpacity = input(0.2);
16743
- this.noItemsMessage = input(NO_ITEMS_MESSAGE);
16744
- this.tooltipPercentOfTotal = input('of total');
16745
- /** Optional label rendered below the X axis. */
16746
- this.xAxisLabel = input('');
16747
- /** Optional label rendered rotated to the left of the Y axis. */
16748
- this.yAxisLabel = input('');
16749
- this.valueClicked = output();
16750
- this.MARGIN = { top: 20, right: 30, bottom: 30, left: 65 };
16751
- this.hasData = computed(() => this.series().some(s => s.data.length > 0));
16752
- this.total = computed(() => this.series().reduce((acc, s) => acc + s.data.reduce((a, d) => a + d.value, 0), 0));
16753
- this.alertMessageAndType = computed(() => {
16754
- if (this.loading()) {
16755
- return undefined;
16756
- }
16757
- if (!this.hasData()) {
16758
- return [this.noItemsMessage(), NO_ITEMS_ALERT_TYPE];
16759
- }
16760
- return undefined;
16761
- });
16762
- this.legendItems = computed(() => {
16763
- if (!this.showLegend()) {
16764
- return [];
16765
- }
16766
- return this.series().map(s => ({ label: s.label, color: s.color }));
16767
- });
16768
- }
16769
- ngOnChanges(_changes) {
16770
- if (!this.svg) {
16771
- return;
16772
- }
16773
- requestAnimationFrame(() => this.updateChart());
16774
- }
16775
- ngAfterViewInit() {
16776
- this.svg = select(this.chartRef().nativeElement);
16777
- super.ngAfterViewInit();
16778
- }
16779
- updateChart() {
16780
- this.svg.selectAll('*').remove();
16781
- if (this.loading() || !this.hasData()) {
16782
- return;
16783
- }
16784
- const { width: containerWidth, height: containerHeight } = this.getContainerDimensions();
16785
- const extraBottom = this.xAxisLabel() ? 16 : 0;
16786
- const extraLeft = this.yAxisLabel() ? 16 : 0;
16787
- const effectiveLeft = this.MARGIN.left + extraLeft;
16788
- const width = containerWidth - effectiveLeft - this.MARGIN.right;
16789
- const height = containerHeight - this.MARGIN.top - (this.MARGIN.bottom + extraBottom);
16790
- const xKeys = [...new Set(this.series().flatMap(s => s.data.map(d => d.x)))];
16791
- const xLabelMap = new Map();
16792
- this.series().forEach(s => s.data.forEach(d => xLabelMap.set(d.x, d.xLabel ?? d.x)));
16793
- const x = scalePoint().domain(xKeys).range([0, width]).padding(0.5);
16794
- const maxY = max(this.series().flatMap(s => s.data.map(d => d.value))) ?? 0;
16795
- const y = scaleLinear().domain([0, maxY]).nice().range([height, 0]);
16796
- const g = this.svg
16797
- .attr('width', width)
16798
- .attr('height', height)
16799
- .append('g')
16800
- .attr('transform', `translate(${effectiveLeft},${this.MARGIN.top})`);
16801
- drawXYAxes(g, x, y, {
16802
- xLabelMap,
16803
- width,
16804
- height,
16805
- xAxisLabel: this.xAxisLabel(),
16806
- yAxisLabel: this.yAxisLabel(),
16807
- effectiveLeft,
16808
- });
16809
- styleGridLines(g);
16810
- this.drawSeries(g, x, y, height);
16811
- }
16812
- drawSeries(g, x, y, height) {
16813
- const areaGenerator = area()
16814
- .x((d) => x(d.x) ?? 0)
16815
- .y0(height)
16816
- .y1((d) => y(d.value))
16817
- .curve(curveMonotoneX);
16818
- const lineGenerator = line()
16819
- .x((d) => x(d.x) ?? 0)
16820
- .y((d) => y(d.value))
16821
- .curve(curveMonotoneX);
16822
- // Draw areas first (behind lines)
16823
- for (const series of this.series()) {
16824
- if (!series.data.length) {
16825
- continue;
16826
- }
16827
- g.append('path')
16828
- .datum(series.data)
16829
- .attr('class', `area area-${series.key}`)
16830
- .style('fill', toChartColor(series.color))
16831
- .attr('fill-opacity', this.areaOpacity())
16832
- .attr('stroke', 'none')
16833
- .attr('d', areaGenerator);
16834
- }
16835
- // Draw lines and dots on top
16836
- for (const series of this.series()) {
16837
- if (!series.data.length) {
16838
- continue;
16839
- }
16840
- g.append('path')
16841
- .datum(series.data)
16842
- .attr('class', `line line-${series.key}`)
16843
- .attr('fill', 'none')
16844
- .style('stroke', toChartColor(series.color))
16845
- .attr('stroke-width', 2)
16846
- .attr('stroke-linejoin', 'round')
16847
- .attr('stroke-linecap', 'round')
16848
- .attr('d', lineGenerator);
16849
- renderDots(g, series, x, y, (el, point, s) => this.openTooltip(el, point, s));
16850
- }
16851
- }
16852
- openTooltip(el, point, series) {
16853
- const rect = el.getBoundingClientRect();
16854
- const container = this.chartRef().nativeElement.getBoundingClientRect();
16855
- this.tooltipPosition.set({
16856
- x: rect.left - container.left + rect.width / 2,
16857
- y: (this.tooltipOrientation() === 'top' ? rect.top : rect.bottom) - container.top,
16858
- });
16859
- this.selectedItem.set({ ...point, seriesKey: series.key, seriesLabel: series.label, color: series.color });
16860
- }
16861
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: AreaChartComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
16862
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: AreaChartComponent, isStandalone: false, selector: "clr-area-chart", inputs: { series: { classPropertyName: "series", publicName: "series", isSignal: true, isRequired: true, transformFunction: null }, tooltipOrientation: { classPropertyName: "tooltipOrientation", publicName: "tooltipOrientation", isSignal: true, isRequired: false, transformFunction: null }, showLegend: { classPropertyName: "showLegend", publicName: "showLegend", isSignal: true, isRequired: false, transformFunction: null }, showExportButton: { classPropertyName: "showExportButton", publicName: "showExportButton", isSignal: true, isRequired: false, transformFunction: null }, exportFilename: { classPropertyName: "exportFilename", publicName: "exportFilename", isSignal: true, isRequired: false, transformFunction: null }, areaOpacity: { classPropertyName: "areaOpacity", publicName: "areaOpacity", isSignal: true, isRequired: false, transformFunction: null }, noItemsMessage: { classPropertyName: "noItemsMessage", publicName: "noItemsMessage", isSignal: true, isRequired: false, transformFunction: null }, tooltipPercentOfTotal: { classPropertyName: "tooltipPercentOfTotal", publicName: "tooltipPercentOfTotal", isSignal: true, isRequired: false, transformFunction: null }, xAxisLabel: { classPropertyName: "xAxisLabel", publicName: "xAxisLabel", isSignal: true, isRequired: false, transformFunction: null }, yAxisLabel: { classPropertyName: "yAxisLabel", publicName: "yAxisLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueClicked: "valueClicked" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"chart-container\">\n <div class=\"chart-area\" #container (cngWindowResize)=\"updateChart()\" [class.pt-3]=\"alertMessageAndType()\">\n <svg [class.d-none]=\"loading() || !hasData()\" #chart></svg>\n\n @if (tooltipPosition()) {\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n [tooltipOrientation]=\"tooltipOrientation()\"\n [squareColor]=\"selectedItem()?.color\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"\n valueClicked.emit({\n seriesKey: selectedItem().seriesKey,\n seriesLabel: selectedItem().seriesLabel,\n x: selectedItem().x,\n xLabel: selectedItem().xLabel,\n value: selectedItem().value,\n })\n \"\n >\n <ng-container ngProjectAs=\"cng-title\">\n ({{ selectedItem().value }}) {{ selectedItem().xLabel || selectedItem().x }}\n </ng-container>\n\n <p class=\"mt-0\">\n <strong>{{ selectedItem().seriesLabel }}:</strong>\n {{ selectedItem().value }}\n </p>\n <p class=\"mt-0\">\n {{ (100 * selectedItem().value) / total() | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n </p>\n </cng-chart-tooltip>\n } @if (loading() || !hasData()) {\n <cng-bar-chart-skeleton orientation=\"vertical\" [skeletonType]=\"loading() ? 'loading' : 'placeholder'\" />\n } @if (alertMessageAndType()) {\n <cng-chart-alert-overlay [alertType]=\"alertMessageAndType()[1]\" [alertMessage]=\"alertMessageAndType()[0]\" />\n }\n </div>\n\n @if (showLegend() && !loading() && legendItems().length) {\n <cng-chart-legend [items]=\"legendItems()\" />\n } @if (showExportButton() && !loading() && hasData()) {\n <cng-chart-export-button class=\"chart-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}:host ::ng-deep .domain{display:none}.chart-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative}.chart-area{flex:1;min-height:0;position:relative}svg{height:100%;width:100%}cng-bar-chart-skeleton{height:100%}.line,.area{pointer-events:none}.dot{transition:r .1s ease}\n"], dependencies: [{ kind: "component", type: ChartAlertOverlayComponent, selector: "cng-chart-alert-overlay", inputs: ["alertMessage", "alertType"] }, { kind: "component", type: ChartExportButtonComponent, selector: "cng-chart-export-button", inputs: ["svgRef", "filename"] }, { kind: "component", type: ChartLegendComponent, selector: "cng-chart-legend", inputs: ["items"] }, { kind: "component", type: ChartSkeletonComponent, selector: "cng-bar-chart-skeleton", inputs: ["skeletonType", "orientation"] }, { kind: "component", type: ChartTooltipComponent, selector: "cng-chart-tooltip", inputs: ["tooltipPosition", "tooltipOrientation", "squareColor", "tooltipClickable"], outputs: ["tooltipClosed", "tooltipHeaderClicked"] }, { kind: "directive", type: OutsideClickDirective, selector: "[cngOutsideClick]", outputs: ["cngOutsideClick"] }, { kind: "directive", type: WindowResizeDirective, selector: "[cngWindowResize]", inputs: ["debounce", "includeFirst"], outputs: ["cngWindowResize"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
16863
- }
16864
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: AreaChartComponent, decorators: [{
16865
- type: Component,
16866
- args: [{ selector: 'clr-area-chart', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"chart-container\">\n <div class=\"chart-area\" #container (cngWindowResize)=\"updateChart()\" [class.pt-3]=\"alertMessageAndType()\">\n <svg [class.d-none]=\"loading() || !hasData()\" #chart></svg>\n\n @if (tooltipPosition()) {\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n [tooltipOrientation]=\"tooltipOrientation()\"\n [squareColor]=\"selectedItem()?.color\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"\n valueClicked.emit({\n seriesKey: selectedItem().seriesKey,\n seriesLabel: selectedItem().seriesLabel,\n x: selectedItem().x,\n xLabel: selectedItem().xLabel,\n value: selectedItem().value,\n })\n \"\n >\n <ng-container ngProjectAs=\"cng-title\">\n ({{ selectedItem().value }}) {{ selectedItem().xLabel || selectedItem().x }}\n </ng-container>\n\n <p class=\"mt-0\">\n <strong>{{ selectedItem().seriesLabel }}:</strong>\n {{ selectedItem().value }}\n </p>\n <p class=\"mt-0\">\n {{ (100 * selectedItem().value) / total() | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n </p>\n </cng-chart-tooltip>\n } @if (loading() || !hasData()) {\n <cng-bar-chart-skeleton orientation=\"vertical\" [skeletonType]=\"loading() ? 'loading' : 'placeholder'\" />\n } @if (alertMessageAndType()) {\n <cng-chart-alert-overlay [alertType]=\"alertMessageAndType()[1]\" [alertMessage]=\"alertMessageAndType()[0]\" />\n }\n </div>\n\n @if (showLegend() && !loading() && legendItems().length) {\n <cng-chart-legend [items]=\"legendItems()\" />\n } @if (showExportButton() && !loading() && hasData()) {\n <cng-chart-export-button class=\"chart-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}:host ::ng-deep .domain{display:none}.chart-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative}.chart-area{flex:1;min-height:0;position:relative}svg{height:100%;width:100%}cng-bar-chart-skeleton{height:100%}.line,.area{pointer-events:none}.dot{transition:r .1s ease}\n"] }]
16867
- }] });
16868
-
16869
- class ComboChartComponent extends ChartBase {
16870
- constructor() {
16871
- super(...arguments);
16872
- // ── Inputs ──────────────────────────────────────────────────────────────────
16873
- this.barSeries = input([]);
16874
- this.lineSeries = input([]);
16875
- this.tooltipOrientation = input('top');
16876
- this.showLegend = input(true);
16877
- this.showExportButton = input(false);
16878
- this.exportFilename = input('combo-chart');
16879
- this.noItemsMessage = input(NO_ITEMS_MESSAGE);
16880
- this.tooltipPercentOfTotal = input('of total');
16881
- /** Optional label rendered below the X axis. */
16882
- this.xAxisLabel = input('');
16883
- /** Optional label rendered rotated to the left of the Y axis (bar scale). */
16884
- this.yAxisLabel = input('');
16885
- /** Optional label rendered rotated to the right of the Y axis (line scale). */
16886
- this.yLineAxisLabel = input('');
16887
- // ── Outputs ─────────────────────────────────────────────────────────────────
16888
- this.valueClicked = output();
16889
- // ── Layout ───────────────────────────────────────────────────────────────────
16890
- this.MARGIN = { top: 20, right: 30, bottom: 30, left: 65 };
16891
- // ── Computed ─────────────────────────────────────────────────────────────────
16892
- this.hasData = computed(() => this.barSeries().some(s => s.data.length > 0) || this.lineSeries().some(s => s.data.length > 0));
16893
- this.total = computed(() => {
16894
- const barTotal = this.barSeries().reduce((acc, s) => acc + s.data.reduce((a, d) => a + d.value, 0), 0);
16895
- const lineTotal = this.lineSeries().reduce((acc, s) => acc + s.data.reduce((a, d) => a + d.value, 0), 0);
16896
- return barTotal + lineTotal;
16897
- });
16898
- this.alertMessageAndType = computed(() => {
16899
- if (this.loading()) {
16900
- return undefined;
16901
- }
16902
- if (!this.hasData()) {
16903
- return [this.noItemsMessage(), NO_ITEMS_ALERT_TYPE];
16904
- }
16905
- return undefined;
16906
- });
16907
- this.legendItems = computed(() => {
16908
- if (!this.showLegend()) {
16909
- return [];
16910
- }
16911
- const barItems = this.barSeries().map(s => ({ label: s.label, color: s.color }));
16912
- const lineItems = this.lineSeries().map(s => ({ label: s.label, color: s.color }));
16913
- return [...barItems, ...lineItems];
16914
- });
16915
- }
16916
- // ── Lifecycle ────────────────────────────────────────────────────────────────
16917
- ngOnChanges(_changes) {
16918
- if (!this.svg) {
16919
- return;
16920
- }
16921
- requestAnimationFrame(() => this.updateChart());
16922
- }
16923
- ngAfterViewInit() {
16924
- this.svg = select(this.chartRef().nativeElement);
16925
- super.ngAfterViewInit();
16926
- }
16927
- // ── Chart rendering ──────────────────────────────────────────────────────────
16928
- updateChart() {
16929
- this.svg.selectAll('*').remove();
16930
- if (this.loading() || !this.hasData()) {
16931
- return;
16932
- }
16933
- const { width: cw, height: ch } = this.getContainerDimensions();
16934
- const extraBottom = this.xAxisLabel() ? 16 : 0;
16935
- const extraLeft = this.yAxisLabel() ? 16 : 0;
16936
- const effectiveLeft = this.MARGIN.left + extraLeft;
16937
- // Reserve extra right margin when line series are present (right Y axis)
16938
- const hasLines = this.lineSeries().some(s => s.data.length > 0);
16939
- const extraRight = hasLines ? (this.yLineAxisLabel() ? 50 : 40) : 0;
16940
- const width = cw - effectiveLeft - this.MARGIN.right - extraRight;
16941
- const height = ch - this.MARGIN.top - (this.MARGIN.bottom + extraBottom);
16942
- // Collect all unique X keys preserving order
16943
- const allBarKeys = this.barSeries().flatMap(s => s.data.map(d => d.x));
16944
- const allLineKeys = this.lineSeries().flatMap(s => s.data.map(d => d.x));
16945
- const xKeys = [...new Set([...allBarKeys, ...allLineKeys])];
16946
- // Build X-label map
16947
- const xLabelMap = new Map();
16948
- this.barSeries().forEach(s => s.data.forEach(d => xLabelMap.set(d.x, d.xLabel ?? d.x)));
16949
- // Outer X scale (one band per category)
16950
- const x = scaleBand().domain(xKeys).range([0, width]).paddingInner(0.35).paddingOuter(0.15);
16951
- // ── Separate Y scales ────────────────────────────────────────────────────
16952
- // Bar Y scale (left axis)
16953
- const xStackTotals = new Map();
16954
- for (const series of this.barSeries()) {
16955
- for (const point of series.data) {
16956
- xStackTotals.set(point.x, (xStackTotals.get(point.x) ?? 0) + point.value);
16957
- }
16958
- }
16959
- const maxBar = max([...xStackTotals.values()]) ?? 0;
16960
- const yBar = scaleLinear()
16961
- .domain([0, maxBar || 1])
16962
- .nice()
16963
- .range([height, 0]);
16964
- // Line Y scale (right axis)
16965
- const maxLine = max(this.lineSeries().flatMap(s => s.data.map(d => d.value))) ?? 0;
16966
- const yLine = scaleLinear()
16967
- .domain([0, maxLine || 1])
16968
- .nice()
16969
- .range([height, 0]);
16970
- const g = this.svg
16971
- .attr('width', width)
16972
- .attr('height', height)
16973
- .append('g')
16974
- .attr('transform', `translate(${effectiveLeft},${this.MARGIN.top})`);
16975
- drawXYAxes(g, x, yBar, {
16976
- xLabelMap,
16977
- width,
16978
- height,
16979
- xAxisLabel: this.xAxisLabel(),
16980
- yAxisLabel: this.yAxisLabel(),
16981
- effectiveLeft,
16982
- });
16983
- styleGridLines(g);
16984
- // ── Right Y axis for line series ─────────────────────────────────────────
16985
- if (hasLines) {
16986
- const lineTickValues = yLine.ticks(5).filter((t) => Number.isInteger(t));
16987
- const rightAxisG = g
16988
- .append('g')
16989
- .attr('transform', `translate(${width},0)`)
16990
- .call(axisRight(yLine).tickValues(lineTickValues).tickFormat(format('~s')).tickSize(-width) // draw grid lines across the chart
16991
- );
16992
- rightAxisG.selectAll('text').style('font-size', '11px').style('fill', 'var(--clr-color-neutral-600, #666)');
16993
- // Style the line-axis grid lines with a distinct dashed color
16994
- rightAxisG
16995
- .selectAll('.tick line')
16996
- .style('stroke', 'var(--clr-color-neutral-400, #b3b3b3)')
16997
- .style('stroke-dasharray', '4 3')
16998
- .style('stroke-opacity', '0.8');
16999
- // Remove the default domain line so it doesn't overlap the chart
17000
- rightAxisG.select('.domain').remove();
17001
- if (this.yLineAxisLabel()) {
17002
- g.append('text')
17003
- .attr('transform', 'rotate(-90)')
17004
- .attr('x', -height / 2)
17005
- .attr('y', width + extraRight - 4)
17006
- .attr('text-anchor', 'middle')
17007
- .style('font-size', '12px')
17008
- .style('fill', 'var(--clr-color-neutral-600, #666)')
17009
- .text(this.yLineAxisLabel());
17010
- }
17011
- }
17012
- this.drawBars(g, x, yBar);
17013
- this.drawLines(g, x, yLine);
17014
- }
17015
- drawBars(g, x, y) {
17016
- // Running stack base (cumulative sum) per X key across all bar series
17017
- const stackBase = new Map();
17018
- for (const series of this.barSeries()) {
17019
- if (!series.data.length) {
17020
- continue;
17021
- }
17022
- const stacked = series.data.map(d => ({
17023
- ...d,
17024
- base: stackBase.get(d.x) ?? 0,
17025
- }));
17026
- // Advance running totals for the next series
17027
- for (const d of series.data) {
17028
- stackBase.set(d.x, (stackBase.get(d.x) ?? 0) + d.value);
17029
- }
17030
- g.selectAll(`.bar-${series.key}`)
17031
- .data(stacked, (d) => d.x)
17032
- .join('rect')
17033
- .attr('class', `bar bar-${series.key}`)
17034
- .attr('x', (d) => x(d.x) ?? 0)
17035
- .attr('y', (d) => y(d.base + d.value))
17036
- .attr('width', x.bandwidth())
17037
- .attr('height', (d) => y(d.base) - y(d.base + d.value))
17038
- .attr('rx', 2)
17039
- .attr('ry', 2)
17040
- .style('fill', toChartColor(series.color))
17041
- .style('cursor', 'pointer')
17042
- .on('mouseover', (e) => {
17043
- select(e.currentTarget).attr('fill-opacity', 0.75);
17044
- })
17045
- .on('mouseout', (e) => {
17046
- select(e.currentTarget).attr('fill-opacity', 1);
17047
- })
17048
- .on('click', (event, d) => {
17049
- event.stopPropagation();
17050
- const rect = event.currentTarget.getBoundingClientRect();
17051
- const container = this.chartRef().nativeElement.getBoundingClientRect();
17052
- this.tooltipPosition.set({
17053
- x: rect.left - container.left + rect.width / 2,
17054
- y: (this.tooltipOrientation() === 'top' ? rect.top : rect.bottom) - container.top,
17055
- });
17056
- this.selectedItem.set({
17057
- seriesKey: series.key,
17058
- seriesLabel: series.label,
17059
- seriesType: 'bar',
17060
- color: series.color,
17061
- x: d.x,
17062
- xLabel: d.xLabel,
17063
- value: d.value,
17064
- total: this.total(),
17065
- });
17066
- });
17067
- }
17068
- }
17069
- drawLines(g, x, y) {
17070
- // Map X key → band center position for line/dot placement
17071
- const lineX = (key) => (x(key) ?? 0) + x.bandwidth() / 2;
17072
- const lineGenerator = line()
17073
- .x((d) => lineX(d.x))
17074
- .y((d) => y(d.value))
17075
- .curve(curveMonotoneX);
17076
- for (const series of this.lineSeries()) {
17077
- if (!series.data.length) {
17078
- continue;
17079
- }
17080
- // Line path
17081
- g.append('path')
17082
- .datum(series.data)
17083
- .attr('class', `combo-line combo-line-${series.key}`)
17084
- .attr('fill', 'none')
17085
- .style('stroke', toChartColor(series.color))
17086
- .attr('stroke-width', 2.5)
17087
- .attr('stroke-linejoin', 'round')
17088
- .attr('stroke-linecap', 'round')
17089
- .attr('d', lineGenerator);
17090
- // Dots
17091
- g.selectAll(`.combo-dot-${series.key}`)
17092
- .data(series.data, (d) => d.x)
17093
- .join('circle')
17094
- .attr('class', `combo-dot combo-dot-${series.key}`)
17095
- .attr('cx', (d) => lineX(d.x))
17096
- .attr('cy', (d) => y(d.value))
17097
- .attr('r', 4)
17098
- .style('fill', toChartColor(series.color))
17099
- .attr('stroke', '#fff')
17100
- .attr('stroke-width', 2)
17101
- .style('cursor', 'pointer')
17102
- .on('mouseover', (e) => {
17103
- select(e.currentTarget).attr('r', 6);
17104
- })
17105
- .on('mouseout', (e) => {
17106
- select(e.currentTarget).attr('r', 4);
17107
- })
17108
- .on('click', (event, d) => {
17109
- event.stopPropagation();
17110
- const el = event.currentTarget;
17111
- const rect = el.getBoundingClientRect();
17112
- const container = this.chartRef().nativeElement.getBoundingClientRect();
17113
- this.tooltipPosition.set({
17114
- x: rect.left - container.left + rect.width / 2,
17115
- y: (this.tooltipOrientation() === 'top' ? rect.top : rect.bottom) - container.top,
17116
- });
17117
- this.selectedItem.set({
17118
- seriesKey: series.key,
17119
- seriesLabel: series.label,
17120
- seriesType: 'line',
17121
- color: series.color,
17122
- x: d.x,
17123
- value: d.value,
17124
- total: this.total(),
17125
- });
17126
- });
17127
- }
17128
- }
17129
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ComboChartComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
17130
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: ComboChartComponent, isStandalone: false, selector: "clr-combo-chart", inputs: { barSeries: { classPropertyName: "barSeries", publicName: "barSeries", isSignal: true, isRequired: false, transformFunction: null }, lineSeries: { classPropertyName: "lineSeries", publicName: "lineSeries", isSignal: true, isRequired: false, transformFunction: null }, tooltipOrientation: { classPropertyName: "tooltipOrientation", publicName: "tooltipOrientation", isSignal: true, isRequired: false, transformFunction: null }, showLegend: { classPropertyName: "showLegend", publicName: "showLegend", isSignal: true, isRequired: false, transformFunction: null }, showExportButton: { classPropertyName: "showExportButton", publicName: "showExportButton", isSignal: true, isRequired: false, transformFunction: null }, exportFilename: { classPropertyName: "exportFilename", publicName: "exportFilename", isSignal: true, isRequired: false, transformFunction: null }, noItemsMessage: { classPropertyName: "noItemsMessage", publicName: "noItemsMessage", isSignal: true, isRequired: false, transformFunction: null }, tooltipPercentOfTotal: { classPropertyName: "tooltipPercentOfTotal", publicName: "tooltipPercentOfTotal", isSignal: true, isRequired: false, transformFunction: null }, xAxisLabel: { classPropertyName: "xAxisLabel", publicName: "xAxisLabel", isSignal: true, isRequired: false, transformFunction: null }, yAxisLabel: { classPropertyName: "yAxisLabel", publicName: "yAxisLabel", isSignal: true, isRequired: false, transformFunction: null }, yLineAxisLabel: { classPropertyName: "yLineAxisLabel", publicName: "yLineAxisLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueClicked: "valueClicked" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"chart-container\">\n <div class=\"chart-area\" #container (cngWindowResize)=\"updateChart()\" [class.pt-3]=\"alertMessageAndType()\">\n <svg [class.d-none]=\"loading() || !hasData()\" #chart></svg>\n\n @if (tooltipPosition()) {\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n [tooltipOrientation]=\"tooltipOrientation()\"\n [squareColor]=\"selectedItem()?.color\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"\n valueClicked.emit({\n seriesKey: selectedItem().seriesKey,\n seriesLabel: selectedItem().seriesLabel,\n seriesType: selectedItem().seriesType,\n x: selectedItem().x,\n xLabel: selectedItem().xLabel,\n value: selectedItem().value,\n })\n \"\n >\n <ng-container ngProjectAs=\"cng-title\">\n ({{ selectedItem().value }}) {{ selectedItem().xLabel || selectedItem().x }}\n </ng-container>\n\n <p class=\"mt-0\">\n <strong>{{ selectedItem().seriesLabel }}:</strong>\n {{ selectedItem().value }}\n </p>\n <p class=\"mt-0\">\n {{ (100 * selectedItem().value) / selectedItem().total | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n </p>\n </cng-chart-tooltip>\n } @if (loading() || !hasData()) {\n <cng-bar-chart-skeleton orientation=\"vertical\" [skeletonType]=\"loading() ? 'loading' : 'placeholder'\" />\n } @if (alertMessageAndType()) {\n <cng-chart-alert-overlay [alertType]=\"alertMessageAndType()[1]\" [alertMessage]=\"alertMessageAndType()[0]\" />\n }\n </div>\n\n @if (showLegend() && !loading() && legendItems().length) {\n <cng-chart-legend [items]=\"legendItems()\" />\n } @if (showExportButton() && !loading() && hasData()) {\n <cng-chart-export-button class=\"chart-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}:host ::ng-deep .domain{display:none}.chart-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative}.chart-area{flex:1;min-height:0;position:relative}svg{height:100%;width:100%}cng-bar-chart-skeleton{height:100%}.combo-line{pointer-events:none}.combo-dot{transition:r .1s ease}\n"], dependencies: [{ kind: "component", type: ChartAlertOverlayComponent, selector: "cng-chart-alert-overlay", inputs: ["alertMessage", "alertType"] }, { kind: "component", type: ChartExportButtonComponent, selector: "cng-chart-export-button", inputs: ["svgRef", "filename"] }, { kind: "component", type: ChartLegendComponent, selector: "cng-chart-legend", inputs: ["items"] }, { kind: "component", type: ChartSkeletonComponent, selector: "cng-bar-chart-skeleton", inputs: ["skeletonType", "orientation"] }, { kind: "component", type: ChartTooltipComponent, selector: "cng-chart-tooltip", inputs: ["tooltipPosition", "tooltipOrientation", "squareColor", "tooltipClickable"], outputs: ["tooltipClosed", "tooltipHeaderClicked"] }, { kind: "directive", type: OutsideClickDirective, selector: "[cngOutsideClick]", outputs: ["cngOutsideClick"] }, { kind: "directive", type: WindowResizeDirective, selector: "[cngWindowResize]", inputs: ["debounce", "includeFirst"], outputs: ["cngWindowResize"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
17131
- }
17132
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ComboChartComponent, decorators: [{
17133
- type: Component,
17134
- args: [{ selector: 'clr-combo-chart', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"chart-container\">\n <div class=\"chart-area\" #container (cngWindowResize)=\"updateChart()\" [class.pt-3]=\"alertMessageAndType()\">\n <svg [class.d-none]=\"loading() || !hasData()\" #chart></svg>\n\n @if (tooltipPosition()) {\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n [tooltipOrientation]=\"tooltipOrientation()\"\n [squareColor]=\"selectedItem()?.color\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"\n valueClicked.emit({\n seriesKey: selectedItem().seriesKey,\n seriesLabel: selectedItem().seriesLabel,\n seriesType: selectedItem().seriesType,\n x: selectedItem().x,\n xLabel: selectedItem().xLabel,\n value: selectedItem().value,\n })\n \"\n >\n <ng-container ngProjectAs=\"cng-title\">\n ({{ selectedItem().value }}) {{ selectedItem().xLabel || selectedItem().x }}\n </ng-container>\n\n <p class=\"mt-0\">\n <strong>{{ selectedItem().seriesLabel }}:</strong>\n {{ selectedItem().value }}\n </p>\n <p class=\"mt-0\">\n {{ (100 * selectedItem().value) / selectedItem().total | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n </p>\n </cng-chart-tooltip>\n } @if (loading() || !hasData()) {\n <cng-bar-chart-skeleton orientation=\"vertical\" [skeletonType]=\"loading() ? 'loading' : 'placeholder'\" />\n } @if (alertMessageAndType()) {\n <cng-chart-alert-overlay [alertType]=\"alertMessageAndType()[1]\" [alertMessage]=\"alertMessageAndType()[0]\" />\n }\n </div>\n\n @if (showLegend() && !loading() && legendItems().length) {\n <cng-chart-legend [items]=\"legendItems()\" />\n } @if (showExportButton() && !loading() && hasData()) {\n <cng-chart-export-button class=\"chart-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}:host ::ng-deep .domain{display:none}.chart-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative}.chart-area{flex:1;min-height:0;position:relative}svg{height:100%;width:100%}cng-bar-chart-skeleton{height:100%}.combo-line{pointer-events:none}.combo-dot{transition:r .1s ease}\n"] }]
17135
- }] });
17136
-
17137
- class PieChartComponent extends ChartBase {
17138
- constructor() {
17139
- super(...arguments);
17140
- this.data = input.required();
17141
- this.donut = input(true);
17142
- this.showLegend = input(true);
17143
- this.showExportButton = input(false);
17144
- this.exportFilename = input('pie-chart');
17145
- this.tooltipOrientation = input('top');
17146
- this.noItemsMessage = input(NO_ITEMS_MESSAGE);
17147
- this.tooltipPercentOfTotal = input('of total');
17148
- this.valueClicked = output();
17149
- this.hasData = computed(() => this.data()?.some(d => d.value > 0));
17150
- this.total = computed(() => this.data()?.reduce((acc, d) => acc + d.value, 0) ?? 0);
17151
- this.alertMessageAndType = computed(() => {
17152
- if (this.loading()) {
17153
- return undefined;
17154
- }
17155
- if (!this.hasData()) {
17156
- return [this.noItemsMessage(), NO_ITEMS_ALERT_TYPE];
17157
- }
17158
- return undefined;
17159
- });
17160
- this.legendItems = computed(() => {
17161
- if (!this.showLegend()) {
17162
- return [];
17163
- }
17164
- return (this.data() ?? []).filter(d => d.value > 0).map(d => ({ label: d.fullLabel ?? d.label, color: d.color }));
17165
- });
17166
- }
17167
- ngOnChanges(_changes) {
17168
- if (!this.svg) {
17169
- return;
17170
- }
17171
- requestAnimationFrame(() => this.updateChart());
17172
- }
17173
- ngAfterViewInit() {
17174
- this.svg = select(this.chartRef().nativeElement);
17175
- super.ngAfterViewInit();
17176
- }
17177
- updateChart() {
17178
- this.svg.selectAll('*').remove();
17179
- if (this.loading() || !this.hasData()) {
17180
- return;
17181
- }
17182
- const { width, height } = this.getContainerDimensions();
17183
- const radius = Math.min(width, height) / 2;
17184
- const innerRadius = this.donut() ? radius * 0.55 : 0;
17185
- const outerRadius = radius * 0.9;
17186
- this.arcGen = arc().innerRadius(innerRadius).outerRadius(outerRadius);
17187
- const hoverArc = arc()
17188
- .innerRadius(innerRadius)
17189
- .outerRadius(outerRadius * 1.06);
17190
- const pieGen = pie()
17191
- .value(d => d.value)
17192
- .sort(null);
17193
- const arcs = pieGen(this.data().filter(d => d.value > 0));
17194
- const g = this.svg
17195
- .attr('width', width)
17196
- .attr('height', height)
17197
- .append('g')
17198
- .attr('transform', `translate(${width / 2},${height / 2})`);
17199
- // Slices
17200
- g.selectAll('.slice')
17201
- .data(arcs, (d) => d.data.key)
17202
- .join('path')
17203
- .attr('class', 'slice')
17204
- .attr('d', this.arcGen)
17205
- .style('fill', (d) => toChartColor(d.data.color))
17206
- .attr('stroke', '#fff')
17207
- .attr('stroke-width', 2)
17208
- .style('cursor', 'pointer')
17209
- .on('mouseover', (e) => {
17210
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
17211
- select(e.currentTarget).attr('d', hoverArc);
17212
- })
17213
- .on('mouseout', (e) => {
17214
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
17215
- select(e.currentTarget).attr('d', this.arcGen);
17216
- })
17217
- .on('click', (event, d) => {
17218
- event.stopPropagation();
17219
- this.openTooltip(d, width, height);
17220
- });
17221
- // Slice labels – only show when the slice angle is large enough to fit text
17222
- const MIN_ANGLE = 0.4; // ~23°
17223
- const labelRadius = this.donut() ? (innerRadius + outerRadius) / 2 : outerRadius * 0.65;
17224
- const labelArc = arc().innerRadius(labelRadius).outerRadius(labelRadius);
17225
- const total = this.total();
17226
- const labelGroups = g
17227
- .selectAll('.slice-label')
17228
- .data(arcs.filter((d) => d.endAngle - d.startAngle >= MIN_ANGLE), (d) => d.data.key)
17229
- .join('g')
17230
- .attr('class', 'slice-label')
17231
- .attr('transform', (d) => {
17232
- const [x, y] = labelArc.centroid(d);
17233
- return `translate(${x},${y})`;
17234
- })
17235
- .style('pointer-events', 'none');
17236
- labelGroups
17237
- .append('text')
17238
- .attr('class', 'label-value')
17239
- .attr('text-anchor', 'middle')
17240
- .attr('dy', '-0.3em')
17241
- .style('font-size', '11px')
17242
- .style('font-weight', '600')
17243
- .style('fill', '#fff')
17244
- .style('pointer-events', 'none')
17245
- .text((d) => format('~s')(d.data.value));
17246
- labelGroups
17247
- .append('text')
17248
- .attr('class', 'label-percent')
17249
- .attr('text-anchor', 'middle')
17250
- .attr('dy', '0.9em')
17251
- .style('font-size', '10px')
17252
- .style('fill', 'rgba(255, 255, 255, 0.9)')
17253
- .style('pointer-events', 'none')
17254
- .text((d) => `${((100 * d.data.value) / total).toFixed(1)}%`);
17255
- // Center label (donut only)
17256
- if (this.donut()) {
17257
- g.append('text')
17258
- .attr('class', 'center-label')
17259
- .attr('text-anchor', 'middle')
17260
- .attr('dy', '-0.1em')
17261
- .style('font-size', '1.4rem')
17262
- .style('font-weight', '600')
17263
- .style('fill', 'var(--clr-color-neutral-900, #21333b)')
17264
- .text(format('~s')(this.total()));
17265
- g.append('text')
17266
- .attr('class', 'center-sublabel')
17267
- .attr('text-anchor', 'middle')
17268
- .attr('dy', '1.2em')
17269
- .style('font-size', '0.65rem')
17270
- .style('fill', 'var(--clr-color-neutral-600, #666)')
17271
- .style('text-transform', 'uppercase')
17272
- .style('letter-spacing', '0.05em')
17273
- .text('Total');
17274
- }
17275
- }
17276
- openTooltip(d, width, height) {
17277
- const [cx, cy] = this.arcGen.centroid(d);
17278
- this.tooltipPosition.set({
17279
- x: width / 2 + cx,
17280
- y: height / 2 + cy,
17281
- });
17282
- this.selectedItem.set(d.data);
17283
- }
17284
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: PieChartComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
17285
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: PieChartComponent, isStandalone: false, selector: "clr-pie-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null }, donut: { classPropertyName: "donut", publicName: "donut", isSignal: true, isRequired: false, transformFunction: null }, showLegend: { classPropertyName: "showLegend", publicName: "showLegend", isSignal: true, isRequired: false, transformFunction: null }, showExportButton: { classPropertyName: "showExportButton", publicName: "showExportButton", isSignal: true, isRequired: false, transformFunction: null }, exportFilename: { classPropertyName: "exportFilename", publicName: "exportFilename", isSignal: true, isRequired: false, transformFunction: null }, tooltipOrientation: { classPropertyName: "tooltipOrientation", publicName: "tooltipOrientation", isSignal: true, isRequired: false, transformFunction: null }, noItemsMessage: { classPropertyName: "noItemsMessage", publicName: "noItemsMessage", isSignal: true, isRequired: false, transformFunction: null }, tooltipPercentOfTotal: { classPropertyName: "tooltipPercentOfTotal", publicName: "tooltipPercentOfTotal", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueClicked: "valueClicked" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"chart-container\">\n <div class=\"chart-area\" #container (cngWindowResize)=\"updateChart()\" [class.pt-3]=\"alertMessageAndType()\">\n <svg [class.d-none]=\"loading() || !hasData()\" #chart></svg>\n\n @if (tooltipPosition()) {\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n [tooltipOrientation]=\"tooltipOrientation()\"\n [squareColor]=\"selectedItem()?.color\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"\n valueClicked.emit({\n key: selectedItem().key,\n label: selectedItem().fullLabel || selectedItem().label,\n value: selectedItem().value,\n })\n \"\n >\n <ng-container ngProjectAs=\"cng-title\">\n ({{ selectedItem().value }}) {{ selectedItem().fullLabel || selectedItem().label }}\n </ng-container>\n\n <p class=\"mt-0\">\n {{ (100 * selectedItem().value) / total() | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n </p>\n </cng-chart-tooltip>\n } @if (loading() || !hasData()) {\n <cng-bar-chart-skeleton orientation=\"vertical\" [skeletonType]=\"loading() ? 'loading' : 'placeholder'\" />\n } @if (alertMessageAndType()) {\n <cng-chart-alert-overlay [alertType]=\"alertMessageAndType()[1]\" [alertMessage]=\"alertMessageAndType()[0]\" />\n }\n </div>\n\n @if (showLegend() && !loading() && legendItems().length) {\n <cng-chart-legend [items]=\"legendItems()\" />\n } @if (showExportButton() && !loading() && hasData()) {\n <cng-chart-export-button class=\"chart-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}.chart-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative}.chart-area{flex:1;min-height:0;position:relative}svg{height:100%;width:100%}cng-bar-chart-skeleton{height:100%}.slice{transition:d .15s ease}.label-value{font-size:11px;font-weight:600;fill:#fff}.label-percent{font-size:10px;fill:#ffffffe6}.center-label{font-size:1.4rem;font-weight:600;fill:var(--clr-color-neutral-900, #21333b)}.center-sublabel{font-size:.65rem;fill:var(--clr-color-neutral-600, #666);text-transform:uppercase;letter-spacing:.05em}\n"], dependencies: [{ kind: "component", type: ChartAlertOverlayComponent, selector: "cng-chart-alert-overlay", inputs: ["alertMessage", "alertType"] }, { kind: "component", type: ChartExportButtonComponent, selector: "cng-chart-export-button", inputs: ["svgRef", "filename"] }, { kind: "component", type: ChartLegendComponent, selector: "cng-chart-legend", inputs: ["items"] }, { kind: "component", type: ChartSkeletonComponent, selector: "cng-bar-chart-skeleton", inputs: ["skeletonType", "orientation"] }, { kind: "component", type: ChartTooltipComponent, selector: "cng-chart-tooltip", inputs: ["tooltipPosition", "tooltipOrientation", "squareColor", "tooltipClickable"], outputs: ["tooltipClosed", "tooltipHeaderClicked"] }, { kind: "directive", type: OutsideClickDirective, selector: "[cngOutsideClick]", outputs: ["cngOutsideClick"] }, { kind: "directive", type: WindowResizeDirective, selector: "[cngWindowResize]", inputs: ["debounce", "includeFirst"], outputs: ["cngWindowResize"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
17286
- }
17287
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: PieChartComponent, decorators: [{
17288
- type: Component,
17289
- args: [{ selector: 'clr-pie-chart', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"chart-container\">\n <div class=\"chart-area\" #container (cngWindowResize)=\"updateChart()\" [class.pt-3]=\"alertMessageAndType()\">\n <svg [class.d-none]=\"loading() || !hasData()\" #chart></svg>\n\n @if (tooltipPosition()) {\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n [tooltipOrientation]=\"tooltipOrientation()\"\n [squareColor]=\"selectedItem()?.color\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"\n valueClicked.emit({\n key: selectedItem().key,\n label: selectedItem().fullLabel || selectedItem().label,\n value: selectedItem().value,\n })\n \"\n >\n <ng-container ngProjectAs=\"cng-title\">\n ({{ selectedItem().value }}) {{ selectedItem().fullLabel || selectedItem().label }}\n </ng-container>\n\n <p class=\"mt-0\">\n {{ (100 * selectedItem().value) / total() | number : '1.0-2' }}%\n {{ tooltipPercentOfTotal() }}\n </p>\n </cng-chart-tooltip>\n } @if (loading() || !hasData()) {\n <cng-bar-chart-skeleton orientation=\"vertical\" [skeletonType]=\"loading() ? 'loading' : 'placeholder'\" />\n } @if (alertMessageAndType()) {\n <cng-chart-alert-overlay [alertType]=\"alertMessageAndType()[1]\" [alertMessage]=\"alertMessageAndType()[0]\" />\n }\n </div>\n\n @if (showLegend() && !loading() && legendItems().length) {\n <cng-chart-legend [items]=\"legendItems()\" />\n } @if (showExportButton() && !loading() && hasData()) {\n <cng-chart-export-button class=\"chart-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}.chart-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative}.chart-area{flex:1;min-height:0;position:relative}svg{height:100%;width:100%}cng-bar-chart-skeleton{height:100%}.slice{transition:d .15s ease}.label-value{font-size:11px;font-weight:600;fill:#fff}.label-percent{font-size:10px;fill:#ffffffe6}.center-label{font-size:1.4rem;font-weight:600;fill:var(--clr-color-neutral-900, #21333b)}.center-sublabel{font-size:.65rem;fill:var(--clr-color-neutral-600, #666);text-transform:uppercase;letter-spacing:.05em}\n"] }]
17290
- }] });
17291
-
17292
- /*
17293
- * Copyright (c) 2018-2026 Porsche Informatik. All Rights Reserved.
17294
- * This software is released under MIT license.
17295
- * The full license information can be found in LICENSE in the root directory of this project.
17296
- */
17297
- // ─── Constants ────────────────────────────────────────────────────────────────
17298
- const DEFAULT_SECTION_COLORS = ['#009ADB', '#66D1FF', '#D9D9D9', '#A6A6A6'];
17299
- const DEFAULT_TEXT_COLORS = ['#eee', '#eee', '#555', '#eee'];
17300
- const DEFAULT_CHART_LABELS = {
17301
- total: 'Total',
17302
- all: 'All',
17303
- };
17304
- class FunnelChartComponent extends ChartBase {
17305
- constructor() {
17306
- super(...arguments);
17307
- // ── Inputs ──────────────────────────────────────────────────────────────────
17308
- this.data = input([]);
17309
- this.showExportButton = input(false);
17310
- this.exportFilename = input('funnel-chart');
17311
- /** Rendering mode. 'default' = horizontal bars with sections; 'centered' = centered trapezoid funnel. */
17312
- this.orientation = input('default');
17313
- /** Width reserved on each side for labels (px). */
17314
- this.textSize = input(200);
17315
- /** Gap between funnel bars and their side-line labels (px). */
17316
- this.lineTextPadding = input(8);
17317
- /** Vertical gap between funnel bars – default mode only (px). */
17318
- this.barGap = input(2);
17319
- /** Vertical padding inside the side measurement lines (px). */
17320
- this.sideLineVerticalPadding = input(4);
17321
- // ── Centered-mode specific inputs ────────────────────────────────────────────
17322
- /** Minimum length of the horizontal measurement lines on each side (px). Centered mode only. */
17323
- this.minLineSize = input(30);
17324
- /** Ratio of bar height to spacer height. Centered mode only. */
17325
- this.barToSpacerRatio = input(2);
17326
- /** Vertical padding inside the center divider lines (px). Centered mode only. */
17327
- this.middleLineVerticalPadding = input(4);
17328
- /** Fill color of the centered-mode bars. Accepts hex or CSS custom property. Centered mode only. */
17329
- this.barColor = input(DEFAULT_SECTION_COLORS[0]);
17330
- /** Fill color of the trapezoid spacers. Accepts hex or CSS custom property. Centered mode only. */
17331
- this.spacerColor = input('#D9D9D9');
17332
- /**
17333
- * Override the chart labels shown in the tooltip (total / all rows).
17334
- * Partial – any key not provided falls back to the English default.
17335
- */
17336
- this.chartLabels = input({});
17337
- /**
17338
- * Override section colors by section key.
17339
- * Accepts hex values ('#009ADB') or CSS custom properties ('--cds-global-color-blue-800').
17340
- * Per-section `color` in the data takes priority over this map; this map takes priority over
17341
- * the built-in default palette.
17342
- */
17343
- this.sectionColors = input({});
17344
- // ── Outputs ─────────────────────────────────────────────────────────────────
17345
- this.valueClicked = output();
17346
- // ── Computed ─────────────────────────────────────────────────────────────────
17347
- this.resolvedChartLabels = computed(() => ({
17348
- ...DEFAULT_CHART_LABELS,
17349
- ...this.chartLabels(),
17350
- }));
17351
- /** CSS-ready color for the centered-mode bar. */
17352
- this.centeredBarCssColor = computed(() => toChartColor(this.barColor()));
17353
- /** CSS-ready spacer color. */
17354
- this.centeredSpacerCssColor = computed(() => toChartColor(this.spacerColor()));
17355
- this.total = computed(() => this.data()[0]?.value ?? 0);
17356
- this.hasData = computed(() => this.data().length > 0);
17357
- // ── State ─────────────────────────────────────────────────────────────────────
17358
- this.selectedItemLabel = computed(() => this.selectedItem()?.fullLabel ?? this.selectedItem()?.label ?? '');
17359
- this.selectedSpacerItem = signal(undefined);
17360
- this.sectionTooltips = signal([]);
17361
- }
17362
- // ── Lifecycle ────────────────────────────────────────────────────────────────
17363
- ngOnChanges(_changes) {
17364
- if (!this.svg) {
17365
- return;
17366
- }
17367
- requestAnimationFrame(() => this.updateChart());
17368
- }
17369
- ngAfterViewInit() {
17370
- this.svg = select(this.chartRef().nativeElement);
17371
- super.ngAfterViewInit();
17372
- }
17373
- // ── Chart rendering ──────────────────────────────────────────────────────────
17374
- updateChart() {
17375
- if (!this.svg) {
17376
- return;
17377
- }
17378
- this.svg.selectAll('*').remove();
17379
- if (this.loading() || !this.hasData()) {
17380
- return;
17381
- }
17382
- const { width: containerWidth, height: containerHeight } = this.getContainerDimensions();
17383
- if (this.orientation() === 'centered') {
17384
- this.renderCenteredChart(containerWidth, containerHeight);
17385
- }
17386
- else {
17387
- const funnelWidth = this.getFunnelWidth(containerWidth);
17388
- const barHeight = this.getBarHeight(containerHeight);
17389
- const dataPoints = this.calculateDataPoints(funnelWidth, barHeight);
17390
- this.renderRightSideText(dataPoints, containerWidth, barHeight);
17391
- this.renderLeftSideText(dataPoints, barHeight);
17392
- this.renderSideLines(dataPoints, containerWidth, funnelWidth, barHeight);
17393
- this.renderFunnel(dataPoints, containerWidth, containerHeight, funnelWidth, barHeight);
17394
- }
17395
- }
17396
- // ── Data calculation ─────────────────────────────────────────────────────────
17397
- calculateDataPoints(funnelWidth, barHeight) {
17398
- const maxValue = max(this.data(), d => d.value) ?? 1;
17399
- const widthScale = scaleLinear().domain([0, maxValue]).range([0, funnelWidth]);
17400
- const sectionColorOverrides = this.sectionColors();
17401
- return this.data().map((d, i) => {
17402
- let xOffset = 0;
17403
- const sections = (d.sections ?? []).map((s, si) => {
17404
- const rawColor = s.color ?? sectionColorOverrides[s.key] ?? DEFAULT_SECTION_COLORS[si % DEFAULT_SECTION_COLORS.length];
17405
- const cssColor = toChartColor(rawColor);
17406
- const hoverColor = rawColor.startsWith('--')
17407
- ? cssColor
17408
- : color(rawColor)?.darker(0.5)?.formatHex() ?? cssColor;
17409
- const textColor = s.textColor ?? DEFAULT_TEXT_COLORS[si % DEFAULT_TEXT_COLORS.length];
17410
- const sWidth = widthScale(s.value);
17411
- const sec = {
17412
- key: s.key,
17413
- label: s.label,
17414
- x: xOffset,
17415
- width: sWidth,
17416
- value: s.value,
17417
- percentage: d.value ? this.round((s.value / d.value) * 100) : 0,
17418
- cssColor,
17419
- hoverColor,
17420
- textColor,
17421
- };
17422
- xOffset += sWidth;
17423
- return sec;
17424
- });
17425
- const point = {
17426
- ...d,
17427
- sections,
17428
- y: i * (barHeight + this.barGap()),
17429
- width: widthScale(d.value),
17430
- percentage: this.total() ? this.round((d.value / this.total()) * 100) : 0,
17431
- };
17432
- if (i > 0) {
17433
- const previousValue = this.data()[i - 1].value;
17434
- point.delta = previousValue - d.value;
17435
- point.deltaPercentage = previousValue === 0 ? 0 : this.round((point.delta / previousValue) * 100);
17436
- }
17437
- return point;
17438
- });
17439
- }
17440
- // ── Rendering helpers ────────────────────────────────────────────────────────
17441
- renderRightSideText(dataPoints, containerWidth, barHeight) {
17442
- const rightG = this.svg.append('g').attr('transform', `translate(${containerWidth - this.textSize()},0)`);
17443
- dataPoints.forEach((d, i) => {
17444
- if (i === 0) {
17445
- return;
17446
- }
17447
- const prev = dataPoints[i - 1];
17448
- const labelY = (prev.y + barHeight + d.y) / 2 + 4;
17449
- rightG
17450
- .append('text')
17451
- .attr('x', 0)
17452
- .attr('y', labelY)
17453
- .attr('text-anchor', 'start')
17454
- .style('font-size', '12px')
17455
- .style('fill', 'var(--clr-color-neutral-600, #666)')
17456
- .text(`-${this.round(d.deltaPercentage)}% (${d.delta})`);
17457
- });
17458
- }
17459
- renderLeftSideText(dataPoints, barHeight) {
17460
- const leftG = this.svg.append('g');
17461
- const ICON_SPACE = 20;
17462
- const positions = [];
17463
- dataPoints.forEach(d => {
17464
- const labelY = (d.y + barHeight + d.y) / 2 + 4;
17465
- const iconSpacing = d.description ? ICON_SPACE : 0;
17466
- const textLength = leftG
17467
- .append('text')
17468
- .attr('x', this.textSize() - iconSpacing)
17469
- .attr('y', labelY - 8)
17470
- .attr('text-anchor', 'end')
17471
- .style('font-size', '13px')
17472
- .style('font-weight', '600')
17473
- .style('fill', 'var(--clr-color-neutral-900, #21333b)')
17474
- .style('cursor', 'pointer')
17475
- .text(d.label)
17476
- .on('click', () => this.valueClicked.emit({ value: d.value, label: d.label, key: d.key }))
17477
- .node()
17478
- .getComputedTextLength();
17479
- if (d.description) {
17480
- positions.push({
17481
- x: this.textSize() - iconSpacing + 5,
17482
- y: labelY - 24,
17483
- key: d.key,
17484
- description: d.description,
17485
- });
17486
- }
17487
- leftG
17488
- .append('line')
17489
- .attr('x1', this.textSize() - textLength - iconSpacing)
17490
- .attr('y1', labelY - 5)
17491
- .attr('x2', this.textSize() - iconSpacing)
17492
- .attr('y2', labelY - 5)
17493
- .attr('stroke', 'var(--clr-color-neutral-600, #666)')
17494
- .attr('stroke-width', 2)
17495
- .attr('stroke-dasharray', '2,2');
17496
- leftG
17497
- .append('text')
17498
- .attr('x', this.textSize())
17499
- .attr('y', labelY + 10)
17500
- .attr('text-anchor', 'end')
17501
- .style('font-size', '12px')
17502
- .style('fill', 'var(--clr-color-neutral-600, #666)')
17503
- .text(`${this.round(d.percentage)}% (${d.value})`);
17504
- });
17505
- this.sectionTooltips.set(positions);
17506
- }
17507
- renderSideLines(dataPoints, containerWidth, funnelWidth, barHeight) {
17508
- const lineG = this.svg.append('g').attr('transform', `translate(${(containerWidth - funnelWidth) / 2},0)`);
17509
- dataPoints.forEach(d => {
17510
- const midY = d.y + barHeight / 2;
17511
- lineG
17512
- .append('line')
17513
- .attr('x1', 0)
17514
- .attr('y1', midY)
17515
- .attr('x2', funnelWidth)
17516
- .attr('y2', midY)
17517
- .attr('stroke', '#D9D9D9')
17518
- .attr('stroke-width', 1);
17519
- this.renderSideLine(lineG, funnelWidth, d.y, barHeight);
17520
- if (d.value === 0) {
17521
- this.renderSideLine(lineG, 0, d.y, barHeight);
17522
- }
17523
- });
17524
- }
17525
- renderSideLine(g, x, y, barHeight) {
17526
- g.append('line')
17527
- .attr('x1', x)
17528
- .attr('y1', y + this.sideLineVerticalPadding())
17529
- .attr('x2', x)
17530
- .attr('y2', y + barHeight - this.sideLineVerticalPadding())
17531
- .attr('stroke', '#D9D9D9')
17532
- .attr('stroke-width', 1);
17533
- }
17534
- renderFunnel(dataPoints, containerWidth, containerHeight, funnelWidth, barHeight) {
17535
- const funnelG = this.svg
17536
- .attr('width', containerWidth)
17537
- .attr('height', containerHeight)
17538
- .append('g')
17539
- .attr('transform', `translate(${(containerWidth - funnelWidth) / 2},0)`);
17540
- // First pass: all rects (z-order: rects below texts)
17541
- dataPoints.forEach(dp => {
17542
- dp.sections.forEach(section => {
17543
- funnelG
17544
- .append('rect')
17545
- .attr('x', section.x)
17546
- .attr('y', dp.y)
17547
- .attr('width', section.width)
17548
- .attr('height', barHeight)
17549
- .style('fill', section.cssColor)
17550
- .style('cursor', 'pointer')
17551
- .on('mouseover', e => {
17552
- select(e.currentTarget).style('fill', section.hoverColor);
17553
- })
17554
- .on('mouseout', e => {
17555
- select(e.currentTarget).style('fill', section.cssColor);
17556
- })
17557
- .on('click', (event) => {
17558
- this.handleSectionClick(event, dp, section.key);
17559
- });
17560
- });
17561
- });
17562
- // Second pass: texts on top
17563
- dataPoints.forEach(dp => {
17564
- dp.sections.forEach(section => {
17565
- if (section.width > 40) {
17566
- funnelG
17567
- .append('text')
17568
- .attr('x', section.x + section.width / 2)
17569
- .attr('y', dp.y + barHeight / 2)
17570
- .attr('text-anchor', 'middle')
17571
- .attr('dominant-baseline', 'middle')
17572
- .style('font-size', '13px')
17573
- .style('font-weight', '600')
17574
- .style('pointer-events', 'none')
17575
- .style('fill', section.textColor)
17576
- .text(`${section.percentage}%`);
17577
- }
17578
- });
17579
- });
17580
- }
17581
- // ── Event handlers ────────────────────────────────────────────────────────────
17582
- handleSectionClick(event, d, _sectionKey) {
17583
- event.stopPropagation();
17584
- const target = event.currentTarget;
17585
- const rect = target.getBoundingClientRect();
17586
- const container = this.chartRef().nativeElement.getBoundingClientRect();
17587
- this.tooltipPosition.set({
17588
- x: rect.left - container.left + rect.width / 2,
17589
- y: rect.bottom - container.top,
17590
- });
17591
- this.selectedItem.set(d);
17592
- }
17593
- resetTooltip() {
17594
- if (this.selectedItem() || this.selectedSpacerItem() || this.tooltipPosition()) {
17595
- this.selectedItem.set(undefined);
17596
- this.selectedSpacerItem.set(undefined);
17597
- this.tooltipPosition.set(undefined);
17598
- }
17599
- }
17600
- /** Formats a percentage to 2 decimal places for template use. */
17601
- pct(value, total) {
17602
- return this.round(total ? (value / total) * 100 : 0);
17603
- }
17604
- // ── Centered chart ────────────────────────────────────────────────────────────
17605
- renderCenteredChart(containerWidth, containerHeight) {
17606
- const maxFunnelWidth = this.getMaxFunnelWidth(containerWidth);
17607
- const barHeight = this.getCenteredBarHeight(containerHeight);
17608
- const spacerHeight = this.getCenteredSpacerHeight(containerHeight);
17609
- const dataPoints = this.calculateCenteredDataPoints(maxFunnelWidth, barHeight, spacerHeight);
17610
- const spacerPoints = this.calculateSpacerDataPoints(dataPoints, maxFunnelWidth, barHeight);
17611
- this.renderCenteredLeftSideText(dataPoints, barHeight);
17612
- this.renderCenteredLeftSideLines(dataPoints, maxFunnelWidth, barHeight);
17613
- this.renderCenteredRightSideText(dataPoints, containerWidth, barHeight);
17614
- this.renderCenteredRightSideLines(dataPoints, containerWidth, maxFunnelWidth, barHeight);
17615
- this.renderCenteredMiddleLines(dataPoints, spacerPoints, containerWidth, barHeight);
17616
- this.renderCenteredFunnel(dataPoints, spacerPoints, containerWidth, containerHeight, maxFunnelWidth, barHeight, spacerHeight);
17617
- }
17618
- calculateCenteredDataPoints(maxFunnelWidth, barHeight, spacerHeight) {
17619
- const total = this.data()[0]?.value ?? 1;
17620
- const maxValue = max(this.data(), d => d.value) ?? 1;
17621
- const scale = scaleLinear().domain([0, maxValue]).range([0, maxFunnelWidth]);
17622
- return this.data().map((d, i) => {
17623
- const point = {
17624
- ...d,
17625
- y: i * (barHeight + spacerHeight),
17626
- width: scale(d.value),
17627
- percentage: total ? this.round((d.value / total) * 100) : 0,
17628
- };
17629
- if (i > 0) {
17630
- const prev = this.data()[i - 1].value;
17631
- point.delta = prev - d.value;
17632
- point.deltaPercentage = prev === 0 ? 0 : this.round((point.delta / prev) * 100);
17633
- }
17634
- return point;
17635
- });
17636
- }
17637
- calculateSpacerDataPoints(dataPoints, maxFunnelWidth, barHeight) {
17638
- const spacers = [];
17639
- for (let i = 0; i < dataPoints.length - 1; i++) {
17640
- const cur = dataPoints[i];
17641
- const next = dataPoints[i + 1];
17642
- spacers.push({
17643
- index: i,
17644
- topWidth: cur.width,
17645
- bottomWidth: next.width,
17646
- topY: cur.y + barHeight,
17647
- bottomY: next.y,
17648
- topX: (maxFunnelWidth - cur.width) / 2,
17649
- bottomX: (maxFunnelWidth - next.width) / 2,
17650
- deltaPercentage: next.deltaPercentage ?? 0,
17651
- });
17652
- }
17653
- return spacers;
17654
- }
17655
- renderCenteredLeftSideText(dataPoints, barHeight) {
17656
- const g = this.svg.append('g').attr('transform', `translate(${this.textSize()},0)`);
17657
- const ICON_SPACE = 20;
17658
- const positions = [];
17659
- dataPoints.forEach(d => {
17660
- const cy = d.y + barHeight / 2;
17661
- const iconSpacing = d.description ? ICON_SPACE : 0;
17662
- const textLen = g
17663
- .append('text')
17664
- .attr('x', -iconSpacing)
17665
- .attr('y', cy - 8)
17666
- .attr('text-anchor', 'end')
17667
- .style('font-size', '13px')
17668
- .style('font-weight', '600')
17669
- .style('fill', 'var(--clr-color-neutral-900, #21333b)')
17670
- .style('cursor', 'pointer')
17671
- .text(d.label)
17672
- .on('click', () => this.valueClicked.emit({ value: d.value, label: d.label, key: d.key }))
17673
- .node()
17674
- .getComputedTextLength();
17675
- if (d.description) {
17676
- positions.push({ key: d.key, x: this.textSize() - iconSpacing + 5, y: cy - 24, description: d.description });
17677
- }
17678
- g.append('line')
17679
- .attr('x1', -textLen - iconSpacing)
17680
- .attr('y1', cy - 5)
17681
- .attr('x2', -iconSpacing)
17682
- .attr('y2', cy - 5)
17683
- .attr('stroke', 'var(--clr-color-neutral-600, #666)')
17684
- .attr('stroke-width', 2)
17685
- .attr('stroke-dasharray', '2,2');
17686
- g.append('text')
17687
- .attr('x', 0)
17688
- .attr('y', cy + 10)
17689
- .attr('text-anchor', 'end')
17690
- .style('font-size', '12px')
17691
- .style('fill', 'var(--clr-color-neutral-600, #666)')
17692
- .text(`${this.round(d.percentage)}% (${d.value})`);
17693
- });
17694
- this.sectionTooltips.set(positions);
17695
- }
17696
- renderCenteredLeftSideLines(dataPoints, maxFunnelWidth, barHeight) {
17697
- const g = this.svg.append('g').attr('transform', `translate(${this.textSize() + this.lineTextPadding()},0)`);
17698
- dataPoints.forEach(d => {
17699
- g.append('line')
17700
- .attr('x1', 0)
17701
- .attr('y1', d.y + barHeight / 2)
17702
- .attr('x2', maxFunnelWidth / 2 + this.minLineSize())
17703
- .attr('y2', d.y + barHeight / 2)
17704
- .attr('stroke', '#D9D9D9')
17705
- .attr('stroke-width', 1);
17706
- });
17707
- }
17708
- renderCenteredRightSideText(dataPoints, containerWidth, barHeight) {
17709
- const g = this.svg.append('g').attr('transform', `translate(${containerWidth - this.textSize()},0)`);
17710
- dataPoints.forEach((d, i) => {
17711
- if (i === 0) {
17712
- return;
17713
- }
17714
- const prev = dataPoints[i - 1];
17715
- const cy = (prev.y + barHeight + d.y) / 2 + 4;
17716
- g.append('text')
17717
- .attr('x', 0)
17718
- .attr('y', cy)
17719
- .attr('text-anchor', 'start')
17720
- .style('font-size', '12px')
17721
- .style('fill', 'var(--clr-color-neutral-600, #666)')
17722
- .text(`-${this.round(d.deltaPercentage)}% (${d.delta})`);
17723
- });
17724
- }
17725
- renderCenteredRightSideLines(dataPoints, containerWidth, maxFunnelWidth, barHeight) {
17726
- const g = this.svg.append('g').attr('transform', `translate(${containerWidth / 2},0)`);
17727
- dataPoints.forEach((d, i) => {
17728
- if (i === 0) {
17729
- return;
17730
- }
17731
- const prev = dataPoints[i - 1];
17732
- const cy = (prev.y + barHeight + d.y) / 2;
17733
- g.append('line')
17734
- .attr('x1', 0)
17735
- .attr('y1', cy)
17736
- .attr('x2', maxFunnelWidth / 2 + this.minLineSize())
17737
- .attr('y2', cy)
17738
- .attr('stroke', '#D9D9D9')
17739
- .attr('stroke-width', 1);
17740
- });
17741
- }
17742
- renderCenteredMiddleLines(dataPoints, spacerPoints, containerWidth, barHeight) {
17743
- const vPad = this.middleLineVerticalPadding();
17744
- const g = this.svg.append('g').attr('transform', `translate(${containerWidth / 2},0)`);
17745
- g.selectAll('.bar-mid')
17746
- .data(dataPoints)
17747
- .enter()
17748
- .append('line')
17749
- .attr('x1', 0)
17750
- .attr('y1', d => d.y + vPad)
17751
- .attr('x2', 0)
17752
- .attr('y2', d => d.y + barHeight - vPad)
17753
- .attr('stroke', '#D9D9D9')
17754
- .attr('stroke-width', 1);
17755
- g.selectAll('.spacer-mid')
17756
- .data(spacerPoints)
17757
- .enter()
17758
- .append('line')
17759
- .attr('x1', 0)
17760
- .attr('y1', d => d.topY + vPad)
17761
- .attr('x2', 0)
17762
- .attr('y2', d => d.bottomY - vPad)
17763
- .attr('stroke', '#D9D9D9')
17764
- .attr('stroke-width', 1);
17765
- }
17766
- renderCenteredFunnel(dataPoints, spacerPoints, containerWidth, containerHeight, maxFunnelWidth, barHeight, spacerHeight) {
17767
- const g = this.svg
17768
- .attr('width', containerWidth)
17769
- .attr('height', containerHeight)
17770
- .append('g')
17771
- .attr('transform', `translate(${(containerWidth - maxFunnelWidth) / 2},0)`);
17772
- // ── Bars ──────────────────────────────────────────────────────────────────
17773
- const rawBarColor = this.barColor();
17774
- const cssBarColor = toChartColor(rawBarColor);
17775
- const hoverBarColor = rawBarColor.startsWith('--')
17776
- ? cssBarColor
17777
- : color(rawBarColor)?.darker(0.5)?.formatHex() ?? cssBarColor;
17778
- const bars = g
17779
- .selectAll('.c-bar')
17780
- .data(dataPoints)
17781
- .enter()
17782
- .append('g')
17783
- .attr('class', 'c-bar');
17784
- bars
17785
- .append('rect')
17786
- .attr('x', d => (maxFunnelWidth - d.width) / 2)
17787
- .attr('y', d => d.y)
17788
- .attr('width', d => d.width)
17789
- .attr('height', barHeight)
17790
- .style('fill', cssBarColor)
17791
- .style('cursor', 'pointer')
17792
- .on('mouseover', e => {
17793
- select(e.currentTarget).style('fill', hoverBarColor);
17794
- })
17795
- .on('mouseout', e => {
17796
- select(e.currentTarget).style('fill', cssBarColor);
17797
- })
17798
- .on('click', (event, d) => {
17799
- event.stopPropagation();
17800
- const rect = event.currentTarget.getBoundingClientRect();
17801
- const cont = this.chartRef().nativeElement.getBoundingClientRect();
17802
- this.tooltipPosition.set({ x: rect.left - cont.left + rect.width / 2, y: rect.top - cont.top });
17803
- this.selectedSpacerItem.set(undefined);
17804
- this.selectedItem.set(d);
17805
- });
17806
- bars
17807
- .selectAll('.c-bar-label')
17808
- .data(dataPoints.filter(d => d.width > 40))
17809
- .enter()
17810
- .append('text')
17811
- .attr('x', maxFunnelWidth / 2)
17812
- .attr('y', d => d.y + barHeight / 2)
17813
- .attr('text-anchor', 'middle')
17814
- .attr('dominant-baseline', 'middle')
17815
- .style('font-size', '13px')
17816
- .style('font-weight', '600')
17817
- .style('pointer-events', 'none')
17818
- .style('fill', '#eee')
17819
- .text(d => `${this.round(d.percentage)}%`);
17820
- // ── Spacers ───────────────────────────────────────────────────────────────
17821
- const rawSpacerColor = this.spacerColor();
17822
- const cssSpacerColor = toChartColor(rawSpacerColor);
17823
- const hoverSpacerColor = rawSpacerColor.startsWith('--')
17824
- ? cssSpacerColor
17825
- : color(rawSpacerColor)?.darker(0.2)?.formatHex() ?? cssSpacerColor;
17826
- g.selectAll('.c-spacer')
17827
- .data(spacerPoints)
17828
- .enter()
17829
- .append('polygon')
17830
- .attr('class', 'c-spacer')
17831
- .attr('points', d => `${d.topX},${d.topY} ${d.topX + d.topWidth},${d.topY} ${d.bottomX + d.bottomWidth},${d.bottomY} ${d.bottomX},${d.bottomY}`)
17832
- .style('fill', cssSpacerColor)
17833
- .style('cursor', 'pointer')
17834
- .on('mouseover', e => {
17835
- select(e.currentTarget).style('fill', hoverSpacerColor);
17836
- })
17837
- .on('mouseout', e => {
17838
- select(e.currentTarget).style('fill', cssSpacerColor);
17839
- })
17840
- .on('click', (event, d) => {
17841
- event.stopPropagation();
17842
- const rect = event.currentTarget.getBoundingClientRect();
17843
- const cont = this.chartRef().nativeElement.getBoundingClientRect();
17844
- this.tooltipPosition.set({ x: rect.left - cont.left + rect.width / 2, y: rect.top - cont.top });
17845
- this.selectedItem.set(undefined);
17846
- this.selectedSpacerItem.set(d);
17847
- });
17848
- g.selectAll('.c-spacer-label')
17849
- .data(spacerPoints.filter(d => d.bottomWidth > 40))
17850
- .enter()
17851
- .append('text')
17852
- .attr('x', maxFunnelWidth / 2)
17853
- .attr('y', d => d.topY + spacerHeight / 2)
17854
- .attr('text-anchor', 'middle')
17855
- .attr('dominant-baseline', 'middle')
17856
- .style('font-size', '13px')
17857
- .style('font-weight', '600')
17858
- .style('pointer-events', 'none')
17859
- .style('fill', '#333')
17860
- .text(d => `-${this.round(d.deltaPercentage)}%`);
17861
- }
17862
- // ── Helpers ───────────────────────────────────────────────────────────────────
17863
- getMaxFunnelWidth(containerWidth) {
17864
- return containerWidth - this.textSize() * 2 - this.minLineSize() * 2 - this.lineTextPadding() * 2;
17865
- }
17866
- getCenteredFractionHeight(containerHeight) {
17867
- return containerHeight / (this.data().length * (this.barToSpacerRatio() + 1) - 1);
17868
- }
17869
- getCenteredBarHeight(containerHeight) {
17870
- return this.getCenteredFractionHeight(containerHeight) * this.barToSpacerRatio();
17871
- }
17872
- getCenteredSpacerHeight(containerHeight) {
17873
- return this.getCenteredFractionHeight(containerHeight);
17874
- }
17875
- getFunnelWidth(containerWidth) {
17876
- return containerWidth - this.textSize() * 2 - this.lineTextPadding() * 2;
17877
- }
17878
- getBarHeight(containerHeight) {
17879
- const n = this.data().length;
17880
- if (n === 0) {
17881
- return 0;
17882
- }
17883
- return (containerHeight - (n - 1) * this.barGap()) / n;
17884
- }
17885
- round(value, decimals = 2) {
17886
- const factor = 10 ** decimals;
17887
- return Math.round(factor * value) / factor;
17888
- }
17889
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: FunnelChartComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
17890
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: FunnelChartComponent, isStandalone: false, selector: "clr-funnel-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, showExportButton: { classPropertyName: "showExportButton", publicName: "showExportButton", isSignal: true, isRequired: false, transformFunction: null }, exportFilename: { classPropertyName: "exportFilename", publicName: "exportFilename", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, textSize: { classPropertyName: "textSize", publicName: "textSize", isSignal: true, isRequired: false, transformFunction: null }, lineTextPadding: { classPropertyName: "lineTextPadding", publicName: "lineTextPadding", isSignal: true, isRequired: false, transformFunction: null }, barGap: { classPropertyName: "barGap", publicName: "barGap", isSignal: true, isRequired: false, transformFunction: null }, sideLineVerticalPadding: { classPropertyName: "sideLineVerticalPadding", publicName: "sideLineVerticalPadding", isSignal: true, isRequired: false, transformFunction: null }, minLineSize: { classPropertyName: "minLineSize", publicName: "minLineSize", isSignal: true, isRequired: false, transformFunction: null }, barToSpacerRatio: { classPropertyName: "barToSpacerRatio", publicName: "barToSpacerRatio", isSignal: true, isRequired: false, transformFunction: null }, middleLineVerticalPadding: { classPropertyName: "middleLineVerticalPadding", publicName: "middleLineVerticalPadding", isSignal: true, isRequired: false, transformFunction: null }, barColor: { classPropertyName: "barColor", publicName: "barColor", isSignal: true, isRequired: false, transformFunction: null }, spacerColor: { classPropertyName: "spacerColor", publicName: "spacerColor", isSignal: true, isRequired: false, transformFunction: null }, chartLabels: { classPropertyName: "chartLabels", publicName: "chartLabels", isSignal: true, isRequired: false, transformFunction: null }, sectionColors: { classPropertyName: "sectionColors", publicName: "sectionColors", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueClicked: "valueClicked" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"funnel-container\" #container (cngWindowResize)=\"updateChart()\">\n <svg [class.d-none]=\"loading() || !hasData()\" #chart></svg>\n\n @if (!loading()) { @for (pos of sectionTooltips(); track pos.key) {\n <clr-signpost [style.left.px]=\"pos.x\" [style.top.px]=\"pos.y\">\n <cds-icon clrSignpostTrigger shape=\"info-circle\" size=\"18\" />\n <clr-signpost-content clrPosition=\"bottom-left\">\n <p class=\"mt-0\">{{ pos.description }}</p>\n </clr-signpost-content>\n </clr-signpost>\n } } @if (loading()) {\n <cng-bar-chart-skeleton orientation=\"vertical\" skeletonType=\"loading\" />\n } @if (tooltipPosition()) { @if (orientation() === 'centered') {\n\n <!-- \u2500\u2500 Centered mode tooltip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n tooltipOrientation=\"bottom\"\n [squareColor]=\"selectedSpacerItem() ? centeredSpacerCssColor() : centeredBarCssColor()\"\n [tooltipClickable]=\"!selectedSpacerItem()\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"\n selectedItem() && valueClicked.emit({\n value: selectedItem().value,\n label: selectedItem().label,\n key: selectedItem().key,\n section: undefined,\n })\n \"\n >\n <ng-container ngProjectAs=\"cng-title\">\n @if (selectedSpacerItem()) {\n {{ data()[selectedSpacerItem().index].label }} \u2192 {{ data()[selectedSpacerItem().index + 1].label }} } @else { ({{\n selectedItem().value\n }}) {{ selectedItem().label }}\n }\n </ng-container>\n\n @if (selectedSpacerItem()) {\n <p class=\"mt-0\">\n <strong\n >{{ data()[selectedSpacerItem().index].value }} \u2192 {{ data()[selectedSpacerItem().index + 1].value }}</strong\n >\n </p>\n @for (section of (data()[selectedSpacerItem().index].sections ?? []); track section.key) { @let drillable =\n section.value > 0;\n <p class=\"tooltip-row mt-0 mt-0-5 d-flex\">\n <strong>{{ section.label }}:&nbsp;</strong>\n <span\n class=\"row-right\"\n [class.has-more-info]=\"drillable\"\n (click)=\"\n drillable && valueClicked.emit({\n value: section.value,\n label: data()[selectedSpacerItem().index].label + ': ' + section.label,\n key: data()[selectedSpacerItem().index].key,\n section: section.key,\n })\n \"\n >{{ section.value }}</span\n >\n &nbsp;({{ pct(section.value, data()[selectedSpacerItem().index].value) }}%)\n </p>\n } } @else {\n <p class=\"mt-0\">{{ pct(selectedItem().value, total()) }}% {{ resolvedChartLabels().all }}</p>\n }\n </cng-chart-tooltip>\n\n } @else {\n\n <!-- \u2500\u2500 Default mode tooltip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n tooltipOrientation=\"bottom\"\n [squareColor]=\"undefined\"\n [tooltipClickable]=\"false\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n >\n <ng-container ngProjectAs=\"cng-title\">{{ selectedItemLabel() }}</ng-container>\n @for (section of selectedItem().sections; track section.key) { @let drillable = section.value > 0;\n <p class=\"tooltip-row mt-0 mt-0-5 d-flex\">\n <span>\n <div class=\"color-square mr-0-5\" [style.background-color]=\"section.cssColor\"></div>\n </span>\n <strong>{{ section.label }}:&nbsp;</strong>\n <span\n class=\"row-right\"\n [class.has-more-info]=\"drillable\"\n (click)=\"\n drillable && valueClicked.emit({\n value: section.value,\n label: selectedItemLabel() + ': ' + section.label,\n key: selectedItem().key,\n section: section.key,\n })\n \"\n >{{ section.value }}</span\n >\n &nbsp;({{ section.percentage }}%)\n </p>\n }\n\n <p class=\"tooltip-row mt-0-5\">\n <strong>{{ resolvedChartLabels().total }}:&nbsp;</strong>\n <span\n class=\"has-more-info\"\n (click)=\"\n valueClicked.emit({\n value: selectedItem().value,\n label: selectedItemLabel(),\n key: selectedItem().key,\n section: undefined\n })\n \"\n >{{ selectedItem().value }}</span\n >\n </p>\n\n <ng-container ngProjectAs=\"cng-footer\">\n <p class=\"tooltip-row mt-0\">\n <strong>{{ resolvedChartLabels().all }}:&nbsp;</strong>\n <span class=\"row-right\">{{ total() }}</span>\n </p>\n </ng-container>\n </cng-chart-tooltip>\n\n } } @if (showExportButton() && !loading() && hasData()) {\n <cng-chart-export-button class=\"funnel-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}.funnel-container{height:100%;width:100%;max-width:1400px;margin:auto;position:relative}svg{width:100%;height:100%}clr-signpost{position:absolute!important}.tooltip-row{display:flex;justify-content:space-between;align-items:center}.row-right{margin-left:auto}.funnel-export-btn{position:absolute;bottom:0;right:0;opacity:0;pointer-events:none;transition:opacity .2s ease}.funnel-container:hover .funnel-export-btn{opacity:1;pointer-events:auto}\n"], dependencies: [{ kind: "directive", type: i1$1.CdsIconCustomTag, selector: "cds-icon" }, { kind: "component", type: i1$1.ClrSignpost, selector: "clr-signpost", inputs: ["clrSignpostTriggerAriaLabel"] }, { kind: "component", type: i1$1.ClrSignpostContent, selector: "clr-signpost-content", inputs: ["clrSignpostCloseAriaLabel", "clrPosition"] }, { kind: "directive", type: i1$1.ClrSignpostTrigger, selector: "[clrSignpostTrigger]" }, { kind: "component", type: ChartExportButtonComponent, selector: "cng-chart-export-button", inputs: ["svgRef", "filename"] }, { kind: "component", type: ChartSkeletonComponent, selector: "cng-bar-chart-skeleton", inputs: ["skeletonType", "orientation"] }, { kind: "component", type: ChartTooltipComponent, selector: "cng-chart-tooltip", inputs: ["tooltipPosition", "tooltipOrientation", "squareColor", "tooltipClickable"], outputs: ["tooltipClosed", "tooltipHeaderClicked"] }, { kind: "directive", type: OutsideClickDirective, selector: "[cngOutsideClick]", outputs: ["cngOutsideClick"] }, { kind: "directive", type: WindowResizeDirective, selector: "[cngWindowResize]", inputs: ["debounce", "includeFirst"], outputs: ["cngWindowResize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
17891
- }
17892
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: FunnelChartComponent, decorators: [{
17893
- type: Component,
17894
- args: [{ selector: 'clr-funnel-chart', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<div class=\"funnel-container\" #container (cngWindowResize)=\"updateChart()\">\n <svg [class.d-none]=\"loading() || !hasData()\" #chart></svg>\n\n @if (!loading()) { @for (pos of sectionTooltips(); track pos.key) {\n <clr-signpost [style.left.px]=\"pos.x\" [style.top.px]=\"pos.y\">\n <cds-icon clrSignpostTrigger shape=\"info-circle\" size=\"18\" />\n <clr-signpost-content clrPosition=\"bottom-left\">\n <p class=\"mt-0\">{{ pos.description }}</p>\n </clr-signpost-content>\n </clr-signpost>\n } } @if (loading()) {\n <cng-bar-chart-skeleton orientation=\"vertical\" skeletonType=\"loading\" />\n } @if (tooltipPosition()) { @if (orientation() === 'centered') {\n\n <!-- \u2500\u2500 Centered mode tooltip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n tooltipOrientation=\"bottom\"\n [squareColor]=\"selectedSpacerItem() ? centeredSpacerCssColor() : centeredBarCssColor()\"\n [tooltipClickable]=\"!selectedSpacerItem()\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n (tooltipHeaderClicked)=\"\n selectedItem() && valueClicked.emit({\n value: selectedItem().value,\n label: selectedItem().label,\n key: selectedItem().key,\n section: undefined,\n })\n \"\n >\n <ng-container ngProjectAs=\"cng-title\">\n @if (selectedSpacerItem()) {\n {{ data()[selectedSpacerItem().index].label }} \u2192 {{ data()[selectedSpacerItem().index + 1].label }} } @else { ({{\n selectedItem().value\n }}) {{ selectedItem().label }}\n }\n </ng-container>\n\n @if (selectedSpacerItem()) {\n <p class=\"mt-0\">\n <strong\n >{{ data()[selectedSpacerItem().index].value }} \u2192 {{ data()[selectedSpacerItem().index + 1].value }}</strong\n >\n </p>\n @for (section of (data()[selectedSpacerItem().index].sections ?? []); track section.key) { @let drillable =\n section.value > 0;\n <p class=\"tooltip-row mt-0 mt-0-5 d-flex\">\n <strong>{{ section.label }}:&nbsp;</strong>\n <span\n class=\"row-right\"\n [class.has-more-info]=\"drillable\"\n (click)=\"\n drillable && valueClicked.emit({\n value: section.value,\n label: data()[selectedSpacerItem().index].label + ': ' + section.label,\n key: data()[selectedSpacerItem().index].key,\n section: section.key,\n })\n \"\n >{{ section.value }}</span\n >\n &nbsp;({{ pct(section.value, data()[selectedSpacerItem().index].value) }}%)\n </p>\n } } @else {\n <p class=\"mt-0\">{{ pct(selectedItem().value, total()) }}% {{ resolvedChartLabels().all }}</p>\n }\n </cng-chart-tooltip>\n\n } @else {\n\n <!-- \u2500\u2500 Default mode tooltip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <cng-chart-tooltip\n [tooltipPosition]=\"tooltipPosition()\"\n tooltipOrientation=\"bottom\"\n [squareColor]=\"undefined\"\n [tooltipClickable]=\"false\"\n (tooltipClosed)=\"resetTooltip()\"\n (cngOutsideClick)=\"resetTooltip()\"\n >\n <ng-container ngProjectAs=\"cng-title\">{{ selectedItemLabel() }}</ng-container>\n @for (section of selectedItem().sections; track section.key) { @let drillable = section.value > 0;\n <p class=\"tooltip-row mt-0 mt-0-5 d-flex\">\n <span>\n <div class=\"color-square mr-0-5\" [style.background-color]=\"section.cssColor\"></div>\n </span>\n <strong>{{ section.label }}:&nbsp;</strong>\n <span\n class=\"row-right\"\n [class.has-more-info]=\"drillable\"\n (click)=\"\n drillable && valueClicked.emit({\n value: section.value,\n label: selectedItemLabel() + ': ' + section.label,\n key: selectedItem().key,\n section: section.key,\n })\n \"\n >{{ section.value }}</span\n >\n &nbsp;({{ section.percentage }}%)\n </p>\n }\n\n <p class=\"tooltip-row mt-0-5\">\n <strong>{{ resolvedChartLabels().total }}:&nbsp;</strong>\n <span\n class=\"has-more-info\"\n (click)=\"\n valueClicked.emit({\n value: selectedItem().value,\n label: selectedItemLabel(),\n key: selectedItem().key,\n section: undefined\n })\n \"\n >{{ selectedItem().value }}</span\n >\n </p>\n\n <ng-container ngProjectAs=\"cng-footer\">\n <p class=\"tooltip-row mt-0\">\n <strong>{{ resolvedChartLabels().all }}:&nbsp;</strong>\n <span class=\"row-right\">{{ total() }}</span>\n </p>\n </ng-container>\n </cng-chart-tooltip>\n\n } } @if (showExportButton() && !loading() && hasData()) {\n <cng-chart-export-button class=\"funnel-export-btn\" [svgRef]=\"svgElement()\" [filename]=\"exportFilename()\" />\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}.funnel-container{height:100%;width:100%;max-width:1400px;margin:auto;position:relative}svg{width:100%;height:100%}clr-signpost{position:absolute!important}.tooltip-row{display:flex;justify-content:space-between;align-items:center}.row-right{margin-left:auto}.funnel-export-btn{position:absolute;bottom:0;right:0;opacity:0;pointer-events:none;transition:opacity .2s ease}.funnel-container:hover .funnel-export-btn{opacity:1;pointer-events:auto}\n"] }]
17895
- }] });
17896
-
17897
- const PREFERRED_ORDER = ['top-right', 'top-left', 'bottom-right', 'bottom-left'];
17898
- class AutoPositionDirective {
17899
- constructor() {
17900
- this.signpostContent = inject(ClrSignpostContent, { optional: true });
17901
- this.elementRef = inject(ElementRef);
17902
- this.handleMouseDown = () => {
17903
- // Only calculate when signpost is closed (about to open)
17904
- if (this.elementRef.nativeElement.classList.contains('is-off-screen')) {
17905
- this.updatePosition();
17906
- }
17907
- };
17908
- }
17909
- ngOnInit() {
17910
- const signpost = this.elementRef.nativeElement.closest('clr-signpost');
17911
- this.trigger = signpost?.querySelector('[clrSignpostTrigger]');
17912
- if (this.trigger) {
17913
- this.trigger.addEventListener('mousedown', this.handleMouseDown);
17914
- }
17915
- this.resizeObserver = new ResizeObserver(() => {
17916
- if (!this.elementRef.nativeElement.classList.contains('is-off-screen')) {
17917
- this.updatePosition();
17918
- }
17919
- });
17920
- this.resizeObserver.observe(document.body);
17921
- }
17922
- updatePosition() {
17923
- if (!this.signpostContent || !this.trigger) {
17924
- return;
17925
- }
17926
- const triggerRect = this.trigger.getBoundingClientRect();
17927
- this.signpostContent.position = this.getBestPosition(triggerRect);
17928
- }
17929
- getBestPosition(triggerRect) {
17930
- const contentWidth = 300;
17931
- const contentHeight = 200;
17932
- const buffer = 50;
17933
- const contentHeaderHeight = document.querySelector('.content-header')?.getBoundingClientRect().height ?? 0;
17934
- const appHeaderHight = document.querySelector('.header')?.getBoundingClientRect().height ?? 0;
17935
- const headerHeight = contentHeaderHeight + appHeaderHight;
17936
- // Find first position with enough space
17937
- for (const pos of PREFERRED_ORDER) {
17938
- if (this.hasSpace(triggerRect, pos, contentWidth, contentHeight, headerHeight, buffer)) {
17939
- return pos;
17940
- }
17941
- }
17942
- return PREFERRED_ORDER[0]; // Fallback to first preference
17943
- }
17944
- hasSpace(rect, position, width, height, headerHeight, buffer) {
17945
- const [vertical, horizontal] = position.split('-');
17946
- const verticalSpace = vertical === 'top'
17947
- ? rect.top - headerHeight - height - buffer
17948
- : window.innerHeight - rect.bottom - height - buffer;
17949
- const horizontalSpace = horizontal === 'left' ? rect.left - width - buffer : window.innerWidth - rect.right - width - buffer;
17950
- return Math.min(verticalSpace, horizontalSpace) > 0;
17951
- }
17952
- ngOnDestroy() {
17953
- if (this.trigger) {
17954
- this.trigger.removeEventListener('mousedown', this.handleMouseDown);
17955
- }
17956
- this.resizeObserver?.disconnect();
17957
- }
17958
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: AutoPositionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
17959
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.18", type: AutoPositionDirective, isStandalone: true, selector: "clr-signpost-content[autoPosition]", ngImport: i0 }); }
17960
- }
17961
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: AutoPositionDirective, decorators: [{
17962
- type: Directive,
17963
- args: [{
17964
- selector: 'clr-signpost-content[autoPosition]',
17965
- standalone: true,
17966
- }]
17967
- }] });
17968
-
17969
- class TenantFullDateRangeDirective {
17970
- constructor() {
17971
- this.start = contentChild(ClrStartDateInput);
17972
- this.end = contentChild(ClrEndDateInput);
17973
- }
17974
- ngAfterContentInit() {
17975
- if (this.start()) {
17976
- const originalStartEmit = this.start().dateChange.emit.bind(this.start().dateChange);
17977
- this.start().dateChange.emit = date => {
17978
- originalStartEmit(this.toTenantDateTime(date, '00:00'));
17979
- };
17980
- }
17981
- if (this.end()) {
17982
- const originalEndEmit = this.end().dateChange.emit.bind(this.end().dateChange);
17983
- this.end().dateChange.emit = date => {
17984
- originalEndEmit(this.toTenantDateTime(date, '23:59'));
17985
- };
17986
- }
17987
- }
17988
- toTenantDateTime(date, time) {
17989
- if (!date) {
17990
- return date;
17991
- }
17992
- const [hours, minutes] = time.split(':').map(Number);
17993
- const result = new Date(date);
17994
- result.setHours(hours, minutes, 0, 0);
17995
- return result;
17996
- }
17997
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: TenantFullDateRangeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
17998
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "19.2.18", type: TenantFullDateRangeDirective, isStandalone: true, selector: "clr-date-range-container[cngTenantFullDateRange]", queries: [{ propertyName: "start", first: true, predicate: ClrStartDateInput, descendants: true, isSignal: true }, { propertyName: "end", first: true, predicate: ClrEndDateInput, descendants: true, isSignal: true }], ngImport: i0 }); }
17999
- }
18000
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: TenantFullDateRangeDirective, decorators: [{
18001
- type: Directive,
18002
- args: [{
18003
- selector: 'clr-date-range-container[cngTenantFullDateRange]',
18004
- standalone: true,
18005
- }]
18006
- }] });
18007
-
18008
- /*
18009
- * Copyright (c) 2026 Porsche Informatik. All Rights Reserved.
18010
- * This software is released under MIT license.
18011
- * The full license information can be found in LICENSE in the root directory of this project.
18012
- */
18013
- const CLR_CHARTS_DECLARATIONS = [
18014
- AreaChartComponent,
18015
- BarChartComponent,
18016
- ComboChartComponent,
18017
- FunnelChartComponent,
18018
- LineChartComponent,
18019
- PieChartComponent,
18020
- ];
18021
- class ClrChartsModule {
18022
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ClrChartsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
18023
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.18", ngImport: i0, type: ClrChartsModule, declarations: [AreaChartComponent,
18024
- BarChartComponent,
18025
- ComboChartComponent,
18026
- FunnelChartComponent,
18027
- LineChartComponent,
18028
- PieChartComponent], imports: [CommonModule,
18029
- DecimalPipe,
18030
- ClrAlertModule,
18031
- ClrIconModule,
18032
- ClrSignpostModule,
18033
- // standalone helpers consumed by the non-standalone chart components
18034
- ChartAlertOverlayComponent,
18035
- ChartExportButtonComponent,
18036
- ChartLegendComponent,
18037
- ChartSkeletonComponent,
18038
- ChartTooltipComponent,
18039
- OutsideClickDirective,
18040
- WindowResizeDirective], exports: [AreaChartComponent,
18041
- BarChartComponent,
18042
- ComboChartComponent,
18043
- FunnelChartComponent,
18044
- LineChartComponent,
18045
- PieChartComponent] }); }
18046
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ClrChartsModule, imports: [CommonModule,
18047
- ClrAlertModule,
18048
- ClrIconModule,
18049
- ClrSignpostModule,
18050
- // standalone helpers consumed by the non-standalone chart components
18051
- ChartAlertOverlayComponent,
18052
- ChartExportButtonComponent,
18053
- ChartSkeletonComponent,
18054
- ChartTooltipComponent] }); }
18055
- }
18056
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ClrChartsModule, decorators: [{
18057
- type: NgModule,
18058
- args: [{
18059
- imports: [
18060
- CommonModule,
18061
- DecimalPipe,
18062
- ClrAlertModule,
18063
- ClrIconModule,
18064
- ClrSignpostModule,
18065
- // standalone helpers consumed by the non-standalone chart components
18066
- ChartAlertOverlayComponent,
18067
- ChartExportButtonComponent,
18068
- ChartLegendComponent,
18069
- ChartSkeletonComponent,
18070
- ChartTooltipComponent,
18071
- OutsideClickDirective,
18072
- WindowResizeDirective,
18073
- ],
18074
- declarations: [...CLR_CHARTS_DECLARATIONS],
18075
- exports: [...CLR_CHARTS_DECLARATIONS],
18076
- }]
18077
- }] });
18078
-
18079
- /*
18080
- * Copyright (c) 2026 Porsche Informatik. All Rights Reserved.
18081
- * This software is released under MIT license.
18082
- * The full license information can be found in LICENSE in the root directory of this project.
18083
- */
18084
-
18085
15604
  /*
18086
15605
  * Copyright (c) 2018-2026 Porsche Informatik. All Rights Reserved.
18087
15606
  * This software is released under MIT license.
@@ -18127,8 +15646,7 @@ class ClrAddonsModule {
18127
15646
  ClrDatagridColumnReorderModule,
18128
15647
  ClrSignpostAddonModule,
18129
15648
  ClrFocusFirstInvalidFieldDirective,
18130
- ClrControlEnterSubmitDirective,
18131
- ClrChartsModule] }); }
15649
+ ClrControlEnterSubmitDirective] }); }
18132
15650
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ClrAddonsModule, imports: [ClrViewEditSectionModule,
18133
15651
  ClrPagerModule,
18134
15652
  ClrDotPagerModule,
@@ -18165,8 +15683,7 @@ class ClrAddonsModule {
18165
15683
  ClrActionPanelModule,
18166
15684
  ClrReadonlyDirectiveModule,
18167
15685
  ClrDatagridColumnReorderModule,
18168
- ClrSignpostAddonModule,
18169
- ClrChartsModule] }); }
15686
+ ClrSignpostAddonModule] }); }
18170
15687
  }
18171
15688
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ClrAddonsModule, decorators: [{
18172
15689
  type: NgModule,
@@ -18213,7 +15730,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImpo
18213
15730
  ClrSignpostAddonModule,
18214
15731
  ClrFocusFirstInvalidFieldDirective,
18215
15732
  ClrControlEnterSubmitDirective,
18216
- ClrChartsModule,
18217
15733
  ],
18218
15734
  }]
18219
15735
  }] });
@@ -19475,5 +16991,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImpo
19475
16991
  * Generated bundle index. Do not edit.
19476
16992
  */
19477
16993
 
19478
- export { ACShape, AcceptanceDateShape, AcceptedBrands, AccessoryPartsShape, AreaChartComponent, AudiBrandShape, AwardWinnerPremiumShape, BIG_ENDIAN, BarChartComponent, BlocksGroupForwardShape, BundleForwardShape, BusinessCustomersCommercialShape, BusinessCustomersPrivateShape, BusinessPartnerWithCar, CLR_BLANK_OPTION, CONTENT_PROVIDER, CalculatorForwardShape, CaliforniaServiceShape, CampaignOutdatedShape, CampaignShape, CarOffSite, CarOnSite, CircleFilled, CircleHalfFilled, CircleQuarterFilled, CircleThreeQuartersFilled, ClrActionPanel, ClrActionPanelContainer, ClrActionPanelContainerContent, ClrActionPanelContainerFooter, ClrActionPanelModule, ClrActiveNotification, ClrAddonsLabel, ClrAddonsModule, ClrAutocompleteOff, ClrAutocompleteOffModule, ClrBackButton, ClrBackButtonModule, ClrBrandAvatar, ClrBrandAvatarModule, ClrBreadcrumb, ClrBreadcrumbModule, ClrBreadcrumbService, ClrChartsModule, ClrCollapseExpandSection, ClrCollapseExpandSectionModule, ClrContentPanel, ClrContentPanelContainer, ClrContentPanelContainerContent, ClrContentPanelContainerFooter, ClrContentPanelModule, ClrContentRef, ClrControlEnterSubmitDirective, ClrCopyToClipboard, ClrDataListPredefinedValidatorDirective, ClrDataListValidatorModule, ClrDataListValidators, ClrDatagridColumnReorderModule, ClrDatagridStatePersistenceModule, ClrDateFilterComponent, ClrDateFilterModule, ClrDateTimeContainer, ClrDateTimeModule, ClrDaterangeMaxValidator, ClrDaterangeMinValidator, ClrDaterangeOrderValidator, ClrDaterangeRequiredValidator, ClrDaterangepickerContainerComponent, ClrDaterangepickerDirective, ClrDaterangepickerModule, ClrDotPager, ClrDotPagerModule, ClrDropdownOverflowDirective, ClrDropdownOverflowModule, ClrEnumFilterComponent, ClrEnumFilterModule, ClrExportDatagridButtonModule, ClrFlowBar, ClrFlowBarModule, ClrFocusFirstInvalidFieldDirective, ClrFormModule, ClrGenericQuickList, ClrGenericQuickListModule, ClrHistory, ClrHistoryModule, ClrHistoryPinned, ClrHistoryService, ClrIconAvatar, ClrIconAvatarModule, ClrIfDaterangeErrorDirective, ClrIfWarning, ClrIfWarningModule, ClrLetterAvatar, ClrLetterAvatarModule, ClrLocationBarModule, ClrMainNavGroup, ClrMainNavGroupItem, ClrMainNavGroupModule, ClrMaxNumeric, ClrMinNumeric, ClrMultilingualInput, ClrMultilingualInputValidators, ClrMultilingualModule, ClrMultilingualSelector, ClrMultilingualTextarea, ClrNotification, ClrNotificationModule, ClrNotificationRef, ClrNotificationService, ClrNumericField, ClrNumericFieldModule, ClrNumericFieldValidators, ClrPagedSearchResultList, ClrPagedSearchResultListModule, ClrPager, ClrPagerModule, ClrProgressSpinnerComponent, ClrProgressSpinnerModule, ClrQuickList, ClrQuickListModule, ClrReadonlyDirective, ClrReadonlyDirectiveModule, ClrRequiredAllMultilang, ClrRequiredOneMultilang, ClrSearchField, ClrSearchFieldModule, ClrSignpostAddonComponent, ClrSignpostAddonModule, ClrSummaryArea, ClrSummaryAreaStateService, ClrSummaryAreaToggle, ClrSummaryItem, ClrSummaryItemValue, ClrTimeInput, ClrTreetable, ClrTreetableActionOverflow, ClrTreetableCell, ClrTreetableColumn, ClrTreetableFilter, ClrTreetableModule, ClrTreetablePlaceholder, ClrTreetableRow, ClrTreetableSortOrder, ClrTreetableStringFilter, ClrTreetableTreeNode, ClrViewEditSection, ClrViewEditSectionModule, ColumnHiddenStatePersistenceDirective, ComboChartComponent, CompletedByDateShape, CupraBrandShape, Customer, CustomerVip, CustomerVipCollection, CustomerWaiting, CustomerWaitingCollection, DATE, DELIMITER_REGEX, DWABrandShape, DatagridColumnReorderDirective, DatagridFieldDirective, DayModel, DeliveryDate, DieselShape, DollarBillForwardShape, DollarBillPartialShape, DynamicCellContentComponent, EnergyShape, ExportDatagridButtonComponent, ExportDatagridService, ExportTypeEnum, ExternalPartForwardShape, FirstRegistrationDate, FunnelChartComponent, GasCarsServiceShape, GasShape, HISTORY_NOTIFICATION_URL_PROVIDER, HISTORY_PROVIDER, HISTORY_TOKEN, HistoryProvider, InternalPartForwardShape, InvoiceReadyShape, InvoiceRecipient, InvoiceShape, ItemsForwardShape, ItemsReceiveShape, LITTLE_ENDIAN, LITTLE_ENDIAN_REGEX, LineChartComponent, LocationBarComponent, LocationBarContentProvider, LocationBarNode, LogoCommissionModule, LogoCommissionModuleFavIcon, LogoCommissionModuleNegative, LogoCommissionModuleNegativeFavIcon, LogoCostApproval, LogoCostApprovalFavIcon, LogoCostApprovalNegative, LogoCostApprovalNegativeFavIcon, LogoCostControlling, LogoCostControllingFavIcon, LogoCostControllingNegative, LogoCostControllingNegativeFavIcon, LogoDigitalServiceReception, LogoDigitalServiceReceptionFavIcon, LogoDigitalServiceReceptionNegative, LogoDigitalServiceReceptionNegativeFavIcon, LogoDocFlow, LogoDocFlowFavIcon, LogoDocFlowNegative, LogoDocFlowNegativeFavIcon, LogoDocScan, LogoDocScanFavIcon, LogoDocScanNegative, LogoDocScanNegativeFavIcon, LogoDocStore, LogoDocStoreFavIcon, LogoDocStoreNegative, LogoDocStoreNegativeFavIcon, LogoEBilling, LogoEBillingFavIcon, LogoEBillingNegative, LogoEBillingNegativeFavIcon, LogoEPayment, LogoEPaymentFavIcon, LogoEPaymentNegative, LogoEPaymentNegativeFavIcon, LogoMobilityPlanner, LogoMobilityPlannerFavIcon, LogoMobilityPlannerNegative, LogoMobilityPlannerNegativeFavIcon, LogoPartsMobile, LogoPartsMobileFavIcon, LogoPartsMobileNegative, LogoPartsMobileNegativeFavIcon, LogoSBO, LogoSBOFavIcon, LogoSBONegative, LogoSBONegativeFavIcon, LogoServiceCube, LogoServiceCubeFavIcon, LogoServiceCubeNegative, LogoServiceCubeNegativeFavIcon, LogoWCP, LogoWCPFavIcon, LogoWCPNegative, LogoWCPNegativeFavIcon, LogoWorkshopOrderTracker, LogoWorkshopOrderTrackerFavIcon, LogoWorkshopOrderTrackerNegative, LogoWorkshopOrderTrackerNegativeFavIcon, MIDDLE_ENDIAN, MIDDLE_ENDIAN_REGEX, MONTH, Mechanic, NewCarUtilityVehicleShape, NodeId, Number0, Number1, Number10, Number11, Number12, Number13, Number14, Number15, Number16, Number17, Number18, Number19, Number2, Number20, Number3, Number4, Number5, Number6, Number7, Number8, Number9, OrderShape, OrderStatusShape, PaintMaterialForwardShape, PaintMaterialShape, ParkingLocation, PartAvailabilityInfoShape, PartAvailabilityNoShape, PartAvailabilityUnknownShape, PartAvailabilityWarningShape, PartAvailabilityYesShape, PartIdenticalPredecessorShape, PartIdenticalShape, PartIdenticalSuccessorShape, PartIdenticalSuccpredecessorShape, PartNonStockForwardShape, PartPredecessorShape, PartSuccessorPredecessorShape, PartSuccessorShape, PartsChangelocation, PartsForwardShape, PartsInventory, PartsNonStockShape, PartsPicking, PartsPickingPlus, PartsReceiving, PartsShape, PieChartComponent, PlusServiceShape, PopoverPositions, PorscheBrandShape, PriceTypeSwitchShape, RepeatRepairCollection, RepeatRepairShape, ReplacementVehicleCollection, ReplacementVehicleShape, ReturnDateShape, SEPARATOR_TEXT_DEFAULT, SeatBrandShape, ServiceAdvisor, SkodaBrandShape, Sort, StatePersistenceKeyDirective, TRANSLATIONS, TaskAndAppointment, TextForward, TimeModel, TopcardShape, TouaregServiceShape, TreetableCellRenderer, TreetableDataStateService, TreetableHeaderRenderer, TreetableItemsDirective, TreetableMainRenderer, TreetableRowRenderer, USER_INPUT_REGEX, VWBrandShape, VWNBrandShape, VWShape, VehicleConversionShape, VinShape, VsfSearchShape, VsfSearchShape48, WCPShape, WrenchForward, YEAR, acceleration, accelerationIcon, acceptanceDateIcon, accessories, accessoriesIcon, accessoryPartsIcon, adblueAppIcon, adblue_app, add, addIcon, airConditionerIcon, air_conditioning, alert, alertFilledAppIcon, alertIcon, alertNotificationFilledAppIcon, alert_filled_app, alert_notification_filled_app, allIcons, ambientLightAppIcon, ambient_light_app, amplifier, amplifierIcon, appConnectAppIcon, app_connect_app, archive, archiveIcon, arrowDownIcon, arrowLeftAlignedAppIcon, arrowLeftIcon, arrowRightIcon, arrowSliderAppIcon, arrowUpIcon, arrow_down, arrow_left, arrow_left_aligned_app, arrow_right, arrow_slider_app, arrow_up, attachment, attachmentIcon, audiBrandIcon, authentPlugChargeAppIcon, authentQrAppIcon, authentRfidAppIcon, authentTouchidAppIcon, authent_plug_charge_app, authent_qr_app, authent_rfid_app, authent_touch_id_app, automaticTempAppIcon, automatic_temp_app, awardWinnerPremiumIcon, award_winner_premium, back, backIcon, battery, batteryIcon, batterySocChargingAppIcon, batterySocDepartureAppIcon, batterySocDestinationAppIcon, battery_soc_charging_app, battery_soc_departure_app, battery_soc_destination_app, bin, binIcon, blocksGroupForwardIcon, bluetooth, bluetoothIcon, bookmark, bookmarkFilledIcon, bookmarkIcon, bookmark_filled, brakeAppIcon, brake_app, brochure, brochureIcon, bulletpointAppIcon, bulletpoint_app, bundleForwardIcon, businessCustomersCommercialIcon, businessCustomersPrivateIcon, businessPartnerWithCarIcon, business_customers_commercial, business_customers_private, calc, calcIcon, calculatorForwardIcon, calendar, calendarCustomIcon, californiaServiceIcon, californiaSpecialistIcon, california_specialist, cameraScanIcon, camera_scan, campaignIcon, campaignOutdatedIcon, carDocumentsIcon, carErrorAppIcon, carInsuranceIcon, carOffSite, carOnSite, carPickupServiceIcon, carPlusIcon, carSettingsIcon, carVerifiedAppIcon, carWashIcon, carWheelAppIcon, car_documents, car_error_app, car_insurance, car_pickup_service, car_plus, car_settings, car_verified_app, car_wheel_app, carwash, certifiedRepairIcon, certifiedRetailerIcon, certified_repair, certified_retailer, challengeAppIcon, challenge_app, charging, chargingIcon, chargingPduAppIcon, chargingStationIcon, chargingTarifOverviewAppIcon, charging_pdu_app, charging_station, charging_tarif_overview_app, chat, chatAppIcon, chatIcon, chat_app, checkboxCheckedAppIcon, checkboxCheckedIcon, checkboxUncheckedAppIcon, checkboxUncheckedIcon, checkbox_checked, checkbox_checked_app, checkbox_unchecked, checkbox_unchecked_app, checkmark, checkmarkAppIcon, checkmarkFilledAppIcon, checkmarkIcon, checkmark_app, checkmark_filled_app, chevronDownIcon, chevronLeftAlignedappIcon, chevronLeftIcon, chevronRightAlignedappIcon, chevronRightIcon, chevronSmallLeftAlignedappIcon, chevronSmallRightAlignedappIcon, chevronUpIcon, chevron_down, chevron_left, chevron_left_alignedapp, chevron_right, chevron_right_alignedapp, chevron_small_left_alignedapp, chevron_small_right_alignedapp, chevron_up, circleFilledIcon, circleHalfFilledIcon, circleQuarterFilledIcon, circleThreeQuartersFilledIcon, city, cityIcon, clearAppIcon, clearRightAlignedappIcon, clear_app, clear_right_alignedapp, clock, clockIcon, close, closeAppIcon, closeCircleIcon, closeIcon, closeLeftAlignedappIcon, closeRightAlignedappIcon, close_app, close_circle, close_left_alignedapp, close_right_alignedapp, clrIconSVG, coffeeFilledAppIcon, coffee_filled_app, compassAppIcon, compass_app, completedByDateIcon, configuratorCommercialIcon, configuratorPrivateIcon, configurator_commercial, configurator_private, construction, constructionIcon, consumptionFuelFilledAppIcon, consumptionIcon, consumption_fuel, consumption_fuel_filled_app, contact, contactDealerFilledAppIcon, contactDealerIcon, contactIcon, contact_dealer, contact_dealer_filled_app, countryRoadIcon, country_road, craft, craftIcon, cupraBrandIcon, customerIcon, customerVipIcon, customerWaitingIcon, customersCenterIcon, customers_center, dataCopyAppIcon, dataExpiredIcon, dataFilledIcon, dataInputIcon, dataPlugAppIcon, dataSearchIcon, dataTimeExtensionIcon, data_copy_app, data_expired, data_filled, data_input, data_plug_app, data_search, data_time_extension, defaultSummaryAreaCollapsedKey, defogDefrostAutoAppIcon, defogDefrostIcon, defog_defrost, defog_defrost_auto_app, deliveryDateIcon, destinationAppIcon, destination_app, dieselIcon, direction, directionIcon, dischargingAppIcon, discharging_app, discountAppIcon, discount_app, discoveryAppIcon, discovery_app, dollarBillForwardIcon, dollarBillPartialIcon, download, downloadCustomIcon, dragIndicatorIcon, drag_indicator, driversAssistanceIcon, drivers_assistance, dropFilledAppIcon, drop_filled_app, dwaBrandIcon, eco, ecoIcon, edit, editIcon, editSmallRightAlignedAppIcon, edit_small_right_aligned_app, efficiency, efficiencyIcon, electricCarsIcon, electricCarsServiceIcon, electric_cars, electric_cars_service, electricity, electricityFilledAppIcon, electricityIcon, electricity_filled_app, emergency, emergencyIcon, emission, emissionIcon, energyIcon, engine, engineIcon, entertainment, entertainmentIcon, escapeHtml, escapeRegex, exportAppIcon, export_app, expressServiceIcon, express_service, exterior, exterior360Icon, exteriorIcon, exterior_360, externalPartForwardIcon, faq, faqIcon, fastForwardIcon, fast_forward, fax, faxIcon, filter, filterIcon, findACarIcon, findADealerIcon, find_a_car, find_a_dealer, firstRegistrationDateIcon, fleetServiceCommercialIcon, fleetServicePrivateIcon, fleet_service_commercial, fleet_service_private, folderFilledAppIcon, folder_filled_app, foodFilledAppIcon, food_filled_app, formatNumber, fullscreenEnterIcon, fullscreenExitIcon, fullscreen_enter, fullscreen_exit, gallery, galleryIcon, garageAppIcon, garage_app, gasAppIcon, gasCarsServiceIcon, gasIcon, gas_app, glassDamageAppIcon, glass_damage_app, gte, gteIcon, heart, heartFilledAppIcon, heartIcon, heart_filled_app, heightAppIcon, height_app, highwayRoadIcon, highway_road, history, historyIcon, homeAppIcon, homeEnergyAppIcon, homeFilledAppIcon, home_app, home_energy_app, home_filled_app, hornAppIcon, hornFilledAppIcon, horn_app, horn_filled_app, hybrid, hybridIcon, immediateChargingAppIcon, immediate_charging_app, info, infoFilledIcon, infoIcon, info_filled, inputHideIcon, inputShowIcon, input_hide, input_show, interior, interior360Icon, interiorIcon, interior_360, internalPartForwardIcon, internet, internetIcon, invitationAppIcon, invitation_app, invoiceIcon, invoiceReadyIcon, invoiceRecipientIcon, itemsForwardIcon, itemsRecieveIcon, jobportal, jobportalIcon, keyAppIcon, keyCardAppIcon, keyDigitalAppIcon, key_app, key_card_app, key_digital_app, keyboardAppIcon, keyboard_app, layerCollapseAppIcon, layerExpandAppIcon, layer_collapse_app, layer_expand_app, layersAppIcon, layers_app, legalTermsAndConditionsAppIcon, legal_terms_and_conditions_app, licencePlateAppIcon, licence_plate_app, lightAssistappIcon, light_assistapp, lightingAppIcon, lighting_app, linkExternAppIcon, link_extern_app, list, listIcon, loadingVolumeIcon, loading_volume, localBusinessIcon, local_business, locate, locateIcon, lock, lockIcon, lockOpenIcon, lock_open, login, loginIcon, logistic, logisticIcon, logoCommissionModuleFavIcon, logoCommissionModuleIcon, logoCommissionModuleNegativeFavIcon, logoCommissionModuleNegativeIcon, logoCostApprovalFavIcon, logoCostApprovalIcon, logoCostApprovalNegativeFavIcon, logoCostApprovalNegativeIcon, logoCrossControllingFavIcon, logoCrossControllingIcon, logoCrossControllingNegativeFavIcon, logoCrossControllingNegativeIcon, logoDigitalServiceReceptionFavIcon, logoDigitalServiceReceptionIcon, logoDigitalServiceReceptionNegativeFavIcon, logoDigitalServiceReceptionNegativeIcon, logoDocFlowFavIcon, logoDocFlowIcon, logoDocFlowNegativeFavIcon, logoDocFlowNegativeIcon, logoDocScanFavIcon, logoDocScanIcon, logoDocScanNegativeFavIcon, logoDocScanNegativeIcon, logoDocStoreFavIcon, logoDocStoreIcon, logoDocStoreNegativeFavIcon, logoDocStoreNegativeIcon, logoEBillingFavIcon, logoEBillingIcon, logoEBillingNegativeFavIcon, logoEBillingNegativeIcon, logoEPaymentFavIcon, logoEPaymentIcon, logoEPaymentNegativeFavIcon, logoEPaymentNegativeIcon, logoMobilityPlannerFavIcon, logoMobilityPlannerIcon, logoMobilityPlannerNegativeFavIcon, logoMobilityPlannerNegativeIcon, logoPartsMobileFavIcon, logoPartsMobileIcon, logoPartsMobileNegativeFavIcon, logoPartsMobileNegativeIcon, logoSBOFavIcon, logoSBOIcon, logoSBONegativeFavIcon, logoSBONegativeIcon, logoServiceCubeFavIcon, logoServiceCubeIcon, logoServiceCubeNegativeFavIcon, logoServiceCubeNegativeIcon, logoWCPFavIcon, logoWCPIcon, logoWCPNegativeFavIcon, logoWCPNegativeIcon, logoWorkshopOrderTrackerFavIcon, logoWorkshopOrderTrackerIcon, logoWorkshopOrderTrackerNegativeFavIcon, logoWorkshopOrderTrackerNegativeIcon, logout, logoutIcon, magnifier, magnifierIcon, magnifierMinusIcon, magnifierPlusIcon, magnifier_minus, magnifier_plus, mail, mailIcon, mailResendAppIcon, mail_resend_app, manual, manualIcon, map, mapIcon, mapToInternalTree, mechanicIcon, media, mediaIcon, menu, menuAppAppIcon, menuIcon, menu_app_app, microphoneAppIcon, microphone_app, mobile, mobileIcon, moreAppIcon, moreAppbarAppIcon, more_app, more_appbar_app, mot, motIcon, motability, motabilityIcon, navigate, navigateFilledAppIcon, navigateIcon, navigate_filled_app, newCarCommercialIcon, newCarPrivateFilledAppIcon, newCarPrivateIcon, newCarUtilityVehicleIcon, new_car_commercial, new_car_private, new_car_private_filled_app, nightServiceIcon, night_service, notification, notificationFilledIcon, notificationIcon, notification_filled, number0Icon, number10Icon, number11Icon, number12Icon, number13Icon, number14Icon, number15Icon, number16Icon, number17Icon, number18Icon, number19Icon, number1Icon, number20Icon, number2Icon, number3Icon, number4Icon, number5Icon, number6Icon, number7Icon, number8Icon, number9Icon, offers, offersFilledAppIcon, offersIcon, offers_filled_app, officeAppIcon, officeFilledAppIcon, office_app, office_filled_app, oilLevelIcon, oilLevelWarningIcon, oilTemperatureAppIcon, oil_level, oil_level_warning, oil_temperature_app, onCallDutyIcon, on_call_duty, openSatIcon, open_sat, orderIcon, orderStatusIcon, paintMaterialForwardIcon, paintMaterialIcon, paintShopIcon, paint_shop, paragraphAppIcon, paragraph_app, parkHeaterAppIcon, park_heater_app, parking, parkingFilledAppIcon, parkingGarageAppIcon, parkingIcon, parkingLocationIcon, parkingRouteAppIcon, parkingValetAppIcon, parking_filled_app, parking_garage_app, parking_route_app, parking_valet_app, partAvailabilityInfoIcon, partAvailabilityNoIcon, partAvailabilityUnknownIcon, partAvailabilityWarningIcon, partAvailabilityYesIcon, partIdenticalIcon, partIdenticalPredecessorIcon, partIdenticalSuccessorIcon, partIdenticalSuccpredecessorIcon, partPredecessorIcon, partSuccessorIcon, partSuccessorPredecessorIcon, partsChangelocationIcon, partsForwardIcon, partsIcon, partsInventoryIcon, partsNonStockForwardIcon, partsNonStockIcon, partsPickingIcon, partsPickingPlusIcon, partsReceivingIcon, pause, pauseIcon, payload, payloadIcon, paymentAppIcon, paymentCashAppIcon, paymentChargingCardAppIcon, paymentCreditcardAppIcon, paymentMachineAppIcon, payment_app, payment_cash_app, payment_charging_card_app, payment_creditcard_app, payment_machine_app, performance, performanceIcon, petrol, petrolIcon, phone, phoneIcon, pin, pinFilledAppIcon, pinGenericFilledAppIcon, pinIcon, pin_filled_app, pin_generic_filled_app, play, playIcon, plugCcsAppIcon, plugChademoAppIcon, plugChargeAppIcon, plugGenericAppIcon, plugSchukoAppIcon, plugType1AppIcon, plugType2AppIcon, plug_ccs_app, plug_chademo_app, plug_charge_app, plug_generic_app, plug_schuko_app, plug_type1_app, plug_type2_app, plusServiceIcon, porscheBrandIcon, power, powerIcon, powerTrainIcon, powertrain, preHeaterAppIcon, pre_heater_app, preciseLaneNavigationAppIcon, precise_lane_navigation_app, presentAppIcon, present_app, priceTypeSwitchIcon, printer, printerIcon, privacyAppIcon, privacy_app, profile, profileIcon, profileRegisterAppIcon, profileVerifiedIcon, profile_register_app, profile_verified, publicServiceIcon, publicTransportAppIcon, public_service, public_transport_app, qualifiedWorkshopIcon, qualified_workshop, questionnaireAppIcon, questionnaire_app, radio, radioButtonInselectedIcon, radioButtonSelectedForDefIcon, radioButtonSelectedIcon, radioIcon, radio_button_inselected, radio_button_selected, radio_button_selected_for_development, range, rangeIcon, reload, reloadIcon, remove, removeIcon, repeat, repeatIcon, repeatRepairIcon, replacementVehicleIcon, returnDateIcon, rewind, rewindIcon, roadsideAssistanceIcon, roadside_assistance, route, routeArrowAppIcon, routeIcon, route_arrow_app, routesHistoryAppIcon, routes_history_app, rss, rssIcon, safety, safetyIcon, save, saveAppIcon, saveIcon, save_app, seat, seatAirIcon, seatBrandIcon, seatIcon, seat_air, secretTipAppIcon, secretTipFilledAppIcon, secret_tip_app, secret_tip_filled_app, selected, selectedIcon, selectedPartnerNetworkAppIcon, selected_partner_network_app, sendToCarAppIcon, send_to_car_app, service, serviceAdvisorIcon, serviceBellIcon, serviceFilledAppIcon, serviceIcon, service_bell, service_filled_app, settings, settingsIcon, shareAndroidIcon, shareIosIcon, share_android, share_ios, shoppingCartFilledAppIcon, shoppingCartIcon, shopping_cart, shopping_cart_filled_app, shuffle, shuffleIcon, size, sizeIcon, skillAppIcon, skill_app, skipBackwardIcon, skipForwardIcon, skip_backward, skip_forward, skodaBrandIcon, softwareDownloadAppIcon, software_download_app, sortingAppIcon, sorting_app, sound, soundIcon, standardEquipmentIcon, standard_equipment, starFilledIcon, starOutlineIcon, star_filled, star_outline, statisticAppIcon, statistic_app, stockLocatorCommercialIcon, stockLocatorPrivateIcon, stock_locator_commercial, stock_locator_private, stop, stopIcon, strip, switchPositionAppIcon, switch_position_app, syncAppIcon, sync_app, taskAndAppointmentIcon, taxiDealerIcon, taxi_dealer, technicalSpecificationIcon, technical_specification, temperatureAppIcon, temperature_app, testDriveIcon, test_drive, textForwardIcon, thumbsdownAppIcon, thumbsdownFilledAppIcon, thumbsdown_app, thumbsdown_filled_app, thumbsupAppIcon, thumbsupFilledAppIcon, thumbsup_app, thumbsup_filled_app, timeClimatisationAppIcon, timePreferredAppIcon, time_climatisation_app, time_preferred_app, timer, timerIcon, topcardIcon, touaregServiceIcon, transcriptDownloadIcon, transcript_download, transmissionAutomaticIcon, transmissionManualIcon, transmission_automatic, transmission_manual, tripAppIcon, tripLongAppIcon, tripPartedAppIcon, tripShortAppIcon, trip_app, trip_long_app, trip_parted_app, trip_short_app, turnSignalsIcon, turn_signals, unselected, unselectedIcon, updateRefreshAppIcon, update_refresh_app, upload, uploadAppIcon, uploadCustomIcon, upload_app, usedCarCommercialIcon, usedCarPrivateIcon, used_car_commercial, used_car_private, vehicleAmarokIcon, vehicleCaddyIcon, vehicleConversionIcon, vehicleCrafterIcon, vehicleHightIcon, vehicleIdBuzzIcon, vehicleMultivanIcon, vehicleTransporterIcon, vehicle_amarok, vehicle_caddy, vehicle_crafter, vehicle_hight, vehicle_idbuzz, vehicle_multivan, vehicle_transporter, videoChatIcon, video_chat, view360Icon, view_360, vinIcon, virtualRealityIcon, virtual_reality, voiceMessageAppIcon, voice_message_app, volkswagenAppIcon, volkswagenIcon, volkswagen_app, volumeMaximumIcon, volumeMediumIcon, volumeMuteIcon, volume_maximum, volume_medium, volume_mute, vsfSearch48Icon, vsfSearchIcon, vwBrandIcon, vwConnectLicenseAppIcon, vw_connect_license_app, vwnBrandIcon, walkingAppIcon, walkingFilledAppIcon, walking_app, walking_filled_app, wallbox, wallboxIcon, wcAppIcon, wc_app, wcpIcon, weAssistAppIcon, weChargeAppIcon, weDeliverAppIcon, weExperienceAppIcon, weParkAppIcon, weUpgradeAppIcon, we_assist_app, we_charge_app, we_deliver_app, we_experience_app, we_park_app, we_upgrade_app, weatherSunAppIcon, weather_sun_app, wheelToWheelIcon, wheel_to_wheel, windscreenWashIcon, windscreen_wash, wlanHotspotIcon, wlan_hotspot, wrenchForwardIcon };
16994
+ export { ACShape, AcceptanceDateShape, AcceptedBrands, AccessoryPartsShape, AudiBrandShape, AwardWinnerPremiumShape, BIG_ENDIAN, BlocksGroupForwardShape, BundleForwardShape, BusinessCustomersCommercialShape, BusinessCustomersPrivateShape, BusinessPartnerWithCar, CLR_BLANK_OPTION, CONTENT_PROVIDER, CalculatorForwardShape, CaliforniaServiceShape, CampaignOutdatedShape, CampaignShape, CarOffSite, CarOnSite, CircleFilled, CircleHalfFilled, CircleQuarterFilled, CircleThreeQuartersFilled, ClrActionPanel, ClrActionPanelContainer, ClrActionPanelContainerContent, ClrActionPanelContainerFooter, ClrActionPanelModule, ClrActiveNotification, ClrAddonsLabel, ClrAddonsModule, ClrAutocompleteOff, ClrAutocompleteOffModule, ClrBackButton, ClrBackButtonModule, ClrBrandAvatar, ClrBrandAvatarModule, ClrBreadcrumb, ClrBreadcrumbModule, ClrBreadcrumbService, ClrCollapseExpandSection, ClrCollapseExpandSectionModule, ClrContentPanel, ClrContentPanelContainer, ClrContentPanelContainerContent, ClrContentPanelContainerFooter, ClrContentPanelModule, ClrContentRef, ClrControlEnterSubmitDirective, ClrCopyToClipboard, ClrDataListPredefinedValidatorDirective, ClrDataListValidatorModule, ClrDataListValidators, ClrDatagridColumnReorderModule, ClrDatagridStatePersistenceModule, ClrDateFilterComponent, ClrDateFilterModule, ClrDateTimeContainer, ClrDateTimeModule, ClrDaterangeMaxValidator, ClrDaterangeMinValidator, ClrDaterangeOrderValidator, ClrDaterangeRequiredValidator, ClrDaterangepickerContainerComponent, ClrDaterangepickerDirective, ClrDaterangepickerModule, ClrDotPager, ClrDotPagerModule, ClrDropdownOverflowDirective, ClrDropdownOverflowModule, ClrEnumFilterComponent, ClrEnumFilterModule, ClrExportDatagridButtonModule, ClrFlowBar, ClrFlowBarModule, ClrFocusFirstInvalidFieldDirective, ClrFormModule, ClrGenericQuickList, ClrGenericQuickListModule, ClrHistory, ClrHistoryModule, ClrHistoryPinned, ClrHistoryService, ClrIconAvatar, ClrIconAvatarModule, ClrIfDaterangeErrorDirective, ClrIfWarning, ClrIfWarningModule, ClrLetterAvatar, ClrLetterAvatarModule, ClrLocationBarModule, ClrMainNavGroup, ClrMainNavGroupItem, ClrMainNavGroupModule, ClrMaxNumeric, ClrMinNumeric, ClrMultilingualInput, ClrMultilingualInputValidators, ClrMultilingualModule, ClrMultilingualSelector, ClrMultilingualTextarea, ClrNotification, ClrNotificationModule, ClrNotificationRef, ClrNotificationService, ClrNumericField, ClrNumericFieldModule, ClrNumericFieldValidators, ClrPagedSearchResultList, ClrPagedSearchResultListModule, ClrPager, ClrPagerModule, ClrProgressSpinnerComponent, ClrProgressSpinnerModule, ClrQuickList, ClrQuickListModule, ClrReadonlyDirective, ClrReadonlyDirectiveModule, ClrRequiredAllMultilang, ClrRequiredOneMultilang, ClrSearchField, ClrSearchFieldModule, ClrSignpostAddonComponent, ClrSignpostAddonModule, ClrSummaryArea, ClrSummaryAreaStateService, ClrSummaryAreaToggle, ClrSummaryItem, ClrSummaryItemValue, ClrTimeInput, ClrTreetable, ClrTreetableActionOverflow, ClrTreetableCell, ClrTreetableColumn, ClrTreetableFilter, ClrTreetableModule, ClrTreetablePlaceholder, ClrTreetableRow, ClrTreetableSortOrder, ClrTreetableStringFilter, ClrTreetableTreeNode, ClrViewEditSection, ClrViewEditSectionModule, ColumnHiddenStatePersistenceDirective, CompletedByDateShape, CupraBrandShape, Customer, CustomerVip, CustomerVipCollection, CustomerWaiting, CustomerWaitingCollection, DATE, DELIMITER_REGEX, DWABrandShape, DatagridColumnReorderDirective, DatagridFieldDirective, DayModel, DeliveryDate, DieselShape, DollarBillForwardShape, DollarBillPartialShape, DynamicCellContentComponent, EnergyShape, ExportDatagridButtonComponent, ExportDatagridService, ExportTypeEnum, ExternalPartForwardShape, FirstRegistrationDate, GasCarsServiceShape, GasShape, HISTORY_NOTIFICATION_URL_PROVIDER, HISTORY_PROVIDER, HISTORY_TOKEN, HistoryProvider, InternalPartForwardShape, InvoiceReadyShape, InvoiceRecipient, InvoiceShape, ItemsForwardShape, ItemsReceiveShape, LITTLE_ENDIAN, LITTLE_ENDIAN_REGEX, LocationBarComponent, LocationBarContentProvider, LocationBarNode, LogoCommissionModule, LogoCommissionModuleFavIcon, LogoCommissionModuleNegative, LogoCommissionModuleNegativeFavIcon, LogoCostApproval, LogoCostApprovalFavIcon, LogoCostApprovalNegative, LogoCostApprovalNegativeFavIcon, LogoCostControlling, LogoCostControllingFavIcon, LogoCostControllingNegative, LogoCostControllingNegativeFavIcon, LogoDigitalServiceReception, LogoDigitalServiceReceptionFavIcon, LogoDigitalServiceReceptionNegative, LogoDigitalServiceReceptionNegativeFavIcon, LogoDocFlow, LogoDocFlowFavIcon, LogoDocFlowNegative, LogoDocFlowNegativeFavIcon, LogoDocScan, LogoDocScanFavIcon, LogoDocScanNegative, LogoDocScanNegativeFavIcon, LogoDocStore, LogoDocStoreFavIcon, LogoDocStoreNegative, LogoDocStoreNegativeFavIcon, LogoEBilling, LogoEBillingFavIcon, LogoEBillingNegative, LogoEBillingNegativeFavIcon, LogoEPayment, LogoEPaymentFavIcon, LogoEPaymentNegative, LogoEPaymentNegativeFavIcon, LogoMobilityPlanner, LogoMobilityPlannerFavIcon, LogoMobilityPlannerNegative, LogoMobilityPlannerNegativeFavIcon, LogoPartsMobile, LogoPartsMobileFavIcon, LogoPartsMobileNegative, LogoPartsMobileNegativeFavIcon, LogoSBO, LogoSBOFavIcon, LogoSBONegative, LogoSBONegativeFavIcon, LogoServiceCube, LogoServiceCubeFavIcon, LogoServiceCubeNegative, LogoServiceCubeNegativeFavIcon, LogoWCP, LogoWCPFavIcon, LogoWCPNegative, LogoWCPNegativeFavIcon, LogoWorkshopOrderTracker, LogoWorkshopOrderTrackerFavIcon, LogoWorkshopOrderTrackerNegative, LogoWorkshopOrderTrackerNegativeFavIcon, MIDDLE_ENDIAN, MIDDLE_ENDIAN_REGEX, MONTH, Mechanic, NewCarUtilityVehicleShape, NodeId, Number0, Number1, Number10, Number11, Number12, Number13, Number14, Number15, Number16, Number17, Number18, Number19, Number2, Number20, Number3, Number4, Number5, Number6, Number7, Number8, Number9, OrderShape, OrderStatusShape, PaintMaterialForwardShape, PaintMaterialShape, ParkingLocation, PartAvailabilityInfoShape, PartAvailabilityNoShape, PartAvailabilityUnknownShape, PartAvailabilityWarningShape, PartAvailabilityYesShape, PartIdenticalPredecessorShape, PartIdenticalShape, PartIdenticalSuccessorShape, PartIdenticalSuccpredecessorShape, PartNonStockForwardShape, PartPredecessorShape, PartSuccessorPredecessorShape, PartSuccessorShape, PartsChangelocation, PartsForwardShape, PartsInventory, PartsNonStockShape, PartsPicking, PartsPickingPlus, PartsReceiving, PartsShape, PlusServiceShape, PopoverPositions, PorscheBrandShape, PriceTypeSwitchShape, RepeatRepairCollection, RepeatRepairShape, ReplacementVehicleCollection, ReplacementVehicleShape, ReturnDateShape, SEPARATOR_TEXT_DEFAULT, SeatBrandShape, ServiceAdvisor, SkodaBrandShape, Sort, StatePersistenceKeyDirective, TRANSLATIONS, TaskAndAppointment, TextForward, TimeModel, TopcardShape, TouaregServiceShape, TreetableCellRenderer, TreetableDataStateService, TreetableHeaderRenderer, TreetableItemsDirective, TreetableMainRenderer, TreetableRowRenderer, USER_INPUT_REGEX, VWBrandShape, VWNBrandShape, VWShape, VehicleConversionShape, VinShape, VsfSearchShape, VsfSearchShape48, WCPShape, WrenchForward, YEAR, acceleration, accelerationIcon, acceptanceDateIcon, accessories, accessoriesIcon, accessoryPartsIcon, adblueAppIcon, adblue_app, add, addIcon, airConditionerIcon, air_conditioning, alert, alertFilledAppIcon, alertIcon, alertNotificationFilledAppIcon, alert_filled_app, alert_notification_filled_app, allIcons, ambientLightAppIcon, ambient_light_app, amplifier, amplifierIcon, appConnectAppIcon, app_connect_app, archive, archiveIcon, arrowDownIcon, arrowLeftAlignedAppIcon, arrowLeftIcon, arrowRightIcon, arrowSliderAppIcon, arrowUpIcon, arrow_down, arrow_left, arrow_left_aligned_app, arrow_right, arrow_slider_app, arrow_up, attachment, attachmentIcon, audiBrandIcon, authentPlugChargeAppIcon, authentQrAppIcon, authentRfidAppIcon, authentTouchidAppIcon, authent_plug_charge_app, authent_qr_app, authent_rfid_app, authent_touch_id_app, automaticTempAppIcon, automatic_temp_app, awardWinnerPremiumIcon, award_winner_premium, back, backIcon, battery, batteryIcon, batterySocChargingAppIcon, batterySocDepartureAppIcon, batterySocDestinationAppIcon, battery_soc_charging_app, battery_soc_departure_app, battery_soc_destination_app, bin, binIcon, blocksGroupForwardIcon, bluetooth, bluetoothIcon, bookmark, bookmarkFilledIcon, bookmarkIcon, bookmark_filled, brakeAppIcon, brake_app, brochure, brochureIcon, bulletpointAppIcon, bulletpoint_app, bundleForwardIcon, businessCustomersCommercialIcon, businessCustomersPrivateIcon, businessPartnerWithCarIcon, business_customers_commercial, business_customers_private, calc, calcIcon, calculatorForwardIcon, calendar, calendarCustomIcon, californiaServiceIcon, californiaSpecialistIcon, california_specialist, cameraScanIcon, camera_scan, campaignIcon, campaignOutdatedIcon, carDocumentsIcon, carErrorAppIcon, carInsuranceIcon, carOffSite, carOnSite, carPickupServiceIcon, carPlusIcon, carSettingsIcon, carVerifiedAppIcon, carWashIcon, carWheelAppIcon, car_documents, car_error_app, car_insurance, car_pickup_service, car_plus, car_settings, car_verified_app, car_wheel_app, carwash, certifiedRepairIcon, certifiedRetailerIcon, certified_repair, certified_retailer, challengeAppIcon, challenge_app, charging, chargingIcon, chargingPduAppIcon, chargingStationIcon, chargingTarifOverviewAppIcon, charging_pdu_app, charging_station, charging_tarif_overview_app, chat, chatAppIcon, chatIcon, chat_app, checkboxCheckedAppIcon, checkboxCheckedIcon, checkboxUncheckedAppIcon, checkboxUncheckedIcon, checkbox_checked, checkbox_checked_app, checkbox_unchecked, checkbox_unchecked_app, checkmark, checkmarkAppIcon, checkmarkFilledAppIcon, checkmarkIcon, checkmark_app, checkmark_filled_app, chevronDownIcon, chevronLeftAlignedappIcon, chevronLeftIcon, chevronRightAlignedappIcon, chevronRightIcon, chevronSmallLeftAlignedappIcon, chevronSmallRightAlignedappIcon, chevronUpIcon, chevron_down, chevron_left, chevron_left_alignedapp, chevron_right, chevron_right_alignedapp, chevron_small_left_alignedapp, chevron_small_right_alignedapp, chevron_up, circleFilledIcon, circleHalfFilledIcon, circleQuarterFilledIcon, circleThreeQuartersFilledIcon, city, cityIcon, clearAppIcon, clearRightAlignedappIcon, clear_app, clear_right_alignedapp, clock, clockIcon, close, closeAppIcon, closeCircleIcon, closeIcon, closeLeftAlignedappIcon, closeRightAlignedappIcon, close_app, close_circle, close_left_alignedapp, close_right_alignedapp, clrIconSVG, coffeeFilledAppIcon, coffee_filled_app, compassAppIcon, compass_app, completedByDateIcon, configuratorCommercialIcon, configuratorPrivateIcon, configurator_commercial, configurator_private, construction, constructionIcon, consumptionFuelFilledAppIcon, consumptionIcon, consumption_fuel, consumption_fuel_filled_app, contact, contactDealerFilledAppIcon, contactDealerIcon, contactIcon, contact_dealer, contact_dealer_filled_app, countryRoadIcon, country_road, craft, craftIcon, cupraBrandIcon, customerIcon, customerVipIcon, customerWaitingIcon, customersCenterIcon, customers_center, dataCopyAppIcon, dataExpiredIcon, dataFilledIcon, dataInputIcon, dataPlugAppIcon, dataSearchIcon, dataTimeExtensionIcon, data_copy_app, data_expired, data_filled, data_input, data_plug_app, data_search, data_time_extension, defaultSummaryAreaCollapsedKey, defogDefrostAutoAppIcon, defogDefrostIcon, defog_defrost, defog_defrost_auto_app, deliveryDateIcon, destinationAppIcon, destination_app, dieselIcon, direction, directionIcon, dischargingAppIcon, discharging_app, discountAppIcon, discount_app, discoveryAppIcon, discovery_app, dollarBillForwardIcon, dollarBillPartialIcon, download, downloadCustomIcon, dragIndicatorIcon, drag_indicator, driversAssistanceIcon, drivers_assistance, dropFilledAppIcon, drop_filled_app, dwaBrandIcon, eco, ecoIcon, edit, editIcon, editSmallRightAlignedAppIcon, edit_small_right_aligned_app, efficiency, efficiencyIcon, electricCarsIcon, electricCarsServiceIcon, electric_cars, electric_cars_service, electricity, electricityFilledAppIcon, electricityIcon, electricity_filled_app, emergency, emergencyIcon, emission, emissionIcon, energyIcon, engine, engineIcon, entertainment, entertainmentIcon, escapeHtml, escapeRegex, exportAppIcon, export_app, expressServiceIcon, express_service, exterior, exterior360Icon, exteriorIcon, exterior_360, externalPartForwardIcon, faq, faqIcon, fastForwardIcon, fast_forward, fax, faxIcon, filter, filterIcon, findACarIcon, findADealerIcon, find_a_car, find_a_dealer, firstRegistrationDateIcon, fleetServiceCommercialIcon, fleetServicePrivateIcon, fleet_service_commercial, fleet_service_private, folderFilledAppIcon, folder_filled_app, foodFilledAppIcon, food_filled_app, formatNumber, fullscreenEnterIcon, fullscreenExitIcon, fullscreen_enter, fullscreen_exit, gallery, galleryIcon, garageAppIcon, garage_app, gasAppIcon, gasCarsServiceIcon, gasIcon, gas_app, glassDamageAppIcon, glass_damage_app, gte, gteIcon, heart, heartFilledAppIcon, heartIcon, heart_filled_app, heightAppIcon, height_app, highwayRoadIcon, highway_road, history, historyIcon, homeAppIcon, homeEnergyAppIcon, homeFilledAppIcon, home_app, home_energy_app, home_filled_app, hornAppIcon, hornFilledAppIcon, horn_app, horn_filled_app, hybrid, hybridIcon, immediateChargingAppIcon, immediate_charging_app, info, infoFilledIcon, infoIcon, info_filled, inputHideIcon, inputShowIcon, input_hide, input_show, interior, interior360Icon, interiorIcon, interior_360, internalPartForwardIcon, internet, internetIcon, invitationAppIcon, invitation_app, invoiceIcon, invoiceReadyIcon, invoiceRecipientIcon, itemsForwardIcon, itemsRecieveIcon, jobportal, jobportalIcon, keyAppIcon, keyCardAppIcon, keyDigitalAppIcon, key_app, key_card_app, key_digital_app, keyboardAppIcon, keyboard_app, layerCollapseAppIcon, layerExpandAppIcon, layer_collapse_app, layer_expand_app, layersAppIcon, layers_app, legalTermsAndConditionsAppIcon, legal_terms_and_conditions_app, licencePlateAppIcon, licence_plate_app, lightAssistappIcon, light_assistapp, lightingAppIcon, lighting_app, linkExternAppIcon, link_extern_app, list, listIcon, loadingVolumeIcon, loading_volume, localBusinessIcon, local_business, locate, locateIcon, lock, lockIcon, lockOpenIcon, lock_open, login, loginIcon, logistic, logisticIcon, logoCommissionModuleFavIcon, logoCommissionModuleIcon, logoCommissionModuleNegativeFavIcon, logoCommissionModuleNegativeIcon, logoCostApprovalFavIcon, logoCostApprovalIcon, logoCostApprovalNegativeFavIcon, logoCostApprovalNegativeIcon, logoCrossControllingFavIcon, logoCrossControllingIcon, logoCrossControllingNegativeFavIcon, logoCrossControllingNegativeIcon, logoDigitalServiceReceptionFavIcon, logoDigitalServiceReceptionIcon, logoDigitalServiceReceptionNegativeFavIcon, logoDigitalServiceReceptionNegativeIcon, logoDocFlowFavIcon, logoDocFlowIcon, logoDocFlowNegativeFavIcon, logoDocFlowNegativeIcon, logoDocScanFavIcon, logoDocScanIcon, logoDocScanNegativeFavIcon, logoDocScanNegativeIcon, logoDocStoreFavIcon, logoDocStoreIcon, logoDocStoreNegativeFavIcon, logoDocStoreNegativeIcon, logoEBillingFavIcon, logoEBillingIcon, logoEBillingNegativeFavIcon, logoEBillingNegativeIcon, logoEPaymentFavIcon, logoEPaymentIcon, logoEPaymentNegativeFavIcon, logoEPaymentNegativeIcon, logoMobilityPlannerFavIcon, logoMobilityPlannerIcon, logoMobilityPlannerNegativeFavIcon, logoMobilityPlannerNegativeIcon, logoPartsMobileFavIcon, logoPartsMobileIcon, logoPartsMobileNegativeFavIcon, logoPartsMobileNegativeIcon, logoSBOFavIcon, logoSBOIcon, logoSBONegativeFavIcon, logoSBONegativeIcon, logoServiceCubeFavIcon, logoServiceCubeIcon, logoServiceCubeNegativeFavIcon, logoServiceCubeNegativeIcon, logoWCPFavIcon, logoWCPIcon, logoWCPNegativeFavIcon, logoWCPNegativeIcon, logoWorkshopOrderTrackerFavIcon, logoWorkshopOrderTrackerIcon, logoWorkshopOrderTrackerNegativeFavIcon, logoWorkshopOrderTrackerNegativeIcon, logout, logoutIcon, magnifier, magnifierIcon, magnifierMinusIcon, magnifierPlusIcon, magnifier_minus, magnifier_plus, mail, mailIcon, mailResendAppIcon, mail_resend_app, manual, manualIcon, map, mapIcon, mapToInternalTree, mechanicIcon, media, mediaIcon, menu, menuAppAppIcon, menuIcon, menu_app_app, microphoneAppIcon, microphone_app, mobile, mobileIcon, moreAppIcon, moreAppbarAppIcon, more_app, more_appbar_app, mot, motIcon, motability, motabilityIcon, navigate, navigateFilledAppIcon, navigateIcon, navigate_filled_app, newCarCommercialIcon, newCarPrivateFilledAppIcon, newCarPrivateIcon, newCarUtilityVehicleIcon, new_car_commercial, new_car_private, new_car_private_filled_app, nightServiceIcon, night_service, notification, notificationFilledIcon, notificationIcon, notification_filled, number0Icon, number10Icon, number11Icon, number12Icon, number13Icon, number14Icon, number15Icon, number16Icon, number17Icon, number18Icon, number19Icon, number1Icon, number20Icon, number2Icon, number3Icon, number4Icon, number5Icon, number6Icon, number7Icon, number8Icon, number9Icon, offers, offersFilledAppIcon, offersIcon, offers_filled_app, officeAppIcon, officeFilledAppIcon, office_app, office_filled_app, oilLevelIcon, oilLevelWarningIcon, oilTemperatureAppIcon, oil_level, oil_level_warning, oil_temperature_app, onCallDutyIcon, on_call_duty, openSatIcon, open_sat, orderIcon, orderStatusIcon, paintMaterialForwardIcon, paintMaterialIcon, paintShopIcon, paint_shop, paragraphAppIcon, paragraph_app, parkHeaterAppIcon, park_heater_app, parking, parkingFilledAppIcon, parkingGarageAppIcon, parkingIcon, parkingLocationIcon, parkingRouteAppIcon, parkingValetAppIcon, parking_filled_app, parking_garage_app, parking_route_app, parking_valet_app, partAvailabilityInfoIcon, partAvailabilityNoIcon, partAvailabilityUnknownIcon, partAvailabilityWarningIcon, partAvailabilityYesIcon, partIdenticalIcon, partIdenticalPredecessorIcon, partIdenticalSuccessorIcon, partIdenticalSuccpredecessorIcon, partPredecessorIcon, partSuccessorIcon, partSuccessorPredecessorIcon, partsChangelocationIcon, partsForwardIcon, partsIcon, partsInventoryIcon, partsNonStockForwardIcon, partsNonStockIcon, partsPickingIcon, partsPickingPlusIcon, partsReceivingIcon, pause, pauseIcon, payload, payloadIcon, paymentAppIcon, paymentCashAppIcon, paymentChargingCardAppIcon, paymentCreditcardAppIcon, paymentMachineAppIcon, payment_app, payment_cash_app, payment_charging_card_app, payment_creditcard_app, payment_machine_app, performance, performanceIcon, petrol, petrolIcon, phone, phoneIcon, pin, pinFilledAppIcon, pinGenericFilledAppIcon, pinIcon, pin_filled_app, pin_generic_filled_app, play, playIcon, plugCcsAppIcon, plugChademoAppIcon, plugChargeAppIcon, plugGenericAppIcon, plugSchukoAppIcon, plugType1AppIcon, plugType2AppIcon, plug_ccs_app, plug_chademo_app, plug_charge_app, plug_generic_app, plug_schuko_app, plug_type1_app, plug_type2_app, plusServiceIcon, porscheBrandIcon, power, powerIcon, powerTrainIcon, powertrain, preHeaterAppIcon, pre_heater_app, preciseLaneNavigationAppIcon, precise_lane_navigation_app, presentAppIcon, present_app, priceTypeSwitchIcon, printer, printerIcon, privacyAppIcon, privacy_app, profile, profileIcon, profileRegisterAppIcon, profileVerifiedIcon, profile_register_app, profile_verified, publicServiceIcon, publicTransportAppIcon, public_service, public_transport_app, qualifiedWorkshopIcon, qualified_workshop, questionnaireAppIcon, questionnaire_app, radio, radioButtonInselectedIcon, radioButtonSelectedForDefIcon, radioButtonSelectedIcon, radioIcon, radio_button_inselected, radio_button_selected, radio_button_selected_for_development, range, rangeIcon, reload, reloadIcon, remove, removeIcon, repeat, repeatIcon, repeatRepairIcon, replacementVehicleIcon, returnDateIcon, rewind, rewindIcon, roadsideAssistanceIcon, roadside_assistance, route, routeArrowAppIcon, routeIcon, route_arrow_app, routesHistoryAppIcon, routes_history_app, rss, rssIcon, safety, safetyIcon, save, saveAppIcon, saveIcon, save_app, seat, seatAirIcon, seatBrandIcon, seatIcon, seat_air, secretTipAppIcon, secretTipFilledAppIcon, secret_tip_app, secret_tip_filled_app, selected, selectedIcon, selectedPartnerNetworkAppIcon, selected_partner_network_app, sendToCarAppIcon, send_to_car_app, service, serviceAdvisorIcon, serviceBellIcon, serviceFilledAppIcon, serviceIcon, service_bell, service_filled_app, settings, settingsIcon, shareAndroidIcon, shareIosIcon, share_android, share_ios, shoppingCartFilledAppIcon, shoppingCartIcon, shopping_cart, shopping_cart_filled_app, shuffle, shuffleIcon, size, sizeIcon, skillAppIcon, skill_app, skipBackwardIcon, skipForwardIcon, skip_backward, skip_forward, skodaBrandIcon, softwareDownloadAppIcon, software_download_app, sortingAppIcon, sorting_app, sound, soundIcon, standardEquipmentIcon, standard_equipment, starFilledIcon, starOutlineIcon, star_filled, star_outline, statisticAppIcon, statistic_app, stockLocatorCommercialIcon, stockLocatorPrivateIcon, stock_locator_commercial, stock_locator_private, stop, stopIcon, strip, switchPositionAppIcon, switch_position_app, syncAppIcon, sync_app, taskAndAppointmentIcon, taxiDealerIcon, taxi_dealer, technicalSpecificationIcon, technical_specification, temperatureAppIcon, temperature_app, testDriveIcon, test_drive, textForwardIcon, thumbsdownAppIcon, thumbsdownFilledAppIcon, thumbsdown_app, thumbsdown_filled_app, thumbsupAppIcon, thumbsupFilledAppIcon, thumbsup_app, thumbsup_filled_app, timeClimatisationAppIcon, timePreferredAppIcon, time_climatisation_app, time_preferred_app, timer, timerIcon, topcardIcon, touaregServiceIcon, transcriptDownloadIcon, transcript_download, transmissionAutomaticIcon, transmissionManualIcon, transmission_automatic, transmission_manual, tripAppIcon, tripLongAppIcon, tripPartedAppIcon, tripShortAppIcon, trip_app, trip_long_app, trip_parted_app, trip_short_app, turnSignalsIcon, turn_signals, unselected, unselectedIcon, updateRefreshAppIcon, update_refresh_app, upload, uploadAppIcon, uploadCustomIcon, upload_app, usedCarCommercialIcon, usedCarPrivateIcon, used_car_commercial, used_car_private, vehicleAmarokIcon, vehicleCaddyIcon, vehicleConversionIcon, vehicleCrafterIcon, vehicleHightIcon, vehicleIdBuzzIcon, vehicleMultivanIcon, vehicleTransporterIcon, vehicle_amarok, vehicle_caddy, vehicle_crafter, vehicle_hight, vehicle_idbuzz, vehicle_multivan, vehicle_transporter, videoChatIcon, video_chat, view360Icon, view_360, vinIcon, virtualRealityIcon, virtual_reality, voiceMessageAppIcon, voice_message_app, volkswagenAppIcon, volkswagenIcon, volkswagen_app, volumeMaximumIcon, volumeMediumIcon, volumeMuteIcon, volume_maximum, volume_medium, volume_mute, vsfSearch48Icon, vsfSearchIcon, vwBrandIcon, vwConnectLicenseAppIcon, vw_connect_license_app, vwnBrandIcon, walkingAppIcon, walkingFilledAppIcon, walking_app, walking_filled_app, wallbox, wallboxIcon, wcAppIcon, wc_app, wcpIcon, weAssistAppIcon, weChargeAppIcon, weDeliverAppIcon, weExperienceAppIcon, weParkAppIcon, weUpgradeAppIcon, we_assist_app, we_charge_app, we_deliver_app, we_experience_app, we_park_app, we_upgrade_app, weatherSunAppIcon, weather_sun_app, wheelToWheelIcon, wheel_to_wheel, windscreenWashIcon, windscreen_wash, wlanHotspotIcon, wlan_hotspot, wrenchForwardIcon };
19479
16995
  //# sourceMappingURL=clr-addons.mjs.map