UNPKG

@rinminase/ng-charts

Version:

Reactive, responsive, beautiful charts for Angular based on ng2-charts

992 lines (982 loc) 39.5 kB
import * as i0 from '@angular/core'; import { Injectable, EventEmitter, Directive, Input, Output, NgModule } from '@angular/core'; import * as chartJs from 'chart.js'; import { cloneDeep } from 'lodash-es'; import { BehaviorSubject } from 'rxjs'; 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], ]; /** * Generate colors by chart type */ 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}`); } function rgba(colour, alpha) { return "rgba(" + colour.concat(alpha).join(",") + ")"; } function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } 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), }; } function formatBarColor(colors) { return { backgroundColor: rgba(colors, 0.6), borderColor: rgba(colors, 1), hoverBackgroundColor: rgba(colors, 0.8), hoverBorderColor: rgba(colors, 1), }; } function formatPieColors(colors) { return { backgroundColor: colors.map((color) => rgba(color, 0.6)), borderColor: colors.map(() => "#fff"), pointBackgroundColor: colors.map((color) => rgba(color, 1)), pointBorderColor: colors.map(() => "#fff"), pointHoverBackgroundColor: colors.map((color) => rgba(color, 1)), pointHoverBorderColor: colors.map((color) => rgba(color, 1)), }; } function formatPolarAreaColors(colors) { return { backgroundColor: colors.map((color) => rgba(color, 0.6)), borderColor: colors.map((color) => rgba(color, 1)), hoverBackgroundColor: colors.map((color) => rgba(color, 0.8)), hoverBorderColor: colors.map((color) => rgba(color, 1)), }; } function getRandomColor() { return [getRandomInt(0, 255), getRandomInt(0, 255), getRandomInt(0, 255)]; } /** * Generate colors for line|bar charts */ function generateColor(index) { return defaultColors[index] || getRandomColor(); } /** * Generate colors for pie|doughnut charts */ function generateColors(count) { const colorsArr = new Array(count); for (let i = 0; i < count; i++) { colorsArr[i] = defaultColors[i] || getRandomColor(); } return colorsArr; } class ThemeService { constructor() { this.pColorschemesOptions = {}; this.colorschemesOptions = new BehaviorSubject({}); } setColorschemesOptions(options) { this.pColorschemesOptions = options; this.colorschemesOptions.next(options); } getColorschemesOptions() { return this.pColorschemesOptions; } } ThemeService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.1.0", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); ThemeService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.1.0", ngImport: i0, type: ThemeService, providedIn: "root" }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.1.0", ngImport: i0, type: ThemeService, decorators: [{ type: Injectable, args: [{ providedIn: "root" }] }], ctorParameters: function () { return []; } }); var UpdateType; (function (UpdateType) { UpdateType[UpdateType["Default"] = 0] = "Default"; UpdateType[UpdateType["Update"] = 1] = "Update"; UpdateType[UpdateType["Refresh"] = 2] = "Refresh"; })(UpdateType || (UpdateType = {})); class BaseChartDirective { 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. */ static registerPlugin(plugin) { chartJs.Chart.plugins.register(plugin); } static unregisterPlugin(plugin) { chartJs.Chart.plugins.unregister(plugin); } ngOnInit() { this.ctx = this.element.nativeElement.getContext("2d"); this.refresh(); this.subs.push(this.themeService.colorschemesOptions.subscribe(() => this.themeChanged())); } themeChanged() { this.refresh(); } ngDoCheck() { if (!this.chart) { return; } let updateRequired = UpdateType.Default; const wantUpdate = (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((x, i) => x.data !== this.old.datasetsDataObjects[i]) .length) { this.old.datasetsDataObjects = this.datasets.map((x) => x.data); wantUpdate(UpdateType.Update); } if (this.datasets && this.datasets.filter((x, i) => x.data.length !== this.old.datasetsDataLengths[i]).length) { this.old.datasetsDataLengths = this.datasets.map((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((x, i) => !this.colorsEqual(x, this.old.colors[i])) .length) { this.old.colors = this.colors.map((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((x, i) => !this.labelsEqual(x, this.old.labels[i])) .length) { this.old.labels = this.labels.map((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 (updateRequired) { case UpdateType.Default: break; case UpdateType.Update: this.update(); break; case UpdateType.Refresh: this.refresh(); break; } } copyLabel(a) { if (Array.isArray(a)) { return [...a]; } return a; } labelsEqual(a, b) { return (true && Array.isArray(a) === Array.isArray(b) && (Array.isArray(a) || a === b) && (!Array.isArray(a) || a.length === b.length) && (!Array.isArray(a) || a.filter((x, i) => x !== b[i]).length === 0)); } copyColor(a) { const rc = { 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, }; return rc; } colorsEqual(a, b) { if (!a !== !b) { return false; } return (!a || (true && 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)); } updateColors() { this.datasets.forEach((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)); } }); } ngOnChanges(changes) { let updateRequired = UpdateType.Default; const wantUpdate = (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 (updateRequired) { case UpdateType.Update: this.update(); break; case UpdateType.Refresh: case UpdateType.Default: this.refresh(); break; } } ngOnDestroy() { if (this.chart) { this.chart.destroy(); this.chart = void 0; } this.subs.forEach((x) => x.unsubscribe()); } update(duration) { if (this.chart) { return this.chart.update(duration); } return null; } hideDataset(index, hidden) { this.chart.getDatasetMeta(index).hidden = hidden; this.chart.update(); } isDatasetHidden(index) { return this.chart.getDatasetMeta(index).hidden; } toBase64Image() { return this.chart.toBase64Image(); } getChartConfiguration() { const datasets = this.getDatasets(); 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 = (event, active) => { if (active && !active.length) { return; } this.chartHover.emit({ event, active }); }; } if (!options.onClick) { options.onClick = (event, active) => { this.chartClick.emit({ event, active }); }; } const mergedOptions = this.smartMerge(options, this.themeService.getColorschemesOptions()); const chartConfig = { type: this.chartType, data: { labels: this.labels || [], datasets, }, plugins: this.plugins, options: mergedOptions, }; return chartConfig; } getChartBuilder(ctx /*, data:any[], options:any*/) { const chartConfig = this.getChartConfiguration(); return new chartJs.Chart(ctx, chartConfig); } smartMerge(options, overrides, level = 0) { if (level === 0) { options = cloneDeep(options); } const keysToUpdate = Object.keys(overrides); keysToUpdate.forEach((key) => { if (Array.isArray(overrides[key])) { const arrayElements = options[key]; if (arrayElements) { arrayElements.forEach((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; } } isMultiLineLabel(label) { return Array.isArray(label); } joinLabel(label) { if (!label) { return null; } if (this.isMultiLineLabel(label)) { return label.join(" "); } else { return label; } } propagateDatasetsToData(datasets) { this.data = this.datasets.map((r) => r.data); if (this.chart) { this.chart.data.datasets = datasets; } this.updateColors(); } propagateDataToDatasets(newDataValues) { if (this.isMultiDataSet(newDataValues)) { if (this.datasets && newDataValues.length === this.datasets.length) { this.datasets.forEach((dataset, i) => { dataset.data = newDataValues[i]; }); } else { this.datasets = newDataValues.map((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(); } isMultiDataSet(data) { return Array.isArray(data[0]); } 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; } return null; } 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.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.1.0", ngImport: i0, type: BaseChartDirective, deps: [{ token: i0.ElementRef }, { token: ThemeService }], target: i0.ɵɵFactoryTarget.Directive }); BaseChartDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.1.0", type: BaseChartDirective, selector: "canvas[baseChart]", inputs: { data: "data", datasets: "datasets", labels: "labels", options: "options", chartType: "chartType", colors: "colors", legend: "legend", plugins: "plugins" }, outputs: { chartClick: "chartClick", chartHover: "chartHover" }, exportAs: ["base-chart"], usesOnChanges: true, ngImport: i0 }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.1.0", ngImport: i0, type: BaseChartDirective, decorators: [{ type: Directive, args: [{ // tslint:disable-next-line:directive-selector selector: "canvas[baseChart]", exportAs: "base-chart", }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: ThemeService }]; }, 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 }] } }); class ChartsModule { } ChartsModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.1.0", ngImport: i0, type: ChartsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); ChartsModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.1.0", ngImport: i0, type: ChartsModule, declarations: [BaseChartDirective], exports: [BaseChartDirective] }); ChartsModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.1.0", ngImport: i0, type: ChartsModule }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.1.0", ngImport: i0, type: ChartsModule, decorators: [{ type: NgModule, args: [{ declarations: [BaseChartDirective], imports: [], exports: [BaseChartDirective], }] }] }); function monkeyPatchChartJsLegend() { if (typeof Chart === "undefined") { console.log("Chart not defined"); return; } const plugins = Chart.plugins.getAll(); const legend = plugins.filter((p) => p.id === "legend")[0]; legend._element.prototype.fit = fit; legend._element.prototype.draw = draw; const helpers = Chart.helpers; const defaults = Chart.defaults; const valueOrDefault = helpers.valueOrDefault; function getBoxWidth(labelOpts, fontSize) { return labelOpts.usePointStyle && labelOpts.boxWidth > fontSize ? fontSize : labelOpts.boxWidth; } function fit() { let me = this; let opts = me.options; let labelOpts = opts.labels; let display = opts.display; let ctx = me.ctx; let labelFont = helpers.options._parseFont(labelOpts); let fontSize = labelFont.size; // Reset hit boxes let hitboxes = (me.legendHitBoxes = []); let minSize = me.minSize; let 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 } let getMaxLineWidth = function (textLines) { return textLines .map(function (textLine) { return ctx.measureText(textLine).width; }) .reduce(function (acc, v) { return v > acc ? v : acc; }, 0); }; // Increase sizes here if (display) { ctx.font = labelFont.string; if (isHorizontal) { // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one let lineWidths = (me.lineWidths = [0]); let lineHeights = (me.lineHeights = []); let currentLineHeight = 0; let lineIndex = 0; ctx.textAlign = "left"; ctx.textBaseline = "top"; helpers.each(me.legendItems, function (legendItem, i) { let width, 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(function (acc, v) { return acc + v; }, 0); } else { let vPadding = labelOpts.padding; let columnWidths = (me.columnWidths = []); let columnHeights = (me.columnHeights = []); let totalWidth = labelOpts.padding; let currentColWidth = 0; let currentColHeight = 0; let columnIndex = 0; helpers.each(me.legendItems, function (legendItem, i) { let itemWidth; let 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; } function draw() { let me = this; let opts = me.options; let labelOpts = opts.labels; let globalDefaults = defaults.global; let defaultColor = globalDefaults.defaultColor; let lineDefault = globalDefaults.elements.line; let legendHeight = me.height; let columnHeights = me.columnHeights; let columnWidths = me.columnWidths; let legendWidth = me.width; let lineWidths = me.lineWidths; let lineHeights = me.lineHeights; if (opts.display) { let ctx = me.ctx; let fontColor = valueOrDefault(labelOpts.fontColor, globalDefaults.defaultFontColor); let labelFont = helpers.options._parseFont(labelOpts); let fontSize = labelFont.size; let 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; let boxWidth = getBoxWidth(labelOpts, fontSize); let hitboxes = me.legendHitBoxes; // current position let drawLegendBox = function (x, y, legendItem) { if (isNaN(boxWidth) || boxWidth <= 0) { return; } // Set the ctx for the box ctx.save(); let 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) let radius = (boxWidth * Math.SQRT2) / 2; let centerX = x + boxWidth / 2; let 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(); }; let drawStrikeThrough = function (x, y, w) { ctx.beginPath(); ctx.lineWidth = 2; ctx.moveTo(x, y); ctx.lineTo(x + w, y); ctx.stroke(); }; let drawCrossOver = 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(); }; let fillText = function (x, y, legendItem, textWidth) { let halfFontSize = fontSize / 2; let xLeft = boxWidth + halfFontSize + x; let yMiddle = y + halfFontSize; if (helpers.isArray(legendItem.text)) { helpers.each(legendItem.text, function (textLine, index) { let 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); } } }; let alignmentOffset = 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 let 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, function (legendItem, i) { let textWidth, height, 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(function (textLine) { return ctx.measureText(textLine).width; }) .reduce(function (acc, v) { return v > acc ? v : acc; }, 0); boxTopOffset = (fontSize / 2) * (legendItem.text.length - 1); height = fontSize * legendItem.text.length; } else { textWidth = ctx.measureText(legendItem.text).width; boxTopOffset = 0; height = fontSize; } let width = boxWidth + fontSize / 2 + textWidth; let x = cursor.x; let y = cursor.y; let topOffset = isHorizontal ? Math.trunc((lineHeights[cursor.line] - hitboxes[i].height) / 2) : 0; drawLegendBox(x, y + boxTopOffset + topOffset, legendItem); hitboxes[i].left = x; hitboxes[i].top = y; // Fill the actual label fillText(x, y + topOffset, legendItem, textWidth); if (isHorizontal) { cursor.x += width + labelOpts.padding; } else { cursor.y += height + labelOpts.padding; } }); } } } function monkeyPatchChartJsTooltip() { if (typeof Chart === "undefined") { console.log("Chart not defined"); return; } Chart.Tooltip.prototype.drawBody = drawBody; const helpers = Chart.helpers; function getAlignedX(vm, align) { return align === "center" ? vm.x + vm.width / 2 : align === "right" ? vm.x + vm.width - vm.xPadding : vm.x + vm.xPadding; } function drawBody(pt, vm, ctx) { let bodyFontSize = vm.bodyFontSize; let bodySpacing = vm.bodySpacing; let bodyAlign = vm._bodyAlign; let body = vm.body; let drawColorBoxes = vm.displayColors; let labelColors = vm.labelColors; let xLinePadding = 0; let colorX = drawColorBoxes ? getAlignedX(vm, "left") : 0; let textColor; ctx.textAlign = bodyAlign; ctx.textBaseline = "top"; ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); pt.x = getAlignedX(vm, bodyAlign); // Before Body let fillLineOfText = function (line) { ctx.fillText(line, pt.x + xLinePadding, pt.y); pt.y += bodyFontSize + bodySpacing; }; // Before body lines ctx.fillStyle = vm.bodyFontColor; helpers.each(vm.beforeBody, fillLineOfText); xLinePadding = drawColorBoxes && bodyAlign !== "right" ? bodyAlign === "center" ? bodyFontSize / 2 + 1 : bodyFontSize + 2 : 0; // Draw body lines now helpers.each(body, function (bodyItem, i) { textColor = vm.labelTextColors[i]; ctx.fillStyle = textColor; helpers.each(bodyItem.before, fillLineOfText); // Draw Legend-like boxes if needed if (drawColorBoxes) { // Fill a white rect so that colours merge nicely if the opacity is < 1 ctx.fillStyle = vm.legendColorBackground; ctx.fillRect(colorX, pt.y, bodyFontSize, bodyFontSize); // Border ctx.lineWidth = 1; ctx.strokeStyle = labelColors[i].borderColor; ctx.strokeRect(colorX, pt.y, bodyFontSize, bodyFontSize); // Inner square ctx.fillStyle = labelColors[i].backgroundColor; ctx.fillRect(colorX + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2); ctx.fillStyle = textColor; } helpers.each(bodyItem.lines, fillLineOfText); helpers.each(bodyItem.after, fillLineOfText); }); // Reset back to 0 for after body xLinePadding = 0; // After body lines helpers.each(vm.afterBody, fillLineOfText); pt.y -= bodySpacing; // Remove last body spacing } } /** * Generated bundle index. Do not edit. */ export { BaseChartDirective, ChartsModule, ThemeService, defaultColors, monkeyPatchChartJsLegend, monkeyPatchChartJsTooltip }; //# sourceMappingURL=rinminase-ng-charts.mjs.map