diff --git a/src/helpers/helpers.options.ts b/src/helpers/helpers.options.ts index c476ccb5e1a..aead9b7a2ae 100644 --- a/src/helpers/helpers.options.ts +++ b/src/helpers/helpers.options.ts @@ -204,7 +204,7 @@ export function _addGrace(minmax: { min: number; max: number; }, grace: number | * @returns */ export function createContext(parentContext: null, context: T): T; -export function createContext(parentContext: P, context: T): P & T; +export function createContext(parentContext: P, context: T): P & T; export function createContext(parentContext: object, context: object) { return Object.assign(Object.create(parentContext), context); } diff --git a/src/helpers/helpers.segment.js b/src/helpers/helpers.segment.ts similarity index 72% rename from src/helpers/helpers.segment.js rename to src/helpers/helpers.segment.ts index 0c6dbfd0ce2..d5fe687f612 100644 --- a/src/helpers/helpers.segment.js +++ b/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, @@ -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 }; } @@ -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; @@ -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]; } @@ -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); @@ -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; @@ -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; @@ -189,17 +188,17 @@ 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]; @@ -207,7 +206,6 @@ function solidSegments(points, start, max, loop) { if (!prev.skip) { loop = false; result.push({start: start % count, end: (end - 1) % count, loop}); - // @ts-ignore start = last = cur.stop ? end : null; } } else { @@ -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; @@ -256,12 +253,11 @@ 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; } @@ -269,13 +265,12 @@ function splitByStyles(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; @@ -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; @@ -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, { @@ -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, @@ -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); } diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 28de6bd6818..48f99fa36c0 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -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; @@ -1685,6 +1686,23 @@ export interface Segment { start: number; end: number; loop: boolean; + style?: SegmentStyle; +} + +export interface SegmentStyle { + backgroundColor: Scriptable, + borderColor: Scriptable, + borderCapStyle: Scriptable; + borderDash: Scriptable; + borderDashOffset: Scriptable; + borderJoinStyle: Scriptable; + borderWidth: Scriptable; +} + +export interface SegmentBounds { + property: string; + start: number; + end: number; } export interface ArcBorderRadius { @@ -1786,15 +1804,7 @@ export interface LineOptions extends CommonElementOptions { */ spanGaps: boolean | number; - segment: { - backgroundColor: Scriptable, - borderColor: Scriptable, - borderCapStyle: Scriptable; - borderDash: Scriptable; - borderDashOffset: Scriptable; - borderJoinStyle: Scriptable; - borderWidth: Scriptable; - }; + segment: SegmentStyle; } export interface LineHoverOptions extends CommonHoverOptions { @@ -1806,15 +1816,19 @@ export interface LineHoverOptions extends CommonHoverOptions { export interface LineElement extends Element, - VisualElement { + Pick { 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 & {