mirror of
https://github.com/chartjs/Chart.js.git
synced 2025-12-08 20:36:08 +00:00
Use interpolation in fill: 'stack' (and fix interpolation) (#7711)
* Add tests and fix _boundSegment * Use interpolate for finding points below * Remove _refPoints logic (getTarget in draw)
This commit is contained in:
parent
689befa807
commit
dd261b22f9
@ -67,7 +67,7 @@ module.exports = function(karma) {
|
|||||||
{pattern: 'test/BasicChartWebWorker.js', included: false},
|
{pattern: 'test/BasicChartWebWorker.js', included: false},
|
||||||
{pattern: 'src/index.js', watched: false},
|
{pattern: 'src/index.js', watched: false},
|
||||||
'node_modules/chartjs-adapter-moment/dist/chartjs-adapter-moment.js',
|
'node_modules/chartjs-adapter-moment/dist/chartjs-adapter-moment.js',
|
||||||
{pattern: specPattern, watched: false}
|
{pattern: specPattern}
|
||||||
],
|
],
|
||||||
|
|
||||||
preprocessors: {
|
preprocessors: {
|
||||||
|
|||||||
@ -78,12 +78,18 @@ export function _boundSegment(segment, points, bounds) {
|
|||||||
const count = points.length;
|
const count = points.length;
|
||||||
const {compare, between, normalize} = propertyFn(property);
|
const {compare, between, normalize} = propertyFn(property);
|
||||||
const {start, end, loop} = getSegment(segment, points, bounds);
|
const {start, end, loop} = getSegment(segment, points, bounds);
|
||||||
|
|
||||||
const result = [];
|
const result = [];
|
||||||
let inside = false;
|
let inside = false;
|
||||||
let subStart = null;
|
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];
|
point = points[i % count];
|
||||||
|
|
||||||
if (point.skip) {
|
if (point.skip) {
|
||||||
@ -93,15 +99,16 @@ export function _boundSegment(segment, points, bounds) {
|
|||||||
value = normalize(point[property]);
|
value = normalize(point[property]);
|
||||||
inside = between(value, startBound, endBound);
|
inside = between(value, startBound, endBound);
|
||||||
|
|
||||||
if (subStart === null && inside) {
|
if (subStart === null && shouldStart()) {
|
||||||
subStart = i > start && compare(value, startBound) > 0 ? prev : i;
|
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));
|
result.push(makeSubSegment(subStart, i, loop, count));
|
||||||
subStart = null;
|
subStart = null;
|
||||||
}
|
}
|
||||||
prev = i;
|
prev = i;
|
||||||
|
prevValue = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subStart !== null) {
|
if (subStart !== null) {
|
||||||
@ -111,6 +118,7 @@ export function _boundSegment(segment, points, bounds) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the segments of the line that are inside given bounds
|
* Returns the segments of the line that are inside given bounds
|
||||||
* @param {Line} line
|
* @param {Line} line
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import * as interpolation from './helpers.interpolation';
|
|||||||
import * as options from './helpers.options';
|
import * as options from './helpers.options';
|
||||||
import * as math from './helpers.math';
|
import * as math from './helpers.math';
|
||||||
import * as rtl from './helpers.rtl';
|
import * as rtl from './helpers.rtl';
|
||||||
|
import * as segment from './helpers.segment';
|
||||||
|
|
||||||
import {color, getHoverColor} from './helpers.color';
|
import {color, getHoverColor} from './helpers.color';
|
||||||
import {requestAnimFrame, fontString} from './helpers.extras';
|
import {requestAnimFrame, fontString} from './helpers.extras';
|
||||||
@ -25,6 +26,7 @@ export default {
|
|||||||
options,
|
options,
|
||||||
math,
|
math,
|
||||||
rtl,
|
rtl,
|
||||||
|
segment,
|
||||||
|
|
||||||
requestAnimFrame,
|
requestAnimFrame,
|
||||||
// -- Canvas methods
|
// -- Canvas methods
|
||||||
|
|||||||
@ -170,11 +170,11 @@ function pointsFromSegments(boundary, line) {
|
|||||||
const first = linePoints[segment.start];
|
const first = linePoints[segment.start];
|
||||||
const last = linePoints[segment.end];
|
const last = linePoints[segment.end];
|
||||||
if (y !== null) {
|
if (y !== null) {
|
||||||
points.push({x: first.x, y, _prop: 'x', _ref: first});
|
points.push({x: first.x, y});
|
||||||
points.push({x: last.x, y, _prop: 'x', _ref: last});
|
points.push({x: last.x, y});
|
||||||
} else if (x !== null) {
|
} else if (x !== null) {
|
||||||
points.push({x, y: first.y, _prop: 'y', _ref: first});
|
points.push({x, y: first.y});
|
||||||
points.push({x, y: last.y, _prop: 'y', _ref: last});
|
points.push({x, y: last.y});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return points;
|
return points;
|
||||||
@ -186,13 +186,11 @@ function pointsFromSegments(boundary, line) {
|
|||||||
*/
|
*/
|
||||||
function buildStackLine(source) {
|
function buildStackLine(source) {
|
||||||
const {chart, scale, index, line} = source;
|
const {chart, scale, index, line} = source;
|
||||||
const linesBelow = getLinesBelow(chart, index);
|
|
||||||
const points = [];
|
const points = [];
|
||||||
const segments = line.segments;
|
const segments = line.segments;
|
||||||
const sourcePoints = line.points;
|
const sourcePoints = line.points;
|
||||||
const startPoints = [];
|
const linesBelow = getLinesBelow(chart, index);
|
||||||
sourcePoints.forEach(point => startPoints.push({x: point.x, y: scale.bottom, _prop: 'x', _ref: point}));
|
linesBelow.push(createBoundaryLine({x: null, y: scale.bottom}, line));
|
||||||
linesBelow.push(new Line({points: startPoints, options: {}}));
|
|
||||||
|
|
||||||
for (let i = 0; i < segments.length; i++) {
|
for (let i = 0; i < segments.length; i++) {
|
||||||
const segment = segments[i];
|
const segment = segments[i];
|
||||||
@ -200,9 +198,11 @@ function buildStackLine(source) {
|
|||||||
addPointsBelow(points, sourcePoints[j], linesBelow);
|
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 {Chart} chart
|
||||||
* @param {number} index
|
* @param {number} index
|
||||||
@ -211,12 +211,13 @@ function buildStackLine(source) {
|
|||||||
function getLinesBelow(chart, index) {
|
function getLinesBelow(chart, index) {
|
||||||
const below = [];
|
const below = [];
|
||||||
const metas = chart.getSortedVisibleDatasetMetas();
|
const metas = chart.getSortedVisibleDatasetMetas();
|
||||||
|
|
||||||
for (let i = 0; i < metas.length; i++) {
|
for (let i = 0; i < metas.length; i++) {
|
||||||
const meta = metas[i];
|
const meta = metas[i];
|
||||||
if (meta.index === index) {
|
if (meta.index === index) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (meta.type === 'line') {
|
if (isLineAndNotInHideAnimation(meta)) {
|
||||||
below.unshift(meta.dataset);
|
below.unshift(meta.dataset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -259,22 +260,27 @@ function addPointsBelow(points, sourcePoint, linesBelow) {
|
|||||||
* @returns {{point?: Point, first?: boolean, last?: boolean}}
|
* @returns {{point?: Point, first?: boolean, last?: boolean}}
|
||||||
*/
|
*/
|
||||||
function findPoint(line, sourcePoint, property) {
|
function findPoint(line, sourcePoint, property) {
|
||||||
|
const point = line.interpolate(sourcePoint, property);
|
||||||
|
if (!point) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const pointValue = point[property];
|
||||||
const segments = line.segments;
|
const segments = line.segments;
|
||||||
const linePoints = line.points;
|
const linePoints = line.points;
|
||||||
|
let first = false;
|
||||||
|
let last = false;
|
||||||
for (let i = 0; i < segments.length; i++) {
|
for (let i = 0; i < segments.length; i++) {
|
||||||
const segment = segments[i];
|
const segment = segments[i];
|
||||||
for (let j = segment.start; j <= segment.end; j++) {
|
const firstValue = linePoints[segment.start][property];
|
||||||
const point = linePoints[j];
|
const lastValue = linePoints[segment.end][property];
|
||||||
if (sourcePoint[property] === point[property]) {
|
if (pointValue >= firstValue && pointValue <= lastValue) {
|
||||||
return {
|
first = pointValue === firstValue;
|
||||||
first: j === segment.start,
|
last = pointValue === lastValue;
|
||||||
last: j === segment.end,
|
break;
|
||||||
point
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {};
|
return {first, last, point};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTarget(source) {
|
function getTarget(source) {
|
||||||
@ -305,7 +311,6 @@ function getTarget(source) {
|
|||||||
function createBoundaryLine(boundary, line) {
|
function createBoundaryLine(boundary, line) {
|
||||||
let points = [];
|
let points = [];
|
||||||
let _loop = false;
|
let _loop = false;
|
||||||
let _refPoints = false;
|
|
||||||
|
|
||||||
if (isArray(boundary)) {
|
if (isArray(boundary)) {
|
||||||
_loop = true;
|
_loop = true;
|
||||||
@ -313,15 +318,13 @@ function createBoundaryLine(boundary, line) {
|
|||||||
points = boundary;
|
points = boundary;
|
||||||
} else {
|
} else {
|
||||||
points = pointsFromSegments(boundary, line);
|
points = pointsFromSegments(boundary, line);
|
||||||
_refPoints = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return points.length ? new Line({
|
return points.length ? new Line({
|
||||||
points,
|
points,
|
||||||
options: {tension: 0},
|
options: {tension: 0},
|
||||||
_loop,
|
_loop,
|
||||||
_fullLoop: _loop,
|
_fullLoop: _loop
|
||||||
_refPoints
|
|
||||||
}) : null;
|
}) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -392,17 +395,6 @@ function _segments(line, target, property) {
|
|||||||
const tpoints = target.points;
|
const tpoints = target.points;
|
||||||
const parts = [];
|
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++) {
|
for (let i = 0; i < segments.length; i++) {
|
||||||
const segment = segments[i];
|
const segment = segments[i];
|
||||||
const bounds = getBounds(property, points[segment.start], points[segment.end], segment.loop);
|
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) {
|
function _fill(ctx, cfg) {
|
||||||
const {line, target, property, color, scale} = 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;
|
ctx.fillStyle = color;
|
||||||
for (let i = 0, ilen = segments.length; i < ilen; ++i) {
|
for (let i = 0, ilen = segments.length; i < ilen; ++i) {
|
||||||
@ -535,8 +527,7 @@ export default {
|
|||||||
fill: decodeFill(line, i, count),
|
fill: decodeFill(line, i, count),
|
||||||
chart,
|
chart,
|
||||||
scale: meta.vScale,
|
scale: meta.vScale,
|
||||||
line,
|
line
|
||||||
target: undefined
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -551,7 +542,6 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
source.fill = resolveTarget(sources, i, propagate);
|
source.fill = resolveTarget(sources, i, propagate);
|
||||||
source.target = source.fill !== false && getTarget(source);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -572,13 +562,14 @@ export default {
|
|||||||
beforeDatasetDraw(chart, args) {
|
beforeDatasetDraw(chart, args) {
|
||||||
const area = chart.chartArea;
|
const area = chart.chartArea;
|
||||||
const ctx = chart.ctx;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {line, target, scale} = meta;
|
const target = getTarget(source);
|
||||||
|
const {line, scale} = source;
|
||||||
const lineOpts = line.options;
|
const lineOpts = line.options;
|
||||||
const fillOption = lineOpts.fill;
|
const fillOption = lineOpts.fill;
|
||||||
const color = lineOpts.backgroundColor;
|
const color = lineOpts.backgroundColor;
|
||||||
|
|||||||
44
test/specs/helpers.segment.tests.js
Normal file
44
test/specs/helpers.segment.tests.js
Normal file
@ -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}]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user