]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Use interpolation in fill: 'stack' (and fix interpolation) (#7711)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Sun, 16 Aug 2020 15:18:46 +0000 (18:18 +0300)
committerGitHub <noreply@github.com>
Sun, 16 Aug 2020 15:18:46 +0000 (11:18 -0400)
* Add tests and fix _boundSegment
* Use interpolate for finding points below
* Remove _refPoints logic (getTarget in draw)

karma.conf.js
src/helpers/helpers.segment.js
src/helpers/index.js
src/plugins/plugin.filler.js
test/specs/helpers.segment.tests.js [new file with mode: 0644]

index 1910931be45af316059804544a171385d2fc3bee..614df15ab8abff8fe720fc2fd68c6778d35c1ad2 100644 (file)
@@ -67,7 +67,7 @@ module.exports = function(karma) {
                        {pattern: 'test/BasicChartWebWorker.js', included: false},
                        {pattern: 'src/index.js', watched: false},
                        'node_modules/chartjs-adapter-moment/dist/chartjs-adapter-moment.js',
-                       {pattern: specPattern, watched: false}
+                       {pattern: specPattern}
                ],
 
                preprocessors: {
index e60a7c4db29e70eab82c5ffa4d4d8406a7ae2ab4..6421396644bb0bd55a8775c185880a3ca070aa8f 100644 (file)
@@ -78,12 +78,18 @@ export function _boundSegment(segment, points, bounds) {
        const count = points.length;
        const {compare, between, normalize} = propertyFn(property);
        const {start, end, loop} = getSegment(segment, points, bounds);
+
        const result = [];
        let inside = false;
        let subStart = null;
-       let i, value, point, prev;
+       let value, point, prevValue;
+
+       const startIsBefore = () => between(startBound, prevValue, value) && compare(startBound, prevValue) !== 0;
+       const endIsBefore = () => compare(endBound, value) === 0 || between(endBound, prevValue, value);
+       const shouldStart = () => inside || startIsBefore();
+       const shouldStop = () => !inside || endIsBefore();
 
-       for (i = start; i <= end; ++i) {
+       for (let i = start, prev = start; i <= end; ++i) {
                point = points[i % count];
 
                if (point.skip) {
@@ -93,15 +99,16 @@ export function _boundSegment(segment, points, bounds) {
                value = normalize(point[property]);
                inside = between(value, startBound, endBound);
 
-               if (subStart === null && inside) {
-                       subStart = i > start && compare(value, startBound) > 0 ? prev : i;
+               if (subStart === null && shouldStart()) {
+                       subStart = compare(value, startBound) === 0 ? i : prev;
                }
 
-               if (subStart !== null && (!inside || compare(value, endBound) === 0)) {
+               if (subStart !== null && shouldStop()) {
                        result.push(makeSubSegment(subStart, i, loop, count));
                        subStart = null;
                }
                prev = i;
+               prevValue = value;
        }
 
        if (subStart !== null) {
@@ -111,6 +118,7 @@ export function _boundSegment(segment, points, bounds) {
        return result;
 }
 
+
 /**
  * Returns the segments of the line that are inside given bounds
  * @param {Line} line
index e9771cb089e84cfa799a272e6615e4823be91509..4dec4c476f6e3acaced6f3a36313bc934c7ad551 100644 (file)
@@ -10,6 +10,7 @@ import * as interpolation from './helpers.interpolation';
 import * as options from './helpers.options';
 import * as math from './helpers.math';
 import * as rtl from './helpers.rtl';
+import * as segment from './helpers.segment';
 
 import {color, getHoverColor} from './helpers.color';
 import {requestAnimFrame, fontString} from './helpers.extras';
@@ -25,6 +26,7 @@ export default {
        options,
        math,
        rtl,
+       segment,
 
        requestAnimFrame,
        // -- Canvas methods
index afb2e31057650534cf6e792b30244e275f37a563..ce903bc472b248a8c7f08d62cdbf8835dd984827 100644 (file)
@@ -170,11 +170,11 @@ function pointsFromSegments(boundary, line) {
                const first = linePoints[segment.start];
                const last = linePoints[segment.end];
                if (y !== null) {
-                       points.push({x: first.x, y, _prop: 'x', _ref: first});
-                       points.push({x: last.x, y, _prop: 'x', _ref: last});
+                       points.push({x: first.x, y});
+                       points.push({x: last.x, y});
                } else if (x !== null) {
-                       points.push({x, y: first.y, _prop: 'y', _ref: first});
-                       points.push({x, y: last.y, _prop: 'y', _ref: last});
+                       points.push({x, y: first.y});
+                       points.push({x, y: last.y});
                }
        });
        return points;
@@ -186,13 +186,11 @@ function pointsFromSegments(boundary, line) {
  */
 function buildStackLine(source) {
        const {chart, scale, index, line} = source;
-       const linesBelow = getLinesBelow(chart, index);
        const points = [];
        const segments = line.segments;
        const sourcePoints = line.points;
-       const startPoints = [];
-       sourcePoints.forEach(point => startPoints.push({x: point.x, y: scale.bottom, _prop: 'x', _ref: point}));
-       linesBelow.push(new Line({points: startPoints, options: {}}));
+       const linesBelow = getLinesBelow(chart, index);
+       linesBelow.push(createBoundaryLine({x: null, y: scale.bottom}, line));
 
        for (let i = 0; i < segments.length; i++) {
                const segment = segments[i];
@@ -200,9 +198,11 @@ function buildStackLine(source) {
                        addPointsBelow(points, sourcePoints[j], linesBelow);
                }
        }
-       return new Line({points, options: {}, _refPoints: true});
+       return new Line({points, options: {}});
 }
 
+const isLineAndNotInHideAnimation = (meta) => meta.type === 'line' && !meta.hidden;
+
 /**
  * @param {Chart} chart
  * @param {number} index
@@ -211,12 +211,13 @@ function buildStackLine(source) {
 function getLinesBelow(chart, index) {
        const below = [];
        const metas = chart.getSortedVisibleDatasetMetas();
+
        for (let i = 0; i < metas.length; i++) {
                const meta = metas[i];
                if (meta.index === index) {
                        break;
                }
-               if (meta.type === 'line') {
+               if (isLineAndNotInHideAnimation(meta)) {
                        below.unshift(meta.dataset);
                }
        }
@@ -259,22 +260,27 @@ function addPointsBelow(points, sourcePoint, linesBelow) {
  * @returns {{point?: Point, first?: boolean, last?: boolean}}
  */
 function findPoint(line, sourcePoint, property) {
+       const point = line.interpolate(sourcePoint, property);
+       if (!point) {
+               return {};
+       }
+
+       const pointValue = point[property];
        const segments = line.segments;
        const linePoints = line.points;
+       let first = false;
+       let last = false;
        for (let i = 0; i < segments.length; i++) {
                const segment = segments[i];
-               for (let j = segment.start; j <= segment.end; j++) {
-                       const point = linePoints[j];
-                       if (sourcePoint[property] === point[property]) {
-                               return {
-                                       first: j === segment.start,
-                                       last: j === segment.end,
-                                       point
-                               };
-                       }
+               const firstValue = linePoints[segment.start][property];
+               const lastValue = linePoints[segment.end][property];
+               if (pointValue >= firstValue && pointValue <= lastValue) {
+                       first = pointValue === firstValue;
+                       last = pointValue === lastValue;
+                       break;
                }
        }
-       return {};
+       return {first, last, point};
 }
 
 function getTarget(source) {
@@ -305,7 +311,6 @@ function getTarget(source) {
 function createBoundaryLine(boundary, line) {
        let points = [];
        let _loop = false;
-       let _refPoints = false;
 
        if (isArray(boundary)) {
                _loop = true;
@@ -313,15 +318,13 @@ function createBoundaryLine(boundary, line) {
                points = boundary;
        } else {
                points = pointsFromSegments(boundary, line);
-               _refPoints = true;
        }
 
        return points.length ? new Line({
                points,
                options: {tension: 0},
                _loop,
-               _fullLoop: _loop,
-               _refPoints
+               _fullLoop: _loop
        }) : null;
 }
 
@@ -392,17 +395,6 @@ function _segments(line, target, property) {
        const tpoints = target.points;
        const parts = [];
 
-       if (target._refPoints) {
-               // Update properties from reference points. (In case those points are animating)
-               for (let i = 0, ilen = tpoints.length; i < ilen; ++i) {
-                       const point = tpoints[i];
-                       const prop = point._prop;
-                       if (prop) {
-                               point[prop] = point._ref[prop];
-                       }
-               }
-       }
-
        for (let i = 0; i < segments.length; i++) {
                const segment = segments[i];
                const bounds = getBounds(property, points[segment.start], points[segment.end], segment.loop);
@@ -464,7 +456,7 @@ function interpolatedLineTo(ctx, target, point, property) {
 
 function _fill(ctx, cfg) {
        const {line, target, property, color, scale} = cfg;
-       const segments = _segments(cfg.line, cfg.target, property);
+       const segments = _segments(line, target, property);
 
        ctx.fillStyle = color;
        for (let i = 0, ilen = segments.length; i < ilen; ++i) {
@@ -535,8 +527,7 @@ export default {
                                        fill: decodeFill(line, i, count),
                                        chart,
                                        scale: meta.vScale,
-                                       line,
-                                       target: undefined
+                                       line
                                };
                        }
 
@@ -551,7 +542,6 @@ export default {
                        }
 
                        source.fill = resolveTarget(sources, i, propagate);
-                       source.target = source.fill !== false && getTarget(source);
                }
        },
 
@@ -572,13 +562,14 @@ export default {
        beforeDatasetDraw(chart, args) {
                const area = chart.chartArea;
                const ctx = chart.ctx;
-               const meta = args.meta.$filler;
+               const source = args.meta.$filler;
 
-               if (!meta || meta.fill === false) {
+               if (!source || source.fill === false) {
                        return;
                }
 
-               const {line, target, scale} = meta;
+               const target = getTarget(source);
+               const {line, scale} = source;
                const lineOpts = line.options;
                const fillOption = lineOpts.fill;
                const color = lineOpts.backgroundColor;
diff --git a/test/specs/helpers.segment.tests.js b/test/specs/helpers.segment.tests.js
new file mode 100644 (file)
index 0000000..2900e7c
--- /dev/null
@@ -0,0 +1,44 @@
+const {_boundSegment} = Chart.helpers.segment;
+
+describe('helpers.segments', function() {
+       describe('_boundSegment', function() {
+               const points = [{x: 10, y: 1}, {x: 20, y: 2}, {x: 30, y: 3}];
+               const segment = {start: 0, end: 2, loop: false};
+
+               it('should not find segment from before the line', function() {
+                       expect(_boundSegment(segment, points, {property: 'x', start: 5, end: 9.99999})).toEqual([]);
+               });
+
+               it('should not find segment from after the line', function() {
+                       expect(_boundSegment(segment, points, {property: 'x', start: 30.00001, end: 800})).toEqual([]);
+               });
+
+               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}]);
+               });
+
+               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}]);
+               });
+
+               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}]);
+               });
+
+               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}]);
+               });
+
+               it('should find whole segment', function() {
+                       expect(_boundSegment(segment, points, {property: 'x', start: 0, end: 50})).toEqual([{start: 0, end: 2, loop: false}]);
+               });
+
+               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}]);
+               });
+
+               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}]);
+               });
+       });
+});