Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: rewrite segment helpers to ts #11122

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion src/helpers/helpers.options.ts
Expand Up @@ -204,7 +204,7 @@ export function _addGrace(minmax: { min: number; max: number; }, grace: number |
* @returns
*/
export function createContext<T extends object>(parentContext: null, context: T): T;
export function createContext<T extends object, P extends T>(parentContext: P, context: T): P & T;
export function createContext<T extends object, P extends object>(parentContext: P, context: T): P & T;
export function createContext(parentContext: object, context: object) {
return Object.assign(Object.create(parentContext), context);
}
107 changes: 51 additions & 56 deletions src/helpers/helpers.segment.js → src/helpers/helpers.segment.ts
@@ -1,13 +1,11 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
import {_angleBetween, _angleDiff, _isBetween, _normalizeAngle} from './helpers.math.js';
import {createContext} from './helpers.options.js';
import type {LineElement, LineOptions, PointElement, Segment, SegmentStyle, SegmentBounds} from '../types/index.js';
import type {Point} from '../types/geometric.js';
import type {AnyObject} from 'src/types/basic.js';

/**
* @typedef { import('../elements/element.line.js').default } LineElement
* @typedef { import('../elements/element.point.js').default } PointElement
* @typedef {{start: number, end: number, loop: boolean, style?: any}} Segment
*/

function propertyFn(property) {
function propertyFn(property: string) {
if (property === 'angle') {
return {
between: _angleBetween,
Expand All @@ -17,8 +15,8 @@ function propertyFn(property) {
}
return {
between: _isBetween,
compare: (a, b) => a - b,
normalize: x => x
compare: (a: number, b: number) => a - b,
normalize: (x) => x
};
}

Expand All @@ -31,13 +29,13 @@ function normalizeSegment({start, end, count, loop, style}) {
};
}

function getSegment(segment, points, bounds) {
function getSegment(segment: Segment, points: Point[], bounds: SegmentBounds) {
const {property, start: startBound, end: endBound} = bounds;
const {between, normalize} = propertyFn(property);
const count = points.length;
// eslint-disable-next-line prefer-const
let {start, end, loop} = segment;
let i, ilen;
let i: number, ilen: number;

if (loop) {
start += count;
Expand All @@ -61,19 +59,20 @@ function getSegment(segment, points, bounds) {

/**
* Returns the sub-segment(s) of a line segment that fall in the given bounds
* @param {object} segment
* @param {number} segment.start - start index of the segment, referring the points array
* @param {number} segment.end - end index of the segment, referring the points array
* @param {boolean} segment.loop - indicates that the segment is a loop
* @param {object} [segment.style] - segment style
* @param {PointElement[]} points - the points that this segment refers to
* @param {object} [bounds]
* @param {string} bounds.property - the property of a `PointElement` we are bounding. `x`, `y` or `angle`.
* @param {number} bounds.start - start value of the property
* @param {number} bounds.end - end value of the property
* @param segment
* @param segment.start - start index of the segment, referring the points array
* @param segment.end - end index of the segment, referring the points array
* @param segment.loop - indicates that the segment is a loop
* @param segment.style - segment style
* @param points - the points that this segment refers to
* @param bounds
* @param bounds.property - the property of a `PointElement` we are bounding. `x`, `y` or `angle`.
* @param bounds.start - start value of the property
* @param bounds.end - end value of the property
* @private
**/
export function _boundSegment(segment, points, bounds) {
// eslint-disable-next-line max-statements, complexity
export function _boundSegment(segment: Segment, points: PointElement[], bounds: SegmentBounds) {
if (!bounds) {
return [segment];
}
Expand All @@ -86,7 +85,7 @@ export function _boundSegment(segment, points, bounds) {
const result = [];
let inside = false;
let subStart = null;
let value, point, prevValue;
let value: number, point: PointElement, prevValue: number;

const startIsBefore = () => between(startBound, prevValue, value) && compare(startBound, prevValue) !== 0;
const endIsBefore = () => compare(endBound, value) === 0 || between(endBound, prevValue, value);
Expand Down Expand Up @@ -130,14 +129,14 @@ export function _boundSegment(segment, points, bounds) {

/**
* Returns the segments of the line that are inside given bounds
* @param {LineElement} line
* @param {object} [bounds]
* @param {string} bounds.property - the property we are bounding with. `x`, `y` or `angle`.
* @param {number} bounds.start - start value of the `property`
* @param {number} bounds.end - end value of the `property`
* @param line
* @param bounds
* @param bounds.property - the property we are bounding with. `x`, `y` or `angle`.
* @param bounds.start - start value of the `property`
* @param bounds.end - end value of the `property`
* @private
*/
export function _boundSegments(line, bounds) {
export function _boundSegments(line: LineElement, bounds: SegmentBounds) {
const result = [];
const segments = line.segments;

Expand All @@ -153,7 +152,7 @@ export function _boundSegments(line, bounds) {
/**
* Find start and end index of a line.
*/
function findStartAndEnd(points, count, loop, spanGaps) {
function findStartAndEnd(points: PointElement[], count: number, loop: boolean, spanGaps: number | boolean) {
let start = 0;
let end = count - 1;

Expand Down Expand Up @@ -189,25 +188,24 @@ function findStartAndEnd(points, count, loop, spanGaps) {

/**
* Compute solid segments from Points, when spanGaps === false
* @param {PointElement[]} points - the points
* @param {number} start - start index
* @param {number} max - max index (can go past count on a loop)
* @param {boolean} loop - boolean indicating that this would be a loop if no gaps are found
* @param points - the points
* @param start - start index
* @param max - max index (can go past count on a loop)
* @param loop - boolean indicating that this would be a loop if no gaps are found
*/
function solidSegments(points, start, max, loop) {
function solidSegments(points: PointElement[], start: number, max: number, loop: boolean) {
const count = points.length;
const result = [];
let last = start;
let prev = points[start];
let end;
let end: number;

for (end = start + 1; end <= max; ++end) {
const cur = points[end % count];
if (cur.skip || cur.stop) {
if (!prev.skip) {
loop = false;
result.push({start: start % count, end: (end - 1) % count, loop});
// @ts-ignore
start = last = cur.stop ? end : null;
}
} else {
Expand All @@ -229,12 +227,11 @@ function solidSegments(points, start, max, loop) {
/**
* Compute the continuous segments that define the whole line
* There can be skipped points within a segment, if spanGaps is true.
* @param {LineElement} line
* @param {object} [segmentOptions]
* @return {Segment[]}
* @param line
* @param segmentOptions
* @private
*/
export function _computeSegments(line, segmentOptions) {
export function _computeSegments(line: LineElement, segmentOptions: AnyObject) {
const points = line.points;
const spanGaps = line.options.spanGaps;
const count = points.length;
Expand All @@ -256,26 +253,24 @@ export function _computeSegments(line, segmentOptions) {
}

/**
* @param {Segment[]} segments
* @param {PointElement[]} points
* @param {object} [segmentOptions]
* @return {Segment[]}
* @param segments
* @param points
* @param segmentOptions
*/
function splitByStyles(line, segments, points, segmentOptions) {
function splitByStyles(line: LineElement, segments: Segment[], points: PointElement[], segmentOptions: AnyObject) {
if (!segmentOptions || !segmentOptions.setContext || !points) {
return segments;
}
return doSplitByStyles(line, segments, points, segmentOptions);
}

/**
* @param {LineElement} line
* @param {Segment[]} segments
* @param {PointElement[]} points
* @param {object} [segmentOptions]
* @return {Segment[]}
* @param line
* @param segments
* @param points
* @param segmentOptions
*/
function doSplitByStyles(line, segments, points, segmentOptions) {
function doSplitByStyles(line: LineElement, segments: Segment[], points: PointElement[], segmentOptions: AnyObject) {
const chartContext = line._chart.getContext();
const baseStyle = readStyle(line.options);
const {_datasetIndex: datasetIndex, options: {spanGaps}} = line;
Expand All @@ -285,7 +280,7 @@ function doSplitByStyles(line, segments, points, segmentOptions) {
let start = segments[0].start;
let i = start;

function addStyle(s, e, l, st) {
function addStyle(s: number, e: number, l: boolean, st: SegmentStyle) {
const dir = spanGaps ? -1 : 1;
if (s === e) {
return;
Expand All @@ -308,7 +303,7 @@ function doSplitByStyles(line, segments, points, segmentOptions) {
for (const segment of segments) {
start = spanGaps ? start : segment.start;
let prev = points[start % count];
let style;
let style: SegmentStyle;
for (i = start + 1; i <= segment.end; i++) {
const pt = points[i % count];
style = readStyle(segmentOptions.setContext(createContext(chartContext, {
Expand All @@ -333,7 +328,7 @@ function doSplitByStyles(line, segments, points, segmentOptions) {
return result;
}

function readStyle(options) {
function readStyle(options: LineOptions): SegmentStyle {
return {
backgroundColor: options.backgroundColor,
borderCapStyle: options.borderCapStyle,
Expand All @@ -345,6 +340,6 @@ function readStyle(options) {
};
}

function styleChanged(style, prevStyle) {
function styleChanged(style: SegmentStyle, prevStyle: SegmentStyle) {
return prevStyle && JSON.stringify(style) !== JSON.stringify(prevStyle);
}
36 changes: 25 additions & 11 deletions src/types/index.d.ts
Expand Up @@ -533,6 +533,7 @@ export declare class Chart<
getDataVisibility(index: number): boolean;
hide(datasetIndex: number, dataIndex?: number): void;
show(datasetIndex: number, dataIndex?: number): void;
getContext(): ScriptableChartContext;

getActiveElements(): ActiveElement[];
setActiveElements(active: ActiveDataPoint[]): void;
Expand Down Expand Up @@ -1685,6 +1686,23 @@ export interface Segment {
start: number;
end: number;
loop: boolean;
style?: SegmentStyle;
}

export interface SegmentStyle {
backgroundColor: Scriptable<Color | undefined, ScriptableLineSegmentContext>,
borderColor: Scriptable<Color | undefined, ScriptableLineSegmentContext>,
borderCapStyle: Scriptable<CanvasLineCap | undefined, ScriptableLineSegmentContext>;
borderDash: Scriptable<number[] | undefined, ScriptableLineSegmentContext>;
borderDashOffset: Scriptable<number | undefined, ScriptableLineSegmentContext>;
borderJoinStyle: Scriptable<CanvasLineJoin | undefined, ScriptableLineSegmentContext>;
borderWidth: Scriptable<number | undefined, ScriptableLineSegmentContext>;
}

export interface SegmentBounds {
property: string;
start: number;
end: number;
}

export interface ArcBorderRadius {
Expand Down Expand Up @@ -1786,15 +1804,7 @@ export interface LineOptions extends CommonElementOptions {
*/
spanGaps: boolean | number;

segment: {
backgroundColor: Scriptable<Color|undefined, ScriptableLineSegmentContext>,
borderColor: Scriptable<Color|undefined, ScriptableLineSegmentContext>,
borderCapStyle: Scriptable<CanvasLineCap|undefined, ScriptableLineSegmentContext>;
borderDash: Scriptable<number[]|undefined, ScriptableLineSegmentContext>;
borderDashOffset: Scriptable<number|undefined, ScriptableLineSegmentContext>;
borderJoinStyle: Scriptable<CanvasLineJoin|undefined, ScriptableLineSegmentContext>;
borderWidth: Scriptable<number|undefined, ScriptableLineSegmentContext>;
};
segment: SegmentStyle;
}

export interface LineHoverOptions extends CommonHoverOptions {
Expand All @@ -1806,15 +1816,19 @@ export interface LineHoverOptions extends CommonHoverOptions {

export interface LineElement<T extends LineProps = LineProps, O extends LineOptions = LineOptions>
extends Element<T, O>,
VisualElement {
Pick<VisualElement, 'draw'> {
updateControlPoints(chartArea: ChartArea, indexAxis?: 'x' | 'y'): void;
points: Point[];
points: PointElement[];
readonly segments: Segment[];
first(): Point | false;
last(): Point | false;
interpolate(point: Point, property: 'x' | 'y'): undefined | Point | Point[];
pathSegment(ctx: CanvasRenderingContext2D, segment: Segment, params: AnyObject): undefined | boolean;
path(ctx: CanvasRenderingContext2D): boolean;
_loop: boolean;
_fullLoop: boolean;
_datasetIndex: number;
_chart: Chart;
}

export declare const LineElement: ChartComponent & {
Expand Down