UNPKG

maneric-charts

Version:

This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.3.0.

307 lines (298 loc) 12.4 kB
import * as i0 from '@angular/core'; import { Injectable, EventEmitter, Output, Input, ViewChild, Component } from '@angular/core'; import { Chart, registerables } from 'chart.js'; import ChartDataLabels from 'chartjs-plugin-datalabels'; import { merge } from 'lodash'; import { CommonModule } from '@angular/common'; const defaultCenterTextPluginOptions = { fontSize: 20, fontColor: '#333', }; const centerTextPlugin = { id: 'centerText', afterDraw(chart, args, options) { const { ctx, chartArea: { left, right, top, bottom }, } = chart; const opt = { ...defaultCenterTextPluginOptions, ...options }; const { fontSize, fontColor, subTitle, text, prefix } = opt; const xCenter = (left + right) / 2; const yCenter = (top + bottom) / 2; const tFontSize = fontSize ?? 24; ctx.save(); const dataset = chart.data.datasets[0].data; const total = text ?? `${dataset.reduce((a, b) => a + b, 0)}`; const totalWithPrefix = prefix ? `${prefix}${total}` : total; ctx.font = `bold ${tFontSize}px Arial`; ctx.fillStyle = fontColor; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(totalWithPrefix, xCenter, yCenter); if (subTitle) { ctx.font = '12px Arial'; ctx.fillStyle = fontColor; ctx.fillText(subTitle, xCenter, (top + bottom) / 2 + tFontSize / 2); } ctx.restore(); }, }; class ChartColorService { // Fallback palettes category10 = [ '#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf', ]; pastel = [ '#aec7e8', '#ffbb78', '#98df8a', '#ff9896', '#c5b0d5', '#c49c94', '#f7b6d2', '#c7c7c7', '#dbdb8d', '#9edae5', ]; getForegroundColor() { const container = document.querySelector('.app-container'); const styles = getComputedStyle(container); return styles.getPropertyValue('--chart-foreground').trim() || '#fff'; } /** * Returns an array of colors for charts based on scheme and theme */ getColors(scheme, count, customColors) { switch (scheme) { case 'material': return this.expandMaterialColors(count); case 'category10': return this.repeatPalette(this.category10, count); case 'pastel': return this.repeatPalette(this.pastel, count); case 'custom': return this.repeatPalette(customColors ?? [], count); default: return this.repeatPalette(this.category10, count); } } /** * Expand Angular Material theme colors into multiple shades */ expandMaterialColors(count) { const container = document.querySelector('.app-container'); const styles = getComputedStyle(container); const baseColors = [ styles.getPropertyValue('--chart-primary').trim() || '#1976d2', styles.getPropertyValue('--chart-accent').trim() || '#9c27b0', styles.getPropertyValue('--chart-warn').trim() || '#009688', ]; // Generate shades for each base color const expanded = baseColors.flatMap((color) => this.generateShades(color, 3)); return this.repeatPalette(expanded, count); } /** * Generate lighter/darker shades of a base color */ generateShades(hex, levels) { const shades = []; for (let i = -levels; i <= levels; i++) { shades.push(this.adjustLightness(hex, i * 15)); } return shades; } /** * Adjust lightness of hex color */ adjustLightness(hex, percent) { const num = parseInt(hex.replace('#', ''), 16); let r = (num >> 16) + percent; let g = ((num >> 8) & 0x00ff) + percent; let b = (num & 0x0000ff) + percent; r = Math.min(255, Math.max(0, r)); g = Math.min(255, Math.max(0, g)); b = Math.min(255, Math.max(0, b)); return `#${(b | (g << 8) | (r << 16)).toString(16).padStart(6, '0')}`; } /** * Repeat palette until count is satisfied */ repeatPalette(palette, count) { const result = []; for (let i = 0; i < count; i++) { result.push(palette[i % palette.length]); } return result; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: ChartColorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: ChartColorService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: ChartColorService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); Chart.register(...registerables, ChartDataLabels, centerTextPlugin); class BaseChartComponent { colorService; canvas; labels = []; datasets = []; title; options; colorScheme = 'material'; colors; pointClick = new EventEmitter(); chartInit = new EventEmitter(); chart; target = document.body.getElementsByTagName('div')[0]; constructor(colorService) { this.colorService = colorService; // Watch for theme changes (when Angular Material theme switches dynamically) const observer = new MutationObserver(() => { console.log('trigger update chart'); this.updateChart(); }); observer.observe(this.target, { attributes: true, attributeFilter: ['class'] }); } ngAfterViewInit() { this.renderChart(); } ngOnChanges(changes) { if (this.chart && (changes['datasets'] || changes['labels'] || changes['colors'])) { this.updateChart(); } } renderChart() { const ctx = this.canvas.nativeElement.getContext('2d'); if (!ctx) return; const datasetColors = this.colorService.getColors(this.colorScheme, this.datasets.length, this.colors); const styledDatasets = this.datasets.map((d, i) => { const backgroundColor = this.colorService.getColors(this.colorScheme, d.data.length, this.colors); return { ...d, backgroundColor, borderColor: d.borderColor, }; }); const config = this.buildConfig(styledDatasets); if (!this.chart) { this.chart = new Chart(ctx, config); } else { this.chart = merge(this.chart, { data: merge(this.chart.data, { datasets: styledDatasets, }), options: config.options, }); this.chart?.update(); } this.chartInit.emit(this.chart); } buildConfig(styledDatasets) { const foregroundColor = this.colorService.getForegroundColor(); console.log('foregroundColor', foregroundColor); const options = merge({ responsive: true, plugins: { legend: { labels: { color: foregroundColor, }, }, centerText: { fontColor: foregroundColor, }, }, onClick: (_e, elements) => { if (elements.length > 0) { const first = elements[0]; const datasetIndex = first.datasetIndex; const index = first.index; const label = this.labels[index]; const value = this.datasets[datasetIndex].data[index]; this.pointClick.emit({ label, value }); } }, }, this.options); return { type: this.getChartType(), data: { labels: this.labels, datasets: styledDatasets, }, options, }; } updateChart() { this.renderChart(); } ngOnDestroy() { this.chart?.destroy(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: BaseChartComponent, deps: [{ token: ChartColorService }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.0", type: BaseChartComponent, isStandalone: true, selector: "ng-component", inputs: { labels: "labels", datasets: "datasets", title: "title", options: "options", colorScheme: "colorScheme", colors: "colors" }, outputs: { pointClick: "pointClick", chartInit: "chartInit" }, viewQueries: [{ propertyName: "canvas", first: true, predicate: ["canvas"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: '', isInline: true }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: BaseChartComponent, decorators: [{ type: Component, args: [{ template: '', // Extended by child components }] }], ctorParameters: () => [{ type: ChartColorService }], propDecorators: { canvas: [{ type: ViewChild, args: ['canvas', { static: true }] }], labels: [{ type: Input }], datasets: [{ type: Input }], title: [{ type: Input }], options: [{ type: Input }], colorScheme: [{ type: Input }], colors: [{ type: Input }], pointClick: [{ type: Output }], chartInit: [{ type: Output }] } }); class BarChartComponent extends BaseChartComponent { getChartType() { return 'bar'; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: BarChartComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.0", type: BarChartComponent, isStandalone: true, selector: "bar-chart", usesInheritance: true, ngImport: i0, template: "<canvas #canvas></canvas>", styles: [":host .chart-container{display:block;position:relative;width:100%;height:400px}\n"] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: BarChartComponent, decorators: [{ type: Component, args: [{ selector: 'bar-chart', template: "<canvas #canvas></canvas>", styles: [":host .chart-container{display:block;position:relative;width:100%;height:400px}\n"] }] }] }); class DonutChartComponent extends BaseChartComponent { getChartType() { return 'doughnut'; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: DonutChartComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.0", type: DonutChartComponent, isStandalone: true, selector: "donut-chart", usesInheritance: true, ngImport: i0, template: "<canvas #canvas></canvas>", styles: [":host .chart-container{display:block;position:relative;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: DonutChartComponent, decorators: [{ type: Component, args: [{ selector: 'donut-chart', standalone: true, imports: [CommonModule], template: "<canvas #canvas></canvas>", styles: [":host .chart-container{display:block;position:relative;width:100%}\n"] }] }] }); /* * Public API Surface of maneric-charts */ // export * from './lib/maneric-charts'; /** * Generated bundle index. Do not edit. */ export { BarChartComponent, BaseChartComponent, DonutChartComponent, centerTextPlugin }; //# sourceMappingURL=maneric-charts.mjs.map