@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
JavaScript
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