Skip to content

Commit

Permalink
Merge branch 'chartjs:master' into fix-chartjs#10755
Browse files Browse the repository at this point in the history
  • Loading branch information
puneetkathar1 committed Oct 9, 2022
2 parents fcdb412 + b0160e1 commit 024b232
Show file tree
Hide file tree
Showing 16 changed files with 187 additions and 223 deletions.
4 changes: 2 additions & 2 deletions docs/configuration/interactions.md
Expand Up @@ -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

Expand Down Expand Up @@ -275,4 +275,4 @@ declare module 'chart.js' {
myCustomMode: InteractionModeFunction;
}
}
```
```
2 changes: 1 addition & 1 deletion 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

Expand Down
2 changes: 1 addition & 1 deletion src/core/core.element.ts
Expand Up @@ -28,8 +28,8 @@ export default class Element<T = AnyObject, O = AnyObject> {
* @param props - properties to get
* @param [final] - get the final value (animation target)
*/
getProps<P extends string>(props: P[], final?: boolean): Partial<Record<P, unknown>>;
getProps<P extends (keyof T)[]>(props: P, final?: boolean): Pick<T, P[number]>;
getProps<P extends string>(props: P[], final?: boolean): Partial<Record<P, unknown>>;
getProps(props: string[], final?: boolean): Partial<Record<string, unknown>> {
const anims = this.$animations;
if (!final || !anims) {
Expand Down
1 change: 1 addition & 0 deletions src/elements/element.arc.js
Expand Up @@ -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([
Expand Down
34 changes: 24 additions & 10 deletions src/elements/element.point.js → 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<PointProps, PointOptions & PointHoverOptions> {

static id = 'point';

parsed: CartesianParsedData;
skip?: boolean;
stop?: boolean;

/**
* @type {any}
*/
Expand Down Expand Up @@ -46,34 +59,34 @@ 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<PointOptions & PointHoverOptions>) {
options = options || this.options || {};
let radius = options.radius || 0;
radius = Math.max(radius, radius && options.hoverRadius || 0);
const borderWidth = radius && options.borderWidth || 0;
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)) {
Expand All @@ -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;
}
}
75 changes: 47 additions & 28 deletions src/helpers/helpers.curve.js → 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

Expand Down Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -144,20 +157,20 @@ 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);

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) {
Expand All @@ -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<typeof splineCurve>;

// Only consider points that are drawn in case the spanGaps option is used
if (options.spanGaps) {
Expand Down

0 comments on commit 024b232

Please sign in to comment.