From 4eb59454be0bdb740550479207a559dc01c178e0 Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Fri, 9 Apr 2021 01:02:12 +0300 Subject: [PATCH] Allow styling of line segments (#8844) Allow styling of line segments * docs & sample * Types * update sample --- docs/.vuepress/config.js | 1 + docs/charts/line.md | 17 ++++- docs/samples/line/segments.md | 43 +++++++++++ src/controllers/controller.line.js | 2 + src/elements/element.line.js | 51 ++++++++----- src/elements/element.point.js | 1 + src/helpers/helpers.segment.js | 71 +++++++++++++++++- test/fixtures/controller.line/segments/gap.js | 22 ++++++ .../fixtures/controller.line/segments/gap.png | Bin 0 -> 12666 bytes .../controller.line/segments/range.js | 33 ++++++++ .../controller.line/segments/range.png | Bin 0 -> 12361 bytes .../controller.line/segments/slope.js | 26 +++++++ .../controller.line/segments/slope.png | Bin 0 -> 13158 bytes types/index.esm.d.ts | 16 ++++ types/tests/controllers/line_segments.ts | 15 ++++ 15 files changed, 274 insertions(+), 24 deletions(-) create mode 100644 docs/samples/line/segments.md create mode 100644 test/fixtures/controller.line/segments/gap.js create mode 100644 test/fixtures/controller.line/segments/gap.png create mode 100644 test/fixtures/controller.line/segments/range.js create mode 100644 test/fixtures/controller.line/segments/range.png create mode 100644 test/fixtures/controller.line/segments/slope.js create mode 100644 test/fixtures/controller.line/segments/slope.png create mode 100644 types/tests/controllers/line_segments.ts diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 436e3ab19..5b4982b1a 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -111,6 +111,7 @@ module.exports = { 'line/interpolation', 'line/styling', // 'line/point-styling', + 'line/segments', ] }, { diff --git a/docs/charts/line.md b/docs/charts/line.md index c04290cab..ef342dfbb 100644 --- a/docs/charts/line.md +++ b/docs/charts/line.md @@ -52,8 +52,8 @@ The line chart allows a number of properties to be specified for each dataset. T | [`borderJoinStyle`](#line-styling) | `string` | Yes | - | `'miter'` | [`borderWidth`](#line-styling) | `number` | Yes | - | `3` | [`clip`](#general) | `number`\|`object` | - | - | `undefined` -| [`data`](#data-structure) | `object`\|`object[]`\| `number[]`\|`string[]` | - | - | **required** | [`cubicInterpolationMode`](#cubicinterpolationmode) | `string` | Yes | - | `'default'` +| [`data`](#data-structure) | `object`\|`object[]`\| `number[]`\|`string[]` | - | - | **required** | [`fill`](#line-styling) | `boolean`\|`string` | Yes | - | `false` | [`hoverBackgroundColor`](#line-styling) | [`Color`](../general/colors.md) | Yes | - | `undefined` | [`hoverBorderCapStyle`](#line-styling) | `string` | Yes | - | `undefined` @@ -64,7 +64,6 @@ The line chart allows a number of properties to be specified for each dataset. T | [`hoverBorderWidth`](#line-styling) | `number` | Yes | - | `undefined` | [`indexAxis`](#general) | `string` | - | - | `'x'` | [`label`](#general) | `string` | - | - | `''` -| [`tension`](#line-styling) | `number` | - | - | `0` | [`order`](#general) | `number` | - | - | `0` | [`pointBackgroundColor`](#point-styling) | `Color` | Yes | Yes | `'rgba(0, 0, 0, 0.1)'` | [`pointBorderColor`](#point-styling) | `Color` | Yes | Yes | `'rgba(0, 0, 0, 0.1)'` @@ -77,10 +76,12 @@ The line chart allows a number of properties to be specified for each dataset. T | [`pointRadius`](#point-styling) | `number` | Yes | Yes | `3` | [`pointRotation`](#point-styling) | `number` | Yes | Yes | `0` | [`pointStyle`](#point-styling) | `string`\|`Image` | Yes | Yes | `'circle'` +| [`segment`](#segment) | `object` | - | - | `undefined` | [`showLine`](#line-styling) | `boolean` | - | - | `true` | [`spanGaps`](#line-styling) | `boolean`\|`number` | - | - | `undefined` | [`stack`](#general) | `string` | - | - | `'line'` | | [`stepped`](#stepped) | `boolean`\|`string` | - | - | `false` +| [`tension`](#line-styling) | `number` | - | - | `0` | [`xAxisID`](#general) | `string` | - | - | first x axis | [`yAxisID`](#general) | `string` | - | - | first y axis @@ -158,6 +159,18 @@ The `'monotone'` algorithm is more suited to `y = f(x)` datasets: it preserves m If left untouched (`undefined`), the global `options.elements.line.cubicInterpolationMode` property is used. +### Segment + +Line segment styles can be overridden by scriptable options in the `segment` object. Currently all of the `border*` options are supported. The segment styles are resolved for each section of the line between each point. `undefined` fallbacks to main line styles. + +Context for the scriptable segment contains the following properties: + +* `type`: `'segment'` +* `p0`: first point element +* `p1`: second point element + +[Example usage](../samples/line/segments.md) + ### Stepped The following values are supported for `stepped`. diff --git a/docs/samples/line/segments.md b/docs/samples/line/segments.md new file mode 100644 index 000000000..b1b6a8735 --- /dev/null +++ b/docs/samples/line/segments.md @@ -0,0 +1,43 @@ +# Line Segment Styling + +```js chart-editor + +// +const skipped = (ctx, value) => ctx.p0.skip || ctx.p1.skip ? value : undefined; +const down = (ctx, value) => ctx.p0.parsed.y > ctx.p1.parsed.y ? value : undefined; +// + +// +const genericOptions = { + fill: false, + interaction: { + intersect: false + }, + radius: 0, +}; +// + +// +const config = { + type: 'line', + data: { + labels: Utils.months({count: 7}), + datasets: [{ + label: 'My First Dataset', + data: [65, 59, NaN, 48, 56, 57, 40], + borderColor: 'rgb(75, 192, 192)', + segment: { + borderColor: ctx => skipped(ctx, 'rgb(0,0,0,0.2)') || down(ctx, 'rgb(192,75,75)'), + borderDash: ctx => skipped(ctx, [6, 6]), + } + }] + }, + options: genericOptions +}; +// + +module.exports = { + actions: [], + config: config, +}; +``` diff --git a/src/controllers/controller.line.js b/src/controllers/controller.line.js index 142c11500..f0f0e39d5 100644 --- a/src/controllers/controller.line.js +++ b/src/controllers/controller.line.js @@ -33,6 +33,7 @@ export default class LineController extends DatasetController { if (!me.options.showLine) { options.borderWidth = 0; } + options.segment = me.options.segment; me.updateElement(line, undefined, { animated: !animationsDisabled, options @@ -62,6 +63,7 @@ export default class LineController extends DatasetController { const y = properties.y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(_stacked ? me.applyStack(yScale, parsed, _stacked) : parsed.y, i); properties.skip = isNaN(x) || isNaN(y); properties.stop = i > 0 && (parsed.x - prevParsed.x) > maxGapLength; + properties.parsed = parsed; if (includeOptions) { properties.options = sharedOptions || me.resolveDataElementOptions(i, mode); diff --git a/src/elements/element.line.js b/src/elements/element.line.js index e81811d59..e6265a128 100644 --- a/src/elements/element.line.js +++ b/src/elements/element.line.js @@ -3,18 +3,19 @@ import {_bezierInterpolation, _pointInLine, _steppedInterpolation} from '../help import {_computeSegments, _boundSegments} from '../helpers/helpers.segment'; import {_steppedLineTo, _bezierCurveTo} from '../helpers/helpers.canvas'; import {_updateBezierControlPoints} from '../helpers/helpers.curve'; +import {valueOrDefault} from '../helpers'; /** * @typedef { import("./element.point").default } PointElement */ -function setStyle(ctx, vm) { - ctx.lineCap = vm.borderCapStyle; - ctx.setLineDash(vm.borderDash); - ctx.lineDashOffset = vm.borderDashOffset; - ctx.lineJoin = vm.borderJoinStyle; - ctx.lineWidth = vm.borderWidth; - ctx.strokeStyle = vm.borderColor; +function setStyle(ctx, options, style = options) { + ctx.lineCap = valueOrDefault(style.borderCapStyle, options.borderCapStyle); + ctx.setLineDash(valueOrDefault(style.borderDash, options.borderDash)); + ctx.lineDashOffset = valueOrDefault(style.borderDashOffset, options.borderDashOffset); + ctx.lineJoin = valueOrDefault(style.borderJoinStyle, options.borderJoinStyle); + ctx.lineWidth = valueOrDefault(style.borderWidth, options.borderWidth); + ctx.strokeStyle = valueOrDefault(style.borderColor, options.borderColor); } function lineTo(ctx, previous, target) { @@ -206,18 +207,33 @@ function strokePathWithCache(ctx, line, start, count) { path.closePath(); } } + setStyle(ctx, line.options); ctx.stroke(path); } + function strokePathDirect(ctx, line, start, count) { - ctx.beginPath(); - if (line.path(ctx, start, count)) { - ctx.closePath(); + const {segments, options} = line; + const segmentMethod = _getSegmentMethod(line); + + for (const segment of segments) { + setStyle(ctx, options, segment.style); + ctx.beginPath(); + if (segmentMethod(ctx, line, segment, {start, end: start + count - 1})) { + ctx.closePath(); + } + ctx.stroke(); } - ctx.stroke(); } const usePath2D = typeof Path2D === 'function'; -const strokePath = usePath2D ? strokePathWithCache : strokePathDirect; + +function draw(ctx, line, start, count) { + if (usePath2D && line.segments.length === 1) { + strokePathWithCache(ctx, line, start, count); + } else { + strokePathDirect(ctx, line, start, count); + } +} export default class LineElement extends Element { @@ -262,7 +278,7 @@ export default class LineElement extends Element { } get segments() { - return this._segments || (this._segments = _computeSegments(this)); + return this._segments || (this._segments = _computeSegments(this, this.options.segment)); } /** @@ -352,15 +368,14 @@ export default class LineElement extends Element { path(ctx, start, count) { const me = this; const segments = me.segments; - const ilen = segments.length; const segmentMethod = _getSegmentMethod(me); let loop = me._loop; start = start || 0; count = count || (me.points.length - start); - for (let i = 0; i < ilen; ++i) { - loop &= segmentMethod(ctx, me, segments[i], {start, end: start + count - 1}); + for (const segment of segments) { + loop &= segmentMethod(ctx, me, segment, {start, end: start + count - 1}); } return !!loop; } @@ -383,9 +398,7 @@ export default class LineElement extends Element { ctx.save(); - setStyle(ctx, options); - - strokePath(ctx, me, start, count); + draw(ctx, me, start, count); ctx.restore(); diff --git a/src/elements/element.point.js b/src/elements/element.point.js index 2d34d1b74..8c4e094cc 100644 --- a/src/elements/element.point.js +++ b/src/elements/element.point.js @@ -14,6 +14,7 @@ export default class PointElement extends Element { super(); this.options = undefined; + this.parsed = undefined; this.skip = undefined; this.stop = undefined; diff --git a/src/helpers/helpers.segment.js b/src/helpers/helpers.segment.js index b68582312..6c117114f 100644 --- a/src/helpers/helpers.segment.js +++ b/src/helpers/helpers.segment.js @@ -3,6 +3,7 @@ import {_angleBetween, _angleDiff, _normalizeAngle} from './helpers.math'; /** * @typedef { import("../elements/element.line").default } LineElement * @typedef { import("../elements/element.point").default } PointElement + * @typedef {{start: number, end: number, loop: boolean, style?: any}} Segment */ function propertyFn(property) { @@ -221,9 +222,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[]} * @private */ -export function _computeSegments(line) { +export function _computeSegments(line, segmentOptions) { const points = line.points; const spanGaps = line.options.spanGaps; const count = points.length; @@ -236,10 +239,72 @@ export function _computeSegments(line) { const {start, end} = findStartAndEnd(points, count, loop, spanGaps); if (spanGaps === true) { - return [{start, end, loop}]; + return splitByStyles([{start, end, loop}], points, segmentOptions); } const max = end < start ? end + count : end; const completeLoop = !!line._fullLoop && start === 0 && end === count - 1; - return solidSegments(points, start, max, completeLoop); + return splitByStyles(solidSegments(points, start, max, completeLoop), points, segmentOptions); +} + +/** + * @param {Segment[]} segments + * @param {PointElement[]} points + * @param {object} [segmentOptions] + * @return {Segment[]} + */ +function splitByStyles(segments, points, segmentOptions) { + if (!segmentOptions || !segmentOptions.setContext || !points) { + return segments; + } + return doSplitByStyles(segments, points, segmentOptions); +} + +/** + * @param {Segment[]} segments + * @param {PointElement[]} points + * @param {object} [segmentOptions] + * @return {Segment[]} + */ +function doSplitByStyles(segments, points, segmentOptions) { + const count = points.length; + const result = []; + let start = segments[0].start; + let i = start; + for (const segment of segments) { + let prevStyle, style; + let prev = points[start % count]; + for (i = start + 1; i <= segment.end; i++) { + const pt = points[i % count]; + style = readStyle(segmentOptions.setContext({type: 'segment', p0: prev, p1: pt})); + if (styleChanged(style, prevStyle)) { + result.push({start: start, end: i - 1, loop: segment.loop, style: prevStyle}); + prevStyle = style; + start = i - 1; + } + prev = pt; + prevStyle = style; + } + if (start < i - 1) { + result.push({start, end: i - 1, loop: segment.loop, style}); + start = i - 1; + } + } + + return result; +} + +function readStyle(options) { + return { + borderCapStyle: options.borderCapStyle, + borderDash: options.borderDash, + borderDashOffset: options.borderDashOffset, + borderJoinStyle: options.borderJoinStyle, + borderWidth: options.borderWidth, + borderColor: options.borderColor, + }; +} + +function styleChanged(style, prevStyle) { + return prevStyle && JSON.stringify(style) !== JSON.stringify(prevStyle); } diff --git a/test/fixtures/controller.line/segments/gap.js b/test/fixtures/controller.line/segments/gap.js new file mode 100644 index 000000000..fd08a4d55 --- /dev/null +++ b/test/fixtures/controller.line/segments/gap.js @@ -0,0 +1,22 @@ +module.exports = { + config: { + type: 'line', + data: { + labels: ['a', 'b', 'c', 'd', 'e', 'f'], + datasets: [{ + data: [1, 3, NaN, NaN, 2, 1], + borderColor: 'black', + segment: { + borderColor: ctx => ctx.p0.skip || ctx.p1.skip ? 'red' : undefined, + borderDash: ctx => ctx.p0.skip || ctx.p1.skip ? [5, 5] : undefined + } + }] + }, + options: { + scales: { + x: {display: false}, + y: {display: false} + } + } + } +}; diff --git a/test/fixtures/controller.line/segments/gap.png b/test/fixtures/controller.line/segments/gap.png new file mode 100644 index 0000000000000000000000000000000000000000..5a7e5aff35180adf4d355c2c77b2bc2ac75e0bb7 GIT binary patch literal 12666 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 lMdA3S+mUh@-1R&= min || ctx.p1.parsed.x >= min) && (ctx.p0.parsed.x < max && ctx.p1.parsed.x < max); +} + +function y(ctx, {min = -Infinity, max = Infinity}) { + return (ctx.p0.parsed.y >= min || ctx.p1.parsed.y >= min) && (ctx.p0.parsed.y < max || ctx.p1.parsed.y < max); +} + +function xy(ctx, xr, yr) { + return x(ctx, xr) && y(ctx, yr); +} + +module.exports = { + config: { + type: 'line', + data: { + datasets: [{ + data: [{x: 0, y: 0}, {x: 1, y: 1}, {x: 2, y: 2}, {x: 3, y: 3}, {x: 4, y: 4}, {x: 5, y: 5}, {x: 6, y: 7}, {x: 7, y: 8}], + borderColor: 'black', + segment: { + borderColor: ctx => x(ctx, {min: 3, max: 4}) ? 'red' : y(ctx, {min: 5}) ? 'green' : xy(ctx, {min: 0}, {max: 1}) ? 'blue' : undefined, + borderDash: ctx => x(ctx, {min: 3, max: 4}) || y(ctx, {min: 5}) ? [5, 5] : undefined, + } + }] + }, + options: { + scales: { + x: {type: 'linear', display: false}, + y: {display: false} + } + } + } +}; diff --git a/test/fixtures/controller.line/segments/range.png b/test/fixtures/controller.line/segments/range.png new file mode 100644 index 0000000000000000000000000000000000000000..eebb8a41daa1c913d7a9cd054ad73b280b9fe8b2 GIT binary patch literal 12361 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 literal 0 Hc-jL100001 diff --git a/test/fixtures/controller.line/segments/slope.js b/test/fixtures/controller.line/segments/slope.js new file mode 100644 index 000000000..7fcc948c1 --- /dev/null +++ b/test/fixtures/controller.line/segments/slope.js @@ -0,0 +1,26 @@ +function slope({p0, p1}) { + return (p0.y - p1.y) / (p1.x - p0.x); +} + +module.exports = { + config: { + type: 'line', + data: { + labels: ['a', 'b', 'c', 'd', 'e', 'f'], + datasets: [{ + data: [1, 2, 3, 3, 2, 1], + borderColor: 'black', + segment: { + borderColor: ctx => slope(ctx) > 0 ? 'green' : slope(ctx) < 0 ? 'red' : undefined, + borderDash: ctx => slope(ctx) < 0 ? [5, 5] : undefined + } + }] + }, + options: { + scales: { + x: {display: false}, + y: {display: false} + } + } + } +}; diff --git a/test/fixtures/controller.line/segments/slope.png b/test/fixtures/controller.line/segments/slope.png new file mode 100644 index 0000000000000000000000000000000000000000..969373030b6c52bdcca120dca9133cd4371f545a GIT binary patch literal 13158 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 { raw: unknown; } +export interface ScriptableLineSegmentContext { + type: 'segment', + p0: PointElement, + p1: PointElement +} + export type Scriptable = T | ((ctx: TContext) => T); export type ScriptableOptions = { [P in keyof T]: Scriptable }; export type ScriptableAndArray = readonly T[] | Scriptable; @@ -1683,6 +1689,15 @@ export interface LineOptions extends CommonElementOptions { * @default false */ stepped: 'before' | 'after' | 'middle' | boolean; + + segment: { + borderColor: Scriptable, + borderCapStyle: Scriptable; + borderDash: Scriptable; + borderDashOffset: Scriptable; + borderJoinStyle: Scriptable; + borderWidth: Scriptable; + }; } export interface LineHoverOptions extends CommonHoverOptions { @@ -1814,6 +1829,7 @@ export interface PointElement, VisualElement { readonly skip: boolean; + readonly parsed: CartesianParsedData; } export const PointElement: ChartComponent & { diff --git a/types/tests/controllers/line_segments.ts b/types/tests/controllers/line_segments.ts new file mode 100644 index 000000000..5c439b74c --- /dev/null +++ b/types/tests/controllers/line_segments.ts @@ -0,0 +1,15 @@ +import { Chart } from '../../index.esm'; + +const chart = new Chart('id', { + type: 'line', + data: { + labels: [], + datasets: [{ + data: [], + segment: { + borderColor: ctx => ctx.p0.skip ? 'gray' : undefined, + borderWidth: ctx => ctx.p1.parsed.y > 10 ? 5 : undefined, + } + }] + }, +}); -- 2.47.3