chartjs-chart-matrix
Version:
Chart.js module for creating matrix charts
260 lines (252 loc) • 7.64 kB
JavaScript
/*!
* chartjs-chart-matrix v0.0.0-development
* https://chartjs-chart-matrix.pages.dev/
* (c) 2025 Jukka Kurkela
* Released under the MIT license
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('chart.js'), require('chart.js/helpers')) :
typeof define === 'function' && define.amd ? define(['exports', 'chart.js', 'chart.js/helpers'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["chartjs-chart-matrix"] = {}, global.Chart, global.Chart.helpers));
})(this, (function (exports, chart_js, helpers) { 'use strict';
var version = "0.0.0-development";
class MatrixController extends chart_js.DatasetController {
initialize() {
this.enableOptionSharing = true;
super.initialize();
}
update(mode) {
const meta = this._cachedMeta;
this.updateElements(meta.data, 0, meta.data.length, mode);
}
updateElements(rects, start, count, mode) {
const reset = mode === 'reset';
const { xScale, yScale } = this._cachedMeta;
const firstOpts = this.resolveDataElementOptions(start, mode);
const sharedOptions = this.getSharedOptions(firstOpts);
for(let i = start; i < start + count; i++){
const parsed = !reset && this.getParsed(i);
const x = reset ? xScale.getBasePixel() : xScale.getPixelForValue(parsed.x);
const y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(parsed.y);
const options = this.resolveDataElementOptions(i, mode);
const { width, height, anchorX, anchorY } = options;
const properties = {
x: resolveX(anchorX, x, width),
y: resolveY(anchorY, y, height),
width,
height,
options
};
this.updateElement(rects[i], i, properties, mode);
}
this.updateSharedOptions(sharedOptions, mode, firstOpts);
}
draw() {
const ctx = this.chart.ctx;
const data = this.getMeta().data || [];
let i, ilen;
for(i = 0, ilen = data.length; i < ilen; ++i){
data[i].draw(ctx);
}
}
}
MatrixController.id = 'matrix';
MatrixController.version = version;
MatrixController.defaults = {
dataElementType: 'matrix',
animations: {
numbers: {
type: 'number',
properties: [
'x',
'y',
'width',
'height'
]
}
}
};
MatrixController.overrides = {
interaction: {
mode: 'nearest',
intersect: true
},
scales: {
x: {
type: 'linear',
offset: true
},
y: {
type: 'linear',
reverse: true
}
}
};
function resolveX(anchorX, x, width) {
if (anchorX === 'left' || anchorX === 'start') {
return x;
}
if (anchorX === 'right' || anchorX === 'end') {
return x - width;
}
return x - width / 2;
}
function resolveY(anchorY, y, height) {
if (anchorY === 'top' || anchorY === 'start') {
return y;
}
if (anchorY === 'bottom' || anchorY === 'end') {
return y - height;
}
return y - height / 2;
}
function getBounds(element, useFinalPosition) {
const { x, y, width, height } = element.getProps([
'x',
'y',
'width',
'height'
], useFinalPosition);
return {
left: x,
top: y,
right: x + width,
bottom: y + height
};
}
function limit(value, min, max) {
return Math.max(Math.min(value, max), min);
}
function parseBorderWidth(options, maxW, maxH) {
const value = options.borderWidth;
let t, r, b, l;
if (helpers.isObject(value)) {
t = +value.top || 0;
r = +value.right || 0;
b = +value.bottom || 0;
l = +value.left || 0;
} else {
t = r = b = l = +value || 0;
}
return {
t: limit(t, 0, maxH),
r: limit(r, 0, maxW),
b: limit(b, 0, maxH),
l: limit(l, 0, maxW)
};
}
function boundingRects(element) {
const bounds = getBounds(element, false);
const width = bounds.right - bounds.left;
const height = bounds.bottom - bounds.top;
const border = parseBorderWidth(element.options, width / 2, height / 2);
return {
outer: {
x: bounds.left,
y: bounds.top,
w: width,
h: height
},
inner: {
x: bounds.left + border.l,
y: bounds.top + border.t,
w: width - border.l - border.r,
h: height - border.t - border.b
}
};
}
function inRange(element, x, y, useFinalPosition) {
const skipX = x === null;
const skipY = y === null;
const bounds = !element || skipX && skipY ? false : getBounds(element, useFinalPosition);
return bounds && (skipX || x >= bounds.left && x <= bounds.right) && (skipY || y >= bounds.top && y <= bounds.bottom);
}
class MatrixElement extends chart_js.Element {
draw(ctx) {
const options = this.options;
const { inner, outer } = boundingRects(this);
const radius = helpers.toTRBLCorners(options.borderRadius);
ctx.save();
if (outer.w !== inner.w || outer.h !== inner.h) {
ctx.beginPath();
helpers.addRoundedRectPath(ctx, {
x: outer.x,
y: outer.y,
w: outer.w,
h: outer.h,
radius
});
helpers.addRoundedRectPath(ctx, {
x: inner.x,
y: inner.y,
w: inner.w,
h: inner.h,
radius
});
ctx.fillStyle = options.backgroundColor;
ctx.fill();
ctx.fillStyle = options.borderColor;
ctx.fill('evenodd');
} else {
ctx.beginPath();
helpers.addRoundedRectPath(ctx, {
x: inner.x,
y: inner.y,
w: inner.w,
h: inner.h,
radius
});
ctx.fillStyle = options.backgroundColor;
ctx.fill();
}
ctx.restore();
}
inRange(mouseX, mouseY, useFinalPosition) {
return inRange(this, mouseX, mouseY, useFinalPosition);
}
inXRange(mouseX, useFinalPosition) {
return inRange(this, mouseX, null, useFinalPosition);
}
inYRange(mouseY, useFinalPosition) {
return inRange(this, null, mouseY, useFinalPosition);
}
getCenterPoint(useFinalPosition) {
const { x, y, width, height } = this.getProps([
'x',
'y',
'width',
'height'
], useFinalPosition);
return {
x: x + width / 2,
y: y + height / 2
};
}
tooltipPosition() {
return this.getCenterPoint();
}
getRange(axis) {
return axis === 'x' ? this.width / 2 : this.height / 2;
}
constructor(cfg){
super();
if (cfg) {
Object.assign(this, cfg);
}
}
}
MatrixElement.id = 'matrix';
MatrixElement.defaults = {
backgroundColor: undefined,
borderColor: undefined,
borderWidth: undefined,
borderRadius: 0,
anchorX: 'center',
anchorY: 'center',
width: 20,
height: 20
};
chart_js.Chart.register(MatrixController, MatrixElement);
exports.MatrixController = MatrixController;
exports.MatrixElement = MatrixElement;
}));