Skip to content

Commit 3a089ba

Browse files
committed
Allow filling above and below with different colors
Two colors allowed : first one to fill above the target, second to fill below Tests added
1 parent 70b32ff commit 3a089ba

16 files changed

+15693
-20
lines changed

dist/Chart.js

Lines changed: 15225 additions & 0 deletions
Large diffs are not rendered by default.

dist/Chart.min.js

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/plugins/plugin.filler.js

Lines changed: 99 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ var mappers = {
5555
// @todo if (fill[0] === '#')
5656
function decodeFill(el, index, count) {
5757
var model = el._model || {};
58-
var fill = model.fill;
58+
var fillOption = model.fill;
59+
var fill = fillOption && typeof fillOption === 'object' ? fillOption.target : fillOption;
5960
var target;
6061

6162
if (fill === undefined) {
@@ -232,47 +233,120 @@ function isDrawable(point) {
232233
return point && !point.skip;
233234
}
234235

235-
function drawArea(ctx, curve0, curve1, len0, len1) {
236+
function fillPointsSets(ctx, curve0, curve1, len0, len1, area, pointSets) {
236237
var i, cx, cy, r;
238+
var fillAreaPointsSet = [];
239+
var clipAboveAreaPointsSet = [];
240+
var clipBelowAreaPointsSet = [];
237241

238242
if (!len0 || !len1) {
239243
return;
240244
}
245+
clipAboveAreaPointsSet.push({x: curve1[len1 - 1].x, y: area.top});
246+
clipBelowAreaPointsSet.push({x: curve0[0].x, y: area.top});
247+
clipBelowAreaPointsSet.push(curve0[0]);
241248

242249
// building first area curve (normal)
243-
ctx.moveTo(curve0[0].x, curve0[0].y);
250+
fillAreaPointsSet.push(curve0[0]);
244251
for (i = 1; i < len0; ++i) {
245-
helpers.canvas.lineTo(ctx, curve0[i - 1], curve0[i]);
252+
curve0[i].flip = false;
253+
fillAreaPointsSet.push(curve0[i]);
254+
clipBelowAreaPointsSet.push(curve0[i]);
246255
}
247256

248257
if (curve1[0].angle !== undefined) {
258+
pointSets.fill.push(fillAreaPointsSet);
259+
var radialSet = [];
249260
cx = curve1[0].cx;
250261
cy = curve1[0].cy;
251262
r = Math.sqrt(Math.pow(curve1[0].x - cx, 2) + Math.pow(curve1[0].y - cy, 2));
252263
for (i = len1 - 1; i > 0; --i) {
253-
ctx.arc(cx, cy, r, curve1[i].angle, curve1[i - 1].angle, true);
264+
radialSet.push({cx: cx, cy: cy, radius: r, startAngle: curve1[i].angle, endAngle: curve1[i - 1].angle});
265+
}
266+
if (radialSet.length) {
267+
pointSets.fill.push(radialSet);
254268
}
255269
return;
256270
}
257-
258271
// joining the two area curves
259-
ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y);
272+
var jointPoint = {};
273+
for (var key in curve1[len1 - 1]) {
274+
if (curve1[len1 - 1].hasOwnProperty(key)) {
275+
jointPoint[key] = curve1[len1 - 1][key];
276+
}
277+
}
278+
jointPoint.joint = true;
279+
fillAreaPointsSet.push(jointPoint);
260280

261281
// building opposite area curve (reverse)
262282
for (i = len1 - 1; i > 0; --i) {
263-
helpers.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true);
283+
curve1[i].flip = true;
284+
clipAboveAreaPointsSet.push(curve1[i]);
285+
curve1[i - 1].flip = true;
286+
fillAreaPointsSet.push(curve1[i - 1]);
264287
}
288+
clipAboveAreaPointsSet.push(curve1[0]);
289+
clipAboveAreaPointsSet.push({x: curve1[0].x, y: area.top});
290+
clipBelowAreaPointsSet.push({x: curve0[len0 - 1].x, y: area.top});
291+
292+
pointSets.clipAbove.push(clipAboveAreaPointsSet);
293+
pointSets.clipBelow.push(clipBelowAreaPointsSet);
294+
pointSets.fill.push(fillAreaPointsSet);
265295
}
266296

267-
function doFill(ctx, points, mapper, view, color, loop) {
297+
function clipAndFill(ctx, clippingPointsSets, fillingPointsSets, color) {
298+
var i, ilen, j, jlen, set;
299+
if (clippingPointsSets) {
300+
ctx.save();
301+
ctx.beginPath();
302+
for (i = 0, ilen = clippingPointsSets.length; i < ilen; i++) {
303+
set = clippingPointsSets[i];
304+
// Have edge lines straight
305+
ctx.moveTo(set[0].x, set[0].y);
306+
ctx.lineTo(set[1].x, set[1].y);
307+
for (j = 2, jlen = set.length; j < jlen - 1; j++) {
308+
helpers.canvas.lineTo(ctx, set[j - 1], set[j], set[j].flip);
309+
}
310+
ctx.lineTo(set[j].x, set[j].y);
311+
}
312+
ctx.closePath();
313+
ctx.clip();
314+
ctx.beginPath();
315+
}
316+
for (i = 0, ilen = fillingPointsSets.length; i < ilen; i++) {
317+
set = fillingPointsSets[i];
318+
if (set[0].startAngle !== undefined) {
319+
for (j = 0, jlen = set.length; j < jlen; j++) {
320+
ctx.arc(set[j].cx, set[j].cy, set[j].radius, set[j].startAngle, set[j].endAngle, true);
321+
}
322+
} else {
323+
ctx.moveTo(set[0].x, set[0].y);
324+
for (j = 1, jlen = set.length; j < jlen; j++) {
325+
if (set[j].joint) {
326+
ctx.lineTo(set[j].x, set[j].y);
327+
} else {
328+
helpers.canvas.lineTo(ctx, set[j - 1], set[j], set[j].flip);
329+
}
330+
}
331+
}
332+
}
333+
ctx.closePath();
334+
ctx.fillStyle = color;
335+
ctx.fill();
336+
ctx.restore();
337+
}
338+
339+
function doFill(ctx, points, mapper, view, colors, loop, area) {
268340
var count = points.length;
269341
var span = view.spanGaps;
270342
var curve0 = [];
271343
var curve1 = [];
272344
var len0 = 0;
273345
var len1 = 0;
346+
var pointSets = {clipBelow: [], clipAbove: [], fill: []};
274347
var i, ilen, index, p0, p1, d0, d1;
275348

349+
ctx.save();
276350
ctx.beginPath();
277351

278352
for (i = 0, ilen = (count + !!loop); i < ilen; ++i) {
@@ -287,7 +361,7 @@ function doFill(ctx, points, mapper, view, color, loop) {
287361
len1 = curve1.push(p1);
288362
} else if (len0 && len1) {
289363
if (!span) {
290-
drawArea(ctx, curve0, curve1, len0, len1);
364+
fillPointsSets(ctx, curve0, curve1, len0, len1, area, pointSets);
291365
len0 = len1 = 0;
292366
curve0 = [];
293367
curve1 = [];
@@ -302,11 +376,14 @@ function doFill(ctx, points, mapper, view, color, loop) {
302376
}
303377
}
304378

305-
drawArea(ctx, curve0, curve1, len0, len1);
379+
fillPointsSets(ctx, curve0, curve1, len0, len1, area, pointSets);
306380

307-
ctx.closePath();
308-
ctx.fillStyle = color;
309-
ctx.fill();
381+
if (colors.below !== colors.above) {
382+
clipAndFill(ctx, pointSets.clipAbove, pointSets.fill, colors.above);
383+
clipAndFill(ctx, pointSets.clipBelow, pointSets.fill, colors.below);
384+
} else {
385+
clipAndFill(ctx, false, pointSets.fill, colors.above);
386+
}
310387
}
311388

312389
module.exports = {
@@ -351,7 +428,7 @@ module.exports = {
351428
beforeDatasetsDraw: function(chart) {
352429
var count = (chart.data.datasets || []).length - 1;
353430
var ctx = chart.ctx;
354-
var meta, i, el, view, points, mapper, color;
431+
var meta, i, el, view, points, mapper, color, colors, fillOption;
355432

356433
for (i = count; i >= 0; --i) {
357434
meta = chart.getDatasetMeta(i).$filler;
@@ -364,11 +441,17 @@ module.exports = {
364441
view = el._view;
365442
points = el._children || [];
366443
mapper = meta.mapper;
444+
fillOption = meta.el._model.fill;
367445
color = view.backgroundColor || defaults.global.defaultColor;
368446

447+
colors = {above: color, below: color};
448+
if (fillOption && typeof fillOption === 'object') {
449+
colors.above = fillOption.above || color;
450+
colors.below = fillOption.below || color;
451+
}
369452
if (mapper && color && points.length) {
370453
helpers.canvas.clipArea(ctx, chart.chartArea);
371-
doFill(ctx, points, mapper, view, color, el._loop);
454+
doFill(ctx, points, mapper, view, colors, el._loop, chart.chartArea);
372455
helpers.canvas.unclipArea(ctx);
373456
}
374457
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"config": {
3+
"type": "line",
4+
"data": {
5+
"labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"],
6+
"datasets": [{
7+
"backgroundColor": "rgba(0, 0, 192, 0.25)",
8+
"data": [null, null, 2, 3, 4, -4, -2, 1, 0]
9+
}, {
10+
"backgroundColor": "rgba(0, 192, 0, 0.25)",
11+
"data": [6, 2, null, 4, 5, null, null, 2, 1]
12+
}, {
13+
"backgroundColor": "rgba(192, 0, 0, 0.25)",
14+
"data": [7, 3, 4, 5, 6, 1, 4, null, null]
15+
}, {
16+
"backgroundColor": "rgba(0, 64, 192, 0.25)",
17+
"data": [8, 7, 6, -6, -4, -6, 4, 5, 8]
18+
}]
19+
},
20+
"options": {
21+
"responsive": false,
22+
"spanGaps": true,
23+
"legend": false,
24+
"title": false,
25+
"scales": {
26+
"xAxes": [{
27+
"display": false
28+
}],
29+
"yAxes": [{
30+
"display": false
31+
}]
32+
},
33+
"elements": {
34+
"point": {
35+
"radius": 0
36+
},
37+
"line": {
38+
"borderColor": "transparent",
39+
"fill": {
40+
"target": "origin",
41+
"below": "rgba(255, 0, 0, 0.25)"
42+
},
43+
"tension": 0
44+
}
45+
}
46+
}
47+
},
48+
"options": {
49+
"canvas": {
50+
"height": 256,
51+
"width": 512
52+
}
53+
}
54+
}
17 KB
Loading
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"config": {
3+
"type": "line",
4+
"data": {
5+
"labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"],
6+
"datasets": [{
7+
"backgroundColor": "rgba(0, 0, 192, 0.25)",
8+
"data": [null, null, 2, 4, 2, 1, -1, 1, 2]
9+
}, {
10+
"backgroundColor": "rgba(0, 192, 0, 0.25)",
11+
"data": [4, 2, null, 3, 2.5, null, -2, 1.5, 3]
12+
}, {
13+
"backgroundColor": "rgba(192, 0, 0, 0.25)",
14+
"data": [3.5, 2, 1, 2.5, -2, 3, -1, null, null]
15+
}, {
16+
"backgroundColor": "rgba(128, 0, 128, 0.25)",
17+
"data": [5, 6, 5, -2, -4, -3, 4, 2, 4.5]
18+
}]
19+
},
20+
"options": {
21+
"responsive": false,
22+
"spanGaps": false,
23+
"legend": false,
24+
"title": false,
25+
"scales": {
26+
"xAxes": [{
27+
"display": false
28+
}],
29+
"yAxes": [{
30+
"display": false
31+
}]
32+
},
33+
"elements": {
34+
"point": {
35+
"radius": 0
36+
},
37+
"line": {
38+
"cubicInterpolationMode": "monotone",
39+
"borderColor": "transparent",
40+
"fill": {
41+
"target": "origin",
42+
"below": "transparent"
43+
}
44+
}
45+
}
46+
}
47+
},
48+
"options": {
49+
"canvas": {
50+
"height": 256,
51+
"width": 512
52+
}
53+
}
54+
}
9.78 KB
Loading
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"config": {
3+
"type": "line",
4+
"data": {
5+
"labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"],
6+
"datasets": [{
7+
"backgroundColor": "rgba(255, 0, 0, 0.25)",
8+
"data": [0, 1, 2, -1, 0, 2, 1, -1, -2],
9+
"fill": {
10+
"target": "+1",
11+
"above": "rgba(255, 0, 0, 0.25)",
12+
"below": "rgba(0, 0, 255, 0.25)"
13+
}
14+
}, {
15+
"data": [0, 0, 0, 0, 0, 0, 0, 0, 0]
16+
}]
17+
},
18+
"options": {
19+
"responsive": false,
20+
"spanGaps": true,
21+
"legend": false,
22+
"title": false,
23+
"scales": {
24+
"xAxes": [{
25+
"display": false
26+
}],
27+
"yAxes": [{
28+
"display": false
29+
}]
30+
},
31+
"elements": {
32+
"point": {
33+
"radius": 0
34+
},
35+
"line": {
36+
"borderColor": "transparent",
37+
"tension": 0
38+
}
39+
}
40+
}
41+
},
42+
"options": {
43+
"canvas": {
44+
"height": 256,
45+
"width": 512
46+
}
47+
}
48+
}
6.04 KB
Loading

0 commit comments

Comments
 (0)