});
```
+If you need to support multiple colors when filling from one dataset to another, you may specify an object with the following option :
+
+| Param | Type | Description |
+| :--- | :--- | :--- |
+| `target` | `number`, `string`, `boolean` | The accepted values are the same as the filling mode values, so you may use absolute and relative dataset indexes and/or boundaries. |
+| `above` | `Color` | If no color is set, the default color will be the background color of the chart. |
+| `below` | `Color` | Same as the above. |
+
+**Example**
+```javascript
+new Chart(ctx, {
+ data: {
+ datasets: [
+ {
+ fill: {
+ target: 'origin',
+ above: 'rgb(255, 0, 0)', // Area will be red above the origin
+ below: 'rgb(0, 0, 255)' // And blue below the origin
+ }
+ }
+ ]
+ }
+});
+```
+
## Configuration
| Option | Type | Default | Description |
| :--- | :--- | :--- | :--- |
// @todo if (fill[0] === '#')
function decodeFill(el, index, count) {
var model = el._model || {};
- var fill = model.fill;
+ var fillOption = model.fill;
+ var fill = fillOption && typeof fillOption.target !== 'undefined' ? fillOption.target : fillOption;
var target;
if (fill === undefined) {
return point && !point.skip;
}
-function drawArea(ctx, curve0, curve1, len0, len1, stepped, tension) {
- const lineTo = stepped ? helpers.canvas._steppedLineTo : helpers.canvas._bezierCurveTo;
- let i, cx, cy, r, target;
+function fillPointsSets(ctx, curve0, curve1, len0, len1, area, pointSets) {
+ const fillAreaPointsSet = [];
+ const clipAboveAreaPointsSet = [];
+ const clipBelowAreaPointsSet = [];
+ const radialSet = [];
+ const jointPoint = {};
+ let i, cx, cy, r;
if (!len0 || !len1) {
return;
}
+ clipAboveAreaPointsSet.push({x: curve1[len1 - 1].x, y: area.top});
+ clipBelowAreaPointsSet.push({x: curve0[0].x, y: area.top});
+ clipBelowAreaPointsSet.push(curve0[0]);
// building first area curve (normal)
- ctx.moveTo(curve0[0].x, curve0[0].y);
+ fillAreaPointsSet.push(curve0[0]);
for (i = 1; i < len0; ++i) {
- target = curve0[i];
- if (!target.boundary && (tension || stepped)) {
- lineTo(ctx, curve0[i - 1], target, false, stepped);
- } else {
- ctx.lineTo(target.x, target.y);
- }
+ curve0[i].flip = false;
+ fillAreaPointsSet.push(curve0[i]);
+ clipBelowAreaPointsSet.push(curve0[i]);
}
if (curve1[0].angle !== undefined) {
+ pointSets.fill.push(fillAreaPointsSet);
cx = curve1[0].cx;
cy = curve1[0].cy;
r = Math.sqrt(Math.pow(curve1[0].x - cx, 2) + Math.pow(curve1[0].y - cy, 2));
for (i = len1 - 1; i > 0; --i) {
- ctx.arc(cx, cy, r, curve1[i].angle, curve1[i - 1].angle, true);
+ radialSet.push({cx: cx, cy: cy, radius: r, startAngle: curve1[i].angle, endAngle: curve1[i - 1].angle});
+ }
+ if (radialSet.length) {
+ pointSets.fill.push(radialSet);
}
return;
}
-
// joining the two area curves
- ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y);
+ for (var key in curve1[len1 - 1]) {
+ if (Object.prototype.hasOwnProperty.call(curve1[len1 - 1], key)) {
+ jointPoint[key] = curve1[len1 - 1][key];
+ }
+ }
+ jointPoint.joint = true;
+ fillAreaPointsSet.push(jointPoint);
// building opposite area curve (reverse)
for (i = len1 - 1; i > 0; --i) {
- target = curve1[i - 1];
- if (!target.boundary && (tension || stepped)) {
- lineTo(ctx, curve1[i], target, true, stepped);
+ curve1[i].flip = true;
+ clipAboveAreaPointsSet.push(curve1[i]);
+ curve1[i - 1].flip = true;
+ fillAreaPointsSet.push(curve1[i - 1]);
+ }
+ clipAboveAreaPointsSet.push(curve1[0]);
+ clipAboveAreaPointsSet.push({x: curve1[0].x, y: area.top});
+ clipBelowAreaPointsSet.push({x: curve0[len0 - 1].x, y: area.top});
+
+ pointSets.clipAbove.push(clipAboveAreaPointsSet);
+ pointSets.clipBelow.push(clipBelowAreaPointsSet);
+ pointSets.fill.push(fillAreaPointsSet);
+}
+
+function clipAndFill(ctx, clippingPointsSets, fillingPointsSets, color, stepped, tension) {
+ const lineTo = stepped ? helpers.canvas._steppedLineTo : helpers.canvas._bezierCurveTo;
+ let i, ilen, j, jlen, set, target;
+ if (clippingPointsSets) {
+ ctx.save();
+ ctx.beginPath();
+ for (i = 0, ilen = clippingPointsSets.length; i < ilen; i++) {
+ set = clippingPointsSets[i];
+ // Have edge lines straight
+ ctx.moveTo(set[0].x, set[0].y);
+ ctx.lineTo(set[1].x, set[1].y);
+ for (j = 2, jlen = set.length; j < jlen - 1; j++) {
+ target = set[j];
+ if (!target.boundary && (tension || stepped)) {
+ lineTo(ctx, set[j - 1], target, target.flip, stepped);
+ } else {
+ ctx.lineTo(target.x, target.y);
+ }
+ }
+ ctx.lineTo(set[j].x, set[j].y);
+ }
+ ctx.closePath();
+ ctx.clip();
+ ctx.beginPath();
+ }
+ for (i = 0, ilen = fillingPointsSets.length; i < ilen; i++) {
+ set = fillingPointsSets[i];
+ if (set[0].startAngle !== undefined) {
+ for (j = 0, jlen = set.length; j < jlen; j++) {
+ ctx.arc(set[j].cx, set[j].cy, set[j].radius, set[j].startAngle, set[j].endAngle, true);
+ }
} else {
- ctx.lineTo(target.x, target.y);
+ ctx.moveTo(set[0].x, set[0].y);
+ for (j = 1, jlen = set.length; j < jlen; j++) {
+ if (set[j].joint) {
+ ctx.lineTo(set[j].x, set[j].y);
+ } else {
+ target = set[j];
+ if (!target.boundary && (tension || stepped)) {
+ lineTo(ctx, set[j - 1], target, target.flip, stepped);
+ } else {
+ ctx.lineTo(target.x, target.y);
+ }
+ }
+ }
}
}
+ ctx.closePath();
+ ctx.fillStyle = color;
+ ctx.fill();
+ ctx.restore();
}
-function doFill(ctx, points, mapper, el) {
+function doFill(ctx, points, mapper, colors, el, area) {
const count = points.length;
const view = el._view;
const loop = el._loop;
let curve1 = [];
let len0 = 0;
let len1 = 0;
+ let pointSets = {clipBelow: [], clipAbove: [], fill: []};
let i, ilen, index, p0, p1, d0, d1, loopOffset;
+ ctx.save();
ctx.beginPath();
for (i = 0, ilen = count; i < ilen; ++i) {
len1 = curve1.push(p1);
} else if (len0 && len1) {
if (!span) {
- drawArea(ctx, curve0, curve1, len0, len1, stepped, tension);
+ fillPointsSets(ctx, curve0, curve1, len0, len1, area, pointSets);
len0 = len1 = 0;
curve0 = [];
curve1 = [];
}
}
- drawArea(ctx, curve0, curve1, len0, len1, stepped, tension);
+ fillPointsSets(ctx, curve0, curve1, len0, len1, area, pointSets);
- ctx.closePath();
- ctx.fillStyle = view.backgroundColor;
- ctx.fill();
+ if (colors.below !== colors.above) {
+ clipAndFill(ctx, pointSets.clipAbove, pointSets.fill, colors.above, stepped, tension);
+ clipAndFill(ctx, pointSets.clipBelow, pointSets.fill, colors.below, stepped, tension);
+ } else {
+ clipAndFill(ctx, false, pointSets.fill, colors.above, stepped, tension);
+ }
}
module.exports = {
beforeDatasetsDraw: function(chart) {
var metasets = chart._getSortedVisibleDatasetMetas();
var ctx = chart.ctx;
- var meta, i, el, points, mapper;
+ var meta, i, el, view, points, mapper, color, colors, fillOption;
for (i = metasets.length - 1; i >= 0; --i) {
meta = metasets[i].$filler;
}
el = meta.el;
+ view = el._view;
points = el._children || [];
mapper = meta.mapper;
+ fillOption = meta.el._model.fill;
+ color = view.backgroundColor || defaults.global.defaultColor;
+ colors = {above: color, below: color};
+ if (fillOption && typeof fillOption === 'object') {
+ colors.above = fillOption.above || color;
+ colors.below = fillOption.below || color;
+ }
if (mapper && points.length) {
helpers.canvas.clipArea(ctx, chart.chartArea);
- doFill(ctx, points, mapper, el);
+ doFill(ctx, points, mapper, colors, el, chart.chartArea);
helpers.canvas.unclipArea(ctx);
}
}
--- /dev/null
+{
+ "config": {
+ "type": "line",
+ "data": {
+ "labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"],
+ "datasets": [{
+ "backgroundColor": "rgba(0, 0, 192, 0.25)",
+ "data": [null, null, 2, 3, 4, -4, -2, 1, 0]
+ }, {
+ "backgroundColor": "rgba(0, 192, 0, 0.25)",
+ "data": [6, 2, null, 4, 5, null, null, 2, 1]
+ }, {
+ "backgroundColor": "rgba(192, 0, 0, 0.25)",
+ "data": [7, 3, 4, 5, 6, 1, 4, null, null]
+ }, {
+ "backgroundColor": "rgba(0, 64, 192, 0.25)",
+ "data": [8, 7, 6, -6, -4, -6, 4, 5, 8]
+ }]
+ },
+ "options": {
+ "responsive": false,
+ "spanGaps": true,
+ "legend": false,
+ "title": false,
+ "scales": {
+ "xAxes": [{
+ "display": false
+ }],
+ "yAxes": [{
+ "display": false
+ }]
+ },
+ "elements": {
+ "point": {
+ "radius": 0
+ },
+ "line": {
+ "borderColor": "transparent",
+ "fill": {
+ "target": "origin",
+ "below": "rgba(255, 0, 0, 0.25)"
+ },
+ "tension": 0
+ }
+ }
+ }
+ },
+ "options": {
+ "canvas": {
+ "height": 256,
+ "width": 512
+ }
+ }
+}
--- /dev/null
+{
+ "config": {
+ "type": "line",
+ "data": {
+ "labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"],
+ "datasets": [{
+ "backgroundColor": "rgba(0, 0, 192, 0.25)",
+ "data": [null, null, 2, 4, 2, 1, -1, 1, 2]
+ }, {
+ "backgroundColor": "rgba(0, 192, 0, 0.25)",
+ "data": [4, 2, null, 3, 2.5, null, -2, 1.5, 3]
+ }, {
+ "backgroundColor": "rgba(192, 0, 0, 0.25)",
+ "data": [3.5, 2, 1, 2.5, -2, 3, -1, null, null]
+ }, {
+ "backgroundColor": "rgba(128, 0, 128, 0.25)",
+ "data": [5, 6, 5, -2, -4, -3, 4, 2, 4.5]
+ }]
+ },
+ "options": {
+ "responsive": false,
+ "spanGaps": false,
+ "legend": false,
+ "title": false,
+ "scales": {
+ "xAxes": [{
+ "display": false
+ }],
+ "yAxes": [{
+ "display": false
+ }]
+ },
+ "elements": {
+ "point": {
+ "radius": 0
+ },
+ "line": {
+ "cubicInterpolationMode": "monotone",
+ "borderColor": "transparent",
+ "fill": {
+ "target": "origin",
+ "below": "transparent"
+ }
+ }
+ }
+ }
+ },
+ "options": {
+ "canvas": {
+ "height": 256,
+ "width": 512
+ }
+ }
+}
--- /dev/null
+{
+ "config": {
+ "type": "line",
+ "data": {
+ "labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"],
+ "datasets": [{
+ "backgroundColor": "rgba(255, 0, 0, 0.25)",
+ "data": [0, 1, 2, -1, 0, 2, 1, -1, -2],
+ "fill": {
+ "target": "+1",
+ "above": "rgba(255, 0, 0, 0.25)",
+ "below": "rgba(0, 0, 255, 0.25)"
+ }
+ }, {
+ "data": [0, 0, 0, 0, 0, 0, 0, 0, 0]
+ }]
+ },
+ "options": {
+ "responsive": false,
+ "spanGaps": true,
+ "legend": false,
+ "title": false,
+ "scales": {
+ "xAxes": [{
+ "display": false
+ }],
+ "yAxes": [{
+ "display": false
+ }]
+ },
+ "elements": {
+ "point": {
+ "radius": 0
+ },
+ "line": {
+ "borderColor": "transparent",
+ "tension": 0
+ }
+ }
+ }
+ },
+ "options": {
+ "canvas": {
+ "height": 256,
+ "width": 512
+ }
+ }
+}
--- /dev/null
+{
+ "config": {
+ "type": "line",
+ "data": {
+ "labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"],
+ "datasets": [{
+ "backgroundColor": "rgba(255, 0, 0, 0.25)",
+ "data": [null, null, 0, -1, 0, 1, 0, -1, 0],
+ "fill": {
+ "target": 1,
+ "above": "rgba(255, 0, 0, 0.25)",
+ "below": "rgba(122, 0, 0, 0.25)"
+ }
+ }, {
+ "backgroundColor": "rgba(0, 255, 0, 0.25)",
+ "data": [1, 0, null, 1, 0, null, -1, 0, 1],
+ "fill": {
+ "target": "+1",
+ "above": "rgba(0, 255, 0, 0.25)",
+ "below": "rgba(0, 255, 120, 0.25)"
+ }
+ }, {
+ "backgroundColor": "rgba(255, 0, 255, 0.25)",
+ "data": [2, 0, -2, 0, 2, 0, -2, 0, 2],
+ "fill": {
+ "target": "-2",
+ "above": "rgba(255, 0, 255, 0.25)",
+ "below": "rgba(255, 0, 120, 0.25)"
+ }
+ }, {
+ "backgroundColor": "rgba(255, 255, 0, 0.25)",
+ "data": [3, 1, -1, -3, -1, 1, 3, 1, -1],
+ "fill": {
+ "target": "-1",
+ "above": "rgba(255, 255, 0, 0.25)",
+ "below": "rgba(255, 120, 0, 0.25)"
+ }
+ }]
+ },
+ "options": {
+ "responsive": false,
+ "spanGaps": true,
+ "legend": false,
+ "title": false,
+ "scales": {
+ "xAxes": [{
+ "display": false
+ }],
+ "yAxes": [{
+ "display": false
+ }]
+ },
+ "elements": {
+ "point": {
+ "radius": 0
+ },
+ "line": {
+ "cubicInterpolationMode": "monotone",
+ "borderColor": "transparent",
+ "tension": 0
+ }
+ }
+ }
+ },
+ "options": {
+ "canvas": {
+ "height": 256,
+ "width": 512
+ }
+ }
+}
--- /dev/null
+{
+ "config": {
+ "type": "line",
+ "data": {
+ "labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"],
+ "datasets": [{
+ "backgroundColor": "rgba(255, 0, 0, 0.25)",
+ "data": [null, null, 0, -1, 0, 1, 0, -1, 0],
+ "fill": {
+ "target": 1,
+ "below": "transparent"
+ }
+ }, {
+ "backgroundColor": "rgba(0, 255, 0, 0.25)",
+ "data": [1, 0, null, 1, 0, null, -1, 0, 1],
+ "fill": {
+ "target": "+1",
+ "below": "transparent"
+ }
+ }, {
+ "backgroundColor": "rgba(255, 0, 255, 0.25)",
+ "data": [2, 0, -2, 0, 2, 0, -2, 0, 2],
+ "fill": {
+ "target": "-2",
+ "below": "transparent"
+ }
+ }, {
+ "backgroundColor": "rgba(255, 255, 0, 0.25)",
+ "data": [3, 1, -1, -3, -1, 1, 3, 1, -1],
+ "fill": {
+ "target": "-1",
+ "below": "transparent"
+ }
+ }]
+ },
+ "options": {
+ "responsive": false,
+ "spanGaps": true,
+ "legend": false,
+ "title": false,
+ "scales": {
+ "xAxes": [{
+ "display": false
+ }],
+ "yAxes": [{
+ "display": false
+ }]
+ },
+ "elements": {
+ "point": {
+ "radius": 0
+ },
+ "line": {
+ "cubicInterpolationMode": "monotone",
+ "borderColor": "transparent"
+ }
+ }
+ }
+ },
+ "options": {
+ "canvas": {
+ "height": 256,
+ "width": 512
+ }
+ }
+}
--- /dev/null
+{
+ "config": {
+ "type": "line",
+ "data": {
+ "labels": ["0", "1", "2", "3", "4", "5", "6", "7", "8"],
+ "datasets": [{
+ "backgroundColor": "rgba(255, 0, 0, 0.25)",
+ "data": [null, null, 0, -1, 0, 1, 0, -1, 0],
+ "fill": {
+ "target": 1,
+ "above": "transparent"
+ }
+ }, {
+ "backgroundColor": "rgba(0, 255, 0, 0.25)",
+ "data": [1, 0, null, 1, 0, null, -1, 0, 1],
+ "fill": {
+ "target": "+1",
+ "above": "transparent"
+ }
+ }, {
+ "backgroundColor": "rgba(255, 0, 255, 0.25)",
+ "data": [2, 0, -2, 0, 2, 0, -2, 0, 2],
+ "fill": {
+ "target": "-2",
+ "above": "transparent"
+ }
+ }, {
+ "backgroundColor": "rgba(255, 255, 0, 0.25)",
+ "data": [3, 1, -1, -3, -1, 1, 3, 1, -1],
+ "fill": {
+ "target": "-1",
+ "above": "transparent"
+ }
+ }]
+ },
+ "options": {
+ "responsive": false,
+ "spanGaps": true,
+ "legend": false,
+ "title": false,
+ "scales": {
+ "xAxes": [{
+ "display": false
+ }],
+ "yAxes": [{
+ "display": false
+ }]
+ },
+ "elements": {
+ "point": {
+ "radius": 0
+ },
+ "line": {
+ "cubicInterpolationMode": "monotone",
+ "borderColor": "transparent"
+ }
+ }
+ }
+ },
+ "options": {
+ "canvas": {
+ "height": 256,
+ "width": 512
+ }
+ }
+}
{fill: ''},
{fill: null},
{fill: []},
- {fill: {}},
]
}
});
false, // empty string
false, // null
false, // array
- false, // object
]);
});
});