mirror of
https://github.com/chartjs/Chart.js.git
synced 2026-02-01 17:47:09 +00:00
refactor: rewrite canvas helpers to ts (#11100)
* refactor: rewrite canvas helpers to ts * refactor: review fixes * refactor: rm src/helpers/types.ts temporary entry point
This commit is contained in:
parent
22c6906bbe
commit
1324672637
@ -11,7 +11,7 @@ const offsetFromEdge = (scale, edge, offset) => edge === 'top' || edge === 'left
|
||||
const getTicksLimit = (ticksLength, maxTicksLimit) => Math.min(maxTicksLimit || ticksLength, ticksLength);
|
||||
|
||||
/**
|
||||
* @typedef { import('./core.controller.js').default } Chart
|
||||
* @typedef { import('../types/index.js').Chart } Chart
|
||||
* @typedef {{value:number | string, label?:string, major?:boolean, $context?:any}} Tick
|
||||
*/
|
||||
|
||||
@ -120,6 +120,7 @@ function createTickContext(parent, index, tick) {
|
||||
}
|
||||
|
||||
function titleAlign(align, position, reverse) {
|
||||
/** @type {CanvasTextAlign} */
|
||||
let ret = _toLeftRightCenter(align);
|
||||
if ((reverse && position !== 'right') || (!reverse && position === 'right')) {
|
||||
ret = reverseAlign(ret);
|
||||
@ -839,7 +840,7 @@ export default class Scale extends Element {
|
||||
} else if (isArray(label)) {
|
||||
// if it is an array let's measure each element
|
||||
for (j = 0, jlen = label.length; j < jlen; ++j) {
|
||||
nestedLabel = label[j];
|
||||
nestedLabel = /** @type {string} */ (label[j]);
|
||||
// Undefined labels and arrays should not be measured
|
||||
if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) {
|
||||
width = _measureText(ctx, cache.data, cache.gc, width, nestedLabel);
|
||||
|
||||
@ -22,6 +22,9 @@ function lineTo(ctx, previous, target) {
|
||||
ctx.lineTo(target.x, target.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {any}
|
||||
*/
|
||||
function getLineMethod(options) {
|
||||
if (options.stepped) {
|
||||
return _steppedLineTo;
|
||||
|
||||
@ -1,25 +1,28 @@
|
||||
import type {
|
||||
Chart,
|
||||
Point,
|
||||
FontSpec,
|
||||
CanvasFontSpec,
|
||||
PointStyle,
|
||||
RenderTextOpts,
|
||||
BackdropOptions
|
||||
} from '../types/index.js';
|
||||
import type {
|
||||
TRBL,
|
||||
SplinePoint,
|
||||
RoundedRect,
|
||||
TRBLCorners
|
||||
} from '../types/geometric.js';
|
||||
import {isArray, isNullOrUndef} from './helpers.core.js';
|
||||
import {PI, TAU, HALF_PI, QUARTER_PI, TWO_THIRDS_PI, RAD_PER_DEG} from './helpers.math.js';
|
||||
|
||||
/**
|
||||
* Note: typedefs are auto-exported, so use a made-up `canvas` namespace where
|
||||
* necessary to avoid duplicates with `export * from './helpers`; see
|
||||
* https://github.com/microsoft/TypeScript/issues/46011
|
||||
* @typedef { import('../core/core.controller.js').default } canvas.Chart
|
||||
* @typedef { import('../types/index.js').Point } Point
|
||||
*/
|
||||
|
||||
/**
|
||||
* @namespace Chart.helpers.canvas
|
||||
*/
|
||||
|
||||
/**
|
||||
* Converts the given font object into a CSS font string.
|
||||
* @param {object} font - A font object.
|
||||
* @return {string|null} The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font
|
||||
* @param font - A font object.
|
||||
* @return The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font
|
||||
* @private
|
||||
*/
|
||||
export function toFontString(font) {
|
||||
export function toFontString(font: FontSpec) {
|
||||
if (!font || isNullOrUndef(font.size) || isNullOrUndef(font.family)) {
|
||||
return null;
|
||||
}
|
||||
@ -33,7 +36,13 @@ export function toFontString(font) {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export function _measureText(ctx, data, gc, longest, string) {
|
||||
export function _measureText(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
data: Record<string, number>,
|
||||
gc: string[],
|
||||
longest: number,
|
||||
string: string
|
||||
) {
|
||||
let textWidth = data[string];
|
||||
if (!textWidth) {
|
||||
textWidth = data[string] = ctx.measureText(string).width;
|
||||
@ -45,10 +54,19 @@ export function _measureText(ctx, data, gc, longest, string) {
|
||||
return longest;
|
||||
}
|
||||
|
||||
type Thing = string | undefined | null
|
||||
type Things = (Thing | Thing[])[]
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export function _longestText(ctx, font, arrayOfThings, cache) {
|
||||
// eslint-disable-next-line complexity
|
||||
export function _longestText(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
font: string,
|
||||
arrayOfThings: Things,
|
||||
cache?: {data?: Record<string, number>, garbageCollect?: string[], font?: string}
|
||||
) {
|
||||
cache = cache || {};
|
||||
let data = cache.data = cache.data || {};
|
||||
let gc = cache.garbageCollect = cache.garbageCollect || [];
|
||||
@ -64,12 +82,12 @@ export function _longestText(ctx, font, arrayOfThings, cache) {
|
||||
ctx.font = font;
|
||||
let longest = 0;
|
||||
const ilen = arrayOfThings.length;
|
||||
let i, j, jlen, thing, nestedThing;
|
||||
let i: number, j: number, jlen: number, thing: Thing | Thing[], nestedThing: Thing | Thing[];
|
||||
for (i = 0; i < ilen; i++) {
|
||||
thing = arrayOfThings[i];
|
||||
|
||||
// Undefined strings and arrays should not be measured
|
||||
if (thing !== undefined && thing !== null && isArray(thing) !== true) {
|
||||
if (thing !== undefined && thing !== null && !isArray(thing)) {
|
||||
longest = _measureText(ctx, data, gc, longest, thing);
|
||||
} else if (isArray(thing)) {
|
||||
// if it is an array lets measure each element
|
||||
@ -98,13 +116,13 @@ export function _longestText(ctx, font, arrayOfThings, cache) {
|
||||
|
||||
/**
|
||||
* Returns the aligned pixel value to avoid anti-aliasing blur
|
||||
* @param {canvas.Chart} chart - The chart instance.
|
||||
* @param {number} pixel - A pixel value.
|
||||
* @param {number} width - The width of the element.
|
||||
* @returns {number} The aligned pixel value.
|
||||
* @param chart - The chart instance.
|
||||
* @param pixel - A pixel value.
|
||||
* @param width - The width of the element.
|
||||
* @returns The aligned pixel value.
|
||||
* @private
|
||||
*/
|
||||
export function _alignPixel(chart, pixel, width) {
|
||||
export function _alignPixel(chart: Chart, pixel: number, width: number) {
|
||||
const devicePixelRatio = chart.currentDevicePixelRatio;
|
||||
const halfWidth = width !== 0 ? Math.max(width / 2, 0.5) : 0;
|
||||
return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth;
|
||||
@ -112,10 +130,8 @@ export function _alignPixel(chart, pixel, width) {
|
||||
|
||||
/**
|
||||
* Clears the entire canvas.
|
||||
* @param {HTMLCanvasElement} canvas
|
||||
* @param {CanvasRenderingContext2D} [ctx]
|
||||
*/
|
||||
export function clearCanvas(canvas, ctx) {
|
||||
export function clearCanvas(canvas: HTMLCanvasElement, ctx?: CanvasRenderingContext2D) {
|
||||
ctx = ctx || canvas.getContext('2d');
|
||||
|
||||
ctx.save();
|
||||
@ -126,12 +142,32 @@ export function clearCanvas(canvas, ctx) {
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
export function drawPoint(ctx, options, x, y) {
|
||||
export interface DrawPointOptions {
|
||||
pointStyle: PointStyle;
|
||||
rotation?: number;
|
||||
radius: number;
|
||||
borderWidth: number;
|
||||
}
|
||||
|
||||
export function drawPoint(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
options: DrawPointOptions,
|
||||
x: number,
|
||||
y: number
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
drawPointLegend(ctx, options, x, y, null);
|
||||
}
|
||||
|
||||
export function drawPointLegend(ctx, options, x, y, w) {
|
||||
let type, xOffset, yOffset, size, cornerRadius, width, xOffsetW, yOffsetW;
|
||||
// eslint-disable-next-line complexity
|
||||
export function drawPointLegend(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
options: DrawPointOptions,
|
||||
x: number,
|
||||
y: number,
|
||||
w: number
|
||||
) {
|
||||
let type: string, xOffset: number, yOffset: number, size: number, cornerRadius: number, width: number, xOffsetW: number, yOffsetW: number;
|
||||
const style = options.pointStyle;
|
||||
const rotation = options.rotation;
|
||||
const radius = options.radius;
|
||||
@ -157,24 +193,24 @@ export function drawPointLegend(ctx, options, x, y, w) {
|
||||
|
||||
switch (style) {
|
||||
// Default includes circle
|
||||
default:
|
||||
if (w) {
|
||||
ctx.ellipse(x, y, w / 2, radius, 0, 0, TAU);
|
||||
} else {
|
||||
ctx.arc(x, y, radius, 0, TAU);
|
||||
}
|
||||
ctx.closePath();
|
||||
break;
|
||||
case 'triangle':
|
||||
width = w ? w / 2 : radius;
|
||||
ctx.moveTo(x + Math.sin(rad) * width, y - Math.cos(rad) * radius);
|
||||
rad += TWO_THIRDS_PI;
|
||||
ctx.lineTo(x + Math.sin(rad) * width, y - Math.cos(rad) * radius);
|
||||
rad += TWO_THIRDS_PI;
|
||||
ctx.lineTo(x + Math.sin(rad) * width, y - Math.cos(rad) * radius);
|
||||
ctx.closePath();
|
||||
break;
|
||||
case 'rectRounded':
|
||||
default:
|
||||
if (w) {
|
||||
ctx.ellipse(x, y, w / 2, radius, 0, 0, TAU);
|
||||
} else {
|
||||
ctx.arc(x, y, radius, 0, TAU);
|
||||
}
|
||||
ctx.closePath();
|
||||
break;
|
||||
case 'triangle':
|
||||
width = w ? w / 2 : radius;
|
||||
ctx.moveTo(x + Math.sin(rad) * width, y - Math.cos(rad) * radius);
|
||||
rad += TWO_THIRDS_PI;
|
||||
ctx.lineTo(x + Math.sin(rad) * width, y - Math.cos(rad) * radius);
|
||||
rad += TWO_THIRDS_PI;
|
||||
ctx.lineTo(x + Math.sin(rad) * width, y - Math.cos(rad) * radius);
|
||||
ctx.closePath();
|
||||
break;
|
||||
case 'rectRounded':
|
||||
// NOTE: the rounded rect implementation changed to use `arc` instead of
|
||||
// `quadraticCurveTo` since it generates better results when rect is
|
||||
// almost a circle. 0.516 (instead of 0.5) produces results with visually
|
||||
@ -182,83 +218,83 @@ export function drawPointLegend(ctx, options, x, y, w) {
|
||||
// circle with `radius`. For more details, see the following PRs:
|
||||
// https://github.com/chartjs/Chart.js/issues/5597
|
||||
// https://github.com/chartjs/Chart.js/issues/5858
|
||||
cornerRadius = radius * 0.516;
|
||||
size = radius - cornerRadius;
|
||||
xOffset = Math.cos(rad + QUARTER_PI) * size;
|
||||
xOffsetW = Math.cos(rad + QUARTER_PI) * (w ? w / 2 - cornerRadius : size);
|
||||
yOffset = Math.sin(rad + QUARTER_PI) * size;
|
||||
yOffsetW = Math.sin(rad + QUARTER_PI) * (w ? w / 2 - cornerRadius : size);
|
||||
ctx.arc(x - xOffsetW, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI);
|
||||
ctx.arc(x + yOffsetW, y - xOffset, cornerRadius, rad - HALF_PI, rad);
|
||||
ctx.arc(x + xOffsetW, y + yOffset, cornerRadius, rad, rad + HALF_PI);
|
||||
ctx.arc(x - yOffsetW, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI);
|
||||
ctx.closePath();
|
||||
break;
|
||||
case 'rect':
|
||||
if (!rotation) {
|
||||
size = Math.SQRT1_2 * radius;
|
||||
width = w ? w / 2 : size;
|
||||
ctx.rect(x - width, y - size, 2 * width, 2 * size);
|
||||
cornerRadius = radius * 0.516;
|
||||
size = radius - cornerRadius;
|
||||
xOffset = Math.cos(rad + QUARTER_PI) * size;
|
||||
xOffsetW = Math.cos(rad + QUARTER_PI) * (w ? w / 2 - cornerRadius : size);
|
||||
yOffset = Math.sin(rad + QUARTER_PI) * size;
|
||||
yOffsetW = Math.sin(rad + QUARTER_PI) * (w ? w / 2 - cornerRadius : size);
|
||||
ctx.arc(x - xOffsetW, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI);
|
||||
ctx.arc(x + yOffsetW, y - xOffset, cornerRadius, rad - HALF_PI, rad);
|
||||
ctx.arc(x + xOffsetW, y + yOffset, cornerRadius, rad, rad + HALF_PI);
|
||||
ctx.arc(x - yOffsetW, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI);
|
||||
ctx.closePath();
|
||||
break;
|
||||
}
|
||||
rad += QUARTER_PI;
|
||||
case 'rect':
|
||||
if (!rotation) {
|
||||
size = Math.SQRT1_2 * radius;
|
||||
width = w ? w / 2 : size;
|
||||
ctx.rect(x - width, y - size, 2 * width, 2 * size);
|
||||
break;
|
||||
}
|
||||
rad += QUARTER_PI;
|
||||
/* falls through */
|
||||
case 'rectRot':
|
||||
xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
|
||||
xOffset = Math.cos(rad) * radius;
|
||||
yOffset = Math.sin(rad) * radius;
|
||||
yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
|
||||
ctx.moveTo(x - xOffsetW, y - yOffset);
|
||||
ctx.lineTo(x + yOffsetW, y - xOffset);
|
||||
ctx.lineTo(x + xOffsetW, y + yOffset);
|
||||
ctx.lineTo(x - yOffsetW, y + xOffset);
|
||||
ctx.closePath();
|
||||
break;
|
||||
case 'crossRot':
|
||||
rad += QUARTER_PI;
|
||||
case 'rectRot':
|
||||
xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
|
||||
xOffset = Math.cos(rad) * radius;
|
||||
yOffset = Math.sin(rad) * radius;
|
||||
yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
|
||||
ctx.moveTo(x - xOffsetW, y - yOffset);
|
||||
ctx.lineTo(x + yOffsetW, y - xOffset);
|
||||
ctx.lineTo(x + xOffsetW, y + yOffset);
|
||||
ctx.lineTo(x - yOffsetW, y + xOffset);
|
||||
ctx.closePath();
|
||||
break;
|
||||
case 'crossRot':
|
||||
rad += QUARTER_PI;
|
||||
/* falls through */
|
||||
case 'cross':
|
||||
xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
|
||||
xOffset = Math.cos(rad) * radius;
|
||||
yOffset = Math.sin(rad) * radius;
|
||||
yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
|
||||
ctx.moveTo(x - xOffsetW, y - yOffset);
|
||||
ctx.lineTo(x + xOffsetW, y + yOffset);
|
||||
ctx.moveTo(x + yOffsetW, y - xOffset);
|
||||
ctx.lineTo(x - yOffsetW, y + xOffset);
|
||||
break;
|
||||
case 'star':
|
||||
xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
|
||||
xOffset = Math.cos(rad) * radius;
|
||||
yOffset = Math.sin(rad) * radius;
|
||||
yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
|
||||
ctx.moveTo(x - xOffsetW, y - yOffset);
|
||||
ctx.lineTo(x + xOffsetW, y + yOffset);
|
||||
ctx.moveTo(x + yOffsetW, y - xOffset);
|
||||
ctx.lineTo(x - yOffsetW, y + xOffset);
|
||||
rad += QUARTER_PI;
|
||||
xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
|
||||
xOffset = Math.cos(rad) * radius;
|
||||
yOffset = Math.sin(rad) * radius;
|
||||
yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
|
||||
ctx.moveTo(x - xOffsetW, y - yOffset);
|
||||
ctx.lineTo(x + xOffsetW, y + yOffset);
|
||||
ctx.moveTo(x + yOffsetW, y - xOffset);
|
||||
ctx.lineTo(x - yOffsetW, y + xOffset);
|
||||
break;
|
||||
case 'line':
|
||||
xOffset = w ? w / 2 : Math.cos(rad) * radius;
|
||||
yOffset = Math.sin(rad) * radius;
|
||||
ctx.moveTo(x - xOffset, y - yOffset);
|
||||
ctx.lineTo(x + xOffset, y + yOffset);
|
||||
break;
|
||||
case 'dash':
|
||||
ctx.moveTo(x, y);
|
||||
ctx.lineTo(x + Math.cos(rad) * (w ? w / 2 : radius), y + Math.sin(rad) * radius);
|
||||
break;
|
||||
case false:
|
||||
ctx.closePath();
|
||||
break;
|
||||
case 'cross':
|
||||
xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
|
||||
xOffset = Math.cos(rad) * radius;
|
||||
yOffset = Math.sin(rad) * radius;
|
||||
yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
|
||||
ctx.moveTo(x - xOffsetW, y - yOffset);
|
||||
ctx.lineTo(x + xOffsetW, y + yOffset);
|
||||
ctx.moveTo(x + yOffsetW, y - xOffset);
|
||||
ctx.lineTo(x - yOffsetW, y + xOffset);
|
||||
break;
|
||||
case 'star':
|
||||
xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
|
||||
xOffset = Math.cos(rad) * radius;
|
||||
yOffset = Math.sin(rad) * radius;
|
||||
yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
|
||||
ctx.moveTo(x - xOffsetW, y - yOffset);
|
||||
ctx.lineTo(x + xOffsetW, y + yOffset);
|
||||
ctx.moveTo(x + yOffsetW, y - xOffset);
|
||||
ctx.lineTo(x - yOffsetW, y + xOffset);
|
||||
rad += QUARTER_PI;
|
||||
xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
|
||||
xOffset = Math.cos(rad) * radius;
|
||||
yOffset = Math.sin(rad) * radius;
|
||||
yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
|
||||
ctx.moveTo(x - xOffsetW, y - yOffset);
|
||||
ctx.lineTo(x + xOffsetW, y + yOffset);
|
||||
ctx.moveTo(x + yOffsetW, y - xOffset);
|
||||
ctx.lineTo(x - yOffsetW, y + xOffset);
|
||||
break;
|
||||
case 'line':
|
||||
xOffset = w ? w / 2 : Math.cos(rad) * radius;
|
||||
yOffset = Math.sin(rad) * radius;
|
||||
ctx.moveTo(x - xOffset, y - yOffset);
|
||||
ctx.lineTo(x + xOffset, y + yOffset);
|
||||
break;
|
||||
case 'dash':
|
||||
ctx.moveTo(x, y);
|
||||
ctx.lineTo(x + Math.cos(rad) * (w ? w / 2 : radius), y + Math.sin(rad) * radius);
|
||||
break;
|
||||
case false:
|
||||
ctx.closePath();
|
||||
break;
|
||||
}
|
||||
|
||||
ctx.fill();
|
||||
@ -269,34 +305,43 @@ export function drawPointLegend(ctx, options, x, y, w) {
|
||||
|
||||
/**
|
||||
* Returns true if the point is inside the rectangle
|
||||
* @param {Point} point - The point to test
|
||||
* @param {object} area - The rectangle
|
||||
* @param {number} [margin] - allowed margin
|
||||
* @returns {boolean}
|
||||
* @param point - The point to test
|
||||
* @param area - The rectangle
|
||||
* @param margin - allowed margin
|
||||
* @private
|
||||
*/
|
||||
export function _isPointInArea(point, area, margin) {
|
||||
export function _isPointInArea(
|
||||
point: Point,
|
||||
area: TRBL,
|
||||
margin?: number
|
||||
) {
|
||||
margin = margin || 0.5; // margin - default is to match rounded decimals
|
||||
|
||||
return !area || (point && point.x > area.left - margin && point.x < area.right + margin &&
|
||||
point.y > area.top - margin && point.y < area.bottom + margin);
|
||||
}
|
||||
|
||||
export function clipArea(ctx, area) {
|
||||
export function clipArea(ctx: CanvasRenderingContext2D, area: TRBL) {
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);
|
||||
ctx.clip();
|
||||
}
|
||||
|
||||
export function unclipArea(ctx) {
|
||||
export function unclipArea(ctx: CanvasRenderingContext2D) {
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export function _steppedLineTo(ctx, previous, target, flip, mode) {
|
||||
export function _steppedLineTo(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
previous: Point,
|
||||
target: Point,
|
||||
flip?: boolean,
|
||||
mode?: string
|
||||
) {
|
||||
if (!previous) {
|
||||
return ctx.lineTo(target.x, target.y);
|
||||
}
|
||||
@ -315,7 +360,12 @@ export function _steppedLineTo(ctx, previous, target, flip, mode) {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export function _bezierCurveTo(ctx, previous, target, flip) {
|
||||
export function _bezierCurveTo(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
previous: SplinePoint,
|
||||
target: SplinePoint,
|
||||
flip?: boolean
|
||||
) {
|
||||
if (!previous) {
|
||||
return ctx.lineTo(target.x, target.y);
|
||||
}
|
||||
@ -328,13 +378,81 @@ export function _bezierCurveTo(ctx, previous, target, flip) {
|
||||
target.y);
|
||||
}
|
||||
|
||||
function setRenderOpts(ctx: CanvasRenderingContext2D, opts: RenderTextOpts) {
|
||||
if (opts.translation) {
|
||||
ctx.translate(opts.translation[0], opts.translation[1]);
|
||||
}
|
||||
|
||||
if (!isNullOrUndef(opts.rotation)) {
|
||||
ctx.rotate(opts.rotation);
|
||||
}
|
||||
|
||||
if (opts.color) {
|
||||
ctx.fillStyle = opts.color;
|
||||
}
|
||||
|
||||
if (opts.textAlign) {
|
||||
ctx.textAlign = opts.textAlign;
|
||||
}
|
||||
|
||||
if (opts.textBaseline) {
|
||||
ctx.textBaseline = opts.textBaseline;
|
||||
}
|
||||
}
|
||||
|
||||
function decorateText(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
line: string,
|
||||
opts: RenderTextOpts
|
||||
) {
|
||||
if (opts.strikethrough || opts.underline) {
|
||||
/**
|
||||
* Now that IE11 support has been dropped, we can use more
|
||||
* of the TextMetrics object. The actual bounding boxes
|
||||
* are unflagged in Chrome, Firefox, Edge, and Safari so they
|
||||
* can be safely used.
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics#Browser_compatibility
|
||||
*/
|
||||
const metrics = ctx.measureText(line);
|
||||
const left = x - metrics.actualBoundingBoxLeft;
|
||||
const right = x + metrics.actualBoundingBoxRight;
|
||||
const top = y - metrics.actualBoundingBoxAscent;
|
||||
const bottom = y + metrics.actualBoundingBoxDescent;
|
||||
const yDecoration = opts.strikethrough ? (top + bottom) / 2 : bottom;
|
||||
|
||||
ctx.strokeStyle = ctx.fillStyle;
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = opts.decorationWidth || 2;
|
||||
ctx.moveTo(left, yDecoration);
|
||||
ctx.lineTo(right, yDecoration);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
function drawBackdrop(ctx: CanvasRenderingContext2D, opts: BackdropOptions) {
|
||||
const oldColor = ctx.fillStyle;
|
||||
|
||||
ctx.fillStyle = opts.color as string;
|
||||
ctx.fillRect(opts.left, opts.top, opts.width, opts.height);
|
||||
ctx.fillStyle = oldColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render text onto the canvas
|
||||
*/
|
||||
export function renderText(ctx, text, x, y, font, opts = {}) {
|
||||
export function renderText(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
text: string | string[],
|
||||
x: number,
|
||||
y: number,
|
||||
font: CanvasFontSpec,
|
||||
opts: RenderTextOpts = {}
|
||||
) {
|
||||
const lines = isArray(text) ? text : [text];
|
||||
const stroke = opts.strokeWidth > 0 && opts.strokeColor !== '';
|
||||
let i, line;
|
||||
let i: number, line: string;
|
||||
|
||||
ctx.save();
|
||||
ctx.font = font.string;
|
||||
@ -362,73 +480,21 @@ export function renderText(ctx, text, x, y, font, opts = {}) {
|
||||
ctx.fillText(line, x, y, opts.maxWidth);
|
||||
decorateText(ctx, x, y, line, opts);
|
||||
|
||||
y += font.lineHeight;
|
||||
y += Number(font.lineHeight);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function setRenderOpts(ctx, opts) {
|
||||
if (opts.translation) {
|
||||
ctx.translate(opts.translation[0], opts.translation[1]);
|
||||
}
|
||||
|
||||
if (!isNullOrUndef(opts.rotation)) {
|
||||
ctx.rotate(opts.rotation);
|
||||
}
|
||||
|
||||
if (opts.color) {
|
||||
ctx.fillStyle = opts.color;
|
||||
}
|
||||
|
||||
if (opts.textAlign) {
|
||||
ctx.textAlign = opts.textAlign;
|
||||
}
|
||||
|
||||
if (opts.textBaseline) {
|
||||
ctx.textBaseline = opts.textBaseline;
|
||||
}
|
||||
}
|
||||
|
||||
function decorateText(ctx, x, y, line, opts) {
|
||||
if (opts.strikethrough || opts.underline) {
|
||||
/**
|
||||
* Now that IE11 support has been dropped, we can use more
|
||||
* of the TextMetrics object. The actual bounding boxes
|
||||
* are unflagged in Chrome, Firefox, Edge, and Safari so they
|
||||
* can be safely used.
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics#Browser_compatibility
|
||||
*/
|
||||
const metrics = ctx.measureText(line);
|
||||
const left = x - metrics.actualBoundingBoxLeft;
|
||||
const right = x + metrics.actualBoundingBoxRight;
|
||||
const top = y - metrics.actualBoundingBoxAscent;
|
||||
const bottom = y + metrics.actualBoundingBoxDescent;
|
||||
const yDecoration = opts.strikethrough ? (top + bottom) / 2 : bottom;
|
||||
|
||||
ctx.strokeStyle = ctx.fillStyle;
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = opts.decorationWidth || 2;
|
||||
ctx.moveTo(left, yDecoration);
|
||||
ctx.lineTo(right, yDecoration);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
function drawBackdrop(ctx, opts) {
|
||||
const oldColor = ctx.fillStyle;
|
||||
|
||||
ctx.fillStyle = opts.color;
|
||||
ctx.fillRect(opts.left, opts.top, opts.width, opts.height);
|
||||
ctx.fillStyle = oldColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a path of a rectangle with rounded corners to the current sub-path
|
||||
* @param {CanvasRenderingContext2D} ctx Context
|
||||
* @param {*} rect Bounding rect
|
||||
* @param ctx - Context
|
||||
* @param rect - Bounding rect
|
||||
*/
|
||||
export function addRoundedRectPath(ctx, rect) {
|
||||
export function addRoundedRectPath(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
rect: RoundedRect & { radius: TRBLCorners }
|
||||
) {
|
||||
const {x, y, w, h, radius} = rect;
|
||||
|
||||
// top left arc
|
||||
@ -1,19 +1,7 @@
|
||||
import {almostEquals, distanceBetweenPoints, sign} from './helpers.math.js';
|
||||
import {_isPointInArea} from './helpers.canvas.js';
|
||||
import type {ChartArea} from '../types/index.js';
|
||||
|
||||
export interface SplinePoint {
|
||||
x: number;
|
||||
y: number;
|
||||
skip?: boolean;
|
||||
|
||||
// Both Bezier and monotone interpolations have these fields
|
||||
// but they are added in different spots
|
||||
cp1x?: number;
|
||||
cp1y?: number;
|
||||
cp2x?: number;
|
||||
cp2y?: number;
|
||||
}
|
||||
import type {SplinePoint} from '../types/geometric.js';
|
||||
|
||||
const EPSILON = Number.EPSILON || 1e-14;
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import type {Point} from '../types/geometric.js';
|
||||
import type {SplinePoint} from './helpers.curve.js';
|
||||
import type {Point, SplinePoint} from '../types/geometric.js';
|
||||
|
||||
/**
|
||||
* @private
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import defaults from '../core/core.defaults.js';
|
||||
import {isArray, isObject, toDimension, valueOrDefault} from './helpers.core.js';
|
||||
import {Point, toFontString} from './helpers.canvas.js';
|
||||
import type {ChartArea, FontSpec} from '../types/index.js';
|
||||
import {toFontString} from './helpers.canvas.js';
|
||||
import type {ChartArea, FontSpec, Point} from '../types/index.js';
|
||||
import type {TRBL, TRBLCorners} from '../types/geometric.js';
|
||||
|
||||
const LINE_HEIGHT = /^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/;
|
||||
@ -104,10 +104,6 @@ export function toPadding(value?: number | TRBL): ChartArea {
|
||||
return obj;
|
||||
}
|
||||
|
||||
export interface CanvasFontSpec extends FontSpec {
|
||||
string: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses font options and returns the font object.
|
||||
* @param options - A object that contains font options to be parsed.
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
/**
|
||||
* Temporary entry point of the types at the time of the transition.
|
||||
* After transition done need to remove it in favor of index.ts
|
||||
*/
|
||||
|
||||
// export * from '..js';
|
||||
export * from './helpers.color.js';
|
||||
export * from './helpers.collection.js';
|
||||
export * from './helpers.core.js';
|
||||
export * from './helpers.curve.js';
|
||||
export * from './helpers.dom.js';
|
||||
export * from './helpers.easing.js';
|
||||
export * from './helpers.extras.js';
|
||||
export * from './helpers.interpolation.js';
|
||||
export * from './helpers.intl.js';
|
||||
export * from './helpers.math.js';
|
||||
export * from './helpers.options.js';
|
||||
export * from './helpers.rtl.js';
|
||||
export * from '../types/helpers/index.js';
|
||||
13
src/types/geometric.d.ts
vendored
13
src/types/geometric.d.ts
vendored
@ -37,3 +37,16 @@ export type RoundedRect = {
|
||||
}
|
||||
|
||||
export type Padding = Partial<TRBL> | number | Point;
|
||||
|
||||
export interface SplinePoint {
|
||||
x: number;
|
||||
y: number;
|
||||
skip?: boolean;
|
||||
|
||||
// Both Bezier and monotone interpolations have these fields
|
||||
// but they are added in different spots
|
||||
cp1x?: number;
|
||||
cp1y?: number;
|
||||
cp2x?: number;
|
||||
cp2y?: number;
|
||||
}
|
||||
|
||||
135
src/types/helpers/helpers.canvas.d.ts
vendored
135
src/types/helpers/helpers.canvas.d.ts
vendored
@ -1,135 +0,0 @@
|
||||
import {PointStyle, Scriptable, ScriptableScaleContext} from '../index.js';
|
||||
import {Color} from '../color.js';
|
||||
import {ChartArea, RoundedRect} from '../geometric.js';
|
||||
import {CanvasFontSpec} from '../../helpers/helpers.options.js';
|
||||
|
||||
export function clearCanvas(canvas: HTMLCanvasElement, ctx?: CanvasRenderingContext2D): void;
|
||||
|
||||
export function clipArea(ctx: CanvasRenderingContext2D, area: ChartArea): void;
|
||||
|
||||
export function unclipArea(ctx: CanvasRenderingContext2D): void;
|
||||
|
||||
export interface DrawPointOptions {
|
||||
pointStyle: PointStyle;
|
||||
rotation?: number;
|
||||
radius: number;
|
||||
borderWidth: number;
|
||||
}
|
||||
|
||||
export function drawPoint(ctx: CanvasRenderingContext2D, options: DrawPointOptions, x: number, y: number): void;
|
||||
|
||||
export function drawPointLegend(ctx: CanvasRenderingContext2D, options: DrawPointOptions, x: number, y: number, w: number): void;
|
||||
|
||||
/**
|
||||
* Converts the given font object into a CSS font string.
|
||||
* @param font a font object
|
||||
* @return The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font
|
||||
*/
|
||||
export function toFontString(font: { size: number; family: string; style?: string; weight?: string }): string | null;
|
||||
|
||||
export interface RenderTextOpts {
|
||||
/**
|
||||
* The fill color of the text. If unset, the existing
|
||||
* fillStyle property of the canvas is unchanged.
|
||||
*/
|
||||
color?: Color;
|
||||
|
||||
/**
|
||||
* The width of the strikethrough / underline
|
||||
* @default 2
|
||||
*/
|
||||
decorationWidth?: number;
|
||||
|
||||
/**
|
||||
* The max width of the text in pixels
|
||||
*/
|
||||
maxWidth?: number;
|
||||
|
||||
/**
|
||||
* A rotation to be applied to the canvas
|
||||
* This is applied after the translation is applied
|
||||
*/
|
||||
rotation?: number;
|
||||
|
||||
/**
|
||||
* Apply a strikethrough effect to the text
|
||||
*/
|
||||
strikethrough?: boolean;
|
||||
|
||||
/**
|
||||
* The color of the text stroke. If unset, the existing
|
||||
* strokeStyle property of the context is unchanged
|
||||
*/
|
||||
strokeColor?: Color;
|
||||
|
||||
/**
|
||||
* The text stroke width. If unset, the existing
|
||||
* lineWidth property of the context is unchanged
|
||||
*/
|
||||
strokeWidth?: number;
|
||||
|
||||
/**
|
||||
* The text alignment to use. If unset, the existing
|
||||
* textAlign property of the context is unchanged
|
||||
*/
|
||||
textAlign?: CanvasTextAlign;
|
||||
|
||||
/**
|
||||
* The text baseline to use. If unset, the existing
|
||||
* textBaseline property of the context is unchanged
|
||||
*/
|
||||
textBaseline?: CanvasTextBaseline;
|
||||
|
||||
/**
|
||||
* If specified, a translation to apply to the context
|
||||
*/
|
||||
translation?: [number, number];
|
||||
|
||||
/**
|
||||
* Underline the text
|
||||
*/
|
||||
underline?: boolean;
|
||||
|
||||
/**
|
||||
* Dimensions for drawing the label backdrop
|
||||
*/
|
||||
backdrop?: BackdropOptions;
|
||||
}
|
||||
|
||||
export interface BackdropOptions {
|
||||
/**
|
||||
* Left position of backdrop as pixel
|
||||
*/
|
||||
left: number;
|
||||
|
||||
/**
|
||||
* Top position of backdrop as pixel
|
||||
*/
|
||||
top: number;
|
||||
|
||||
/**
|
||||
* Width of backdrop in pixels
|
||||
*/
|
||||
width: number;
|
||||
|
||||
/**
|
||||
* Height of backdrop in pixels
|
||||
*/
|
||||
height: number;
|
||||
|
||||
/**
|
||||
* Color of label backdrops.
|
||||
*/
|
||||
color: Scriptable<Color, ScriptableScaleContext>;
|
||||
}
|
||||
|
||||
export function renderText(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
text: string | string[],
|
||||
x: number,
|
||||
y: number,
|
||||
font: CanvasFontSpec,
|
||||
opts?: RenderTextOpts
|
||||
): void;
|
||||
|
||||
export function addRoundedRectPath(ctx: CanvasRenderingContext2D, rect: RoundedRect): void;
|
||||
1
src/types/helpers/helpers.segment.d.ts
vendored
1
src/types/helpers/helpers.segment.d.ts
vendored
@ -1 +0,0 @@
|
||||
export {};
|
||||
3
src/types/helpers/index.d.ts
vendored
3
src/types/helpers/index.d.ts
vendored
@ -1,3 +0,0 @@
|
||||
export * from './helpers.canvas.js';
|
||||
export * from './helpers.canvas.js';
|
||||
export * from './helpers.segment.js';
|
||||
110
src/types/index.d.ts
vendored
110
src/types/index.d.ts
vendored
@ -10,9 +10,7 @@ import {Color} from './color.js';
|
||||
import Element from '../core/core.element.js';
|
||||
import {ChartArea, Padding, Point} from './geometric.js';
|
||||
import {LayoutItem, LayoutPosition} from './layout.js';
|
||||
import {RenderTextOpts} from './helpers/helpers.canvas.js';
|
||||
import {CanvasFontSpec} from '../helpers/helpers.options.js';
|
||||
import type {ColorsPluginOptions} from '../plugins/plugin.colors.js';
|
||||
import {ColorsPluginOptions} from '../plugins/plugin.colors.js';
|
||||
|
||||
export {EasingFunction} from '../helpers/helpers.easing.js';
|
||||
export {default as ArcElement, ArcProps} from '../elements/element.arc.js';
|
||||
@ -548,6 +546,8 @@ export declare class Chart<
|
||||
|
||||
isPluginEnabled(pluginId: string): boolean;
|
||||
|
||||
getContext(): { chart: Chart, type: string };
|
||||
|
||||
static readonly defaults: Defaults;
|
||||
static readonly overrides: Overrides;
|
||||
static readonly version: string;
|
||||
@ -1359,6 +1359,102 @@ export interface ScriptableScalePointLabelContext {
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface RenderTextOpts {
|
||||
/**
|
||||
* The fill color of the text. If unset, the existing
|
||||
* fillStyle property of the canvas is unchanged.
|
||||
*/
|
||||
color?: Color;
|
||||
|
||||
/**
|
||||
* The width of the strikethrough / underline
|
||||
* @default 2
|
||||
*/
|
||||
decorationWidth?: number;
|
||||
|
||||
/**
|
||||
* The max width of the text in pixels
|
||||
*/
|
||||
maxWidth?: number;
|
||||
|
||||
/**
|
||||
* A rotation to be applied to the canvas
|
||||
* This is applied after the translation is applied
|
||||
*/
|
||||
rotation?: number;
|
||||
|
||||
/**
|
||||
* Apply a strikethrough effect to the text
|
||||
*/
|
||||
strikethrough?: boolean;
|
||||
|
||||
/**
|
||||
* The color of the text stroke. If unset, the existing
|
||||
* strokeStyle property of the context is unchanged
|
||||
*/
|
||||
strokeColor?: Color;
|
||||
|
||||
/**
|
||||
* The text stroke width. If unset, the existing
|
||||
* lineWidth property of the context is unchanged
|
||||
*/
|
||||
strokeWidth?: number;
|
||||
|
||||
/**
|
||||
* The text alignment to use. If unset, the existing
|
||||
* textAlign property of the context is unchanged
|
||||
*/
|
||||
textAlign?: CanvasTextAlign;
|
||||
|
||||
/**
|
||||
* The text baseline to use. If unset, the existing
|
||||
* textBaseline property of the context is unchanged
|
||||
*/
|
||||
textBaseline?: CanvasTextBaseline;
|
||||
|
||||
/**
|
||||
* If specified, a translation to apply to the context
|
||||
*/
|
||||
translation?: [number, number];
|
||||
|
||||
/**
|
||||
* Underline the text
|
||||
*/
|
||||
underline?: boolean;
|
||||
|
||||
/**
|
||||
* Dimensions for drawing the label backdrop
|
||||
*/
|
||||
backdrop?: BackdropOptions;
|
||||
}
|
||||
|
||||
export interface BackdropOptions {
|
||||
/**
|
||||
* Left position of backdrop as pixel
|
||||
*/
|
||||
left: number;
|
||||
|
||||
/**
|
||||
* Top position of backdrop as pixel
|
||||
*/
|
||||
top: number;
|
||||
|
||||
/**
|
||||
* Width of backdrop in pixels
|
||||
*/
|
||||
width: number;
|
||||
|
||||
/**
|
||||
* Height of backdrop in pixels
|
||||
*/
|
||||
height: number;
|
||||
|
||||
/**
|
||||
* Color of label backdrops.
|
||||
*/
|
||||
color: Scriptable<Color, ScriptableScaleContext>;
|
||||
}
|
||||
|
||||
export interface LabelItem {
|
||||
label: string | string[];
|
||||
font: CanvasFontSpec;
|
||||
@ -1658,6 +1754,10 @@ export interface FontSpec {
|
||||
lineHeight: number | string;
|
||||
}
|
||||
|
||||
export interface CanvasFontSpec extends FontSpec {
|
||||
string: string;
|
||||
}
|
||||
|
||||
export type TextAlign = 'left' | 'center' | 'right';
|
||||
export type Align = 'start' | 'center' | 'end';
|
||||
|
||||
@ -3655,6 +3755,8 @@ export interface ChartData<
|
||||
TLabel = unknown
|
||||
> {
|
||||
labels?: TLabel[];
|
||||
xLabels?: TLabel[];
|
||||
yLabels?: TLabel[];
|
||||
datasets: ChartDataset<TType, TData>[];
|
||||
}
|
||||
|
||||
@ -3664,6 +3766,8 @@ export interface ChartDataCustomTypesPerDataset<
|
||||
TLabel = unknown
|
||||
> {
|
||||
labels?: TLabel[];
|
||||
xLabels?: TLabel[];
|
||||
yLabels?: TLabel[];
|
||||
datasets: ChartDatasetCustomTypesPerDataset<TType, TData>[];
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user