]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Filler: support segment backgroundColor (#8864)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Fri, 9 Apr 2021 23:10:48 +0000 (02:10 +0300)
committerGitHub <noreply@github.com>
Fri, 9 Apr 2021 23:10:48 +0000 (19:10 -0400)
docs/charts/line.md
src/helpers/helpers.segment.js
src/plugins/plugin.filler.js
test/fixtures/plugin.filler/line/segments/gap.js [new file with mode: 0644]
test/fixtures/plugin.filler/line/segments/gap.png [new file with mode: 0644]
test/fixtures/plugin.filler/line/segments/slope.js [new file with mode: 0644]
test/fixtures/plugin.filler/line/segments/slope.png [new file with mode: 0644]
test/specs/helpers.segment.tests.js
types/index.esm.d.ts
types/tests/controllers/line_segments.ts

index ef342dfbb75dcb12bea450b5114c0fa0d65501ba..bc5f6ceb78e5328839b09c37cbb545a75de6a4eb 100644 (file)
@@ -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:
 
index 6c117114fd187e0fae6f6b3ec75f2bd4b78d357f..fe79403027c73c6247a741ab1a663dc6a597a1f3 100644 (file)
@@ -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
   };
 }
 
index 8ecb7b06491d5a1455a564f489a53cf1086b35de..4d36f4bd71b0faa6a3f0d7ac9df582e2b207c670 100644 (file)
@@ -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 (file)
index 0000000..6dfb831
--- /dev/null
@@ -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 (file)
index 0000000..eee6321
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 (file)
index 0000000..d9a4d25
--- /dev/null
@@ -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 (file)
index 0000000..bb9edd2
Binary files /dev/null and b/test/fixtures/plugin.filler/line/segments/slope.png differ
index eca3c3fab7f332332c4a80424602fc393f7eaa74..0553a2aac4a40f2a7b2f67543f408a1c53913a65 100644 (file)
@@ -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},
       ]);
     });
   });
index 39c3dcb9438cdaff44f65b7240028826f207b20c..01415b2732f7dea4bbd2ab85fc51193fa4a8a48f 100644 (file)
@@ -1691,6 +1691,7 @@ export interface LineOptions extends CommonElementOptions {
   stepped: 'before' | 'after' | 'middle' | boolean;
 
   segment: {
+    backgroundColor: Scriptable<Color|undefined, ScriptableLineSegmentContext>,
     borderColor: Scriptable<Color|undefined, ScriptableLineSegmentContext>,
     borderCapStyle: Scriptable<CanvasLineCap|undefined, ScriptableLineSegmentContext>;
     borderDash: Scriptable<number[]|undefined, ScriptableLineSegmentContext>;
index 5c439b74cb40903ae45c77de25e95668d97a423e..9e99e3d21cd0f7392cc903d387131bf84666a203 100644 (file)
@@ -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,
       }