@rinminase/ng-charts
Version:
Reactive, responsive, beautiful charts for Angular based on ng2-charts
992 lines (982 loc) • 39.5 kB
JavaScript
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