Skip to content

Commit dd261b2

Browse files
authored
Use interpolation in fill: 'stack' (and fix interpolation) (chartjs#7711)
* Add tests and fix _boundSegment * Use interpolate for finding points below * Remove _refPoints logic (getTarget in draw)
1 parent 689befa commit dd261b2

File tree

5 files changed

+93
-48
lines changed

5 files changed

+93
-48
lines changed

karma.conf.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ module.exports = function(karma) {
6767
{pattern: 'test/BasicChartWebWorker.js', included: false},
6868
{pattern: 'src/index.js', watched: false},
6969
'node_modules/chartjs-adapter-moment/dist/chartjs-adapter-moment.js',
70-
{pattern: specPattern, watched: false}
70+
{pattern: specPattern}
7171
],
7272

7373
preprocessors: {

src/helpers/helpers.segment.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,18 @@ export function _boundSegment(segment, points, bounds) {
7878
const count = points.length;
7979
const {compare, between, normalize} = propertyFn(property);
8080
const {start, end, loop} = getSegment(segment, points, bounds);
81+
8182
const result = [];
8283
let inside = false;
8384
let subStart = null;
84-
let i, value, point, prev;
85+
let value, point, prevValue;
86+
87+
const startIsBefore = () => between(startBound, prevValue, value) && compare(startBound, prevValue) !== 0;
88+
const endIsBefore = () => compare(endBound, value) === 0 || between(endBound, prevValue, value);
89+
const shouldStart = () => inside || startIsBefore();
90+
const shouldStop = () => !inside || endIsBefore();
8591

86-
for (i = start; i <= end; ++i) {
92+
for (let i = start, prev = start; i <= end; ++i) {
8793
point = points[i % count];
8894

8995
if (point.skip) {
@@ -93,15 +99,16 @@ export function _boundSegment(segment, points, bounds) {
9399
value = normalize(point[property]);
94100
inside = between(value, startBound, endBound);
95101

96-
if (subStart === null && inside) {
97-
subStart = i > start && compare(value, startBound) > 0 ? prev : i;
102+
if (subStart === null && shouldStart()) {
103+
subStart = compare(value, startBound) === 0 ? i : prev;
98104
}
99105

100-
if (subStart !== null && (!inside || compare(value, endBound) === 0)) {
106+
if (subStart !== null && shouldStop()) {
101107
result.push(makeSubSegment(subStart, i, loop, count));
102108
subStart = null;
103109
}
104110
prev = i;
111+
prevValue = value;
105112
}
106113

107114
if (subStart !== null) {
@@ -111,6 +118,7 @@ export function _boundSegment(segment, points, bounds) {
111118
return result;
112119
}
113120

121+
114122
/**
115123
* Returns the segments of the line that are inside given bounds
116124
* @param {Line} line

src/helpers/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import * as interpolation from './helpers.interpolation';
1010
import * as options from './helpers.options';
1111
import * as math from './helpers.math';
1212
import * as rtl from './helpers.rtl';
13+
import * as segment from './helpers.segment';
1314

1415
import {color, getHoverColor} from './helpers.color';
1516
import {requestAnimFrame, fontString} from './helpers.extras';
@@ -25,6 +26,7 @@ export default {
2526
options,
2627
math,
2728
rtl,
29+
segment,
2830

2931
requestAnimFrame,
3032
// -- Canvas methods

src/plugins/plugin.filler.js

Lines changed: 33 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,11 @@ function pointsFromSegments(boundary, line) {
170170
const first = linePoints[segment.start];
171171
const last = linePoints[segment.end];
172172
if (y !== null) {
173-
points.push({x: first.x, y, _prop: 'x', _ref: first});
174-
points.push({x: last.x, y, _prop: 'x', _ref: last});
173+
points.push({x: first.x, y});
174+
points.push({x: last.x, y});
175175
} else if (x !== null) {
176-
points.push({x, y: first.y, _prop: 'y', _ref: first});
177-
points.push({x, y: last.y, _prop: 'y', _ref: last});
176+
points.push({x, y: first.y});
177+
points.push({x, y: last.y});
178178
}
179179
});
180180
return points;
@@ -186,23 +186,23 @@ function pointsFromSegments(boundary, line) {
186186
*/
187187
function buildStackLine(source) {
188188
const {chart, scale, index, line} = source;
189-
const linesBelow = getLinesBelow(chart, index);
190189
const points = [];
191190
const segments = line.segments;
192191
const sourcePoints = line.points;
193-
const startPoints = [];
194-
sourcePoints.forEach(point => startPoints.push({x: point.x, y: scale.bottom, _prop: 'x', _ref: point}));
195-
linesBelow.push(new Line({points: startPoints, options: {}}));
192+
const linesBelow = getLinesBelow(chart, index);
193+
linesBelow.push(createBoundaryLine({x: null, y: scale.bottom}, line));
196194

197195
for (let i = 0; i < segments.length; i++) {
198196
const segment = segments[i];
199197
for (let j = segment.start; j <= segment.end; j++) {
200198
addPointsBelow(points, sourcePoints[j], linesBelow);
201199
}
202200
}
203-
return new Line({points, options: {}, _refPoints: true});
201+
return new Line({points, options: {}});
204202
}
205203

204+
const isLineAndNotInHideAnimation = (meta) => meta.type === 'line' && !meta.hidden;
205+
206206
/**
207207
* @param {Chart} chart
208208
* @param {number} index
@@ -211,12 +211,13 @@ function buildStackLine(source) {
211211
function getLinesBelow(chart, index) {
212212
const below = [];
213213
const metas = chart.getSortedVisibleDatasetMetas();
214+
214215
for (let i = 0; i < metas.length; i++) {
215216
const meta = metas[i];
216217
if (meta.index === index) {
217218
break;
218219
}
219-
if (meta.type === 'line') {
220+
if (isLineAndNotInHideAnimation(meta)) {
220221
below.unshift(meta.dataset);
221222
}
222223
}
@@ -259,22 +260,27 @@ function addPointsBelow(points, sourcePoint, linesBelow) {
259260
* @returns {{point?: Point, first?: boolean, last?: boolean}}
260261
*/
261262
function findPoint(line, sourcePoint, property) {
263+
const point = line.interpolate(sourcePoint, property);
264+
if (!point) {
265+
return {};
266+
}
267+
268+
const pointValue = point[property];
262269
const segments = line.segments;
263270
const linePoints = line.points;
271+
let first = false;
272+
let last = false;
264273
for (let i = 0; i < segments.length; i++) {
265274
const segment = segments[i];
266-
for (let j = segment.start; j <= segment.end; j++) {
267-
const point = linePoints[j];
268-
if (sourcePoint[property] === point[property]) {
269-
return {
270-
first: j === segment.start,
271-
last: j === segment.end,
272-
point
273-
};
274-
}
275+
const firstValue = linePoints[segment.start][property];
276+
const lastValue = linePoints[segment.end][property];
277+
if (pointValue >= firstValue && pointValue <= lastValue) {
278+
first = pointValue === firstValue;
279+
last = pointValue === lastValue;
280+
break;
275281
}
276282
}
277-
return {};
283+
return {first, last, point};
278284
}
279285

280286
function getTarget(source) {
@@ -305,23 +311,20 @@ function getTarget(source) {
305311
function createBoundaryLine(boundary, line) {
306312
let points = [];
307313
let _loop = false;
308-
let _refPoints = false;
309314

310315
if (isArray(boundary)) {
311316
_loop = true;
312317
// @ts-ignore
313318
points = boundary;
314319
} else {
315320
points = pointsFromSegments(boundary, line);
316-
_refPoints = true;
317321
}
318322

319323
return points.length ? new Line({
320324
points,
321325
options: {tension: 0},
322326
_loop,
323-
_fullLoop: _loop,
324-
_refPoints
327+
_fullLoop: _loop
325328
}) : null;
326329
}
327330

@@ -392,17 +395,6 @@ function _segments(line, target, property) {
392395
const tpoints = target.points;
393396
const parts = [];
394397

395-
if (target._refPoints) {
396-
// Update properties from reference points. (In case those points are animating)
397-
for (let i = 0, ilen = tpoints.length; i < ilen; ++i) {
398-
const point = tpoints[i];
399-
const prop = point._prop;
400-
if (prop) {
401-
point[prop] = point._ref[prop];
402-
}
403-
}
404-
}
405-
406398
for (let i = 0; i < segments.length; i++) {
407399
const segment = segments[i];
408400
const bounds = getBounds(property, points[segment.start], points[segment.end], segment.loop);
@@ -464,7 +456,7 @@ function interpolatedLineTo(ctx, target, point, property) {
464456

465457
function _fill(ctx, cfg) {
466458
const {line, target, property, color, scale} = cfg;
467-
const segments = _segments(cfg.line, cfg.target, property);
459+
const segments = _segments(line, target, property);
468460

469461
ctx.fillStyle = color;
470462
for (let i = 0, ilen = segments.length; i < ilen; ++i) {
@@ -535,8 +527,7 @@ export default {
535527
fill: decodeFill(line, i, count),
536528
chart,
537529
scale: meta.vScale,
538-
line,
539-
target: undefined
530+
line
540531
};
541532
}
542533

@@ -551,7 +542,6 @@ export default {
551542
}
552543

553544
source.fill = resolveTarget(sources, i, propagate);
554-
source.target = source.fill !== false && getTarget(source);
555545
}
556546
},
557547

@@ -572,13 +562,14 @@ export default {
572562
beforeDatasetDraw(chart, args) {
573563
const area = chart.chartArea;
574564
const ctx = chart.ctx;
575-
const meta = args.meta.$filler;
565+
const source = args.meta.$filler;
576566

577-
if (!meta || meta.fill === false) {
567+
if (!source || source.fill === false) {
578568
return;
579569
}
580570

581-
const {line, target, scale} = meta;
571+
const target = getTarget(source);
572+
const {line, scale} = source;
582573
const lineOpts = line.options;
583574
const fillOption = lineOpts.fill;
584575
const color = lineOpts.backgroundColor;

test/specs/helpers.segment.tests.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const {_boundSegment} = Chart.helpers.segment;
2+
3+
describe('helpers.segments', function() {
4+
describe('_boundSegment', function() {
5+
const points = [{x: 10, y: 1}, {x: 20, y: 2}, {x: 30, y: 3}];
6+
const segment = {start: 0, end: 2, loop: false};
7+
8+
it('should not find segment from before the line', function() {
9+
expect(_boundSegment(segment, points, {property: 'x', start: 5, end: 9.99999})).toEqual([]);
10+
});
11+
12+
it('should not find segment from after the line', function() {
13+
expect(_boundSegment(segment, points, {property: 'x', start: 30.00001, end: 800})).toEqual([]);
14+
});
15+
16+
it('should find segment when starting before line', function() {
17+
expect(_boundSegment(segment, points, {property: 'x', start: 5, end: 15})).toEqual([{start: 0, end: 1, loop: false}]);
18+
});
19+
20+
it('should find segment directly on point', function() {
21+
expect(_boundSegment(segment, points, {property: 'x', start: 10, end: 10})).toEqual([{start: 0, end: 0, loop: false}]);
22+
});
23+
24+
it('should find segment from range between points', function() {
25+
expect(_boundSegment(segment, points, {property: 'x', start: 11, end: 14})).toEqual([{start: 0, end: 1, loop: false}]);
26+
});
27+
28+
it('should find segment from point between points', function() {
29+
expect(_boundSegment(segment, points, {property: 'x', start: 22, end: 22})).toEqual([{start: 1, end: 2, loop: false}]);
30+
});
31+
32+
it('should find whole segment', function() {
33+
expect(_boundSegment(segment, points, {property: 'x', start: 0, end: 50})).toEqual([{start: 0, end: 2, loop: false}]);
34+
});
35+
36+
it('should find correct segment from near points', function() {
37+
expect(_boundSegment(segment, points, {property: 'x', start: 10.001, end: 29.999})).toEqual([{start: 0, end: 2, loop: false}]);
38+
});
39+
40+
it('should find segment from after the line', function() {
41+
expect(_boundSegment(segment, points, {property: 'x', start: 25, end: 35})).toEqual([{start: 1, end: 2, loop: false}]);
42+
});
43+
});
44+
});

0 commit comments

Comments
 (0)