UNPKG

10.3 kBPlain TextView Raw
1import {
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
14import { Chart } from 'chart.js';
15
16/* tslint:disable-next-line */
17@Directive({selector: 'canvas[baseChart]', exportAs: 'base-chart'})
18export 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 // Check if the changes are in the data or datasets
68 if (changes.hasOwnProperty('data') || changes.hasOwnProperty('datasets')) {
69 this.refreshZero();
70 } else {
71 // otherwise rebuild the chart
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/*, data:Array<any>, options: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 // hock for onHover and onClick events
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 // in case if datasets is not provided, but data is present
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/*, data, this.options*/);
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// private helper functions
222export 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// pie | doughnut
250export interface Colors extends Color {
251 data?:number[];
252 label?:string;
253}
254
255function rgba(colour:Array<number>, alpha:number):string {
256 return 'rgba(' + colour.concat(alpha).join(',') + ')';
257}
258
259function getRandomInt(min:number, max:number):number {
260 return Math.floor(Math.random() * (max - min + 1)) + min;
261}
262
263function 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
274function 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
283function 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
294function 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
303function getRandomColor():number[] {
304 return [getRandomInt(0, 255), getRandomInt(0, 255), getRandomInt(0, 255)];
305}
306
307/**
308 * Generate colors for line|bar charts
309 * @param index
310 * @returns {number[]|Color}
311 */
312function generateColor(index:number):number[] {
313 return BaseChartDirective.defaultColors[index] || getRandomColor();
314}
315
316/**
317 * Generate colors for pie|doughnut charts
318 * @param count
319 * @returns {Colors}
320 */
321function 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 * Format chart colors when supplied
331 * @param chartType
332 * @param index
333 * @param count
334 * @returns {Color}
335 */
336function 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 * Generate colors by chart type
357 * @param chartType
358 * @param index
359 * @param count
360 * @returns {Color}
361 */
362function 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})
390export class NgChartModule {
391}