diff --git a/karma.conf.js b/karma.conf.js index 1910931be..614df15ab 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -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: { diff --git a/src/helpers/helpers.segment.js b/src/helpers/helpers.segment.js index e60a7c4db..642139664 100644 --- a/src/helpers/helpers.segment.js +++ b/src/helpers/helpers.segment.js @@ -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; - for (i = start; i <= end; ++i) { + 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 (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 diff --git a/src/helpers/index.js b/src/helpers/index.js index e9771cb08..4dec4c476 100644 --- a/src/helpers/index.js +++ b/src/helpers/index.js @@ -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 diff --git a/src/plugins/plugin.filler.js b/src/plugins/plugin.filler.js index afb2e3105..ce903bc47 100644 --- a/src/plugins/plugin.filler.js +++ b/src/plugins/plugin.filler.js @@ -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 index 000000000..2900e7c35 --- /dev/null +++ b/test/specs/helpers.segment.tests.js @@ -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}]); + }); + }); +});