From e6892a92cba764b5c032e30d31393313e05b1031 Mon Sep 17 00:00:00 2001 From: Evert Timberg Date: Thu, 6 Oct 2022 15:37:42 -0400 Subject: [PATCH 1/7] Convert helpers.extra to TS (#10728) Co-authored-by: Chart.js <> --- .../{helpers.extras.js => helpers.extras.ts} | 51 +++++++------------ src/helpers/types.ts | 1 + src/platform/platform.dom.js | 5 +- types/helpers/helpers.extras.d.ts | 23 --------- types/helpers/index.d.ts | 1 - 5 files changed, 21 insertions(+), 60 deletions(-) rename src/helpers/{helpers.extras.js => helpers.extras.ts} (65%) delete mode 100644 types/helpers/helpers.extras.d.ts diff --git a/src/helpers/helpers.extras.js b/src/helpers/helpers.extras.ts similarity index 65% rename from src/helpers/helpers.extras.js rename to src/helpers/helpers.extras.ts index 8bab58ae182..1008aaacc59 100644 --- a/src/helpers/helpers.extras.js +++ b/src/helpers/helpers.extras.ts @@ -1,7 +1,9 @@ +import {type ChartMeta, type PointElement} from '../../types'; + import {_limitValue} from './helpers.math'; import {_lookupByKey} from './helpers.collection'; -export function fontString(pixelSize, fontStyle, fontFamily) { +export function fontString(pixelSize: number, fontStyle: string, fontFamily: string) { return fontStyle + ' ' + pixelSize + 'px ' + fontFamily; } @@ -20,18 +22,14 @@ export const requestAnimFrame = (function() { /** * Throttles calling `fn` once per animation frame * Latest arguments are used on the actual call - * @param {function} fn - * @param {*} thisArg - * @param {function} [updateFn] */ -export function throttled(fn, thisArg, updateFn) { - const updateArgs = updateFn || ((args) => Array.prototype.slice.call(args)); +export function throttled>( + fn: (...args: TArgs) => void, + thisArg: any, +) { let ticking = false; - let args = []; - - return function(...rest) { - args = updateArgs(rest); + return function(...args: TArgs) { if (!ticking) { ticking = true; requestAnimFrame.call(window, () => { @@ -44,18 +42,15 @@ export function throttled(fn, thisArg, updateFn) { /** * Debounces calling `fn` for `delay` ms - * @param {function} fn - Function to call. - * @param {number} delay - Delay in ms. 0 = immediate invocation. - * @returns {function} */ -export function debounce(fn, delay) { +export function debounce>(fn: (...args: TArgs) => void, delay: number) { let timeout; - return function(...args) { + return function(...args: TArgs) { if (delay) { clearTimeout(timeout); timeout = setTimeout(fn, delay, args); } else { - fn.apply(this, args); + fn.apply(this, args); } return delay; }; @@ -63,42 +58,30 @@ export function debounce(fn, delay) { /** * Converts 'start' to 'left', 'end' to 'right' and others to 'center' - * @param {string} align start, end, center * @private */ -export const _toLeftRightCenter = (align) => align === 'start' ? 'left' : align === 'end' ? 'right' : 'center'; +export const _toLeftRightCenter = (align: 'start' | 'end' | 'center') => align === 'start' ? 'left' : align === 'end' ? 'right' : 'center'; /** * Returns `start`, `end` or `(start + end) / 2` depending on `align`. Defaults to `center` - * @param {string} align start, end, center - * @param {number} start value for start - * @param {number} end value for end * @private */ -export const _alignStartEnd = (align, start, end) => align === 'start' ? start : align === 'end' ? end : (start + end) / 2; +export const _alignStartEnd = (align: 'start' | 'end' | 'center', start: number, end: number) => align === 'start' ? start : align === 'end' ? end : (start + end) / 2; /** * Returns `left`, `right` or `(left + right) / 2` depending on `align`. Defaults to `left` - * @param {string} align start, end, center - * @param {number} left value for start - * @param {number} right value for end - * @param {boolean} rtl Is this an RTL draw * @private */ -export const _textX = (align, left, right, rtl) => { +export const _textX = (align: 'left' | 'right' | 'center', left: number, right: number, rtl: boolean) => { const check = rtl ? 'left' : 'right'; return align === check ? right : align === 'center' ? (left + right) / 2 : left; }; /** * Return start and count of visible points. - * @param {object} meta - dataset meta. - * @param {array} points - array of point elements. - * @param {boolean} animationsDisabled - if true animation is disabled. - * @returns {{start: number; count: number}} * @private */ -export function _getStartAndCountOfVisiblePoints(meta, points, animationsDisabled) { +export function _getStartAndCountOfVisiblePoints(meta: ChartMeta<'line' | 'scatter'>, points: PointElement[], animationsDisabled: boolean) { const pointCount = points.length; let start = 0; @@ -111,13 +94,17 @@ export function _getStartAndCountOfVisiblePoints(meta, points, animationsDisable if (minDefined) { start = _limitValue(Math.min( + // @ts-expect-error Need to type _parsed _lookupByKey(_parsed, iScale.axis, min).lo, + // @ts-expect-error Need to fix types on _lookupByKey animationsDisabled ? pointCount : _lookupByKey(points, axis, iScale.getPixelForValue(min)).lo), 0, pointCount - 1); } if (maxDefined) { count = _limitValue(Math.max( + // @ts-expect-error Need to type _parsed _lookupByKey(_parsed, iScale.axis, max, true).hi + 1, + // @ts-expect-error Need to fix types on _lookupByKey animationsDisabled ? 0 : _lookupByKey(points, axis, iScale.getPixelForValue(max), true).hi + 1), start, pointCount) - start; } else { diff --git a/src/helpers/types.ts b/src/helpers/types.ts index 81d586e4173..9806aa54fd3 100644 --- a/src/helpers/types.ts +++ b/src/helpers/types.ts @@ -8,6 +8,7 @@ export * from './helpers.color'; export * from './helpers.collection'; export * from './helpers.core'; export * from './helpers.easing'; +export * from './helpers.extras'; export * from './helpers.interpolation'; export * from './helpers.intl'; export * from './helpers.math'; diff --git a/src/platform/platform.dom.js b/src/platform/platform.dom.js index 502886d07b3..c25bd25810c 100644 --- a/src/platform/platform.dom.js +++ b/src/platform/platform.dom.js @@ -242,10 +242,7 @@ function createProxyAndListen(chart, type, listener) { if (chart.ctx !== null) { listener(fromNativeEvent(event, chart)); } - }, chart, (args) => { - const event = args[0]; - return [event, event.offsetX, event.offsetY]; - }); + }, chart); addListener(canvas, type, proxy); diff --git a/types/helpers/helpers.extras.d.ts b/types/helpers/helpers.extras.d.ts deleted file mode 100644 index cb445c32793..00000000000 --- a/types/helpers/helpers.extras.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -export function fontString(pixelSize: number, fontStyle: string, fontFamily: string): string; - -/** - * Request animation polyfill - */ -export function requestAnimFrame(cb: () => void): void; - -/** - * Throttles calling `fn` once per animation frame - * Latest arguments are used on the actual call - * @param {function} fn - * @param {*} thisArg - * @param {function} [updateFn] - */ -export function throttled(fn: (...args: unknown[]) => void, thisArg: unknown, updateFn?: (...args: unknown[]) => unknown[]): (...args: unknown[]) => void; - -/** - * Debounces calling `fn` for `delay` ms - * @param {function} fn - Function to call. No arguments are passed. - * @param {number} delay - Delay in ms. 0 = immediate invocation. - * @returns {function} - */ -export function debounce(fn: () => void, delay: number): () => number; diff --git a/types/helpers/index.d.ts b/types/helpers/index.d.ts index 5db25e24e94..905c0cb0f48 100644 --- a/types/helpers/index.d.ts +++ b/types/helpers/index.d.ts @@ -1,7 +1,6 @@ export * from './helpers.canvas'; export * from './helpers.curve'; export * from './helpers.dom'; -export * from './helpers.extras'; export * from './helpers.options'; export * from './helpers.canvas'; export * from './helpers.segment'; From 72b185afa610f9cb8c2f61bbe16a036741de8d7c Mon Sep 17 00:00:00 2001 From: Jacco van den Berg Date: Thu, 6 Oct 2022 21:41:51 +0200 Subject: [PATCH 2/7] Allow to configure X and Y axis ID in TS for bubble charts (#10739) * Update namespace * Allow for x and y axisId to be passed to bubbledataset controller --- types/index.d.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/types/index.d.ts b/types/index.d.ts index 0c5f64c209d..7154257cabd 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -157,7 +157,16 @@ export declare const BarController: ChartComponent & { export interface BubbleControllerDatasetOptions extends ControllerDatasetOptions, ScriptableAndArrayOptions>, - ScriptableAndArrayOptions> {} + ScriptableAndArrayOptions> { + /** + * The ID of the x axis to plot this dataset on. + */ + xAxisID: string; + /** + * The ID of the y axis to plot this dataset on. + */ + yAxisID: string; +} export interface BubbleDataPoint extends Point { /** From 813943ac72d11dc58bc21f3a93bfac16c3970ffd Mon Sep 17 00:00:00 2001 From: Brainshaker95 Date: Fri, 7 Oct 2022 06:36:57 +0200 Subject: [PATCH 3/7] Types: Change grid option of CartesianScaleOptions and RadialLinearScaleOptions to Partial of GridLineOptions (#10760) --- types/index.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/index.d.ts b/types/index.d.ts index 7154257cabd..35420b1b74f 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -3075,7 +3075,7 @@ export interface CartesianScaleOptions extends CoreScaleOptions { */ offset: boolean; - grid: GridLineOptions; + grid: Partial; border: BorderOptions; @@ -3359,7 +3359,7 @@ export type RadialLinearScaleOptions = CoreScaleOptions & { */ beginAtZero: boolean; - grid: GridLineOptions; + grid: Partial; /** * User defined minimum number for the scale, overrides minimum value from data. From 06bbadb00f0a79ff1d4f44a3b76c702d090b485d Mon Sep 17 00:00:00 2001 From: Evert Timberg Date: Fri, 7 Oct 2022 08:28:03 -0400 Subject: [PATCH 4/7] Convert the PointElement to TS (#10730) Co-authored-by: Chart.js <> --- src/core/core.element.ts | 2 +- src/elements/element.arc.js | 1 + .../{element.point.js => element.point.ts} | 34 +++++++++++++------ types/index.d.ts | 18 ++-------- 4 files changed, 29 insertions(+), 26 deletions(-) rename src/elements/{element.point.js => element.point.ts} (67%) diff --git a/src/core/core.element.ts b/src/core/core.element.ts index 93006b58ed4..9bfca13b3c9 100644 --- a/src/core/core.element.ts +++ b/src/core/core.element.ts @@ -28,8 +28,8 @@ export default class Element { * @param props - properties to get * @param [final] - get the final value (animation target) */ - getProps

(props: P[], final?: boolean): Partial>; getProps

(props: P, final?: boolean): Pick; + getProps

(props: P[], final?: boolean): Partial>; getProps(props: string[], final?: boolean): Partial> { const anims = this.$animations; if (!final || !anims) { diff --git a/src/elements/element.arc.js b/src/elements/element.arc.js index 7996330872a..6ed0dbfdeab 100644 --- a/src/elements/element.arc.js +++ b/src/elements/element.arc.js @@ -311,6 +311,7 @@ export default class ArcElement extends Element { * @param {boolean} [useFinalPosition] */ inRange(chartX, chartY, useFinalPosition) { + // @ts-ignore This will be fixed when the arc element is converted to TS const point = /** @type {Point} */ (this.getProps(['x', 'y'], useFinalPosition)); const {angle, distance} = getAngleFromPoint(point, {x: chartX, y: chartY}); const {startAngle, endAngle, innerRadius, outerRadius, circumference} = /** @type {ArcProps} */ (this.getProps([ diff --git a/src/elements/element.point.js b/src/elements/element.point.ts similarity index 67% rename from src/elements/element.point.js rename to src/elements/element.point.ts index b651d65b198..780a412991f 100644 --- a/src/elements/element.point.js +++ b/src/elements/element.point.ts @@ -1,17 +1,30 @@ import Element from '../core/core.element'; import {drawPoint, _isPointInArea} from '../helpers/helpers.canvas'; - -function inRange(el, pos, axis, useFinalPosition) { +import { + type CartesianParsedData, + type ChartArea, + type Point, + type PointHoverOptions, + type PointOptions, +} from '../../types'; + +function inRange(el: PointElement, pos: number, axis: 'x' | 'y', useFinalPosition?: boolean) { const options = el.options; const {[axis]: value} = el.getProps([axis], useFinalPosition); return (Math.abs(pos - value) < options.radius + options.hitRadius); } -export default class PointElement extends Element { +export type PointProps = Point + +export default class PointElement extends Element { static id = 'point'; + parsed: CartesianParsedData; + skip?: boolean; + stop?: boolean; + /** * @type {any} */ @@ -46,26 +59,26 @@ export default class PointElement extends Element { } } - inRange(mouseX, mouseY, useFinalPosition) { + inRange(mouseX: number, mouseY: number, useFinalPosition?: boolean) { const options = this.options; - const {x, y} = /** @type {{ x: number, y: number }} */ (this.getProps(['x', 'y'], useFinalPosition)); + const {x, y} = this.getProps(['x', 'y'], useFinalPosition); return ((Math.pow(mouseX - x, 2) + Math.pow(mouseY - y, 2)) < Math.pow(options.hitRadius + options.radius, 2)); } - inXRange(mouseX, useFinalPosition) { + inXRange(mouseX: number, useFinalPosition?: boolean) { return inRange(this, mouseX, 'x', useFinalPosition); } - inYRange(mouseY, useFinalPosition) { + inYRange(mouseY: number, useFinalPosition?: boolean) { return inRange(this, mouseY, 'y', useFinalPosition); } - getCenterPoint(useFinalPosition) { + getCenterPoint(useFinalPosition?: boolean) { const {x, y} = this.getProps(['x', 'y'], useFinalPosition); return {x, y}; } - size(options) { + size(options?: Partial) { options = options || this.options || {}; let radius = options.radius || 0; radius = Math.max(radius, radius && options.hoverRadius || 0); @@ -73,7 +86,7 @@ export default class PointElement extends Element { return (radius + borderWidth) * 2; } - draw(ctx, area) { + draw(ctx: CanvasRenderingContext2D, area: ChartArea) { const options = this.options; if (this.skip || options.radius < 0.1 || !_isPointInArea(this, area, this.size(options) / 2)) { @@ -88,6 +101,7 @@ export default class PointElement extends Element { getRange() { const options = this.options || {}; + // @ts-expect-error Fallbacks should never be hit in practice return options.radius + options.hitRadius; } } diff --git a/types/index.d.ts b/types/index.d.ts index 35420b1b74f..4e56d2f1e5b 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,6 +1,7 @@ import { DeepPartial, DistributiveArray, UnionToIntersection } from './utils'; import { TimeUnit } from '../src/core/core.adapters'; +import PointElement from '../src/elements/element.point'; import { EasingFunction } from '../src/helpers/helpers.easing'; import { AnimationEvent } from './animation'; import { AnyObject, EmptyObject } from './basic'; @@ -10,6 +11,7 @@ import { ChartArea, Padding, Point } from './geometric'; import { LayoutItem, LayoutPosition } from './layout'; export { EasingFunction } from '../src/helpers/helpers.easing'; +export { default as PointElement, PointProps } from '../src/elements/element.point'; export { Animation, Animations, Animator, AnimationEvent } from './animation'; export { Color } from './color'; export { ChartArea, Point } from './geometric'; @@ -1821,8 +1823,6 @@ export declare const LineElement: ChartComponent & { new (cfg: AnyObject): LineElement; }; -export interface PointProps extends Point {} - export type PointStyle = | 'circle' | 'cross' @@ -1923,18 +1923,6 @@ export interface PointPrefixedHoverOptions { pointHoverRadius: number; } -export interface PointElement - extends Element, - VisualElement { - readonly skip: boolean; - readonly parsed: CartesianParsedData; -} - -export declare const PointElement: ChartComponent & { - prototype: PointElement; - new (cfg: AnyObject): PointElement; -}; - export interface BarProps extends Point { base: number; horizontal: boolean; @@ -3477,7 +3465,7 @@ export interface ScaleTypeRegistry extends CartesianScaleTypeRegistry, RadialSca export type ScaleType = keyof ScaleTypeRegistry; -interface CartesianParsedData extends Point { +export interface CartesianParsedData extends Point { // Only specified when stacked bars are enabled _stacks?: { // Key is the stack ID which is generally the axis ID From 41612d1320db0fcdd08da9cab550ca2deb3a418f Mon Sep 17 00:00:00 2001 From: Evert Timberg Date: Fri, 7 Oct 2022 08:28:24 -0400 Subject: [PATCH 5/7] Convert the curve helpers to TS (#10733) * Convert the curve helpers to TS * Remove old type --- .../{helpers.curve.js => helpers.curve.ts} | 75 ++++++++++++------- src/helpers/helpers.interpolation.ts | 4 +- src/helpers/types.ts | 1 + types/helpers/helpers.curve.d.ts | 34 --------- types/helpers/index.d.ts | 1 - 5 files changed, 50 insertions(+), 65 deletions(-) rename src/helpers/{helpers.curve.js => helpers.curve.ts} (74%) delete mode 100644 types/helpers/helpers.curve.d.ts diff --git a/src/helpers/helpers.curve.js b/src/helpers/helpers.curve.ts similarity index 74% rename from src/helpers/helpers.curve.js rename to src/helpers/helpers.curve.ts index 9c42c1dc6b1..14a6bc33268 100644 --- a/src/helpers/helpers.curve.js +++ b/src/helpers/helpers.curve.ts @@ -1,11 +1,35 @@ import {almostEquals, distanceBetweenPoints, sign} from './helpers.math'; import {_isPointInArea} from './helpers.canvas'; +import {ChartArea} from '../../types'; + +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; +} const EPSILON = Number.EPSILON || 1e-14; -const getPoint = (points, i) => i < points.length && !points[i].skip && points[i]; -const getValueAxis = (indexAxis) => indexAxis === 'x' ? 'y' : 'x'; -export function splineCurve(firstPoint, middlePoint, afterPoint, t) { +type OptionalSplinePoint = SplinePoint | false +const getPoint = (points: SplinePoint[], i: number): OptionalSplinePoint => i < points.length && !points[i].skip && points[i]; +const getValueAxis = (indexAxis: 'x' | 'y') => indexAxis === 'x' ? 'y' : 'x'; + +export function splineCurve( + firstPoint: SplinePoint, + middlePoint: SplinePoint, + afterPoint: SplinePoint, + t: number +): { + previous: SplinePoint + next: SplinePoint + } { // Props to Rob Spencer at scaled innovation for his post on splining between points // http://scaledinnovation.com/analytics/splines/aboutSplines.html @@ -42,10 +66,10 @@ export function splineCurve(firstPoint, middlePoint, afterPoint, t) { /** * Adjust tangents to ensure monotonic properties */ -function monotoneAdjust(points, deltaK, mK) { +function monotoneAdjust(points: SplinePoint[], deltaK: number[], mK: number[]) { const pointsLen = points.length; - let alphaK, betaK, tauK, squaredMagnitude, pointCurrent; + let alphaK: number, betaK: number, tauK: number, squaredMagnitude: number, pointCurrent: OptionalSplinePoint; let pointAfter = getPoint(points, 0); for (let i = 0; i < pointsLen - 1; ++i) { pointCurrent = pointAfter; @@ -72,10 +96,10 @@ function monotoneAdjust(points, deltaK, mK) { } } -function monotoneCompute(points, mK, indexAxis = 'x') { +function monotoneCompute(points: SplinePoint[], mK: number[], indexAxis: 'x' | 'y' = 'x') { const valueAxis = getValueAxis(indexAxis); const pointsLen = points.length; - let delta, pointBefore, pointCurrent; + let delta: number, pointBefore: OptionalSplinePoint, pointCurrent: OptionalSplinePoint; let pointAfter = getPoint(points, 0); for (let i = 0; i < pointsLen; ++i) { @@ -106,26 +130,15 @@ function monotoneCompute(points, mK, indexAxis = 'x') { * but preserves monotonicity of the provided data and ensures no local extremums are added * between the dataset discrete points due to the interpolation. * See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation - * - * @param {{ - * x: number, - * y: number, - * skip?: boolean, - * cp1x?: number, - * cp1y?: number, - * cp2x?: number, - * cp2y?: number, - * }[]} points - * @param {string} indexAxis */ -export function splineCurveMonotone(points, indexAxis = 'x') { +export function splineCurveMonotone(points: SplinePoint[], indexAxis: 'x' | 'y' = 'x') { const valueAxis = getValueAxis(indexAxis); const pointsLen = points.length; - const deltaK = Array(pointsLen).fill(0); - const mK = Array(pointsLen); + const deltaK: number[] = Array(pointsLen).fill(0); + const mK: number[] = Array(pointsLen); // Calculate slopes (deltaK) and initialize tangents (mK) - let i, pointBefore, pointCurrent; + let i, pointBefore: OptionalSplinePoint, pointCurrent: OptionalSplinePoint; let pointAfter = getPoint(points, 0); for (i = 0; i < pointsLen; ++i) { @@ -144,8 +157,8 @@ export function splineCurveMonotone(points, indexAxis = 'x') { } mK[i] = !pointBefore ? deltaK[i] : !pointAfter ? deltaK[i - 1] - : (sign(deltaK[i - 1]) !== sign(deltaK[i])) ? 0 - : (deltaK[i - 1] + deltaK[i]) / 2; + : (sign(deltaK[i - 1]) !== sign(deltaK[i])) ? 0 + : (deltaK[i - 1] + deltaK[i]) / 2; } monotoneAdjust(points, deltaK, mK); @@ -153,11 +166,11 @@ export function splineCurveMonotone(points, indexAxis = 'x') { monotoneCompute(points, mK, indexAxis); } -function capControlPoint(pt, min, max) { +function capControlPoint(pt: number, min: number, max: number) { return Math.max(Math.min(pt, max), min); } -function capBezierPoints(points, area) { +function capBezierPoints(points: SplinePoint[], area: ChartArea) { let i, ilen, point, inArea, inAreaPrev; let inAreaNext = _isPointInArea(points[0], area); for (i = 0, ilen = points.length; i < ilen; ++i) { @@ -182,8 +195,14 @@ function capBezierPoints(points, area) { /** * @private */ -export function _updateBezierControlPoints(points, options, area, loop, indexAxis) { - let i, ilen, point, controlPoints; +export function _updateBezierControlPoints( + points: SplinePoint[], + options, + area: ChartArea, + loop: boolean, + indexAxis: 'x' | 'y' +) { + let i: number, ilen: number, point: SplinePoint, controlPoints: ReturnType; // Only consider points that are drawn in case the spanGaps option is used if (options.spanGaps) { diff --git a/src/helpers/helpers.interpolation.ts b/src/helpers/helpers.interpolation.ts index 9d86528dac6..81f1948df3b 100644 --- a/src/helpers/helpers.interpolation.ts +++ b/src/helpers/helpers.interpolation.ts @@ -1,5 +1,5 @@ import type {Point} from '../../types/geometric'; -import type {MonotoneSplinePoint} from '../../types/helpers'; +import type {SplinePoint} from './helpers.curve'; /** * @private @@ -30,7 +30,7 @@ export function _steppedInterpolation( /** * @private */ -export function _bezierInterpolation(p1: MonotoneSplinePoint, p2: MonotoneSplinePoint, t: number, mode?) { // eslint-disable-line @typescript-eslint/no-unused-vars +export function _bezierInterpolation(p1: SplinePoint, p2: SplinePoint, t: number, mode?) { // eslint-disable-line @typescript-eslint/no-unused-vars const cp1 = {x: p1.cp2x, y: p1.cp2y}; const cp2 = {x: p2.cp1x, y: p2.cp1y}; const a = _pointInLine(p1, cp1, t); diff --git a/src/helpers/types.ts b/src/helpers/types.ts index 9806aa54fd3..2cbf3a04cb6 100644 --- a/src/helpers/types.ts +++ b/src/helpers/types.ts @@ -7,6 +7,7 @@ export * from './helpers.color'; export * from './helpers.collection'; export * from './helpers.core'; +export * from './helpers.curve'; export * from './helpers.easing'; export * from './helpers.extras'; export * from './helpers.interpolation'; diff --git a/types/helpers/helpers.curve.d.ts b/types/helpers/helpers.curve.d.ts deleted file mode 100644 index 28d9ee4a629..00000000000 --- a/types/helpers/helpers.curve.d.ts +++ /dev/null @@ -1,34 +0,0 @@ -export interface SplinePoint { - x: number; - y: number; -} - -/** - * Props to Rob Spencer at scaled innovation for his post on splining between points - * http://scaledinnovation.com/analytics/splines/aboutSplines.html - */ -export function splineCurve( - firstPoint: SplinePoint & { skip?: boolean }, - middlePoint: SplinePoint, - afterPoint: SplinePoint, - t: number -): { - previous: SplinePoint; - next: SplinePoint; -}; - -export interface MonotoneSplinePoint extends SplinePoint { - skip: boolean; - cp1x?: number; - cp1y?: number; - cp2x?: number; - cp2y?: number; -} - -/** - * This function calculates Bézier control points in a similar way than |splineCurve|, - * but preserves monotonicity of the provided data and ensures no local extremums are added - * between the dataset discrete points due to the interpolation. - * @see https://en.wikipedia.org/wiki/Monotone_cubic_interpolation - */ -export function splineCurveMonotone(points: readonly MonotoneSplinePoint[], indexAxis?: 'x' | 'y'): void; diff --git a/types/helpers/index.d.ts b/types/helpers/index.d.ts index 905c0cb0f48..68da2dde289 100644 --- a/types/helpers/index.d.ts +++ b/types/helpers/index.d.ts @@ -1,5 +1,4 @@ export * from './helpers.canvas'; -export * from './helpers.curve'; export * from './helpers.dom'; export * from './helpers.options'; export * from './helpers.canvas'; From 7410a3c30e5b06d91ccc6a621136c6b061ee9b5d Mon Sep 17 00:00:00 2001 From: Amin Cheloh Date: Fri, 7 Oct 2022 20:56:07 +0700 Subject: [PATCH 6/7] docs: fix typos (#10763) --- docs/configuration/interactions.md | 4 ++-- docs/migration/v4-migration.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/configuration/interactions.md b/docs/configuration/interactions.md index 4bcf350a62e..8b4a672fc91 100644 --- a/docs/configuration/interactions.md +++ b/docs/configuration/interactions.md @@ -101,7 +101,7 @@ const chart = new Chart(ctx, { }); ``` -When using a bundler, the helper functions have to be imported seperatly, for a full explanation of this please head over to the [integration](../getting-started/integration.md#helper-functions) page +When using a bundler, the helper functions have to be imported separately, for a full explanation of this please head over to the [integration](../getting-started/integration.md#helper-functions) page ## Modes @@ -275,4 +275,4 @@ declare module 'chart.js' { myCustomMode: InteractionModeFunction; } } -``` \ No newline at end of file +``` diff --git a/docs/migration/v4-migration.md b/docs/migration/v4-migration.md index cce70bc4af2..7c0eb647b45 100644 --- a/docs/migration/v4-migration.md +++ b/docs/migration/v4-migration.md @@ -1,6 +1,6 @@ # 4.x Migration Guide -Chart.js 4.0 introduces a number of breaking changes. We tried keeping the amount of breaking changes to a minimum. For some features and bug fixes it was necessary to break backwars compatibility, but we aimed to do so only when worth the benefit. +Chart.js 4.0 introduces a number of breaking changes. We tried keeping the amount of breaking changes to a minimum. For some features and bug fixes it was necessary to break backwards compatibility, but we aimed to do so only when worth the benefit. ## End user migration From b0160e138acf8f0b1a65cf2d90b074b799e35ff2 Mon Sep 17 00:00:00 2001 From: Lucca Miranda <42002892+luckened@users.noreply.github.com> Date: Sun, 9 Oct 2022 10:21:12 -0300 Subject: [PATCH 7/7] refactor: migrate helpers.dom to typescript (#10734) --- .../{helpers.dom.js => helpers.dom.ts} | 116 +++++++++++------- src/helpers/types.ts | 1 + types/helpers/helpers.dom.d.ts | 20 --- types/helpers/index.d.ts | 1 - 4 files changed, 72 insertions(+), 66 deletions(-) rename src/helpers/{helpers.dom.js => helpers.dom.ts} (73%) delete mode 100644 types/helpers/helpers.dom.d.ts diff --git a/src/helpers/helpers.dom.js b/src/helpers/helpers.dom.ts similarity index 73% rename from src/helpers/helpers.dom.js rename to src/helpers/helpers.dom.ts index 78b128f7c85..864b23f0d86 100644 --- a/src/helpers/helpers.dom.js +++ b/src/helpers/helpers.dom.ts @@ -1,3 +1,6 @@ +import {ChartArea, Scale} from '../../types'; +import Chart from '../core/core.controller'; +import {ChartEvent} from '../types'; import {INFINITY} from './helpers.math'; /** @@ -11,33 +14,34 @@ import {INFINITY} from './helpers.math'; /** * @private */ -export function _isDomSupported() { +export function _isDomSupported(): boolean { return typeof window !== 'undefined' && typeof document !== 'undefined'; } /** * @private */ -export function _getParentNode(domNode) { +export function _getParentNode(domNode: HTMLCanvasElement): HTMLCanvasElement { let parent = domNode.parentNode; if (parent && parent.toString() === '[object ShadowRoot]') { - parent = parent.host; + parent = (parent as ShadowRoot).host; } - return parent; + return parent as HTMLCanvasElement; } /** * convert max-width/max-height values that may be percentages into a number * @private */ -function parseMaxStyle(styleValue, node, parentProperty) { - let valueInPixels; + +function parseMaxStyle(styleValue: string | number, node: HTMLElement, parentProperty: string) { + let valueInPixels: number; if (typeof styleValue === 'string') { valueInPixels = parseInt(styleValue, 10); if (styleValue.indexOf('%') !== -1) { // percentage * size in dimension - valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty]; + valueInPixels = (valueInPixels / 100) * node.parentNode[parentProperty]; } } else { valueInPixels = styleValue; @@ -46,15 +50,16 @@ function parseMaxStyle(styleValue, node, parentProperty) { return valueInPixels; } -const getComputedStyle = (element) => element.ownerDocument.defaultView.getComputedStyle(element, null); +const getComputedStyle = (element: HTMLElement): CSSStyleDeclaration => + element.ownerDocument.defaultView.getComputedStyle(element, null); -export function getStyle(el, property) { +export function getStyle(el: HTMLElement, property: string): string { return getComputedStyle(el).getPropertyValue(property); } const positions = ['top', 'right', 'bottom', 'left']; -function getPositionedStyle(styles, style, suffix) { - const result = {}; +function getPositionedStyle(styles: CSSStyleDeclaration, style: string, suffix?: string): ChartArea { + const result = {} as ChartArea; suffix = suffix ? '-' + suffix : ''; for (let i = 0; i < 4; i++) { const pos = positions[i]; @@ -65,18 +70,25 @@ function getPositionedStyle(styles, style, suffix) { return result; } -const useOffsetPos = (x, y, target) => (x > 0 || y > 0) && (!target || !target.shadowRoot); +const useOffsetPos = (x: number, y: number, target: HTMLElement | EventTarget) => + (x > 0 || y > 0) && (!target || !(target as HTMLElement).shadowRoot); /** - * @param {Event} e - * @param {HTMLCanvasElement} canvas - * @returns {{x: number, y: number, box: boolean}} + * @param e + * @param canvas + * @returns Canvas position */ -function getCanvasPosition(e, canvas) { - // @ts-ignore - const touches = e.touches; - const source = touches && touches.length ? touches[0] : e; - const {offsetX, offsetY} = source; +function getCanvasPosition( + e: Event | TouchEvent | MouseEvent, + canvas: HTMLCanvasElement +): { + x: number; + y: number; + box: boolean; + } { + const touches = (e as TouchEvent).touches; + const source = (touches && touches.length ? touches[0] : e) as MouseEvent; + const {offsetX, offsetY} = source as MouseEvent; let box = false; let x, y; if (useOffsetPos(offsetX, offsetY, e.target)) { @@ -93,13 +105,17 @@ function getCanvasPosition(e, canvas) { /** * Gets an event's x, y coordinates, relative to the chart area - * @param {Event|ChartEvent} evt - * @param {dom.Chart} chart - * @returns {{x: number, y: number}} + * @param event + * @param chart + * @returns x and y coordinates of the event */ -export function getRelativePosition(evt, chart) { - if ('native' in evt) { - return evt; + +export function getRelativePosition( + event: Event | ChartEvent | TouchEvent | MouseEvent, + chart: Chart +): { x: number; y: number } { + if ('native' in event) { + return event; } const {canvas, currentDevicePixelRatio} = chart; @@ -107,7 +123,7 @@ export function getRelativePosition(evt, chart) { const borderBox = style.boxSizing === 'border-box'; const paddings = getPositionedStyle(style, 'padding'); const borders = getPositionedStyle(style, 'border', 'width'); - const {x, y, box} = getCanvasPosition(evt, canvas); + const {x, y, box} = getCanvasPosition(event, canvas); const xOffset = paddings.left + (box && borders.left); const yOffset = paddings.top + (box && borders.top); @@ -122,8 +138,8 @@ export function getRelativePosition(evt, chart) { }; } -function getContainerSize(canvas, width, height) { - let maxWidth, maxHeight; +function getContainerSize(canvas: HTMLCanvasElement, width: number, height: number): Partial { + let maxWidth: number, maxHeight: number; if (width === undefined || height === undefined) { const container = _getParentNode(canvas); @@ -149,9 +165,15 @@ function getContainerSize(canvas, width, height) { }; } -const round1 = v => Math.round(v * 10) / 10; +const round1 = (v: number) => Math.round(v * 10) / 10; -export function getMaximumSize(canvas, bbWidth, bbHeight, aspectRatio) { +// eslint-disable-next-line complexity +export function getMaximumSize( + canvas: HTMLCanvasElement, + bbWidth?: number, + bbHeight?: number, + aspectRatio?: number +): { width: number; height: number } { const style = getComputedStyle(canvas); const margins = getPositionedStyle(style, 'margin'); const maxWidth = parseMaxStyle(style.maxWidth, canvas, 'clientWidth') || INFINITY; @@ -182,19 +204,20 @@ export function getMaximumSize(canvas, bbWidth, bbHeight, aspectRatio) { width = round1(Math.floor(height * aspectRatio)); } - return { - width, - height - }; + return {width, height}; } /** - * @param {import('../core/core.controller').default} chart - * @param {number} [forceRatio] - * @param {boolean} [forceStyle] - * @returns {boolean} True if the canvas context size or transformation has changed. + * @param chart + * @param forceRatio + * @param forceStyle + * @returns True if the canvas context size or transformation has changed. */ -export function retinaScale(chart, forceRatio, forceStyle) { +export function retinaScale( + chart: Chart, + forceRatio: number, + forceStyle?: boolean +): boolean | void { const pixelRatio = forceRatio || 1; const deviceHeight = Math.floor(chart.height * pixelRatio); const deviceWidth = Math.floor(chart.width * pixelRatio); @@ -237,10 +260,9 @@ export const supportsEventListenerOptions = (function() { passiveSupported = true; return false; } - }; - // @ts-ignore + } as EventListenerOptions; + window.addEventListener('test', null, options); - // @ts-ignore window.removeEventListener('test', null, options); } catch (e) { // continue regardless of error @@ -255,9 +277,13 @@ export const supportsEventListenerOptions = (function() { * `element` has a size relative to its parent and this last one is not yet displayed, * for example because of `display: none` on a parent node. * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value - * @returns {number=} Size in pixels or undefined if unknown. + * @returns Size in pixels or undefined if unknown. */ -export function readUsedSize(element, property) { + +export function readUsedSize( + element: HTMLElement, + property: 'width' | 'height' +): number | undefined { const value = getStyle(element, property); const matches = value && value.match(/^(\d+)(\.\d+)?px$/); return matches ? +matches[1] : undefined; diff --git a/src/helpers/types.ts b/src/helpers/types.ts index 2cbf3a04cb6..690e8a0c737 100644 --- a/src/helpers/types.ts +++ b/src/helpers/types.ts @@ -8,6 +8,7 @@ export * from './helpers.color'; export * from './helpers.collection'; export * from './helpers.core'; export * from './helpers.curve'; +export * from './helpers.dom'; export * from './helpers.easing'; export * from './helpers.extras'; export * from './helpers.interpolation'; diff --git a/types/helpers/helpers.dom.d.ts b/types/helpers/helpers.dom.d.ts deleted file mode 100644 index a595b6b5b7e..00000000000 --- a/types/helpers/helpers.dom.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ChartEvent } from '..'; - -export function getMaximumSize(node: HTMLElement, width?: number, height?: number, aspectRatio?: number): { width: number, height: number }; -export function getRelativePosition( - evt: MouseEvent | ChartEvent, - chart: { readonly canvas: HTMLCanvasElement } -): { x: number; y: number }; -export function getStyle(el: HTMLElement, property: string): string; -export function retinaScale( - chart: { - currentDevicePixelRatio: number; - readonly canvas: HTMLCanvasElement; - readonly width: number; - readonly height: number; - readonly ctx: CanvasRenderingContext2D; - }, - forceRatio: number, - forceStyle?: boolean -): void; -export function readUsedSize(element: HTMLElement, property: 'width' | 'height'): number | undefined; diff --git a/types/helpers/index.d.ts b/types/helpers/index.d.ts index 68da2dde289..591a6f2beaa 100644 --- a/types/helpers/index.d.ts +++ b/types/helpers/index.d.ts @@ -1,5 +1,4 @@ export * from './helpers.canvas'; -export * from './helpers.dom'; export * from './helpers.options'; export * from './helpers.canvas'; export * from './helpers.segment';