mirror of
https://github.com/chartjs/Chart.js.git
synced 2026-03-05 16:04:03 +01:00
225 lines
6.1 KiB
JavaScript
225 lines
6.1 KiB
JavaScript
import Element from '../core/core.element';
|
|
import {isObject, _limitValue} from '../helpers';
|
|
import {addRoundedRectPath} from '../helpers/helpers.canvas';
|
|
import {toTRBL, toTRBLCorners} from '../helpers/helpers.options';
|
|
|
|
/**
|
|
* Helper function to get the bounds of the bar regardless of the orientation
|
|
* @param {BarElement} bar the bar
|
|
* @param {boolean} [useFinalPosition]
|
|
* @return {object} bounds of the bar
|
|
* @private
|
|
*/
|
|
function getBarBounds(bar, useFinalPosition) {
|
|
const {x, y, base, width, height} = bar.getProps(['x', 'y', 'base', 'width', 'height'], useFinalPosition);
|
|
|
|
let left, right, top, bottom, half;
|
|
|
|
if (bar.horizontal) {
|
|
half = height / 2;
|
|
left = Math.min(x, base);
|
|
right = Math.max(x, base);
|
|
top = y - half;
|
|
bottom = y + half;
|
|
} else {
|
|
half = width / 2;
|
|
left = x - half;
|
|
right = x + half;
|
|
top = Math.min(y, base);
|
|
bottom = Math.max(y, base);
|
|
}
|
|
|
|
return {left, top, right, bottom};
|
|
}
|
|
|
|
function skipOrLimit(skip, value, min, max) {
|
|
return skip ? 0 : _limitValue(value, min, max);
|
|
}
|
|
|
|
function parseBorderWidth(bar, maxW, maxH) {
|
|
const value = bar.options.borderWidth;
|
|
const skip = bar.borderSkipped;
|
|
const o = toTRBL(value);
|
|
|
|
return {
|
|
t: skipOrLimit(skip.top, o.top, 0, maxH),
|
|
r: skipOrLimit(skip.right, o.right, 0, maxW),
|
|
b: skipOrLimit(skip.bottom, o.bottom, 0, maxH),
|
|
l: skipOrLimit(skip.left, o.left, 0, maxW)
|
|
};
|
|
}
|
|
|
|
function parseBorderRadius(bar, maxW, maxH) {
|
|
const {enableBorderRadius} = bar.getProps(['enableBorderRadius']);
|
|
const value = bar.options.borderRadius;
|
|
const o = toTRBLCorners(value);
|
|
const maxR = Math.min(maxW, maxH);
|
|
const skip = bar.borderSkipped;
|
|
|
|
// If the value is an object, assume the user knows what they are doing
|
|
// and apply as directed.
|
|
const enableBorder = enableBorderRadius || isObject(value);
|
|
|
|
return {
|
|
topLeft: skipOrLimit(!enableBorder || skip.top || skip.left, o.topLeft, 0, maxR),
|
|
topRight: skipOrLimit(!enableBorder || skip.top || skip.right, o.topRight, 0, maxR),
|
|
bottomLeft: skipOrLimit(!enableBorder || skip.bottom || skip.left, o.bottomLeft, 0, maxR),
|
|
bottomRight: skipOrLimit(!enableBorder || skip.bottom || skip.right, o.bottomRight, 0, maxR)
|
|
};
|
|
}
|
|
|
|
function boundingRects(bar) {
|
|
const bounds = getBarBounds(bar);
|
|
const width = bounds.right - bounds.left;
|
|
const height = bounds.bottom - bounds.top;
|
|
const border = parseBorderWidth(bar, width / 2, height / 2);
|
|
const radius = parseBorderRadius(bar, width / 2, height / 2);
|
|
|
|
return {
|
|
outer: {
|
|
x: bounds.left,
|
|
y: bounds.top,
|
|
w: width,
|
|
h: height,
|
|
radius
|
|
},
|
|
inner: {
|
|
x: bounds.left + border.l,
|
|
y: bounds.top + border.t,
|
|
w: width - border.l - border.r,
|
|
h: height - border.t - border.b,
|
|
radius: {
|
|
topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
|
|
topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)),
|
|
bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
|
|
bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r)),
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
function inRange(bar, x, y, useFinalPosition) {
|
|
const skipX = x === null;
|
|
const skipY = y === null;
|
|
const skipBoth = skipX && skipY;
|
|
const bounds = bar && !skipBoth && getBarBounds(bar, useFinalPosition);
|
|
|
|
return bounds
|
|
&& (skipX || x >= bounds.left && x <= bounds.right)
|
|
&& (skipY || y >= bounds.top && y <= bounds.bottom);
|
|
}
|
|
|
|
function hasRadius(radius) {
|
|
return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
|
|
}
|
|
|
|
/**
|
|
* Add a path of a rectangle to the current sub-path
|
|
* @param {CanvasRenderingContext2D} ctx Context
|
|
* @param {*} rect Bounding rect
|
|
*/
|
|
function addNormalRectPath(ctx, rect) {
|
|
ctx.rect(rect.x, rect.y, rect.w, rect.h);
|
|
}
|
|
|
|
function inflateRect(rect, amount, refRect = {}) {
|
|
const x = rect.x !== refRect.x ? -amount : 0;
|
|
const y = rect.y !== refRect.y ? -amount : 0;
|
|
const w = (rect.x + rect.w !== refRect.x + refRect.w ? amount : 0) - x;
|
|
const h = (rect.y + rect.h !== refRect.y + refRect.h ? amount : 0) - y;
|
|
return {
|
|
x: rect.x + x,
|
|
y: rect.y + y,
|
|
w: rect.w + w,
|
|
h: rect.h + h,
|
|
radius: rect.radius
|
|
};
|
|
}
|
|
|
|
export default class BarElement extends Element {
|
|
|
|
constructor(cfg) {
|
|
super();
|
|
|
|
this.options = undefined;
|
|
this.horizontal = undefined;
|
|
this.base = undefined;
|
|
this.width = undefined;
|
|
this.height = undefined;
|
|
|
|
if (cfg) {
|
|
Object.assign(this, cfg);
|
|
}
|
|
}
|
|
|
|
draw(ctx) {
|
|
const options = this.options;
|
|
const {inner, outer} = boundingRects(this);
|
|
const addRectPath = hasRadius(outer.radius) ? addRoundedRectPath : addNormalRectPath;
|
|
const inflateAmount = 0.33;
|
|
|
|
ctx.save();
|
|
|
|
if (outer.w !== inner.w || outer.h !== inner.h) {
|
|
ctx.beginPath();
|
|
addRectPath(ctx, inflateRect(outer, inflateAmount, inner));
|
|
ctx.clip();
|
|
addRectPath(ctx, inflateRect(inner, -inflateAmount, outer));
|
|
ctx.fillStyle = options.borderColor;
|
|
ctx.fill('evenodd');
|
|
}
|
|
|
|
ctx.beginPath();
|
|
addRectPath(ctx, inflateRect(inner, inflateAmount, outer));
|
|
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, base, horizontal} = this.getProps(['x', 'y', 'base', 'horizontal'], useFinalPosition);
|
|
return {
|
|
x: horizontal ? (x + base) / 2 : x,
|
|
y: horizontal ? y : (y + base) / 2
|
|
};
|
|
}
|
|
|
|
getRange(axis) {
|
|
return axis === 'x' ? this.width / 2 : this.height / 2;
|
|
}
|
|
}
|
|
|
|
BarElement.id = 'bar';
|
|
|
|
/**
|
|
* @type {any}
|
|
*/
|
|
BarElement.defaults = {
|
|
borderSkipped: 'start',
|
|
borderWidth: 0,
|
|
borderRadius: 0,
|
|
enableBorderRadius: true,
|
|
pointStyle: undefined
|
|
};
|
|
|
|
/**
|
|
* @type {any}
|
|
*/
|
|
BarElement.defaultRoutes = {
|
|
backgroundColor: 'backgroundColor',
|
|
borderColor: 'borderColor'
|
|
};
|