UNPKG

@tapsellorg/angular-material-library

Version:

Angular library for Tapsell

740 lines (727 loc) 73.1 kB
import * as i1 from '@angular/common'; import { getCurrencySymbol, CommonModule, DecimalPipe, PercentPipe, CurrencyPipe } from '@angular/common'; import * as i0 from '@angular/core'; import { signal, Injectable, InjectionToken, Inject, input, Optional, ViewEncapsulation, Component, model, ViewChild, inject, Directive, output, ContentChild, ContentChildren, DEFAULT_CURRENCY_CODE, LOCALE_ID, NgModule } from '@angular/core'; import { ObserversModule } from '@angular/cdk/observers'; import * as i4 from 'highcharts-angular'; import { HighchartsChartModule } from 'highcharts-angular'; import * as i2 from '@tapsellorg/angular-material-library/src/lib/help-indicator'; import { PghHelpIndicatorModule } from '@tapsellorg/angular-material-library/src/lib/help-indicator'; import Highcharts from 'highcharts'; import { ObjectUtils, MiscUtils, withDestroy, StringUtils } from '@tapsellorg/angular-material-library/src/lib/common'; import highchartsMap from 'highcharts/modules/map'; import { DateUtils } from '@tapsellorg/angular-material-library/src/lib/jalali-date-adapter'; import { ReplaySubject } from 'rxjs'; import * as i5 from '@tapsellorg/angular-material-library/src/lib/translate'; import { TranslateModule } from '@tapsellorg/angular-material-library/src/lib/translate'; import iranMap from '@highcharts/map-collection/countries/ir/ir-all.geo.json'; import { PghFilterChipsComponent } from '@tapsellorg/angular-material-library/src/lib/filter-chips'; import { takeUntil, distinctUntilChanged, debounceTime } from 'rxjs/operators'; import * as i4$1 from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon'; import * as i1$1 from '@angular/material/core'; import { MatRippleModule } from '@angular/material/core'; highchartsMap(Highcharts); class ChartParent { constructor() { this.highcharts = Highcharts; this.tooltip = signal(null); } get hasNoData() { return (!this.series()?.length || (this.series().length === 1 && !this.series()[0].data?.length) || this.series() === this.CHART_PLACEHOLDER_SERIES); } getChartSeriesColor(index) { const activeStatBoxes = this.statBoxService?.activeStatBoxes; if (!activeStatBoxes || activeStatBoxes.length !== this.series()?.length) { return this.chartColorsService.getChartSeriesColor(index); } return activeStatBoxes[index].color; } getChartSeries() { if (this.hasNoData) { return this.CHART_PLACEHOLDER_SERIES; } return (this.series() ?? []).map((s, i) => ({ ...s, type: s.type ?? this.defaultSeriesType(), data: s.data.slice(), color: this.getChartSeriesColor(i), })); } createChartOptions(customOptions) { const tooltipFormatter = this.getTooltipFormatter(); const series = this.getChartSeries(); return ObjectUtils.mergeDeep({ chart: { backgroundColor: 'var(--fff)', style: { fontFamily: 'inherit', }, }, colors: this.chartColorsService.chartColors, title: { text: undefined }, credits: { enabled: false }, tooltip: { borderWidth: 0, backgroundColor: '', useHTML: true, formatter() { return tooltipFormatter(this); }, shared: true, }, legend: { itemStyle: { cursor: 'pointer', 'font-size': '12px', 'font-weight': 'bold', color: 'var(--000)', fill: 'var(--000)', }, }, series, }, customOptions, this.userCustomChartOptions()); } getTooltipFormatter() { if (typeof this.tooltip() === 'function') { return this.chartTooltipService.wrapTemplateOnFormatter(this.tooltip(), this.series()); } else { return this.chartTooltipService.wrapTemplateOnFormatter(this.chartTooltipService.getPredefinedFormatter(this.tooltip() ?? 'number'), this.series()); } } } class PghChartTooltipService { constructor(currencyPipe, percentPipe, decimalPipe) { this.currencyPipe = currencyPipe; this.percentPipe = percentPipe; this.decimalPipe = decimalPipe; // // *** // The followings are formatter util functions. They just get a point and format it // *** // this.tooltipTemplate = (y, x, data) => { const yAxisValuesTemplate = (data.points || [data]) .map((point, i) => `<div class="pgh-chart-tooltip-point"> <span>${y[i] || ''}</span> <span class="pgh-chart-tooltip-point-indicator ms-1" style="--series-color:${point.color}"></span> </div>`) .join(''); return `<div class="pgh-highcharts-tooltip"> <div class="pgh-highcharts-tooltip-values">${yAxisValuesTemplate}</div> <div class="tooltip-x" dir="auto">${x}</div> </div>`; }; // TODO: 'y' property existence isn't guaranteed this.numberFormatter = (point) => this.decimalPipe.transform(point.y) ?? point.y.toString(); // TODO: 'y' property existence isn't guaranteed and adding '?? 0' is not safe this.percentFormatter = (point) => this.percentPipe.transform(point.y ?? 0 / 100, '.2') ?? point.y.toString(); this.percentPieFormatter = (point) => `${point.percentage?.toFixed(2)}%`; // TODO: 'y' property existence isn't guaranteed this.currencyFormatter = (point) => this.currencyPipe.transform(point.y) ?? point.y.toString(); // TODO: 'x' property existence isn't guaranteed this.xAxisDateTimeFormatter = (data) => { const x = data.x || data.key; return DateUtils.isValidDate(x) ? `${DateUtils.gregorianToJalali(x).format()} ${DateUtils.format(x, 'HH:MM')}` : x.toString(); }; // TODO: 'x' property existence isn't guaranteed this.xAxisDateFormatter = (data) => { const x = data.x || data.key; return DateUtils.isValidDate(x) ? DateUtils.gregorianToJalali(x).format() : x.toString(); }; // TODO: 'x' property existence isn't guaranteed this.xAxisTimeFormatter = (data) => { const x = data.x || data.key; return DateUtils.isValidDate(x) ? DateUtils.format(x, 'HH:MM') : x.toString(); }; this.yAxisFormatters = { number: this.numberFormatter, currency: this.currencyFormatter, percent: this.percentFormatter, 'percent-pie': this.percentPieFormatter, }; this.xAxisFormatters = { 'date-time': this.xAxisDateTimeFormatter, date: this.xAxisDateFormatter, time: this.xAxisTimeFormatter, }; } getPredefinedYAxisFormatter(tooltipType) { if (typeof tooltipType === 'function') { return tooltipType; } return this.yAxisFormatters[tooltipType]; } getPredefinedFormatter(tooltipType) { const [yAxis, xAxis] = tooltipType.split(':'); const xAxisFormatter = this.xAxisFormatters[xAxis]; const yAxisFormatter = this.yAxisFormatters[yAxis]; if (!yAxisFormatter && !xAxisFormatter) { throw new Error(`PghChartTooltipService: ${tooltipType} tooltip not found.`); } return (data) => ({ data: (data.points || [data]).map(p => yAxisFormatter?.(p) ?? p.y), name: xAxisFormatter?.(data) ?? data.key, }); } wrapTemplateOnFormatter(tooltip, series) { return (data) => { const res = tooltip(data); this.applySeriesTooltipFormatters(res, data, series); // TODO: 'res.name ?? data.key' doesn't satisfy the 'tooltipTemplate' argument type and adding '?? ''' isn't very safe return this.tooltipTemplate(res.data ?? (data.points || [data]).map(p => p.y), res.name ?? data.key ?? '', data); }; } applySeriesTooltipFormatters(res, data, series) { res.data = (data.points || [data]).map((p, i) => { const seriesTooltipFormatter = this.getPredefinedYAxisFormatter(series[i].tooltip || ''); return seriesTooltipFormatter ? seriesTooltipFormatter(p) : res.data?.[i]; }); } fillUndefinedAxisFormattersWithDefaults(tooltip, defaultValues) { if (typeof tooltip === 'function') return tooltip; const [yAxis, xAxis] = tooltip.split(':'); return `${yAxis || defaultValues.yAxis}:${xAxis || defaultValues.xAxis}`; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghChartTooltipService, deps: [{ token: i1.CurrencyPipe }, { token: i1.PercentPipe }, { token: i1.DecimalPipe }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghChartTooltipService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghChartTooltipService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: () => [{ type: i1.CurrencyPipe }, { type: i1.PercentPipe }, { type: i1.DecimalPipe }] }); const PGH_CHART_COLORS = new InjectionToken('chartColors', { providedIn: 'root', factory: () => ['var(--primary)', 'var(--accent)'], }); const PGH_CHART_OPTIONS = new InjectionToken('chartOptions', { providedIn: 'root', factory: () => ({}), }); class PghChartColorsService { constructor(colors) { this.FALL_BACK_COLORS = [...MiscUtils.ALL_MATERIAL_COLORS, ...MiscUtils.RANDOM_COLORS]; this.colors = []; this.updateColors(colors); } get chartColors() { return this.colors; } updateColors(colors) { this.colors = [...colors, ...this.FALL_BACK_COLORS.filter(c => !colors.includes(c))]; } getStatBoxColor(index) { return this.colors[index % this.colors.length]; } getChartSeriesColor(index) { return this.colors[index % this.colors.length]; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghChartColorsService, deps: [{ token: PGH_CHART_COLORS }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghChartColorsService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghChartColorsService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: undefined, decorators: [{ type: Inject, args: [PGH_CHART_COLORS] }] }] }); class PghStatBoxService { get activeStatBoxes() { return this.statBoxes.filter(sb => sb.isActive); } constructor() { this.statBoxes = []; this.$activeStatBoxes = new ReplaySubject(1); } setActiveStatBoxes(statBoxes) { this.$activeStatBoxes.next(statBoxes); } addActiveStatBox(statBox) { statBox.isActive = true; this.setActiveStatBoxes(this.statBoxes.filter(sb => sb.isActive)); } removeActiveStatBox(statBox) { statBox.isActive = false; this.setActiveStatBoxes(this.statBoxes.filter(sb => sb.isActive)); } setOnlyActiveStatBox(statBox) { this.statBoxes.forEach(sb => { sb.isActive = false; }); statBox.isActive = true; this.setActiveStatBoxes([statBox]); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghStatBoxService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghStatBoxService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghStatBoxService, decorators: [{ type: Injectable }], ctorParameters: () => [] }); class PghMiniChartComponent extends ChartParent { constructor(chartTooltipService, chartColorsService, statBoxService) { super(); this.chartTooltipService = chartTooltipService; this.chartColorsService = chartColorsService; this.statBoxService = statBoxService; this.chartOptions = signal({}); this.userCustomChartOptions = input({}, { alias: 'customChartOptions', }); this.series = input([]); this.defaultSeriesType = input('column', { alias: 'seriesType', }); this.CHART_PLACEHOLDER_SERIES = [ { name: 'name', data: [3, 5, 1, 2, 3, 2, 3, 5, 1, 10, 9, 13], type: this.defaultSeriesType() }, ]; } ngOnChanges(_changes) { this.updateChartOptions(); } updateChartOptions() { const columnBorderRadius = (this.chartOptions().series?.length ?? 0) > 1 ? 0 : 4; this.chartOptions.set(this.createChartOptions({ chart: { className: 'mini-chart', width: 90, height: 80, }, xAxis: { lineWidth: 0, gridLineWidth: 0, lineColor: 'transparent', minorTickLength: 0, title: { text: undefined }, labels: { enabled: false }, tickLength: 0, }, yAxis: { min: 0, title: { text: '' }, lineWidth: 0, gridLineWidth: 0, lineColor: 'transparent', minorTickLength: 0, tickLength: 0, labels: { enabled: false }, }, plotOptions: { column: { pointPadding: 0.1, borderWidth: 0, stacking: 'normal', borderRadius: columnBorderRadius, }, }, legend: { enabled: false }, tooltip: { enabled: false }, })); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghMiniChartComponent, deps: [{ token: PghChartTooltipService }, { token: PghChartColorsService }, { token: PghStatBoxService, optional: true }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.13", type: PghMiniChartComponent, isStandalone: false, selector: "pgh-mini-chart", inputs: { userCustomChartOptions: { classPropertyName: "userCustomChartOptions", publicName: "customChartOptions", isSignal: true, isRequired: false, transformFunction: null }, series: { classPropertyName: "series", publicName: "series", isSignal: true, isRequired: false, transformFunction: null }, defaultSeriesType: { classPropertyName: "defaultSeriesType", publicName: "seriesType", isSignal: true, isRequired: false, transformFunction: null } }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"pgh-chart-wrapper\">\n <div class=\"pgh-chart-content\" [class.pgh-chart-no-data]=\"hasNoData\">\n <highcharts-chart\n [Highcharts]=\"highcharts\"\n constructorType=\"chart\"\n [options]=\"chartOptions()\"\n [oneToOne]=\"true\"\n [runOutsideAngular]=\"true\"\n ></highcharts-chart>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: i4.HighchartsChartComponent, selector: "highcharts-chart", inputs: ["Highcharts", "constructorType", "callbackFunction", "oneToOne", "runOutsideAngular", "options", "update"], outputs: ["updateChange", "chartInstance"] }], encapsulation: i0.ViewEncapsulation.None }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghMiniChartComponent, decorators: [{ type: Component, args: [{ selector: 'pgh-mini-chart', encapsulation: ViewEncapsulation.None, standalone: false, template: "<div class=\"pgh-chart-wrapper\">\n <div class=\"pgh-chart-content\" [class.pgh-chart-no-data]=\"hasNoData\">\n <highcharts-chart\n [Highcharts]=\"highcharts\"\n constructorType=\"chart\"\n [options]=\"chartOptions()\"\n [oneToOne]=\"true\"\n [runOutsideAngular]=\"true\"\n ></highcharts-chart>\n </div>\n</div>\n" }] }], ctorParameters: () => [{ type: PghChartTooltipService }, { type: PghChartColorsService }, { type: PghStatBoxService, decorators: [{ type: Optional }] }] }); class PghFullChartComponent extends ChartParent { /** TODO: 'chartColorsService' injected but never used review its usage if the 'updateColors' in its constructor doesn't do anything in this scenario */ constructor(chartTooltipService, chartColorsService, globalChartOptions, statBoxService) { super(); this.chartTooltipService = chartTooltipService; this.chartColorsService = chartColorsService; this.globalChartOptions = globalChartOptions; this.statBoxService = statBoxService; this.MIN_ZOOM_ENABLED_CHART_SERIES = 8; this.chartOptions = signal({}); this.userCustomChartOptions = input({}, { alias: 'customChartOptions', }); this.series = input([]); this.defaultSeriesType = input('areaspline', { alias: 'seriesType', }); this.xAxis = input([]); this.yAxis = model([{}]); // TODO: use typescript's template string `${PghChartTooltipType}:${PghChartTooltipXAxis}` this.tooltip = model('percent-pie'); this.CHART_PLACEHOLDER_SERIES = [ { name: 'name', data: [3, 5, 1, 2, 3, 2, 3, 5, 1, 10, 9, 13], type: this.defaultSeriesType() }, ]; this.getXAxis = (xAxisValue) => DateUtils.isValidDate(xAxisValue) ? DateUtils.gregorianToJalali(xAxisValue).format() : xAxisValue.toString(); } ngAfterViewChecked() { this.handleChartReflow(); } handleChartReflow() { if (!this.chartInstance || this.lastChartWrapperWidth === this.chartWrapper?.nativeElement.offsetWidth) return; this.lastChartWrapperWidth = this.chartWrapper?.nativeElement.offsetWidth; /** * TODO: using ChangeDetectorRef can be a better option instead of setTimeout * see https://stackoverflow.com/questions/73145234/what-to-use-in-angular-instead-of-settimeout-for-certain-changes * The decorator introduced in the above link is quite useful for this purpose * */ setTimeout(() => { this.chartInstance?.reflow(); }); } ngOnChanges(changes) { if (changes.yAxis && this.yAxis().length === 0) { this.yAxis.set([{}]); } this.updateChartOptions(); } updateChartOptions() { this.tooltip.set(this.chartTooltipService.fillUndefinedAxisFormattersWithDefaults(this.tooltip(), { xAxis: this.globalChartOptions.fullChartTooltip?.xAxis || 'date', yAxis: this.globalChartOptions.fullChartTooltip?.yAxis, })); const xAxis = this.xAxis() ?? []; this.chartOptions.set(this.createChartOptions({ chart: { zooming: { type: xAxis.length > this.MIN_ZOOM_ENABLED_CHART_SERIES ? 'x' : undefined, }, className: 'full-chart', type: 'areaspline', }, xAxis: { categories: xAxis, labels: { style: { fontSize: '14px', color: 'var(--444)' }, formatter: that => this.getXAxis(that.value), }, zoomEnabled: true, crosshair: true, }, yAxis: this.yAxis().map(y => ({ title: { text: null }, labels: { format: y?.labels?.format ?? '', style: { fontSize: '13px', color: 'var(--666)' }, }, opposite: y.opposite, gridLineColor: 'var(--eee)', gridLineDashStyle: 'LongDashDot', })), plotOptions: { column: { borderColor: 'var(--fff)', }, }, })); } onSetChartInstance(chartInstance) { this.chartInstance = chartInstance; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghFullChartComponent, deps: [{ token: PghChartTooltipService }, { token: PghChartColorsService }, { token: PGH_CHART_OPTIONS }, { token: PghStatBoxService, optional: true }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.13", type: PghFullChartComponent, isStandalone: false, selector: "pgh-full-chart", inputs: { userCustomChartOptions: { classPropertyName: "userCustomChartOptions", publicName: "customChartOptions", isSignal: true, isRequired: false, transformFunction: null }, series: { classPropertyName: "series", publicName: "series", isSignal: true, isRequired: false, transformFunction: null }, defaultSeriesType: { classPropertyName: "defaultSeriesType", publicName: "seriesType", isSignal: true, isRequired: false, transformFunction: null }, xAxis: { classPropertyName: "xAxis", publicName: "xAxis", isSignal: true, isRequired: false, transformFunction: null }, yAxis: { classPropertyName: "yAxis", publicName: "yAxis", isSignal: true, isRequired: false, transformFunction: null }, tooltip: { classPropertyName: "tooltip", publicName: "tooltip", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { yAxis: "yAxisChange", tooltip: "tooltipChange" }, viewQueries: [{ propertyName: "chartWrapper", first: true, predicate: ["chartWrapperRef"], descendants: true }], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div #chartWrapperRef class=\"pgh-chart-wrapper\">\n <span class=\"pgh-chart-placeholder-text\">{{ 'FULL_CHART_PLACEHOLDER_TEXT' | translate }}</span>\n <div class=\"pgh-chart-content\" [class.pgh-chart-no-data]=\"hasNoData\">\n <highcharts-chart\n [Highcharts]=\"highcharts\"\n constructorType=\"chart\"\n [options]=\"chartOptions()\"\n [oneToOne]=\"true\"\n class=\"w-100 d-block\"\n [runOutsideAngular]=\"true\"\n (chartInstance)=\"onSetChartInstance($event)\"\n ></highcharts-chart>\n </div>\n</div>\n", styles: [".pgh-chart-placeholder-text{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:1.25rem}.pgh-chart-tooltip-point-indicator{height:8px;width:8px;border-radius:50%;display:inline-block;background:var(--series-color)}.pgh-chart-tooltip-point{display:flex;justify-content:flex-end;align-items:center;color:var(--000)}.pgh-highcharts-tooltip-values{text-shadow:0 0 .3px currentcolor}.highcharts-area{opacity:.4}\n"], dependencies: [{ kind: "component", type: i4.HighchartsChartComponent, selector: "highcharts-chart", inputs: ["Highcharts", "constructorType", "callbackFunction", "oneToOne", "runOutsideAngular", "options", "update"], outputs: ["updateChange", "chartInstance"] }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }], encapsulation: i0.ViewEncapsulation.None }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghFullChartComponent, decorators: [{ type: Component, args: [{ selector: 'pgh-full-chart', encapsulation: ViewEncapsulation.None, standalone: false, template: "<div #chartWrapperRef class=\"pgh-chart-wrapper\">\n <span class=\"pgh-chart-placeholder-text\">{{ 'FULL_CHART_PLACEHOLDER_TEXT' | translate }}</span>\n <div class=\"pgh-chart-content\" [class.pgh-chart-no-data]=\"hasNoData\">\n <highcharts-chart\n [Highcharts]=\"highcharts\"\n constructorType=\"chart\"\n [options]=\"chartOptions()\"\n [oneToOne]=\"true\"\n class=\"w-100 d-block\"\n [runOutsideAngular]=\"true\"\n (chartInstance)=\"onSetChartInstance($event)\"\n ></highcharts-chart>\n </div>\n</div>\n", styles: [".pgh-chart-placeholder-text{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:1.25rem}.pgh-chart-tooltip-point-indicator{height:8px;width:8px;border-radius:50%;display:inline-block;background:var(--series-color)}.pgh-chart-tooltip-point{display:flex;justify-content:flex-end;align-items:center;color:var(--000)}.pgh-highcharts-tooltip-values{text-shadow:0 0 .3px currentcolor}.highcharts-area{opacity:.4}\n"] }] }], ctorParameters: () => [{ type: PghChartTooltipService }, { type: PghChartColorsService }, { type: undefined, decorators: [{ type: Inject, args: [PGH_CHART_OPTIONS] }] }, { type: PghStatBoxService, decorators: [{ type: Optional }] }], propDecorators: { chartWrapper: [{ type: ViewChild, args: ['chartWrapperRef'] }] } }); class PghMapChartComponent extends ChartParent { constructor(statBoxService) { super(); this.statBoxService = statBoxService; this.chartInstance = signal(undefined); this.chartOptions = signal({}); this.series = input([]); this.userCustomChartOptions = input({}, { alias: 'customChartOptions', }); this.defaultSeriesType = signal('map'); this.CHART_PLACEHOLDER_SERIES = []; this.chartTooltipService = inject(PghChartTooltipService); this.chartColorsService = inject(PghChartColorsService); } ngOnChanges(changes) { if (changes['userCustomChartOptions']) { this.setChartOptions(); } if (changes['series']) { this.updateChartData(); } } ngOnInit() { this.setChartOptions(); } setChartOptions() { this.chartOptions.set(this.createChartOptions({ chart: { map: iranMap, backgroundColor: 'transparent', }, title: { text: undefined }, credits: { enabled: false }, legend: { enabled: false }, colorAxis: { stops: [ [0, '#B3ECDA'], [1, '#00865C'], ], }, tooltip: { enabled: false, }, plotOptions: { series: { animation: false, }, }, series: [ { type: 'map', animation: false, joinBy: ['hc-key', 'id'], borderColor: '#FFFFFF', borderWidth: 1, states: { hover: { color: '#006042', borderColor: '#FFFFFF', borderWidth: 2, }, }, }, ], })); } updateChartData() { const seriesData = this.series()?.[0].data ?? []; const chart = this.chartInstance(); if (chart && seriesData?.length) { chart.series[0].setData(seriesData, true, false); } } onChartInstanceReady(chart) { this.chartInstance.set(chart); this.updateChartData(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghMapChartComponent, deps: [{ token: PghStatBoxService, optional: true }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.13", type: PghMapChartComponent, isStandalone: false, selector: "pgh-map-chart", inputs: { series: { classPropertyName: "series", publicName: "series", isSignal: true, isRequired: false, transformFunction: null }, userCustomChartOptions: { classPropertyName: "userCustomChartOptions", publicName: "customChartOptions", isSignal: true, isRequired: false, transformFunction: null } }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div #chartWrapperRef class=\"pgh-chart-wrapper\">\n <div class=\"pgh-chart-content\" [class.pgh-chart-no-data]=\"hasNoData\">\n <highcharts-chart\n [Highcharts]=\"highcharts\"\n constructorType=\"mapChart\"\n [options]=\"chartOptions()\"\n [oneToOne]=\"true\"\n [runOutsideAngular]=\"true\"\n (chartInstance)=\"onChartInstanceReady($event)\"\n class=\"pgh-iran-map-chart w-100 d-block\"\n ></highcharts-chart>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: i4.HighchartsChartComponent, selector: "highcharts-chart", inputs: ["Highcharts", "constructorType", "callbackFunction", "oneToOne", "runOutsideAngular", "options", "update"], outputs: ["updateChange", "chartInstance"] }], encapsulation: i0.ViewEncapsulation.None }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghMapChartComponent, decorators: [{ type: Component, args: [{ selector: 'pgh-map-chart', encapsulation: ViewEncapsulation.None, standalone: false, template: "<div #chartWrapperRef class=\"pgh-chart-wrapper\">\n <div class=\"pgh-chart-content\" [class.pgh-chart-no-data]=\"hasNoData\">\n <highcharts-chart\n [Highcharts]=\"highcharts\"\n constructorType=\"mapChart\"\n [options]=\"chartOptions()\"\n [oneToOne]=\"true\"\n [runOutsideAngular]=\"true\"\n (chartInstance)=\"onChartInstanceReady($event)\"\n class=\"pgh-iran-map-chart w-100 d-block\"\n ></highcharts-chart>\n </div>\n</div>\n" }] }], ctorParameters: () => [{ type: PghStatBoxService, decorators: [{ type: Optional }] }] }); class PghChartStatBoxDirective { constructor(template) { this.template = template; this.statBoxData = input(undefined, { alias: 'pghStatsBox', }); } ngOnChanges(changes) { this.handleNoStatBoxDataError(); } ngOnInit() { this.handleNoStatBoxDataError(); } handleNoStatBoxDataError() { if (!this.statBoxData()) { throw new Error('Pass a statBox data to *pghStatsBox so you can use it in the (activeStatBoxesChange) listener'); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghChartStatBoxDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.13", type: PghChartStatBoxDirective, isStandalone: false, selector: "[pghStatsBox]", inputs: { statBoxData: { classPropertyName: "statBoxData", publicName: "pghStatsBox", isSignal: true, isRequired: false, transformFunction: null } }, exportAs: ["pghStatsBox"], usesOnChanges: true, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghChartStatBoxDirective, decorators: [{ type: Directive, args: [{ selector: '[pghStatsBox]', exportAs: 'pghStatsBox', standalone: false, }] }], ctorParameters: () => [{ type: i0.TemplateRef }] }); class PghChartStatBox { get inputData() { return this.markerDirective.statBoxData(); } constructor(markerDirective, index, color) { this.markerDirective = markerDirective; this.index = index; this.color = color; this.isActive = false; } } class PghChartDateRangePickerDirective { constructor() { } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghChartDateRangePickerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.13", type: PghChartDateRangePickerDirective, isStandalone: false, selector: "[pgh-chart-date-range-picker]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghChartDateRangePickerDirective, decorators: [{ type: Directive, args: [{ selector: '[pgh-chart-date-range-picker]', standalone: false, }] }], ctorParameters: () => [] }); class PghChartHeaderDirective { constructor() { } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghChartHeaderDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.13", type: PghChartHeaderDirective, isStandalone: false, selector: "[pgh-chart-header]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghChartHeaderDirective, decorators: [{ type: Directive, args: [{ selector: '[pgh-chart-header]', standalone: false, }] }], ctorParameters: () => [] }); class PghChartBoxComponent extends withDestroy() { constructor(statBoxService, chartColorsService, chartOptions) { super(); this.statBoxService = statBoxService; this.chartColorsService = chartColorsService; this.chartTitle = input(); this.activeMax = model(1); this.colors = input(); this.disableChartBoxScroll = model(false); this.activeStatBoxesChange = output(); this.dateRangePickerDirective = PghChartDateRangePickerDirective; this.displayStatBoxesVertically = input(false); this.isContainerOverflown = signal(false); this.isContainerAtStart = signal(true); this.isContainerAtEnd = signal(false); this.activeMax.set(chartOptions.maxActiveChartBoxes ?? this.activeMax()); this.disableChartBoxScroll.set(chartOptions.disableChartBoxScroll ?? this.disableChartBoxScroll()); } ngOnInit() { this.handleActiveMaxChange(); let noDuplicate$ = this.statBoxService.$activeStatBoxes.pipe(takeUntil(this._destroyed$), distinctUntilChanged()); if (this.activeMax() === 'infinite' || Number(this.activeMax()) > 2) { noDuplicate$ = noDuplicate$.pipe(debounceTime(700)); } noDuplicate$.subscribe(activeStatBoxes => { this.activeStatBoxesChange.emit({ statBoxes: activeStatBoxes.map(sb => sb), indexes: activeStatBoxes.map(sb => sb.index), }); }); } ngOnChanges(changes) { if (changes.color && this.colors()) { this.chartColorsService.updateColors(this.colors()); } if (changes.activeMax) { this.handleActiveMaxChange(); } } handleActiveMaxChange() { if (this.activeMax() === 'infinite') { this.activeMax.set(999999999999999); } else if (Number(this.activeMax()) < 1) { this.activeMax.set(1); } } ngAfterContentInit() { this.statBoxesTemplateChanged(); this.statBoxesDirectives?.changes.pipe(takeUntil(this._destroyed$)).subscribe(() => { this.statBoxesTemplateChanged(); }); } ngAfterContentChecked() { this.isContainerOverflown.set(this.isStatBoxesContainerOverflown()); } statBoxesTemplateChanged() { this.updateStatBoxesBasedOnDirectives(); const { activeStatBoxes } = this.statBoxService; if (!activeStatBoxes.length && this.statBoxService.statBoxes.length) { this.changeStatBoxActiveState(this.statBoxService.statBoxes[0]); } } updateStatBoxesBasedOnDirectives() { if (!this.statBoxesDirectives) return; this.statBoxService.statBoxes = this.statBoxesDirectives.map((directive, i) => { const existingStatBoxWithSameId = this.statBoxService.statBoxes.find(sb => sb.markerDirective.statBoxData() === directive.statBoxData()); return (existingStatBoxWithSameId ?? new PghChartStatBox(directive, i, this.chartColorsService.getStatBoxColor(i))); }); } changeStatBoxActiveState(statBox, statBoxContainerElm) { const activeCount = this.statBoxService.activeStatBoxes.length; if (this.activeMax() === 1) { if (statBox.isActive) return; this.statBoxService.setOnlyActiveStatBox(statBox); this.scrollIntoView(statBoxContainerElm); return; } const canChangeState = this.activeMax() === 'infinite' || activeCount < Number(this.activeMax()) || (statBox.isActive && activeCount > 1); if (!canChangeState) return; if (statBox.isActive) { this.statBoxService.removeActiveStatBox(statBox); } else { this.statBoxService.addActiveStatBox(statBox); this.scrollIntoView(statBoxContainerElm); } } shift(direction) { if (!this.statBoxesScroller || (direction === 1 && this.isContainerAtStart()) || (direction === -1 && this.isContainerAtEnd())) return; this.statBoxesScroller.nativeElement.scrollBy({ left: direction * 300 }); } isStatBoxesContainerOverflown() { if (!this.statBoxesScroller || this.disableChartBoxScroll()) { return false; } const element = this.statBoxesScroller.nativeElement; return element.scrollWidth > element.clientWidth; } scrollIntoView(element) { if (!element) return; element.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center', }); } statBoxContainerScrollHandler() { if (!this.statBoxesScroller) return; const element = this.statBoxesScroller.nativeElement; this.isContainerAtStart.set(!element.scrollLeft); this.isContainerAtEnd.set(element.scrollWidth + element.scrollLeft === element.clientWidth); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghChartBoxComponent, deps: [{ token: PghStatBoxService }, { token: PghChartColorsService }, { token: PGH_CHART_OPTIONS }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.13", type: PghChartBoxComponent, isStandalone: false, selector: "pgh-chart-box", inputs: { chartTitle: { classPropertyName: "chartTitle", publicName: "chartTitle", isSignal: true, isRequired: false, transformFunction: null }, activeMax: { classPropertyName: "activeMax", publicName: "activeMax", isSignal: true, isRequired: false, transformFunction: null }, colors: { classPropertyName: "colors", publicName: "colors", isSignal: true, isRequired: false, transformFunction: null }, disableChartBoxScroll: { classPropertyName: "disableChartBoxScroll", publicName: "disableChartBoxScroll", isSignal: true, isRequired: false, transformFunction: null }, displayStatBoxesVertically: { classPropertyName: "displayStatBoxesVertically", publicName: "displayStatBoxesVertically", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeMax: "activeMaxChange", disableChartBoxScroll: "disableChartBoxScrollChange", activeStatBoxesChange: "activeStatBoxesChange" }, providers: [PghStatBoxService], queries: [{ propertyName: "filterChipsComponent", first: true, predicate: PghFilterChipsComponent, descendants: true }, { propertyName: "dateRangePickerDirective", first: true, predicate: PghChartDateRangePickerDirective, descendants: true }, { propertyName: "headerDirective", first: true, predicate: PghChartHeaderDirective, descendants: true }, { propertyName: "statBoxesDirectives", predicate: PghChartStatBoxDirective }], viewQueries: [{ propertyName: "statBoxesScroller", first: true, predicate: ["statBoxesScroller"], descendants: true, static: true }], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"pgh-chart-box\" [class.pgh-chart-box-vertical-stats]=\"displayStatBoxesVertically()\">\n <div class=\"pgh-chart-box-header\">\n <div class=\"pgh-chart-box-header-section\" [hidden]=\"!chartTitle() && !headerDirective\">\n @if (chartTitle()) {\n <h4>{{ chartTitle() }}</h4>\n }\n <ng-content select=\"[pgh-chart-header]\"></ng-content>\n </div>\n <div class=\"pgh-chart-box-header-section\" [hidden]=\"!dateRangePickerDirective\">\n <ng-content select=\"[pgh-chart-date-range-picker]\"></ng-content>\n </div>\n </div>\n\n <div\n class=\"pgh-filters-wrapper\"\n [hidden]=\"!filtersWrapperRef.innerHTML.trim() && !actionButtonsWrapperRef.innerHTML.trim()\"\n >\n <div #filtersWrapperRef>\n <ng-content select=\"pgh-filter-chips\"></ng-content>\n <ng-content select=\"[pgh-chart-filters]\"></ng-content>\n </div>\n <div #actionButtonsWrapperRef class=\"pgh-action-buttons\">\n <ng-content select=\"[pgh-action-buttons]\"></ng-content>\n </div>\n </div>\n\n <div\n class=\"pgh-stats-container\"\n [hidden]=\"!statBoxService.statBoxes.length\"\n [class.pgh-chart-box-no-scroll-stats]=\"disableChartBoxScroll()\"\n >\n <div class=\"pgh-chart-stat-boxes\">\n <div\n #statBoxesScroller\n class=\"pgh-chart-stat-scroller\"\n [class.pgh-chart-stat-boxes-overflow]=\"isContainerOverflown()\"\n (scroll)=\"statBoxContainerScrollHandler()\"\n >\n @for (sb of statBoxService.statBoxes; track sb) {\n <div\n class=\"pgh-stat-box-container\"\n [class.pgh-is-active]=\"sb.isActive\"\n #statBoxContainerElm\n (click)=\"changeStatBoxActiveState(sb, statBoxContainerElm)\"\n [style.--chart-stat-box-bg-color]=\"sb.color\"\n >\n <ng-container\n *ngTemplateOutlet=\"sb.markerDirective.template; context: { isActive: sb.isActive }\"\n ></ng-container>\n </div>\n }\n </div>\n </div>\n @if (isContainerOverflown()) {\n <div\n class=\"pgh-slide-button pgh-chart-stat-boxes-slide-right\"\n [class.pgh-disabled]=\"isContainerAtStart()\"\n (click)=\"shift(1)\"\n >\n <mat-icon svgIcon=\"chevron_right\"></mat-icon>\n </div>\n } @if (isContainerOverflown()) {\n <div\n class=\"pgh-slide-button pgh-chart-stat-boxes-slide-left\"\n [class.pgh-disabled]=\"isContainerAtEnd()\"\n (click)=\"shift(-1)\"\n >\n <mat-icon svgIcon=\"chevron_left\"></mat-icon>\n </div>\n }\n </div>\n\n <div class=\"pgh-chart-container\">\n <ng-content></ng-content>\n </div>\n</div>\n", styles: [".font-monospaced-numbers{font-feature-settings:\"ss03\"!important}.font-english-numbers{font-feature-settings:normal!important}.font-weight-light{font-weight:300}.font-weight-normal{font-weight:400}.font-weight-bold{font-weight:600}.black-50{background-color:hsl(var(--_body-hue),var(--_body-saturation),var(--lightness-0),50%)!important}.white-50{background-color:hsl(var(--_body-hue),var(--_body-saturation),var(--lightness-1000),50%)!important}.pgh-chart-box{border-radius:var(--box-radius);background-color:var(--fff);box-shadow:var(--shadow);display:grid;grid-template-areas:\"header\" \"filter\" \"stats\" \"chart\";grid-template-rows:auto auto auto 1fr;grid-template-columns:1fr}.pgh-chart-box .pgh-filters-wrapper{min-width:0}.box .pgh-chart-box{box-shadow:none}.pgh-chart-box-header{grid-area:header;min-width:0}.pgh-chart-box-header .pgh-chart-box-header-section{padding-block:.25rem;padding-inline:1rem;min-height:3.125rem;border-bottom:1px solid var(--light-border-color);display:flex;align-items:center}.pgh-chart-box-header .pgh-chart-box-header-section h4{font-weight:600;font-size:1rem;margin-bottom:0}.pgh-stats-container{grid-area:stats;position:relative;min-width:0}.pgh-chart-stat-boxes{background-color:var(--eee);border-bottom:1px solid var(--light-border-color)}[dark=true] .pgh-chart-stat-boxes{background-color:var(--fff)}.pgh-chart-stat-scroller{display:flex;overflow-x:scroll;-webkit-overflow-scrolling:touch;transition:all .3s;-ms-overflow-style:none;scrollbar-width:none;position:relative}.pgh-chart-stat-scroller::-webkit-scrollbar{display:none}.pgh-chart-stat-scroller.pgh-chart-stat-boxes-overflow{margin-block:0;margin-inline:2rem}.pgh-chart-box-no-scroll-stats .pgh-chart-stat-boxes{padding-block:.125rem;padding-inline:0}.pgh-chart-box-no-scroll-stats .pgh-chart-stat-scroller{flex-wrap:wrap;border:1px solid var(--light-border-color);border-inline-start:none;border-inline-end:none}.pgh-chart-box-no-scroll-stats .pgh-stat-box-container:not(:last-child){margin-bottom:.125rem}.pgh-chart-box-no-scroll-stats .pgh-stat-box-container{flex-grow:1}.pgh-chart-box-vertical-stats{grid-template-areas:\"header header\" \"filter filter\" \"stats chart\";grid-template-rows:auto auto 1fr;grid-template-columns:max(200px,20%) 1fr}.pgh-chart-box-vertical-stats .pgh-chart-box-no-scroll-stats .pgh-chart-stat-boxes{padding:0;border-bottom:none}.pgh-chart-box-vertical-stats .pgh-stats-container:not(.pgh-chart-box-no-scroll-stats){min-height:100%;overflow:auto;height:0}.pgh-chart-box-vertical-stats .pgh-chart-stat-scroller{border:none;flex-direction:column}.pgh-chart-box-vertical-stats .pgh-stat-box-container:not(:last-child){margin-bottom:.125rem}.pgh-slide-button{cursor:pointer;position:absolute;top:0;bottom:0;width:1.875rem;display:flex;background-color:var(--fff);border:1px solid var(--light-border-color);border-top:none;align-items:center;justify-content:center}.pgh-slide-button.pgh-disabled{box-shadow:none;opacity:.5;cursor:initial}.pgh-chart-stat-boxes-slide-left{left:0;border-inline-end:none;box-shadow:6px 2px 10px #0000000f}.pgh-chart-stat-boxes-slide-right{right:0;border-inline-start:none;box-shadow:-6px 2px 10px #0000000f}.pgh-chart-container{grid-area:chart;padding-block:1rem;padding-inline:0;min-width:0}\n"], dependencies: [{ kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], encapsulation: i0.ViewEncapsulation.None }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: PghChartBoxComponent, decorators: [{ type: Component, args: [{ selector: 'pgh-chart-box', encapsulation: ViewEncapsulation.None, providers: [PghStatBoxService], standalone: false, template: "<div class=\"pgh-chart-box\" [class.pgh-chart-box-vertical-stats]=\"displayStatBoxesVertically()\">\n <div class=\"pgh-chart-box-header\">\n <div class=\"pgh-chart-box-header-section\" [hidden]=\"!chartTitle() && !headerDirective\">\n @if (chartTitle()) {\n <h4>{{ chartTitle() }}</h4>\n }\n <ng-content select=\"[pgh-chart-header]\"></ng-content>\n </div>\n <div class=\"pgh-chart-box-header-section\" [hidden]=\"!dateRangePickerDirective\">\n <ng-content select=\"[pgh-chart-date-range-picker]\"></ng-content>\n </div>\n </div>\n\n <div\n class=\"pgh-filters-wrapper\"\n [hidden]=\"!filtersWrapperRef.innerHTML.trim() && !actionButtonsWrapperRef.innerHTML.trim()\"\n >\n <div #filtersWrapperRef>\n <ng-content select=\"pgh-filter-chips\"></ng-content>\n <ng-content select=\"[pgh-chart-filters]\"></ng-content>\n </div>\n <div #actionButtonsWrapperRef class=\"pgh-action-buttons\">\n <ng-content select=\"[pgh-action-buttons]\"></ng-content>\n </div>\n </div>\n\n <div\n class=\"pgh-stats-container\"\n [hidden]=\"!statBoxService.statBoxes.length\"\n [class.pgh-chart-box-no-scroll-stats]=\"disableChartBoxScroll()\"\n >\n <div class=\"pgh-chart-stat-boxes\">\n <div\n #statBoxesScroller\n class=\"pgh-chart-stat-scroller\"\n [class.pgh-chart-stat-boxes-overflow]=\"isContainerOverflown()\"\n (scroll)=\"statBoxContainerScrollHandler()\"\n >\n @for (sb of statBoxService.statBoxes; track sb) {\n <div\n class=\"pgh-stat-box-container\"\n [class.pgh-is-active]=\"sb.isActive\"\n #statBoxContainerElm\n (click)=\"changeStatBoxActiveState(sb, statBoxContainerElm)\"\n [style.--chart-stat-box-bg-color]=\"sb.color\"\n >\n <ng-container\n *ngTemplateOutlet=\"sb.markerDirective.template; context: { isActive: sb.isActive }\"\n ></ng-container>\n </div>\n }\n </div>\n </div>\n @if (isContainerOverflown()) {\n <div\n class=\"pgh-slide-button pgh-chart-stat-boxes-slide-right\"\n