UNPKG

ng2-charts

Version:

Reactive, responsive, beautiful charts for Angular2 based on Chart.js

1,512 lines (1,504 loc) 56.1 kB
import { Injectable, ɵɵdefineInjectable, EventEmitter, Directive, ElementRef, Input, Output, NgModule } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; import { cloneDeep } from 'lodash-es'; import { pluginService, Chart as Chart$1 } from 'chart.js'; /** * @fileoverview added by tsickle * Generated from: lib/default-colors.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @type {?} */ const defaultColors = [ [255, 99, 132], [54, 162, 235], [255, 206, 86], [231, 233, 237], [75, 192, 192], [151, 187, 205], [220, 220, 220], [247, 70, 74], [70, 191, 189], [253, 180, 92], [148, 159, 177], [77, 83, 96] ]; /** * @fileoverview added by tsickle * Generated from: lib/get-colors.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Generate colors by chart type * @param {?} chartType * @param {?} index * @param {?} count * @return {?} */ function getColors(chartType, index, count) { if (chartType === 'pie' || chartType === 'doughnut') { return formatPieColors(generateColors(count)); } if (chartType === 'polarArea') { return formatPolarAreaColors(generateColors(count)); } if (chartType === 'line' || chartType === 'radar') { return formatLineColor(generateColor(index)); } if (chartType === 'bar' || chartType === 'horizontalBar') { return formatBarColor(generateColor(index)); } if (chartType === 'bubble') { return formatPieColors(generateColors(count)); } if (chartType === 'scatter') { return formatPieColors(generateColors(count)); } throw new Error(`getColors - Unsupported chart type ${chartType}`); } /** * @param {?} colour * @param {?} alpha * @return {?} */ function rgba(colour, alpha) { return 'rgba(' + colour.concat(alpha).join(',') + ')'; } /** * @param {?} min * @param {?} max * @return {?} */ function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } /** * @param {?} colors * @return {?} */ function formatLineColor(colors) { return { backgroundColor: rgba(colors, 0.4), borderColor: rgba(colors, 1), pointBackgroundColor: rgba(colors, 1), pointBorderColor: '#fff', pointHoverBackgroundColor: '#fff', pointHoverBorderColor: rgba(colors, 0.8) }; } /** * @param {?} colors * @return {?} */ function formatBarColor(colors) { return { backgroundColor: rgba(colors, 0.6), borderColor: rgba(colors, 1), hoverBackgroundColor: rgba(colors, 0.8), hoverBorderColor: rgba(colors, 1) }; } /** * @param {?} colors * @return {?} */ function formatPieColors(colors) { return { backgroundColor: colors.map((/** * @param {?} color * @return {?} */ (color) => rgba(color, 0.6))), borderColor: colors.map((/** * @return {?} */ () => '#fff')), pointBackgroundColor: colors.map((/** * @param {?} color * @return {?} */ (color) => rgba(color, 1))), pointBorderColor: colors.map((/** * @return {?} */ () => '#fff')), pointHoverBackgroundColor: colors.map((/** * @param {?} color * @return {?} */ (color) => rgba(color, 1))), pointHoverBorderColor: colors.map((/** * @param {?} color * @return {?} */ (color) => rgba(color, 1))) }; } /** * @param {?} colors * @return {?} */ function formatPolarAreaColors(colors) { return { backgroundColor: colors.map((/** * @param {?} color * @return {?} */ (color) => rgba(color, 0.6))), borderColor: colors.map((/** * @param {?} color * @return {?} */ (color) => rgba(color, 1))), hoverBackgroundColor: colors.map((/** * @param {?} color * @return {?} */ (color) => rgba(color, 0.8))), hoverBorderColor: colors.map((/** * @param {?} color * @return {?} */ (color) => rgba(color, 1))) }; } /** * @return {?} */ function getRandomColor() { return [getRandomInt(0, 255), getRandomInt(0, 255), getRandomInt(0, 255)]; } /** * Generate colors for line|bar charts * @param {?} index * @return {?} */ function generateColor(index) { return defaultColors[index] || getRandomColor(); } /** * Generate colors for pie|doughnut charts * @param {?} count * @return {?} */ function generateColors(count) { /** @type {?} */ const colorsArr = new Array(count); for (let i = 0; i < count; i++) { colorsArr[i] = defaultColors[i] || getRandomColor(); } return colorsArr; } /** * @fileoverview added by tsickle * Generated from: lib/theme.service.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class ThemeService { constructor() { this.pColorschemesOptions = {}; this.colorschemesOptions = new BehaviorSubject({}); } /** * @param {?} options * @return {?} */ setColorschemesOptions(options) { this.pColorschemesOptions = options; this.colorschemesOptions.next(options); } /** * @return {?} */ getColorschemesOptions() { return this.pColorschemesOptions; } } ThemeService.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] } ]; /** @nocollapse */ ThemeService.ctorParameters = () => []; /** @nocollapse */ ThemeService.ɵprov = ɵɵdefineInjectable({ factory: function ThemeService_Factory() { return new ThemeService(); }, token: ThemeService, providedIn: "root" }); if (false) { /** * @type {?} * @private */ ThemeService.prototype.pColorschemesOptions; /** @type {?} */ ThemeService.prototype.colorschemesOptions; } /** * @fileoverview added by tsickle * Generated from: lib/base-chart.directive.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @record */ function OldState() { } if (false) { /** @type {?} */ OldState.prototype.dataExists; /** @type {?} */ OldState.prototype.dataLength; /** @type {?} */ OldState.prototype.datasetsExists; /** @type {?} */ OldState.prototype.datasetsLength; /** @type {?} */ OldState.prototype.datasetsDataObjects; /** @type {?} */ OldState.prototype.datasetsDataLengths; /** @type {?} */ OldState.prototype.colorsExists; /** @type {?} */ OldState.prototype.colors; /** @type {?} */ OldState.prototype.labelsExist; /** @type {?} */ OldState.prototype.labels; /** @type {?} */ OldState.prototype.legendExists; /** @type {?} */ OldState.prototype.legend; } /** @enum {number} */ const UpdateType = { Default: 0, Update: 1, Refresh: 2, }; UpdateType[UpdateType.Default] = 'Default'; UpdateType[UpdateType.Update] = 'Update'; UpdateType[UpdateType.Refresh] = 'Refresh'; class BaseChartDirective { /** * @param {?} element * @param {?} themeService */ constructor(element, themeService) { this.element = element; this.themeService = themeService; this.options = {}; this.chartClick = new EventEmitter(); this.chartHover = new EventEmitter(); this.old = { dataExists: false, dataLength: 0, datasetsExists: false, datasetsLength: 0, datasetsDataObjects: [], datasetsDataLengths: [], colorsExists: false, colors: [], labelsExist: false, labels: [], legendExists: false, legend: {}, }; this.subs = []; } /** * Register a plugin. * @param {?} plugin * @return {?} */ static registerPlugin(plugin) { pluginService.register(plugin); } /** * @param {?} plugin * @return {?} */ static unregisterPlugin(plugin) { pluginService.unregister(plugin); } /** * @return {?} */ ngOnInit() { this.ctx = this.element.nativeElement.getContext('2d'); this.refresh(); this.subs.push(this.themeService.colorschemesOptions.subscribe((/** * @param {?} r * @return {?} */ r => this.themeChanged(r)))); } /** * @private * @param {?} options * @return {?} */ themeChanged(options) { this.refresh(); } /** * @return {?} */ ngDoCheck() { if (!this.chart) { return; } /** @type {?} */ let updateRequired = UpdateType.Default; /** @type {?} */ const wantUpdate = (/** * @param {?} x * @return {?} */ (x) => { updateRequired = x > updateRequired ? x : updateRequired; }); if (!!this.data !== this.old.dataExists) { this.propagateDataToDatasets(this.data); this.old.dataExists = !!this.data; wantUpdate(UpdateType.Update); } if (this.data && this.data.length !== this.old.dataLength) { this.old.dataLength = this.data && this.data.length || 0; wantUpdate(UpdateType.Update); } if (!!this.datasets !== this.old.datasetsExists) { this.old.datasetsExists = !!this.datasets; wantUpdate(UpdateType.Update); } if (this.datasets && this.datasets.length !== this.old.datasetsLength) { this.old.datasetsLength = this.datasets && this.datasets.length || 0; wantUpdate(UpdateType.Update); } if (this.datasets && this.datasets.filter((/** * @param {?} x * @param {?} i * @return {?} */ (x, i) => x.data !== this.old.datasetsDataObjects[i])).length) { this.old.datasetsDataObjects = this.datasets.map((/** * @param {?} x * @return {?} */ x => x.data)); wantUpdate(UpdateType.Update); } if (this.datasets && this.datasets.filter((/** * @param {?} x * @param {?} i * @return {?} */ (x, i) => x.data.length !== this.old.datasetsDataLengths[i])).length) { this.old.datasetsDataLengths = this.datasets.map((/** * @param {?} x * @return {?} */ x => x.data.length)); wantUpdate(UpdateType.Update); } if (!!this.colors !== this.old.colorsExists) { this.old.colorsExists = !!this.colors; this.updateColors(); wantUpdate(UpdateType.Update); } // This smells of inefficiency, might need to revisit this if (this.colors && this.colors.filter((/** * @param {?} x * @param {?} i * @return {?} */ (x, i) => !this.colorsEqual(x, this.old.colors[i]))).length) { this.old.colors = this.colors.map((/** * @param {?} x * @return {?} */ x => this.copyColor(x))); this.updateColors(); wantUpdate(UpdateType.Update); } if (!!this.labels !== this.old.labelsExist) { this.old.labelsExist = !!this.labels; wantUpdate(UpdateType.Update); } if (this.labels && this.labels.filter((/** * @param {?} x * @param {?} i * @return {?} */ (x, i) => !this.labelsEqual(x, this.old.labels[i]))).length) { this.old.labels = this.labels.map((/** * @param {?} x * @return {?} */ x => this.copyLabel(x))); wantUpdate(UpdateType.Update); } if (!!this.options.legend !== this.old.legendExists) { this.old.legendExists = !!this.options.legend; wantUpdate(UpdateType.Refresh); } if (this.options.legend && this.options.legend.position !== this.old.legend.position) { this.old.legend.position = this.options.legend.position; wantUpdate(UpdateType.Refresh); } switch ((/** @type {?} */ (updateRequired))) { case UpdateType.Default: break; case UpdateType.Update: this.update(); break; case UpdateType.Refresh: this.refresh(); break; } } /** * @param {?} a * @return {?} */ copyLabel(a) { if (Array.isArray(a)) { return [...a]; } return a; } /** * @param {?} a * @param {?} b * @return {?} */ labelsEqual(a, b) { return Array.isArray(a) === Array.isArray(b) && (Array.isArray(a) || a === b) && (!Array.isArray(a) || a.length === b.length) && (!Array.isArray(a) || a.filter((/** * @param {?} x * @param {?} i * @return {?} */ (x, i) => x !== b[i])).length === 0); } /** * @param {?} a * @return {?} */ copyColor(a) { return { backgroundColor: a.backgroundColor, borderWidth: a.borderWidth, borderColor: a.borderColor, borderCapStyle: a.borderCapStyle, borderDash: a.borderDash, borderDashOffset: a.borderDashOffset, borderJoinStyle: a.borderJoinStyle, pointBorderColor: a.pointBorderColor, pointBackgroundColor: a.pointBackgroundColor, pointBorderWidth: a.pointBorderWidth, pointRadius: a.pointRadius, pointHoverRadius: a.pointHoverRadius, pointHitRadius: a.pointHitRadius, pointHoverBackgroundColor: a.pointHoverBackgroundColor, pointHoverBorderColor: a.pointHoverBorderColor, pointHoverBorderWidth: a.pointHoverBorderWidth, pointStyle: a.pointStyle, hoverBackgroundColor: a.hoverBackgroundColor, hoverBorderColor: a.hoverBorderColor, hoverBorderWidth: a.hoverBorderWidth, }; } /** * @param {?} a * @param {?} b * @return {?} */ colorsEqual(a, b) { if (!a !== !b) { return false; } return !a || (a.backgroundColor === b.backgroundColor) && (a.borderWidth === b.borderWidth) && (a.borderColor === b.borderColor) && (a.borderCapStyle === b.borderCapStyle) && (a.borderDash === b.borderDash) && (a.borderDashOffset === b.borderDashOffset) && (a.borderJoinStyle === b.borderJoinStyle) && (a.pointBorderColor === b.pointBorderColor) && (a.pointBackgroundColor === b.pointBackgroundColor) && (a.pointBorderWidth === b.pointBorderWidth) && (a.pointRadius === b.pointRadius) && (a.pointHoverRadius === b.pointHoverRadius) && (a.pointHitRadius === b.pointHitRadius) && (a.pointHoverBackgroundColor === b.pointHoverBackgroundColor) && (a.pointHoverBorderColor === b.pointHoverBorderColor) && (a.pointHoverBorderWidth === b.pointHoverBorderWidth) && (a.pointStyle === b.pointStyle) && (a.hoverBackgroundColor === b.hoverBackgroundColor) && (a.hoverBorderColor === b.hoverBorderColor) && (a.hoverBorderWidth === b.hoverBorderWidth); } /** * @return {?} */ updateColors() { this.datasets.forEach((/** * @param {?} elm * @param {?} index * @return {?} */ (elm, index) => { if (this.colors && this.colors[index]) { Object.assign(elm, this.colors[index]); } else { Object.assign(elm, getColors(this.chartType, index, elm.data.length), Object.assign({}, elm)); } })); } /** * @param {?} changes * @return {?} */ ngOnChanges(changes) { /** @type {?} */ let updateRequired = UpdateType.Default; /** @type {?} */ const wantUpdate = (/** * @param {?} x * @return {?} */ (x) => { updateRequired = x > updateRequired ? x : updateRequired; }); // Check if the changes are in the data or datasets or labels or legend if (changes.hasOwnProperty('data') && changes.data.currentValue) { this.propagateDataToDatasets(changes.data.currentValue); wantUpdate(UpdateType.Update); } if (changes.hasOwnProperty('datasets') && changes.datasets.currentValue) { this.propagateDatasetsToData(changes.datasets.currentValue); wantUpdate(UpdateType.Update); } if (changes.hasOwnProperty('labels')) { if (this.chart) { this.chart.data.labels = changes.labels.currentValue; } wantUpdate(UpdateType.Update); } if (changes.hasOwnProperty('legend')) { if (this.chart) { this.chart.config.options.legend.display = changes.legend.currentValue; this.chart.generateLegend(); } wantUpdate(UpdateType.Update); } if (changes.hasOwnProperty('options')) { wantUpdate(UpdateType.Refresh); } switch ((/** @type {?} */ (updateRequired))) { case UpdateType.Update: this.update(); break; case UpdateType.Refresh: case UpdateType.Default: this.refresh(); break; } } /** * @return {?} */ ngOnDestroy() { if (this.chart) { this.chart.destroy(); this.chart = void 0; } this.subs.forEach((/** * @param {?} x * @return {?} */ x => x.unsubscribe())); } /** * @param {?=} duration * @return {?} */ update(duration) { if (this.chart) { return this.chart.update(duration); } } /** * @param {?} index * @param {?} hidden * @return {?} */ hideDataset(index, hidden) { this.chart.getDatasetMeta(index).hidden = hidden; this.chart.update(); } /** * @param {?} index * @return {?} */ isDatasetHidden(index) { return this.chart.getDatasetMeta(index).hidden; } /** * @return {?} */ toBase64Image() { return this.chart.toBase64Image(); } /** * @return {?} */ getChartConfiguration() { /** @type {?} */ const datasets = this.getDatasets(); /** @type {?} */ const options = Object.assign({}, this.options); if (this.legend === false) { options.legend = { display: false }; } // hook for onHover and onClick events options.hover = options.hover || {}; if (!options.hover.onHover) { options.hover.onHover = (/** * @param {?} event * @param {?} active * @return {?} */ (event, active) => { if (active && !active.length) { return; } this.chartHover.emit({ event, active }); }); } if (!options.onClick) { options.onClick = (/** * @param {?=} event * @param {?=} active * @return {?} */ (event, active) => { this.chartClick.emit({ event, active }); }); } /** @type {?} */ const mergedOptions = this.smartMerge(options, this.themeService.getColorschemesOptions()); return { type: this.chartType, data: { labels: this.labels || [], datasets }, plugins: this.plugins, options: mergedOptions, }; } /** * @param {?} ctx * @return {?} */ getChartBuilder(ctx /*, data:any[], options:any*/) { /** @type {?} */ const chartConfig = this.getChartConfiguration(); return new Chart$1(ctx, chartConfig); } /** * @param {?} options * @param {?} overrides * @param {?=} level * @return {?} */ smartMerge(options, overrides, level = 0) { if (level === 0) { options = cloneDeep(options); } /** @type {?} */ const keysToUpdate = Object.keys(overrides); keysToUpdate.forEach((/** * @param {?} key * @return {?} */ key => { if (Array.isArray(overrides[key])) { /** @type {?} */ const arrayElements = options[key]; if (arrayElements) { arrayElements.forEach((/** * @param {?} r * @return {?} */ r => { this.smartMerge(r, overrides[key][0], level + 1); })); } } else if (typeof (overrides[key]) === 'object') { if (!(key in options)) { options[key] = {}; } this.smartMerge(options[key], overrides[key], level + 1); } else { options[key] = overrides[key]; } })); if (level === 0) { return options; } } /** * @private * @param {?} label * @return {?} */ isMultiLineLabel(label) { return Array.isArray(label); } /** * @private * @param {?} label * @return {?} */ joinLabel(label) { if (!label) { return null; } if (this.isMultiLineLabel(label)) { return label.join(' '); } else { return label; } } /** * @private * @param {?} datasets * @return {?} */ propagateDatasetsToData(datasets) { this.data = this.datasets.map((/** * @param {?} r * @return {?} */ r => r.data)); if (this.chart) { this.chart.data.datasets = datasets; } this.updateColors(); } /** * @private * @param {?} newDataValues * @return {?} */ propagateDataToDatasets(newDataValues) { if (this.isMultiDataSet(newDataValues)) { if (this.datasets && newDataValues.length === this.datasets.length) { this.datasets.forEach((/** * @param {?} dataset * @param {?} i * @return {?} */ (dataset, i) => { dataset.data = newDataValues[i]; })); } else { this.datasets = newDataValues.map((/** * @param {?} data * @param {?} index * @return {?} */ (data, index) => { return { data, label: this.joinLabel(this.labels[index]) || `Label ${index}` }; })); if (this.chart) { this.chart.data.datasets = this.datasets; } } } else { if (!this.datasets) { this.datasets = [{ data: newDataValues }]; if (this.chart) { this.chart.data.datasets = this.datasets; } } else { this.datasets[0].data = newDataValues; this.datasets.splice(1); // Remove all elements but the first } } this.updateColors(); } /** * @private * @param {?} data * @return {?} */ isMultiDataSet(data) { return Array.isArray(data[0]); } /** * @private * @return {?} */ getDatasets() { if (!this.datasets && !this.data) { throw new Error(`ng-charts configuration error, data or datasets field are required to render chart ${this.chartType}`); } // If `datasets` is defined, use it over the `data` property. if (this.datasets) { this.propagateDatasetsToData(this.datasets); return this.datasets; } if (this.data) { this.propagateDataToDatasets(this.data); return this.datasets; } } /** * @private * @return {?} */ refresh() { // if (this.options && this.options.responsive) { // setTimeout(() => this.refresh(), 50); // } // todo: remove this line, it is producing flickering if (this.chart) { this.chart.destroy(); this.chart = void 0; } if (this.ctx) { this.chart = this.getChartBuilder(this.ctx /*, data, this.options*/); } } } BaseChartDirective.decorators = [ { type: Directive, args: [{ // tslint:disable-next-line:directive-selector selector: 'canvas[baseChart]', exportAs: 'base-chart' },] } ]; /** @nocollapse */ BaseChartDirective.ctorParameters = () => [ { type: ElementRef }, { type: ThemeService } ]; BaseChartDirective.propDecorators = { data: [{ type: Input }], datasets: [{ type: Input }], labels: [{ type: Input }], options: [{ type: Input }], chartType: [{ type: Input }], colors: [{ type: Input }], legend: [{ type: Input }], plugins: [{ type: Input }], chartClick: [{ type: Output }], chartHover: [{ type: Output }] }; if (false) { /** @type {?} */ BaseChartDirective.prototype.data; /** @type {?} */ BaseChartDirective.prototype.datasets; /** @type {?} */ BaseChartDirective.prototype.labels; /** @type {?} */ BaseChartDirective.prototype.options; /** @type {?} */ BaseChartDirective.prototype.chartType; /** @type {?} */ BaseChartDirective.prototype.colors; /** @type {?} */ BaseChartDirective.prototype.legend; /** @type {?} */ BaseChartDirective.prototype.plugins; /** @type {?} */ BaseChartDirective.prototype.chartClick; /** @type {?} */ BaseChartDirective.prototype.chartHover; /** @type {?} */ BaseChartDirective.prototype.ctx; /** @type {?} */ BaseChartDirective.prototype.chart; /** * @type {?} * @private */ BaseChartDirective.prototype.old; /** * @type {?} * @private */ BaseChartDirective.prototype.subs; /** * @type {?} * @private */ BaseChartDirective.prototype.element; /** * @type {?} * @private */ BaseChartDirective.prototype.themeService; } /** * @fileoverview added by tsickle * Generated from: lib/charts.module.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class ChartsModule { } ChartsModule.decorators = [ { type: NgModule, args: [{ declarations: [ BaseChartDirective ], imports: [], exports: [ BaseChartDirective ] },] } ]; /** * @fileoverview added by tsickle * Generated from: lib/color.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @record */ function Color() { } if (false) { /** @type {?|undefined} */ Color.prototype.backgroundColor; /** @type {?|undefined} */ Color.prototype.borderWidth; /** @type {?|undefined} */ Color.prototype.borderColor; /** @type {?|undefined} */ Color.prototype.borderCapStyle; /** @type {?|undefined} */ Color.prototype.borderDash; /** @type {?|undefined} */ Color.prototype.borderDashOffset; /** @type {?|undefined} */ Color.prototype.borderJoinStyle; /** @type {?|undefined} */ Color.prototype.pointBorderColor; /** @type {?|undefined} */ Color.prototype.pointBackgroundColor; /** @type {?|undefined} */ Color.prototype.pointBorderWidth; /** @type {?|undefined} */ Color.prototype.pointRadius; /** @type {?|undefined} */ Color.prototype.pointHoverRadius; /** @type {?|undefined} */ Color.prototype.pointHitRadius; /** @type {?|undefined} */ Color.prototype.pointHoverBackgroundColor; /** @type {?|undefined} */ Color.prototype.pointHoverBorderColor; /** @type {?|undefined} */ Color.prototype.pointHoverBorderWidth; /** @type {?|undefined} */ Color.prototype.pointStyle; /** @type {?|undefined} */ Color.prototype.hoverBackgroundColor; /** @type {?|undefined} */ Color.prototype.hoverBorderColor; /** @type {?|undefined} */ Color.prototype.hoverBorderWidth; } /** * @fileoverview added by tsickle * Generated from: lib/colors.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @record */ function Colors() { } if (false) { /** @type {?|undefined} */ Colors.prototype.data; /** @type {?|undefined} */ Colors.prototype.label; } /** * @fileoverview added by tsickle * Generated from: lib/monkey-patch-chart-js-legend.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // tslint:disable:variable-name // tslint:disable:no-var-keyword // tslint:disable:prefer-const // tslint:disable:only-arrow-functions // tslint:disable:one-variable-per-declaration // tslint:disable:object-literal-shorthand // tslint:disable:space-before-function-paren /** * @return {?} */ function monkeyPatchChartJsLegend() { if (typeof Chart === 'undefined') { console.log('Chart not defined (guessing this is a universal build, and I don\'t know why this happens -- Aviad)'); return; } /** @type {?} */ const plugins = Chart.plugins.getAll(); /** @type {?} */ const legend = plugins.filter((/** * @param {?} p * @return {?} */ p => p.id === 'legend'))[0]; legend._element.prototype.fit = fit; legend._element.prototype.draw = draw; /** @type {?} */ const helpers = Chart.helpers; /** @type {?} */ const defaults = Chart.defaults; /** @type {?} */ const valueOrDefault = helpers.valueOrDefault; /** * @param {?} labelOpts * @param {?} fontSize * @return {?} */ function getBoxWidth(labelOpts, fontSize) { return labelOpts.usePointStyle && labelOpts.boxWidth > fontSize ? fontSize : labelOpts.boxWidth; } /** * @return {?} */ function fit() { /** @type {?} */ var me = this; /** @type {?} */ var opts = me.options; /** @type {?} */ var labelOpts = opts.labels; /** @type {?} */ var display = opts.display; /** @type {?} */ var ctx = me.ctx; /** @type {?} */ var labelFont = helpers.options._parseFont(labelOpts); /** @type {?} */ var fontSize = labelFont.size; // Reset hit boxes /** @type {?} */ var hitboxes = me.legendHitBoxes = []; /** @type {?} */ var minSize = me.minSize; /** @type {?} */ var isHorizontal = me.isHorizontal(); if (isHorizontal) { minSize.width = me.maxWidth; // fill all the width minSize.height = display ? 10 : 0; } else { minSize.width = display ? 10 : 0; minSize.height = me.maxHeight; // fill all the height } /** @type {?} */ var getMaxLineWidth = (/** * @param {?} textLines * @return {?} */ function (textLines) { return textLines.map((/** * @param {?} textLine * @return {?} */ function (textLine) { return ctx.measureText(textLine).width; })).reduce((/** * @param {?} acc * @param {?} v * @return {?} */ function (acc, v) { return v > acc ? v : acc; }), 0); }); // Increase sizes here if (display) { ctx.font = labelFont.string; if (isHorizontal) { // Labels // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one /** @type {?} */ var lineWidths = me.lineWidths = [0]; /** @type {?} */ var lineHeights = me.lineHeights = []; /** @type {?} */ var currentLineHeight = 0; /** @type {?} */ var lineIndex = 0; ctx.textAlign = 'left'; ctx.textBaseline = 'top'; helpers.each(me.legendItems, (/** * @param {?} legendItem * @param {?} i * @return {?} */ function (legendItem, i) { /** @type {?} */ var width; /** @type {?} */ var height; if (helpers.isArray(legendItem.text)) { width = getMaxLineWidth(legendItem.text); height = fontSize * legendItem.text.length + labelOpts.padding; } else { width = ctx.measureText(legendItem.text).width; height = fontSize + labelOpts.padding; } width += getBoxWidth(labelOpts, fontSize) + (fontSize / 2); if (lineWidths[lineWidths.length - 1] + width + 2 * labelOpts.padding > minSize.width) { lineHeights.push(currentLineHeight); currentLineHeight = 0; lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0; lineIndex++; } legendItem.lineOrColumnIndex = lineIndex; if (height > currentLineHeight) { currentLineHeight = height; } // Store the hitbox width and height here. Final position will be updated in `draw` hitboxes[i] = { left: 0, top: 0, width: width, height: height, }; lineWidths[lineWidths.length - 1] += width + labelOpts.padding; })); lineHeights.push(currentLineHeight); minSize.height += lineHeights.reduce((/** * @param {?} acc * @param {?} v * @return {?} */ function (acc, v) { return acc + v; }), 0); } else { /** @type {?} */ var vPadding = labelOpts.padding; /** @type {?} */ var columnWidths = me.columnWidths = []; /** @type {?} */ var columnHeights = me.columnHeights = []; /** @type {?} */ var totalWidth = labelOpts.padding; /** @type {?} */ var currentColWidth = 0; /** @type {?} */ var currentColHeight = 0; /** @type {?} */ var columnIndex = 0; helpers.each(me.legendItems, (/** * @param {?} legendItem * @param {?} i * @return {?} */ function (legendItem, i) { /** @type {?} */ var itemWidth; /** @type {?} */ var height; if (helpers.isArray(legendItem.text)) { itemWidth = getMaxLineWidth(legendItem.text); height = fontSize * legendItem.text.length; } else { itemWidth = ctx.measureText(legendItem.text).width; height = fontSize; } itemWidth += getBoxWidth(labelOpts, fontSize) + (fontSize / 2); // If too tall, go to new column if (currentColHeight + fontSize + 2 * vPadding > minSize.height) { totalWidth += currentColWidth + labelOpts.padding; columnWidths.push(currentColWidth); // previous column width columnHeights.push(currentColHeight); currentColWidth = 0; currentColHeight = 0; columnIndex++; } legendItem.lineOrColumnIndex = columnIndex; // Get max width currentColWidth = Math.max(currentColWidth, itemWidth); currentColHeight += height + vPadding; // Store the hitbox width and height here. Final position will be updated in `draw` hitboxes[i] = { left: 0, top: 0, width: itemWidth, height: height }; })); totalWidth += currentColWidth; columnWidths.push(currentColWidth); columnHeights.push(currentColHeight); minSize.width += totalWidth; } } me.width = minSize.width; me.height = minSize.height; } /** * @return {?} */ function draw() { /** @type {?} */ var me = this; /** @type {?} */ var opts = me.options; /** @type {?} */ var labelOpts = opts.labels; /** @type {?} */ var globalDefaults = defaults.global; /** @type {?} */ var defaultColor = globalDefaults.defaultColor; /** @type {?} */ var lineDefault = globalDefaults.elements.line; /** @type {?} */ var legendHeight = me.height; /** @type {?} */ var columnHeights = me.columnHeights; /** @type {?} */ var columnWidths = me.columnWidths; /** @type {?} */ var legendWidth = me.width; /** @type {?} */ var lineWidths = me.lineWidths; /** @type {?} */ var lineHeights = me.lineHeights; if (opts.display) { /** @type {?} */ var ctx = me.ctx; /** @type {?} */ var fontColor = valueOrDefault(labelOpts.fontColor, globalDefaults.defaultFontColor); /** @type {?} */ var labelFont = helpers.options._parseFont(labelOpts); /** @type {?} */ var fontSize = labelFont.size; /** @type {?} */ var cursor; // Canvas setup ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; ctx.lineWidth = 0.5; ctx.strokeStyle = fontColor; // for strikethrough effect ctx.fillStyle = fontColor; // render in correct colour ctx.font = labelFont.string; /** @type {?} */ var boxWidth = getBoxWidth(labelOpts, fontSize); /** @type {?} */ var hitboxes = me.legendHitBoxes; // current position /** @type {?} */ var drawLegendBox = (/** * @param {?} x * @param {?} y * @param {?} legendItem * @return {?} */ function (x, y, legendItem) { if (isNaN(boxWidth) || boxWidth <= 0) { return; } // Set the ctx for the box ctx.save(); /** @type {?} */ var lineWidth = valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth); ctx.fillStyle = valueOrDefault(legendItem.fillStyle, defaultColor); ctx.lineCap = valueOrDefault(legendItem.lineCap, lineDefault.borderCapStyle); ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset); ctx.lineJoin = valueOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle); ctx.lineWidth = lineWidth; ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, defaultColor); if (ctx.setLineDash) { // IE 9 and 10 do not support line dash ctx.setLineDash(valueOrDefault(legendItem.lineDash, lineDefault.borderDash)); } if (opts.labels && opts.labels.usePointStyle) { // Recalculate x and y for drawPoint() because its expecting // x and y to be center of figure (instead of top left) /** @type {?} */ var radius = boxWidth * Math.SQRT2 / 2; /** @type {?} */ var centerX = x + boxWidth / 2; /** @type {?} */ var centerY = y + fontSize / 2; // Draw pointStyle as legend symbol helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY); } else { // Draw box as legend symbol if (lineWidth !== 0) { ctx.strokeRect(x, y, boxWidth, fontSize); } ctx.fillRect(x, y, boxWidth, fontSize); } ctx.restore(); }); /** @type {?} */ var drawStrikeThrough = (/** * @param {?} x * @param {?} y * @param {?} w * @return {?} */ function (x, y, w) { ctx.beginPath(); ctx.lineWidth = 2; ctx.moveTo(x, y); ctx.lineTo(x + w, y); ctx.stroke(); }); /** @type {?} */ var drawCrossOver = (/** * @param {?} x * @param {?} y * @param {?} w * @param {?} h * @return {?} */ function (x, y, w, h) { ctx.beginPath(); ctx.lineWidth = 2; ctx.moveTo(x, y); ctx.lineTo(x + w, y + h); ctx.moveTo(x, y + h); ctx.lineTo(x + w, y); ctx.stroke(); }); /** @type {?} */ var fillText = (/** * @param {?} x * @param {?} y * @param {?} legendItem * @param {?} textWidth * @return {?} */ function (x, y, legendItem, textWidth) { /** @type {?} */ var halfFontSize = fontSize / 2; /** @type {?} */ var xLeft = boxWidth + halfFontSize + x; /** @type {?} */ var yMiddle = y + halfFontSize; if (helpers.isArray(legendItem.text)) { helpers.each(legendItem.text, (/** * @param {?} textLine * @param {?} index * @return {?} */ function (textLine, index) { /** @type {?} */ var lineOffset = index * fontSize; ctx.fillText(textLine, xLeft, yMiddle + lineOffset); })); } else { ctx.fillText(legendItem.text, xLeft, yMiddle); } if (legendItem.hidden) { if (helpers.isArray(legendItem.text)) { drawCrossOver(xLeft, yMiddle, textWidth, (legendItem.text.length - 1) * (fontSize - 1)); } else { drawStrikeThrough(xLeft, yMiddle, textWidth); } } }); /** @type {?} */ var alignmentOffset = (/** * @param {?} dimension * @param {?} blockSize * @return {?} */ function (dimension, blockSize) { switch (opts.align) { case 'start': return labelOpts.padding; case 'end': return dimension - blockSize; default: // center return (dimension - blockSize + labelOpts.padding) / 2; } }); // Horizontal /** @type {?} */ var isHorizontal = me.isHorizontal(); if (isHorizontal) { cursor = { x: me.left + alignmentOffset(legendWidth, lineWidths[0]), y: me.top + labelOpts.padding, line: 0 }; } else { cursor = { x: me.left + labelOpts.padding, y: me.top + alignmentOffset(legendHeight, columnHeights[0]), line: 0 }; } helpers.each(me.legendItems, (/** * @param {?} legendItem * @param {?} i * @return {?} */ function (legendItem, i) { /** @type {?} */ var textWidth; /** @type {?} */ var height; /** @type {?} */ var boxTopOffset; if (legendItem.lineOrColumnIndex > cursor.line) { if (isHorizontal) { cursor.y += lineHeights[cursor.line]; cursor.line = legendItem.lineOrColumnIndex; cursor.x = me.left + alignmentOffset(legendWidth, lineWidths[cursor.line]); } else { cursor.x += columnWidths[cursor.line] + labelOpts.padding; cursor.line = legendItem.lineOrColumnIndex; cursor.y = me.top + alignmentOffset(legendHeight, columnHeights[cursor.line]); } } if (helpers.isArray(legendItem.text)) { textWidth = legendItem.text.map((/** * @param {?} textLine * @return {?} */ function (textLine) { return ctx.measureText(textLine).width; })).reduce((/** * @param {?} acc * @param {?} v * @return {?} */ function (acc, v) { return v > acc ? v : acc; }), 0); boxTo