From: Jukka Kurkela Date: Fri, 9 Apr 2021 23:10:48 +0000 (+0300) Subject: Filler: support segment backgroundColor (#8864) X-Git-Tag: v3.1.0~11 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=ba84cc5c2aaf500739b202702fac24da74ede50d;p=thirdparty%2FChart.js.git Filler: support segment backgroundColor (#8864) --- diff --git a/docs/charts/line.md b/docs/charts/line.md index ef342dfbb..bc5f6ceb7 100644 --- a/docs/charts/line.md +++ b/docs/charts/line.md @@ -161,7 +161,7 @@ If left untouched (`undefined`), the global `options.elements.line.cubicInterpol ### 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. +Line segment styles can be overridden by scriptable options in the `segment` object. Currently all of the `border*` and `backgroundColor` 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: diff --git a/src/helpers/helpers.segment.js b/src/helpers/helpers.segment.js index 6c117114f..fe7940302 100644 --- a/src/helpers/helpers.segment.js +++ b/src/helpers/helpers.segment.js @@ -21,11 +21,12 @@ function propertyFn(property) { }; } -function makeSubSegment(start, end, loop, count) { +function normalizeSegment({start, end, count, loop, style}) { return { start: start % count, end: end % count, - loop: loop && (end - start + 1) % count === 0 + loop: loop && (end - start + 1) % count === 0, + style }; } @@ -54,7 +55,7 @@ function getSegment(segment, points, bounds) { if (end < start) { end += count; } - return {start, end, loop}; + return {start, end, loop, style: segment.style}; } /** @@ -63,6 +64,7 @@ function getSegment(segment, points, bounds) { * @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`. @@ -78,7 +80,7 @@ export function _boundSegment(segment, points, bounds) { const {property, start: startBound, end: endBound} = bounds; const count = points.length; const {compare, between, normalize} = propertyFn(property); - const {start, end, loop} = getSegment(segment, points, bounds); + const {start, end, loop, style} = getSegment(segment, points, bounds); const result = []; let inside = false; @@ -105,7 +107,7 @@ export function _boundSegment(segment, points, bounds) { } if (subStart !== null && shouldStop()) { - result.push(makeSubSegment(subStart, i, loop, count)); + result.push(normalizeSegment({start: subStart, end: i, loop, count, style})); subStart = null; } prev = i; @@ -113,7 +115,7 @@ export function _boundSegment(segment, points, bounds) { } if (subStart !== null) { - result.push(makeSubSegment(subStart, end, loop, count)); + result.push(normalizeSegment({start: subStart, end, loop, count, style})); } return result; @@ -296,12 +298,13 @@ function doSplitByStyles(segments, points, segmentOptions) { function readStyle(options) { return { + backgroundColor: options.backgroundColor, borderCapStyle: options.borderCapStyle, borderDash: options.borderDash, borderDashOffset: options.borderDashOffset, borderJoinStyle: options.borderJoinStyle, borderWidth: options.borderWidth, - borderColor: options.borderColor, + borderColor: options.borderColor }; } diff --git a/src/plugins/plugin.filler.js b/src/plugins/plugin.filler.js index 8ecb7b064..4d36f4bd7 100644 --- a/src/plugins/plugin.filler.js +++ b/src/plugins/plugin.filler.js @@ -405,8 +405,7 @@ function _segments(line, target, property) { const tpoints = target.points; const parts = []; - for (let i = 0; i < segments.length; i++) { - const segment = segments[i]; + for (const segment of segments) { const bounds = getBounds(property, points[segment.start], points[segment.end], segment.loop); if (!target.segments) { @@ -422,24 +421,22 @@ function _segments(line, target, property) { } // Get all segments from `target` that intersect the bounds of current segment of `line` - const subs = _boundSegments(target, bounds); + const targetSegments = _boundSegments(target, bounds); - for (let j = 0; j < subs.length; ++j) { - const sub = subs[j]; - const subBounds = getBounds(property, tpoints[sub.start], tpoints[sub.end], sub.loop); + for (const tgt of targetSegments) { + const subBounds = getBounds(property, tpoints[tgt.start], tpoints[tgt.end], tgt.loop); const fillSources = _boundSegment(segment, points, subBounds); - for (let k = 0; k < fillSources.length; k++) { + for (const fillSource of fillSources) { parts.push({ - source: fillSources[k], - target: sub, + source: fillSource, + target: tgt, start: { [property]: _getEdge(bounds, subBounds, 'start', Math.max) }, end: { [property]: _getEdge(bounds, subBounds, 'end', Math.min) } - }); } } @@ -468,11 +465,10 @@ function _fill(ctx, cfg) { const {line, target, property, color, scale} = cfg; const segments = _segments(line, target, property); - ctx.fillStyle = color; - for (let i = 0, ilen = segments.length; i < ilen; ++i) { - const {source: src, target: tgt, start, end} = segments[i]; - + for (const {source: src, target: tgt, start, end} of segments) { + const {style: {backgroundColor = color} = {}} = src; ctx.save(); + ctx.fillStyle = backgroundColor; clipBounds(ctx, scale, getBounds(property, start, end)); diff --git a/test/fixtures/plugin.filler/line/segments/gap.js b/test/fixtures/plugin.filler/line/segments/gap.js new file mode 100644 index 000000000..6dfb83156 --- /dev/null +++ b/test/fixtures/plugin.filler/line/segments/gap.js @@ -0,0 +1,23 @@ +module.exports = { + config: { + type: 'line', + data: { + labels: ['a', 'b', 'c', 'd', 'e', 'f'], + datasets: [{ + data: [1, 3, NaN, NaN, 2, 1], + borderColor: 'transparent', + backgroundColor: 'black', + fill: true, + segment: { + backgroundColor: ctx => ctx.p0.skip || ctx.p1.skip ? 'red' : undefined, + } + }] + }, + options: { + scales: { + x: {display: false}, + y: {display: false} + } + } + } +}; diff --git a/test/fixtures/plugin.filler/line/segments/gap.png b/test/fixtures/plugin.filler/line/segments/gap.png new file mode 100644 index 000000000..eee63218c Binary files /dev/null and b/test/fixtures/plugin.filler/line/segments/gap.png differ diff --git a/test/fixtures/plugin.filler/line/segments/slope.js b/test/fixtures/plugin.filler/line/segments/slope.js new file mode 100644 index 000000000..d9a4d2506 --- /dev/null +++ b/test/fixtures/plugin.filler/line/segments/slope.js @@ -0,0 +1,30 @@ +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], + backgroundColor: 'black', + borderColor: 'orange', + fill: true, + segment: { + backgroundColor: ctx => slope(ctx) > 0 ? 'green' : slope(ctx) < 0 ? 'red' : undefined, + } + }] + }, + options: { + plugins: { + legend: false + }, + scales: { + x: {display: false}, + y: {display: false} + } + } + } +}; diff --git a/test/fixtures/plugin.filler/line/segments/slope.png b/test/fixtures/plugin.filler/line/segments/slope.png new file mode 100644 index 000000000..bb9edd28f Binary files /dev/null and b/test/fixtures/plugin.filler/line/segments/slope.png differ diff --git a/test/specs/helpers.segment.tests.js b/test/specs/helpers.segment.tests.js index eca3c3fab..0553a2aac 100644 --- a/test/specs/helpers.segment.tests.js +++ b/test/specs/helpers.segment.tests.js @@ -14,39 +14,39 @@ describe('helpers.segments', function() { }); it('should find segment when starting before line', function() { - expect(_boundSegment(segment, points, {property: 'x', start: 5, end: 15})).toEqual([{start: 0, end: 1, loop: false}]); + expect(_boundSegment(segment, points, {property: 'x', start: 5, end: 15})).toEqual([{start: 0, end: 1, loop: false, style: undefined}]); }); it('should find segment directly on point', function() { - expect(_boundSegment(segment, points, {property: 'x', start: 10, end: 10})).toEqual([{start: 0, end: 0, loop: false}]); + expect(_boundSegment(segment, points, {property: 'x', start: 10, end: 10})).toEqual([{start: 0, end: 0, loop: false, style: undefined}]); }); it('should find segment from range between points', function() { - expect(_boundSegment(segment, points, {property: 'x', start: 11, end: 14})).toEqual([{start: 0, end: 1, loop: false}]); + expect(_boundSegment(segment, points, {property: 'x', start: 11, end: 14})).toEqual([{start: 0, end: 1, loop: false, style: undefined}]); }); it('should find segment from point between points', function() { - expect(_boundSegment(segment, points, {property: 'x', start: 22, end: 22})).toEqual([{start: 1, end: 2, loop: false}]); + expect(_boundSegment(segment, points, {property: 'x', start: 22, end: 22})).toEqual([{start: 1, end: 2, loop: false, style: undefined}]); }); it('should find whole segment', function() { - expect(_boundSegment(segment, points, {property: 'x', start: 0, end: 50})).toEqual([{start: 0, end: 2, loop: false}]); + expect(_boundSegment(segment, points, {property: 'x', start: 0, end: 50})).toEqual([{start: 0, end: 2, loop: false, style: undefined}]); }); it('should find correct segment from near points', function() { - expect(_boundSegment(segment, points, {property: 'x', start: 10.001, end: 29.999})).toEqual([{start: 0, end: 2, loop: false}]); + expect(_boundSegment(segment, points, {property: 'x', start: 10.001, end: 29.999})).toEqual([{start: 0, end: 2, loop: false, style: undefined}]); }); it('should find segment from after the line', function() { - expect(_boundSegment(segment, points, {property: 'x', start: 25, end: 35})).toEqual([{start: 1, end: 2, loop: false}]); + expect(_boundSegment(segment, points, {property: 'x', start: 25, end: 35})).toEqual([{start: 1, end: 2, loop: false, style: undefined}]); }); it('should find multiple segments', function() { const points2 = [{x: 0, y: 100}, {x: 1, y: 50}, {x: 2, y: 70}, {x: 4, y: 80}, {x: 5, y: -100}]; expect(_boundSegment({start: 0, end: 4, loop: false}, points2, {property: 'y', start: 60, end: 60})).toEqual([ - {start: 0, end: 1, loop: false}, - {start: 1, end: 2, loop: false}, - {start: 3, end: 4, loop: false}, + {start: 0, end: 1, loop: false, style: undefined}, + {start: 1, end: 2, loop: false, style: undefined}, + {start: 3, end: 4, loop: false, style: undefined}, ]); }); }); diff --git a/types/index.esm.d.ts b/types/index.esm.d.ts index 39c3dcb94..01415b273 100644 --- a/types/index.esm.d.ts +++ b/types/index.esm.d.ts @@ -1691,6 +1691,7 @@ export interface LineOptions extends CommonElementOptions { stepped: 'before' | 'after' | 'middle' | boolean; segment: { + backgroundColor: Scriptable, borderColor: Scriptable, borderCapStyle: Scriptable; borderDash: Scriptable; diff --git a/types/tests/controllers/line_segments.ts b/types/tests/controllers/line_segments.ts index 5c439b74c..9e99e3d21 100644 --- a/types/tests/controllers/line_segments.ts +++ b/types/tests/controllers/line_segments.ts @@ -7,6 +7,7 @@ const chart = new Chart('id', { datasets: [{ data: [], segment: { + backgroundColor: ctx => ctx.p0.skip ? 'transparent' : undefined, borderColor: ctx => ctx.p0.skip ? 'gray' : undefined, borderWidth: ctx => ctx.p1.parsed.y > 10 ? 5 : undefined, }