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) {
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) {
return result;
}
+
/**
* Returns the segments of the line that are inside given bounds
* @param {Line} 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;
*/
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];
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
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);
}
}
* @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) {
function createBoundaryLine(boundary, line) {
let points = [];
let _loop = false;
- let _refPoints = false;
if (isArray(boundary)) {
_loop = true;
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;
}
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);
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) {
fill: decodeFill(line, i, count),
chart,
scale: meta.vScale,
- line,
- target: undefined
+ line
};
}
}
source.fill = resolveTarget(sources, i, propagate);
- source.target = source.fill !== false && getTarget(source);
}
},
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;
--- /dev/null
+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}]);
+ });
+ });
+});