### Segment
-Line segment styles can be overridden by scriptable options in the `segment` object. Currently all of the `border*` options are supported. The segment styles are resolved for each section of the line between each point. `undefined` fallbacks to main line styles.
+Line segment styles can be overridden by scriptable options in the `segment` object. Currently all of the `border*` and `backgroundColor` options are supported. The segment styles are resolved for each section of the line between each point. `undefined` fallbacks to main line styles.
Context for the scriptable segment contains the following properties:
};
}
-function makeSubSegment(start, end, loop, count) {
+function normalizeSegment({start, end, count, loop, style}) {
return {
start: start % count,
end: end % count,
- loop: loop && (end - start + 1) % count === 0
+ loop: loop && (end - start + 1) % count === 0,
+ style
};
}
if (end < start) {
end += count;
}
- return {start, end, loop};
+ return {start, end, loop, style: segment.style};
}
/**
* @param {number} segment.start - start index of the segment, referring the points array
* @param {number} segment.end - end index of the segment, referring the points array
* @param {boolean} segment.loop - indicates that the segment is a loop
+ * @param {object} [segment.style] - segment style
* @param {PointElement[]} points - the points that this segment refers to
* @param {object} [bounds]
* @param {string} bounds.property - the property of a `PointElement` we are bounding. `x`, `y` or `angle`.
const {property, start: startBound, end: endBound} = bounds;
const count = points.length;
const {compare, between, normalize} = propertyFn(property);
- const {start, end, loop} = getSegment(segment, points, bounds);
+ const {start, end, loop, style} = getSegment(segment, points, bounds);
const result = [];
let inside = false;
}
if (subStart !== null && shouldStop()) {
- result.push(makeSubSegment(subStart, i, loop, count));
+ result.push(normalizeSegment({start: subStart, end: i, loop, count, style}));
subStart = null;
}
prev = i;
}
if (subStart !== null) {
- result.push(makeSubSegment(subStart, end, loop, count));
+ result.push(normalizeSegment({start: subStart, end, loop, count, style}));
}
return result;
function readStyle(options) {
return {
+ backgroundColor: options.backgroundColor,
borderCapStyle: options.borderCapStyle,
borderDash: options.borderDash,
borderDashOffset: options.borderDashOffset,
borderJoinStyle: options.borderJoinStyle,
borderWidth: options.borderWidth,
- borderColor: options.borderColor,
+ borderColor: options.borderColor
};
}
const tpoints = target.points;
const parts = [];
- for (let i = 0; i < segments.length; i++) {
- const segment = segments[i];
+ for (const segment of segments) {
const bounds = getBounds(property, points[segment.start], points[segment.end], segment.loop);
if (!target.segments) {
}
// Get all segments from `target` that intersect the bounds of current segment of `line`
- const subs = _boundSegments(target, bounds);
+ const targetSegments = _boundSegments(target, bounds);
- for (let j = 0; j < subs.length; ++j) {
- const sub = subs[j];
- const subBounds = getBounds(property, tpoints[sub.start], tpoints[sub.end], sub.loop);
+ for (const tgt of targetSegments) {
+ const subBounds = getBounds(property, tpoints[tgt.start], tpoints[tgt.end], tgt.loop);
const fillSources = _boundSegment(segment, points, subBounds);
- for (let k = 0; k < fillSources.length; k++) {
+ for (const fillSource of fillSources) {
parts.push({
- source: fillSources[k],
- target: sub,
+ source: fillSource,
+ target: tgt,
start: {
[property]: _getEdge(bounds, subBounds, 'start', Math.max)
},
end: {
[property]: _getEdge(bounds, subBounds, 'end', Math.min)
}
-
});
}
}
const {line, target, property, color, scale} = cfg;
const segments = _segments(line, target, property);
- ctx.fillStyle = color;
- for (let i = 0, ilen = segments.length; i < ilen; ++i) {
- const {source: src, target: tgt, start, end} = segments[i];
-
+ for (const {source: src, target: tgt, start, end} of segments) {
+ const {style: {backgroundColor = color} = {}} = src;
ctx.save();
+ ctx.fillStyle = backgroundColor;
clipBounds(ctx, scale, getBounds(property, start, end));
--- /dev/null
+module.exports = {
+ config: {
+ type: 'line',
+ data: {
+ labels: ['a', 'b', 'c', 'd', 'e', 'f'],
+ datasets: [{
+ data: [1, 3, NaN, NaN, 2, 1],
+ borderColor: 'transparent',
+ backgroundColor: 'black',
+ fill: true,
+ segment: {
+ backgroundColor: ctx => ctx.p0.skip || ctx.p1.skip ? 'red' : undefined,
+ }
+ }]
+ },
+ options: {
+ scales: {
+ x: {display: false},
+ y: {display: false}
+ }
+ }
+ }
+};
--- /dev/null
+function slope({p0, p1}) {
+ return (p0.y - p1.y) / (p1.x - p0.x);
+}
+
+module.exports = {
+ config: {
+ type: 'line',
+ data: {
+ labels: ['a', 'b', 'c', 'd', 'e', 'f'],
+ datasets: [{
+ data: [1, 2, 3, 3, 2, 1],
+ backgroundColor: 'black',
+ borderColor: 'orange',
+ fill: true,
+ segment: {
+ backgroundColor: ctx => slope(ctx) > 0 ? 'green' : slope(ctx) < 0 ? 'red' : undefined,
+ }
+ }]
+ },
+ options: {
+ plugins: {
+ legend: false
+ },
+ scales: {
+ x: {display: false},
+ y: {display: false}
+ }
+ }
+ }
+};
});
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}]);
+ expect(_boundSegment(segment, points, {property: 'x', start: 5, end: 15})).toEqual([{start: 0, end: 1, loop: false, style: undefined}]);
});
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}]);
+ expect(_boundSegment(segment, points, {property: 'x', start: 10, end: 10})).toEqual([{start: 0, end: 0, loop: false, style: undefined}]);
});
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}]);
+ expect(_boundSegment(segment, points, {property: 'x', start: 11, end: 14})).toEqual([{start: 0, end: 1, loop: false, style: undefined}]);
});
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}]);
+ expect(_boundSegment(segment, points, {property: 'x', start: 22, end: 22})).toEqual([{start: 1, end: 2, loop: false, style: undefined}]);
});
it('should find whole segment', function() {
- expect(_boundSegment(segment, points, {property: 'x', start: 0, end: 50})).toEqual([{start: 0, end: 2, loop: false}]);
+ expect(_boundSegment(segment, points, {property: 'x', start: 0, end: 50})).toEqual([{start: 0, end: 2, loop: false, style: undefined}]);
});
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}]);
+ expect(_boundSegment(segment, points, {property: 'x', start: 10.001, end: 29.999})).toEqual([{start: 0, end: 2, loop: false, style: undefined}]);
});
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}]);
+ expect(_boundSegment(segment, points, {property: 'x', start: 25, end: 35})).toEqual([{start: 1, end: 2, loop: false, style: undefined}]);
});
it('should find multiple segments', function() {
const points2 = [{x: 0, y: 100}, {x: 1, y: 50}, {x: 2, y: 70}, {x: 4, y: 80}, {x: 5, y: -100}];
expect(_boundSegment({start: 0, end: 4, loop: false}, points2, {property: 'y', start: 60, end: 60})).toEqual([
- {start: 0, end: 1, loop: false},
- {start: 1, end: 2, loop: false},
- {start: 3, end: 4, loop: false},
+ {start: 0, end: 1, loop: false, style: undefined},
+ {start: 1, end: 2, loop: false, style: undefined},
+ {start: 3, end: 4, loop: false, style: undefined},
]);
});
});
stepped: 'before' | 'after' | 'middle' | boolean;
segment: {
+ backgroundColor: Scriptable<Color|undefined, ScriptableLineSegmentContext>,
borderColor: Scriptable<Color|undefined, ScriptableLineSegmentContext>,
borderCapStyle: Scriptable<CanvasLineCap|undefined, ScriptableLineSegmentContext>;
borderDash: Scriptable<number[]|undefined, ScriptableLineSegmentContext>;
datasets: [{
data: [],
segment: {
+ backgroundColor: ctx => ctx.p0.skip ? 'transparent' : undefined,
borderColor: ctx => ctx.p0.skip ? 'gray' : undefined,
borderWidth: ctx => ctx.p1.parsed.y > 10 ? 5 : undefined,
}