| Relative dataset index <sup>1</sup> | `string` | `'-1'`, `'-2'`, `'+1'`, ... |
| Boundary <sup>2</sup> | `string` | `'start'`, `'end'`, `'origin'` |
| Disabled <sup>3</sup> | `boolean` | `false` |
+| Stacked value below <sup>4</sup> | `string` | `'stack'` |
> <sup>1</sup> dataset filling modes have been introduced in version 2.6.0<br/>
-> <sup>2</sup> prior version 2.6.0, boundary values was `'zero'`, `'top'`, `'bottom'` (deprecated)<br/>
+> <sup>2</sup> prior version 2.6.0, boundary values was `'zero'`, `'top'`, `'bottom'` (not supported anymore)<br/>
> <sup>3</sup> for backward compatibility, `fill: true` (default) is equivalent to `fill: 'origin'`<br/>
+> <sup>4</sup> stack mode has been introduced in version 3.0.0<br/>
**Example**
+
```javascript
new Chart(ctx, {
data: {
| `below` | `Color` | Same as the above. |
**Example**
+
```javascript
new Chart(ctx, {
data: {
```
## Configuration
+
| Option | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| [`plugins.filler.propagate`](#propagate) | `boolean` | `true` | Fill propagation when target is hidden.
### propagate
+
`propagate` takes a `boolean` value (default: `true`).
If `true`, the fill area will be recursively extended to the visible target defined by the `fill` value of hidden dataset targets:
**Example**
+
```javascript
new Chart(ctx, {
data: {
```
`propagate: true`:
-- if dataset 2 is hidden, dataset 4 will fill to dataset 1
-- if dataset 2 and 1 are hidden, dataset 4 will fill to `'origin'`
+-if dataset 2 is hidden, dataset 4 will fill to dataset 1
+-if dataset 2 and 1 are hidden, dataset 4 will fill to `'origin'`
`propagate: false`:
-- if dataset 2 and/or 4 are hidden, dataset 4 will not be filled
+-if dataset 2 and/or 4 are hidden, dataset 4 will not be filled
import {isArray, isFinite, valueOrDefault} from '../helpers/helpers.core';
import {_normalizeAngle} from '../helpers/helpers.math';
+/**
+ * @typedef { import('../core/core.controller').default } Chart
+ * @typedef { import('../core/core.scale').default } Scale
+ * @typedef { import("../elements/element.point").default } Point
+ */
+
+/**
+ * @param {Chart} chart
+ * @param {number} index
+ */
function getLineByIndex(chart, index) {
const meta = chart.getDatasetMeta(index);
const visible = meta && chart.isDatasetVisible(index);
return visible ? meta.dataset : null;
}
+/**
+ * @param {Line} line
+ */
function parseFillOption(line) {
const options = line.options;
const fillOption = options.fill;
return fill;
}
-// @todo if (fill[0] === '#')
+/**
+ * @param {Line} line
+ * @param {number} index
+ * @param {number} count
+ */
function decodeFill(line, index, count) {
const fill = parseFillOption(line);
let target = parseFloat(fill);
return target;
}
- return ['origin', 'start', 'end'].indexOf(fill) >= 0 ? fill : false;
+ return ['origin', 'start', 'end', 'stack'].indexOf(fill) >= 0 && fill;
}
function computeLinearBoundary(source) {
return points;
}
+/**
+ * @param {{ chart: Chart; scale: Scale; index: number; line: Line; }} source
+ * @return {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: {}}));
+
+ for (let i = 0; i < segments.length; i++) {
+ const segment = segments[i];
+ for (let j = segment.start; j <= segment.end; j++) {
+ addPointsBelow(points, sourcePoints[j], linesBelow);
+ }
+ }
+ return new Line({points, options: {}, _refPoints: true});
+}
+
+/**
+ * @param {Chart} chart
+ * @param {number} index
+ * @return {Line[]}
+ */
+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') {
+ below.unshift(meta.dataset);
+ }
+ }
+ return below;
+}
+
+/**
+ * @param {Point[]} points
+ * @param {Point} sourcePoint
+ * @param {Line[]} linesBelow
+ */
+function addPointsBelow(points, sourcePoint, linesBelow) {
+ const postponed = [];
+ for (let j = 0; j < linesBelow.length; j++) {
+ const line = linesBelow[j];
+ const {first, last, point} = findPoint(line, sourcePoint, 'x');
+
+ if (!point || (first && last)) {
+ continue;
+ }
+ if (first) {
+ // First point of an segment -> need to add another point before this,
+ // from next line below.
+ postponed.unshift(point);
+ } else {
+ points.push(point);
+ if (!last) {
+ // In the middle of an segment, no need to add more points.
+ break;
+ }
+ }
+ }
+ points.push(...postponed);
+}
+
+/**
+ * @param {Line} line
+ * @param {Point} sourcePoint
+ * @param {string} property
+ * @returns {{point?: Point, first?: boolean, last?: boolean}}
+ */
+function findPoint(line, sourcePoint, property) {
+ const segments = line.segments;
+ const linePoints = line.points;
+ 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
+ };
+ }
+ }
+ }
+ return {};
+}
+
function getTarget(source) {
const {chart, fill, line} = source;
return getLineByIndex(chart, fill);
}
+ if (fill === 'stack') {
+ return buildStackLine(source);
+ }
+
const boundary = computeBoundary(source);
- let points = [];
- let _loop = false;
- let _refPoints = false;
if (boundary instanceof simpleArc) {
return boundary;
}
+ return createBoundaryLine(boundary, line);
+}
+
+/**
+ * @param {Point[] | { x: number; y: number; }} boundary
+ * @param {Line} line
+ * @return {Line?}
+ */
+function createBoundaryLine(boundary, line) {
+ let points = [];
+ let _loop = false;
+ let _refPoints = false;
+
if (isArray(boundary)) {
_loop = true;
// @ts-ignore
points = pointsFromSegments(boundary, line);
_refPoints = true;
}
+
return points.length ? new Line({
points,
options: {tension: 0},
if (line && line.options && line instanceof Line) {
source = {
visible: chart.isDatasetVisible(i),
+ index: i,
fill: decodeFill(line, i, count),
chart,
scale: meta.vScale,
--- /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, null, 0, 1],
+ "fill": "stack"
+ }, {
+ "backgroundColor": "rgba(0, 255, 0, 0.25)",
+ "data": [1, 1, null, 1, 0, null, 1, 1, 0],
+ "fill": "stack"
+ }, {
+ "backgroundColor": "rgba(0, 0, 255, 0.25)",
+ "data": [0, 2, null, 2, 0, 2, 0],
+ "fill": "stack"
+ }, {
+ "backgroundColor": "rgba(255, 0, 255, 0.25)",
+ "data": [2, 0, null, 0, 2, 0, 2, 0, 2],
+ "fill": "stack"
+ }, {
+ "backgroundColor": "rgba(0, 0, 0, 0.25)",
+ "data": [null, null, null, 2, null, 2, 2],
+ "fill": "stack"
+ }, {
+ "backgroundColor": "rgba(255, 255, 0, 0.25)",
+ "data": [3, 1, 1, 3, 1, 1, 3, 1, 1],
+ "fill": "stack"
+ }]
+ },
+ "options": {
+ "responsive": false,
+ "spanGaps": false,
+ "legend": false,
+ "title": false,
+ "scales": {
+ "x": {
+ "display": false
+ },
+ "y": {
+ "display": false,
+ "stacked": true,
+ "min": 0
+ }
+ },
+ "elements": {
+ "point": {
+ "radius": 0
+ },
+ "line": {
+ "borderColor": "transparent",
+ "tension": 0
+ }
+ }
+ }
+ },
+ "options": {
+ "canvas": {
+ "height": 256,
+ "width": 512
+ }
+ }
+}