Files
Chart.js/src/elements/element.bar.js
Jukka Kurkela ddc72fcfbb Align font options with CSS (#8066)
* Align font options with CSS

* Review comments
2020-11-19 20:59:24 +02:00

269 lines
6.6 KiB
JavaScript

import Element from '../core/core.element';
import {toTRBL, toTRBLCorners} from '../helpers/helpers.options';
import {PI, HALF_PI} from '../helpers/helpers.math';
/**
* 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 parseBorderSkipped(bar) {
let edge = bar.options.borderSkipped;
const res = {};
if (!edge) {
return res;
}
edge = bar.horizontal
? parseEdge(edge, 'left', 'right', bar.base > bar.x)
: parseEdge(edge, 'bottom', 'top', bar.base < bar.y);
res[edge] = true;
return res;
}
function parseEdge(edge, a, b, reverse) {
if (reverse) {
edge = swap(edge, a, b);
edge = startEnd(edge, b, a);
} else {
edge = startEnd(edge, a, b);
}
return edge;
}
function swap(orig, v1, v2) {
return orig === v1 ? v2 : orig === v2 ? v1 : orig;
}
function startEnd(v, start, end) {
return v === 'start' ? start : v === 'end' ? end : v;
}
function skipOrLimit(skip, value, min, max) {
return skip ? 0 : Math.max(Math.min(value, max), min);
}
function parseBorderWidth(bar, maxW, maxH) {
const value = bar.options.borderWidth;
const skip = parseBorderSkipped(bar);
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 value = bar.options.borderRadius;
const o = toTRBLCorners(value);
const maxR = Math.min(maxW, maxH);
const skip = parseBorderSkipped(bar);
return {
topLeft: skipOrLimit(skip.top || skip.left, o.topLeft, 0, maxR),
topRight: skipOrLimit(skip.top || skip.right, o.topRight, 0, maxR),
bottomLeft: skipOrLimit(skip.bottom || skip.left, o.bottomLeft, 0, maxR),
bottomRight: skipOrLimit(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 with rounded corners to the current sub-path
* @param {CanvasRenderingContext2D} ctx Context
* @param {*} rect Bounding rect
*/
function addRoundedRectPath(ctx, rect) {
const {x, y, w, h, radius} = rect;
// top left arc
ctx.arc(x + radius.topLeft, y + radius.topLeft, radius.topLeft, -HALF_PI, PI, true);
// line from top left to bottom left
ctx.lineTo(x, y + h - radius.bottomLeft);
// bottom left arc
ctx.arc(x + radius.bottomLeft, y + h - radius.bottomLeft, radius.bottomLeft, PI, HALF_PI, true);
// line from bottom left to bottom right
ctx.lineTo(x + w - radius.bottomRight, y + h);
// bottom right arc
ctx.arc(x + w - radius.bottomRight, y + h - radius.bottomRight, radius.bottomRight, HALF_PI, 0, true);
// line from bottom right to top right
ctx.lineTo(x + w, y + radius.topRight);
// top right arc
ctx.arc(x + w - radius.topRight, y + radius.topRight, radius.topRight, 0, -HALF_PI, true);
// line from top right to top left
ctx.lineTo(x + radius.topLeft, y);
}
/**
* 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);
}
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;
ctx.save();
if (outer.w !== inner.w || outer.h !== inner.h) {
ctx.beginPath();
addRectPath(ctx, outer);
ctx.clip();
addRectPath(ctx, inner);
ctx.fillStyle = options.borderColor;
ctx.fill('evenodd');
}
ctx.beginPath();
addRectPath(ctx, inner);
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
};
/**
* @type {any}
*/
BarElement.defaultRoutes = {
backgroundColor: 'backgroundColor',
borderColor: 'borderColor'
};