1 | import {
|
2 | OnDestroy,
|
3 | OnInit,
|
4 | OnChanges,
|
5 | EventEmitter,
|
6 | ElementRef,
|
7 | Input,
|
8 | Output,
|
9 | NgModule,
|
10 | SimpleChanges,
|
11 | Directive
|
12 | } from '@angular/core';
|
13 |
|
14 | import { Chart } from 'chart.js';
|
15 |
|
16 |
|
17 | @Directive({selector: 'canvas[baseChart]', exportAs: 'base-chart'})
|
18 | export class BaseChartDirective implements OnDestroy, OnChanges, OnInit {
|
19 | public static defaultColors:Array<number[]> = [
|
20 | [255, 99, 132],
|
21 | [54, 162, 235],
|
22 | [255, 206, 86],
|
23 | [231, 233, 237],
|
24 | [75, 192, 192],
|
25 | [151, 187, 205],
|
26 | [220, 220, 220],
|
27 | [247, 70, 74],
|
28 | [70, 191, 189],
|
29 | [253, 180, 92],
|
30 | [148, 159, 177],
|
31 | [77, 83, 96]
|
32 | ];
|
33 |
|
34 | @Input() public data:number[] | any[];
|
35 | @Input() public datasets:any[];
|
36 | @Input() public labels:Array<any> = [];
|
37 | @Input() public options:any = {};
|
38 | @Input() public chartType:string;
|
39 | @Input() public colors:Array<any>;
|
40 | @Input() public legend:boolean;
|
41 |
|
42 | @Output() public chartClick:EventEmitter<any> = new EventEmitter();
|
43 | @Output() public chartHover:EventEmitter<any> = new EventEmitter();
|
44 |
|
45 | public ctx:any;
|
46 | public chart:any;
|
47 | private cvs:any;
|
48 | private initFlag:boolean = false;
|
49 |
|
50 | private element:ElementRef;
|
51 |
|
52 | public constructor(element:ElementRef) {
|
53 | this.element = element;
|
54 | }
|
55 |
|
56 | public ngOnInit():any {
|
57 | this.ctx = this.element.nativeElement.getContext('2d');
|
58 | this.cvs = this.element.nativeElement;
|
59 | this.initFlag = true;
|
60 | if (this.data || this.datasets) {
|
61 | this.refresh();
|
62 | }
|
63 | }
|
64 |
|
65 | public ngOnChanges(changes: SimpleChanges): void {
|
66 | if (this.initFlag) {
|
67 |
|
68 | if (changes.hasOwnProperty('data') || changes.hasOwnProperty('datasets')) {
|
69 | this.refreshZero();
|
70 | } else {
|
71 |
|
72 | this.refresh();
|
73 | }
|
74 | }
|
75 | }
|
76 |
|
77 | public ngOnDestroy():any {
|
78 | if (this.chart) {
|
79 | this.chart.destroy();
|
80 | this.chart = void 0;
|
81 | }
|
82 | }
|
83 |
|
84 | public getChartBuilder(ctx:any):any {
|
85 | let datasets:any = this.getDatasets();
|
86 |
|
87 | let options:any = Object.assign({}, this.options);
|
88 | if (this.legend === false) {
|
89 | options.legend = {display: false};
|
90 | }
|
91 |
|
92 | options.hover = options.hover || {};
|
93 | if (!options.hover.onHover) {
|
94 | options.hover.onHover = (active:Array<any>) => {
|
95 | if (active && !active.length) {
|
96 | return;
|
97 | }
|
98 | this.chartHover.emit({active});
|
99 | };
|
100 | }
|
101 |
|
102 | if (!options.onClick) {
|
103 | options.onClick = (event:any, active:Array<any>) => {
|
104 | this.chartClick.emit({event, active});
|
105 | };
|
106 | }
|
107 |
|
108 | let opts = {
|
109 | type: this.chartType,
|
110 | data: {
|
111 | labels: this.labels,
|
112 | datasets: datasets
|
113 | },
|
114 | options: options
|
115 | };
|
116 |
|
117 | return new Chart(ctx, opts);
|
118 | }
|
119 |
|
120 | private updateChartData(newDataValues: number[] | any[]): void {
|
121 | if (Array.isArray(newDataValues[0].data)) {
|
122 | this.chart.data.datasets.forEach((dataset: any, i: number) => {
|
123 | dataset.data = newDataValues[i].data;
|
124 |
|
125 | if (newDataValues[i].label) {
|
126 | dataset.label = newDataValues[i].label;
|
127 | }
|
128 | });
|
129 | } else {
|
130 | this.chart.data.datasets[0].data = newDataValues;
|
131 | }
|
132 | }
|
133 |
|
134 | private getDatasets():any {
|
135 | let datasets:any = void 0;
|
136 |
|
137 | if (!this.datasets || !this.datasets.length && (this.data && this.data.length)) {
|
138 | if (Array.isArray(this.data[0])) {
|
139 | datasets = (this.data as Array<number[]>).map((data:number[], index:number) => {
|
140 | return {data, label: this.labels[index] || `Label ${index}`};
|
141 | });
|
142 | } else {
|
143 | datasets = [{data: this.data, label: `Label 0`}];
|
144 | }
|
145 | }
|
146 |
|
147 | if (this.datasets && this.datasets.length ||
|
148 | (datasets && datasets.length)) {
|
149 | datasets = (this.datasets || datasets)
|
150 | .map((elm:number, index:number) => {
|
151 | let newElm:any = Object.assign({}, elm);
|
152 | if (this.colors && this.colors.length) {
|
153 | Object.assign(newElm, formatColors(this.chartType, index, newElm.data.length, this.colors));
|
154 | } else {
|
155 | Object.assign(newElm, getColors(this.chartType, index, newElm.data.length));
|
156 | }
|
157 | return newElm;
|
158 | });
|
159 | }
|
160 |
|
161 | if (!datasets) {
|
162 | throw new Error(`ng-charts configuration error,
|
163 | data or datasets field are required to render char ${this.chartType}`);
|
164 | }
|
165 |
|
166 | return datasets;
|
167 | }
|
168 |
|
169 | private refresh():any {
|
170 | // if (this.options && this.options.responsive) {
|
171 | // setTimeout(() => this.refresh(), 50);
|
172 | // }
|
173 |
|
174 | // todo: remove this line, it is producing flickering
|
175 | this.ngOnDestroy();
|
176 | this.chart = this.getChartBuilder(this.ctx);
|
177 | }
|
178 |
|
179 | private refreshZero():any {
|
180 | this.ngOnDestroy();
|
181 | let datasets:any = this.getDatasets();
|
182 | let options:any = Object.assign({}, this.options);
|
183 |
|
184 | if (this.legend === false) {
|
185 | options.legend = {display: false};
|
186 | }
|
187 | // hock for onHover and onClick events
|
188 | options.hover = options.hover || {};
|
189 | if (!options.hover.onHover) {
|
190 | options.hover.onHover = (active:Array<any>) => {
|
191 | if (active && !active.length) {
|
192 | return;
|
193 | }
|
194 | this.chartHover.emit({active});
|
195 | };
|
196 | }
|
197 |
|
198 | if (!options.onClick) {
|
199 | options.onClick = (event:any, active:Array<any>) => {
|
200 | this.chartClick.emit({event, active});
|
201 | };
|
202 | }
|
203 |
|
204 | options['animations'] = {
|
205 | duration: 0
|
206 | }
|
207 |
|
208 | let opts = {
|
209 | type: this.chartType,
|
210 | data: {
|
211 | labels: this.labels,
|
212 | datasets: datasets
|
213 | },
|
214 | options: options
|
215 | };
|
216 |
|
217 | return new Chart(this.ctx, opts);
|
218 | }
|
219 | }
|
220 |
|
221 |
|
222 | export interface Color {
|
223 | backgroundColor?:string | string[];
|
224 | borderWidth?:number | number[];
|
225 | borderColor?:string | string[];
|
226 | borderCapStyle?:string;
|
227 | borderDash?:number[];
|
228 | borderDashOffset?:number;
|
229 | borderJoinStyle?:string;
|
230 |
|
231 | pointBorderColor?:string | string[];
|
232 | pointBackgroundColor?:string | string[];
|
233 | pointBorderWidth?:number | number[];
|
234 |
|
235 | pointRadius?:number | number[];
|
236 | pointHoverRadius?:number | number[];
|
237 | pointHitRadius?:number | number[];
|
238 |
|
239 | pointHoverBackgroundColor?:string | string[];
|
240 | pointHoverBorderColor?:string | string[];
|
241 | pointHoverBorderWidth?:number | number[];
|
242 | pointStyle?:string | string[];
|
243 |
|
244 | hoverBackgroundColor?:string | string[];
|
245 | hoverBorderColor?:string | string[];
|
246 | hoverBorderWidth?:number;
|
247 | }
|
248 |
|
249 |
|
250 | export interface Colors extends Color {
|
251 | data?:number[];
|
252 | label?:string;
|
253 | }
|
254 |
|
255 | function rgba(colour:Array<number>, alpha:number):string {
|
256 | return 'rgba(' + colour.concat(alpha).join(',') + ')';
|
257 | }
|
258 |
|
259 | function getRandomInt(min:number, max:number):number {
|
260 | return Math.floor(Math.random() * (max - min + 1)) + min;
|
261 | }
|
262 |
|
263 | function formatLineColor(colors:Array<number>):Color {
|
264 | return {
|
265 | backgroundColor: rgba(colors, 0.4),
|
266 | borderColor: rgba(colors, 1),
|
267 | pointBackgroundColor: rgba(colors, 1),
|
268 | pointBorderColor: '#fff',
|
269 | pointHoverBackgroundColor: '#fff',
|
270 | pointHoverBorderColor: rgba(colors, 0.8)
|
271 | };
|
272 | }
|
273 |
|
274 | function formatBarColor(colors:Array<number>):Color {
|
275 | return {
|
276 | backgroundColor: rgba(colors, 0.6),
|
277 | borderColor: rgba(colors, 1),
|
278 | hoverBackgroundColor: rgba(colors, 0.8),
|
279 | hoverBorderColor: rgba(colors, 1)
|
280 | };
|
281 | }
|
282 |
|
283 | function formatPieColors(colors:Array<number[]>):Colors {
|
284 | return {
|
285 | backgroundColor: colors.map((color:number[]) => rgba(color, 0.6)),
|
286 | borderColor: colors.map(() => '#fff'),
|
287 | pointBackgroundColor: colors.map((color:number[]) => rgba(color, 1)),
|
288 | pointBorderColor: colors.map(() => '#fff'),
|
289 | pointHoverBackgroundColor: colors.map((color:number[]) => rgba(color, 1)),
|
290 | pointHoverBorderColor: colors.map((color:number[]) => rgba(color, 1))
|
291 | };
|
292 | }
|
293 |
|
294 | function formatPolarAreaColors(colors:Array<number[]>):Color {
|
295 | return {
|
296 | backgroundColor: colors.map((color:number[]) => rgba(color, 0.6)),
|
297 | borderColor: colors.map((color:number[]) => rgba(color, 1)),
|
298 | hoverBackgroundColor: colors.map((color:number[]) => rgba(color, 0.8)),
|
299 | hoverBorderColor: colors.map((color:number[]) => rgba(color, 1))
|
300 | };
|
301 | }
|
302 |
|
303 | function getRandomColor():number[] {
|
304 | return [getRandomInt(0, 255), getRandomInt(0, 255), getRandomInt(0, 255)];
|
305 | }
|
306 |
|
307 |
|
308 |
|
309 |
|
310 |
|
311 |
|
312 | function generateColor(index:number):number[] {
|
313 | return BaseChartDirective.defaultColors[index] || getRandomColor();
|
314 | }
|
315 |
|
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 | function generateColors(count:number):Array<number[]> {
|
322 | let colorsArr:Array<number[]> = new Array(count);
|
323 | for (let i = 0; i < count; i++) {
|
324 | colorsArr[i] = BaseChartDirective.defaultColors[i] || getRandomColor();
|
325 | }
|
326 | return colorsArr;
|
327 | }
|
328 |
|
329 |
|
330 |
|
331 |
|
332 |
|
333 |
|
334 |
|
335 |
|
336 | function formatColors(chartType:string, index:number, count:number, colors: any[]) {
|
337 | if (chartType === 'pie' || chartType === 'doughnut') {
|
338 | return formatPieColors(colors);
|
339 | }
|
340 |
|
341 | if (chartType === 'polarArea') {
|
342 | return formatPolarAreaColors(colors);
|
343 | }
|
344 |
|
345 | if (chartType === 'line' || chartType === 'radar') {
|
346 | return formatLineColor(colors[index]);
|
347 | }
|
348 |
|
349 | if (chartType === 'bar' || chartType === 'horizontalBar') {
|
350 | return formatBarColor(colors[index]);
|
351 | }
|
352 | return colors[index];
|
353 | }
|
354 |
|
355 |
|
356 |
|
357 |
|
358 |
|
359 |
|
360 |
|
361 |
|
362 | function getColors(chartType:string, index:number, count:number):Color {
|
363 | if (chartType === 'pie' || chartType === 'doughnut') {
|
364 | return formatPieColors(generateColors(count));
|
365 | }
|
366 |
|
367 | if (chartType === 'polarArea') {
|
368 | return formatPolarAreaColors(generateColors(count));
|
369 | }
|
370 |
|
371 | if (chartType === 'line' || chartType === 'radar') {
|
372 | return formatLineColor(generateColor(index));
|
373 | }
|
374 |
|
375 | if (chartType === 'bar' || chartType === 'horizontalBar') {
|
376 | return formatBarColor(generateColor(index));
|
377 | }
|
378 | return generateColor(index);
|
379 | }
|
380 |
|
381 | @NgModule({
|
382 | declarations: [
|
383 | BaseChartDirective
|
384 | ],
|
385 | exports: [
|
386 | BaseChartDirective
|
387 | ],
|
388 | imports: []
|
389 | })
|
390 | export class NgChartModule {
|
391 | }
|