UNPKG

@engie-group/ngx-gem-spaas

Version:

This library contains services, components, images and styles to provide a unified look and way-of-working throughout GEM SPaaS.

586 lines (579 loc) 24.9 kB
import * as am5 from '@amcharts/amcharts5'; import { Label, Legend } from '@amcharts/amcharts5'; import * as am5xy from '@amcharts/amcharts5/xy'; import { CategoryAxis } from '@amcharts/amcharts5/xy'; import * as i0 from '@angular/core'; import { Injectable } from '@angular/core'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import * as i1 from '@engie-group/ngx-gem-spaas'; import { UtilsService, FONT_FAMILY } from '@engie-group/ngx-gem-spaas'; import * as am5percent from '@amcharts/amcharts5/percent'; const DEFAULT_DATE_FORMAT = 'yyyy-MM-ddTHH:mm:ssZ'; class Am5RootConfig { constructor(config) { this.legendWithoutValueLabels = UtilsService.hasProp(config, 'legendWithoutValueLabels') ? config.legendWithoutValueLabels : true; if (config.tooltipContainerBounds) { this.tooltipContainerBounds = { top: config.tooltipContainerBounds.top || 0, right: config.tooltipContainerBounds.right || 0, bottom: config.tooltipContainerBounds.bottom || 0, left: config.tooltipContainerBounds.left || 0, }; } this.withLegend = UtilsService.hasProp(config, 'withLegend') ? config.withLegend : true; } } class Am5AxisConfig { constructor(type, config) { this.title = config.title || ''; this.withGrid = UtilsService.hasProp(config, 'withGrid') ? config.withGrid : type === 'value'; this.withLabels = UtilsService.hasProp(config, 'withLabels') ? config.withLabels : true; this.withTooltip = UtilsService.hasProp(config, 'withTooltip') ? config.withTooltip : type === 'date'; this.xOrY = config.xOrY; } } class Am5PieSeriesConfig { constructor(config) { this.name = config.name || config.xValue || ''; this.tooltipText = config.tooltipText || ''; this.xValue = config.xValue; this.yValue = config.yValue; this.withTooltip = UtilsService.hasProp(config, 'withTooltip') ? config.withTooltip : true; } } class Am5SeriesConfig extends Am5PieSeriesConfig { constructor(config) { super(config); this.color = config.color || ''; this.dateFormat = config.dateFormat || ''; this.stacked = UtilsService.hasProp(config, 'stacked') ? config.stacked : false; this.strokeWidth = config.strokeWidth || 2; this.type = config.type; this.yOpenValue = config.yOpenValue || ''; } } class Am5DateRangeConfig { constructor(config) { this.beginDt = config.beginDt; this.endDt = config.endDt; this.clearRanges = UtilsService.hasProp(config, 'clearRanges') ? config.clearRanges : true; this.strokeColor = config.strokeColor || ''; this.strokeDasharray = config.strokeDasharray || 0; this.strokeWidth = UtilsService.hasProp(config, 'strokeWidth') ? config.strokeWidth : 1; this.fillColor = config.fillColor || ''; this.fillOpacity = UtilsService.hasProp(config, 'fillOpacity') ? config.fillOpacity : 0.05; } } am5.addLicense('AM5C-5327-9399-0224-1489'); class Am5Service { constructor(themeService) { this.themeService = themeService; this.theme = null; this.onDestroy$ = new Subject(); this.onNewTheme(); } ngOnDestroy() { this.onDestroy$.next(); this.onDestroy$.complete(); } onNewTheme() { this.themeService.onNewActiveTheme() .pipe(takeUntil(this.onDestroy$)) .subscribe((theme) => { this.theme = theme; }); } getTheme() { return this.theme; } // ******************************************************************************************************** // CHART CREATION/DESTRUCTION // ******************************************************************************************************** // create xy chart createXyChart(chartDiv, config) { const themeColor = this.theme?.getRgb('color'); const rootConfig = new Am5RootConfig(config || {}); const root = am5.Root.new(chartDiv, { tooltipContainerBounds: rootConfig.tooltipContainerBounds, }); let chart = root.container.children.push(am5xy.XYChart.new(root, { layout: root.verticalLayout, // paddingBottom: 5, paddingLeft: 18, paddingRight: 4, })); // zoom-out button const bg = am5.RoundedRectangle.new(root, { cornerRadiusTL: 4, cornerRadiusTR: 4, cornerRadiusBR: 4, cornerRadiusBL: 4, fill: am5.color(themeColor), strokeWidth: 0, }); bg.states.create('hover', {}).setAll({ fill: am5.color(this.theme?.getRgb('primary')), }); chart.zoomOutButton.setAll({ background: bg, icon: am5.Rectangle.new(root, { fill: am5.color(this.theme?.getRgb('bg')), height: 2, width: 12, }), paddingTop: 7, paddingRight: 8, paddingBottom: 7, paddingLeft: 8, dx: 10, dy: -18, }); // cursor const cursor = chart.set('cursor', am5xy.XYCursor.new(root, { behavior: 'zoomX' })); cursor.lineX.setAll({ // fill: am5.color(this.theme?.getRgb('color') as string), stroke: am5.color(themeColor), strokeWidth: 1, strokeDasharray: [4, 4] }); cursor.lineY.setAll({ visible: false }); this.createLegend({ root: root, chart: chart }, rootConfig.withLegend, rootConfig.legendWithoutValueLabels); return { root: root, chart: chart }; } createLegend(am5Container, withLegend, legendWithoutValueLabels) { if (withLegend) { const legend = am5Container.chart.children.unshift(am5.Legend.new(am5Container.root, { centerX: am5.percent(50), x: am5.percent(50), layout: am5Container.root.gridLayout, })); legend.labels.template.setAll({ fill: am5.color(this.theme?.getRgb('color')), fontFamily: FONT_FAMILY, fontSize: '0.85em', }); legend.valueLabels.template.setAll({ fill: am5.color(this.theme?.getRgb('color')), fontFamily: FONT_FAMILY, fontSize: '0.85em', }); legend.markers.template.setAll({ height: 10, width: 10, }); legend.valueLabels.template.set('forceHidden', legendWithoutValueLabels); } } destroyChart(am5Container) { if (am5Container?.root) { am5Container.root.dispose(); } } // ******************************************************************************************************** // AXES // ******************************************************************************************************** addCategoryAxis(am5XyContainer, categoryField, config) { const axisConfig = new Am5AxisConfig('category', config); const axes = axisConfig.xOrY === 'x' ? am5XyContainer.chart.xAxes : am5XyContainer.chart.yAxes; const renderer = axisConfig.xOrY === 'x' ? am5xy.AxisRendererX.new(am5XyContainer.root, { minGridDistance: 1 }) : am5xy.AxisRendererY.new(am5XyContainer.root, {}); const axis = axes.push(am5xy.CategoryAxis.new(am5XyContainer.root, { categoryField: categoryField, renderer: renderer, tooltip: this.createAxisTooltip(am5XyContainer.root, axisConfig.withTooltip) })); this.setGenericAxisProps(renderer, axisConfig.withGrid, axisConfig.withLabels); this.setAxisTitle(am5XyContainer.root, axis, axisConfig.xOrY, axisConfig.title); return axis; } addDateAxis(am5XyContainer, config, baseInterval = { timeUnit: 'minute', count: 15 }) { const axisConfig = new Am5AxisConfig('date', config); const axes = axisConfig.xOrY === 'x' ? am5XyContainer.chart.xAxes : am5XyContainer.chart.yAxes; const renderer = axisConfig.xOrY === 'x' ? am5xy.AxisRendererX.new(am5XyContainer.root, { minGridDistance: 40 }) : am5xy.AxisRendererY.new(am5XyContainer.root, {}); const axis = axes.push(am5xy.DateAxis.new(am5XyContainer.root, { baseInterval: baseInterval, renderer: renderer, tooltip: this.createAxisTooltip(am5XyContainer.root, axisConfig.withTooltip), tooltipDateFormat: 'HH:mm', })); this.setGenericAxisProps(renderer, axisConfig.withGrid, axisConfig.withLabels); this.setAxisTitle(am5XyContainer.root, axis, axisConfig.xOrY, axisConfig.title); return axis; } addValueAxis(am5XyContainer, config) { const axisConfig = new Am5AxisConfig('value', config); const axes = axisConfig.xOrY === 'x' ? am5XyContainer.chart.xAxes : am5XyContainer.chart.yAxes; const renderer = axisConfig.xOrY === 'x' ? am5xy.AxisRendererX.new(am5XyContainer.root, {}) : am5xy.AxisRendererY.new(am5XyContainer.root, {}); const axis = axes.push(am5xy.ValueAxis.new(am5XyContainer.root, { renderer: renderer, tooltip: this.createAxisTooltip(am5XyContainer.root, axisConfig.withTooltip) })); this.setGenericAxisProps(renderer, axisConfig.withGrid, axisConfig.withLabels); this.setAxisTitle(am5XyContainer.root, axis, axisConfig.xOrY, axisConfig.title); return axis; } createAxisTooltip(root, withToolTip) { if (!withToolTip) { return undefined; } const tooltip = am5.Tooltip.new(root, { background: am5.Rectangle.new(root, { fill: am5.color(this.theme?.getRgb('primary')), strokeWidth: 0, }), paddingBottom: 2, paddingTop: 2, }); tooltip.label.set('fontFamily', FONT_FAMILY); return tooltip; } setGenericAxisProps(renderer, withGrid, withLabels) { // grid renderer.grid.template.setAll({ forceHidden: !withGrid, location: 1, stroke: am5.color(this.theme?.getRgb('color')), }); // labels renderer.labels.template.setAll({ forceHidden: !withLabels, fontFamily: FONT_FAMILY, fontSize: '0.7em', fill: am5.color(this.theme?.getRgb('color')), // minWidth: 10, // impacts space (width) for y-axis labels paddingTop: 7, }); } setAxisTitle(root, axis, xOrY, title = '') { if (title) { if (xOrY === 'x') { axis.children.push(am5.Label.new(root, { fill: am5.color(this.theme?.getRgb('color')), fontFamily: FONT_FAMILY, fontWeight: '500', centerX: am5.p50, text: title, x: am5.p50, })); } else { axis.children.unshift(am5.Label.new(root, { fill: am5.color(this.theme?.getRgb('color')), fontFamily: FONT_FAMILY, fontWeight: '500', centerX: am5.p50, rotation: -90, text: title, y: am5.p50, })); } } } updateAxisTitle(axis, newTitle) { // search for label (title) let title; for (const child of axis.children) { if (child instanceof Label) { title = child; break; } } if (title) { title.set('text', newTitle); } } // ******************************************************************************************************** // SERIES // ******************************************************************************************************** addSeries(am5XyContainer, xAxis, yAxis, config) { const seriesConfig = new Am5SeriesConfig(config); const seriesTooltip = am5.Tooltip.new(am5XyContainer.root, { autoTextColor: !seriesConfig.color, getFillFromSprite: !seriesConfig.color, labelText: seriesConfig.tooltipText || '[bold]{name}:[/] {valueY}', paddingTop: 4, paddingBottom: 4, }); seriesTooltip.label.set('fontFamily', FONT_FAMILY); seriesTooltip.get('background')?.setAll({ stroke: am5.color(this.theme?.getRgb('bg')), // the border of the tooltip }); const commonSeriesSettings = { name: seriesConfig.name, stacked: seriesConfig.stacked, xAxis: xAxis, yAxis: yAxis, tooltip: seriesConfig.withTooltip ? seriesTooltip : undefined, }; // special attributes for category series const hasCategoryAxis = xAxis instanceof CategoryAxis ? 'x' : yAxis instanceof CategoryAxis ? 'y' : ''; if (hasCategoryAxis) { if (hasCategoryAxis === 'x') { // category is on x-axis commonSeriesSettings.categoryXField = seriesConfig.xValue; commonSeriesSettings.valueYField = seriesConfig.yValue; } else { // category is on y-axis commonSeriesSettings.valueXField = seriesConfig.xValue; commonSeriesSettings.categoryYField = seriesConfig.yValue; } } else { // normal series (not category) commonSeriesSettings.valueXField = seriesConfig.xValue; commonSeriesSettings.valueYField = seriesConfig.yValue; commonSeriesSettings.openValueYField = seriesConfig.yOpenValue; } // if color is provided, set it if (seriesConfig.color) { // first the strokes and fills of the series itself commonSeriesSettings.stroke = am5.color(seriesConfig.color); if (seriesConfig.type === 'bar' || seriesConfig.yOpenValue) { commonSeriesSettings.fill = am5.color(seriesConfig.color); } // then of the tooltip seriesTooltip.get('background')?.setAll({ fill: am5.color(seriesConfig.color), }); seriesTooltip.label.setAll({ fill: am5.Color.alternative(am5.color(seriesConfig.color), am5.color('#f4f4f4'), am5.color('#121212')), }); } let series; switch (seriesConfig.type) { case 'line': series = am5xy.LineSeries.new(am5XyContainer.root, commonSeriesSettings); series.set('connect', false); this.setSeriesStrokeAndFill(seriesConfig, series, seriesTooltip); break; case 'bar': series = am5xy.ColumnSeries.new(am5XyContainer.root, commonSeriesSettings); series.columns.template.setAll({ fillOpacity: 0.4, strokeWidth: 1, width: am5.percent(80), }); break; case 'stepline': series = am5xy.StepLineSeries.new(am5XyContainer.root, commonSeriesSettings); this.setSeriesStrokeAndFill(seriesConfig, series, seriesTooltip); break; } // if date series, add a data processor if (seriesConfig.dateFormat) { const dateOnX = xAxis instanceof am5xy.DateAxis; series.data.processor = am5.DataProcessor.new(am5XyContainer.root, { numericFields: [dateOnX ? seriesConfig.yValue : seriesConfig.xValue], dateFields: [dateOnX ? seriesConfig.xValue : seriesConfig.yValue], dateFormat: seriesConfig.dateFormat, }); } am5XyContainer.chart.series.push(series); return series; } setSeriesStrokeAndFill(seriesConfig, series, seriesTooltip) { if (seriesConfig.yOpenValue) { // area series series.fills.template.setAll({ fillOpacity: 0.2, visible: true }); series.strokes.template.setAll({ visible: false, }); seriesTooltip.set('labelText', seriesConfig.tooltipText || '[bold]{name}:[/] {valueY} to {openValueY}'); } else { // normal line series series.strokes.template.setAll({ strokeWidth: seriesConfig.strokeWidth, }); } } // ******************************************************************************************************** // DATA & RANGES // ******************************************************************************************************** setDataForAll(am5XyContainer, data, axes = []) { // always take a copy in case there are data-processors (will mutate data) const chartData = UtilsService.copy(data); for (const series of am5XyContainer.chart.series) { series.data.setAll(chartData); } for (const axis of axes) { axis.data.setAll(chartData); } this.setDataForLegend(am5XyContainer); } setDataForSeries(series, data) { // always take a copy in case there are data-processors (will mutate data) const chartData = UtilsService.copy(data); series.data.setAll(chartData); } setDataForLegend(am5XyContainer) { // search for legend const legend = this.getLegend(am5XyContainer); if (legend) { legend.data.setAll(am5XyContainer.chart.series.values); } } getLegend(am5XyContainer) { // search for legend let legend; for (const child of am5XyContainer.chart.children) { if (child instanceof Legend) { legend = child; break; } } return legend; } addDateRange(dateAxis, config) { const rangeConfig = new Am5DateRangeConfig(config); if (rangeConfig.clearRanges) { dateAxis.axisRanges.clear(); } const dataItem = dateAxis.makeDataItem({ value: rangeConfig.beginDt.toJSDate().getTime(), endValue: rangeConfig.endDt.toJSDate().getTime(), }); const range = dateAxis.createAxisRange(dataItem); const defaultColor = this.theme?.getRgb('color', 0.6) || ''; range.get('axisFill')?.setAll({ fillOpacity: rangeConfig.fillOpacity, fill: am5.color(rangeConfig.fillColor || defaultColor), stroke: am5.color(rangeConfig.strokeColor || defaultColor), strokeOpacity: 1, strokeDasharray: rangeConfig.strokeDasharray, strokeWidth: rangeConfig.strokeWidth, visible: true }); return range; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: Am5Service, deps: [{ token: i1.ThemeService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: Am5Service, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: Am5Service, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i1.ThemeService }] }); class Am5PieService { constructor(am5Service) { this.am5Service = am5Service; this.onDestroy$ = new Subject(); } ngOnDestroy() { this.onDestroy$.next(); this.onDestroy$.complete(); } // ******************************************************************************************************** // CHART CREATION/DESTRUCTION // ******************************************************************************************************** // create xy chart createPieChart(chartDiv, config, innerRadiusPercent = 0) { const rootConfig = new Am5RootConfig(config || {}); const root = am5.Root.new(chartDiv, { tooltipContainerBounds: rootConfig.tooltipContainerBounds, }); let chart = root.container.children.push(am5percent.PieChart.new(root, { innerRadius: innerRadiusPercent ? am5.percent(innerRadiusPercent) : 0, layout: root.verticalLayout, })); this.am5Service.createLegend({ root: root, chart: chart }, rootConfig.withLegend, rootConfig.legendWithoutValueLabels); return { root: root, chart: chart }; } // ******************************************************************************************************** // SERIES // ******************************************************************************************************** addSeries(am5PieContainer, config, withLabels = true, withBorders = true) { const theme = this.am5Service.getTheme(); const seriesConfig = new Am5PieSeriesConfig(config); const commonSeriesSettings = { name: seriesConfig.name, categoryField: seriesConfig.xValue, valueField: seriesConfig.yValue, }; const series = am5percent.PieSeries.new(am5PieContainer.root, commonSeriesSettings); // tooltip if (seriesConfig.withTooltip) { const seriesTooltip = am5.Tooltip.new(am5PieContainer.root, { labelText: seriesConfig.tooltipText || '[bold]{category}:[/] {value}', paddingTop: 4, paddingBottom: 4, }); seriesTooltip.label.set('fontFamily', FONT_FAMILY); seriesTooltip.get('background')?.setAll({ stroke: am5.color(theme?.getRgb('bg')), // the border of the tooltip }); series.slices.template.set('tooltip', seriesTooltip); } else { series.slices.template.set('tooltipText', ''); } // configure labels and ticks series.labels.template.setAll({ fill: am5.color(theme?.getRgb('color')), fontFamily: FONT_FAMILY, visible: withLabels, }); series.ticks.template.setAll({ stroke: am5.color(theme?.getRgb('color')), strokeWidth: 2, visible: withLabels, }); // and borders if needed if (withBorders) { series.slices.template.setAll({ stroke: am5.color(theme?.getRgb('bg')), strokeWidth: 2, }); } am5PieContainer.chart.series.push(series); return series; } // ******************************************************************************************************** // DATA & RANGES // ******************************************************************************************************** setData(am5PieContainer, data) { for (const series of am5PieContainer.chart.series) { series.data.setAll(data); } this.setDataForLegend(am5PieContainer); } setDataForLegend(am5Container) { // search for legend let legend; for (const child of am5Container.chart.children) { if (child instanceof Legend) { legend = child; break; } } if (legend) { const series = am5Container.chart.series.getIndex(0); if (series) { legend.data.setAll(series.dataItems); } } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: Am5PieService, deps: [{ token: Am5Service }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: Am5PieService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: Am5PieService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: Am5Service }] }); // SERVICES /** * Generated bundle index. Do not edit. */ export { Am5AxisConfig, Am5DateRangeConfig, Am5PieSeriesConfig, Am5PieService, Am5RootConfig, Am5SeriesConfig, Am5Service, DEFAULT_DATE_FORMAT }; //# sourceMappingURL=engie-group-ngx-gem-spaas-am5.mjs.map