Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/docs/axes/_common.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Namespace: `options.scales[scaleId]`
| `min` | `number` | | User defined minimum number for the scale, overrides minimum value from data. [more...](./index.mdx#axis-range-settings)
| `max` | `number` | | User defined maximum number for the scale, overrides maximum value from data. [more...](./index.mdx#axis-range-settings)
| `reverse` | `boolean` | `false` | Reverse the scale.
| `stacked` | `boolean`\|`string` | `false` | Should the data be stacked. [more...](./index.mdx#stacking)
| `suggestedMax` | `number` | | Adjustment used when calculating the maximum data value. [more...](./index.mdx#axis-range-settings)
| `suggestedMin` | `number` | | Adjustment used when calculating the minimum data value. [more...](./index.mdx#axis-range-settings)
| `ticks` | `object` | | Tick configuration. [more...](#tick-configuration)
Expand Down
7 changes: 6 additions & 1 deletion docs/docs/axes/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ let chart = new Chart(ctx, {

In contrast to the `suggested*` settings, the `min` and `max` settings set explicit ends to the axes. When these are set, some data points may not be visible.

### Callbacks
## Stacking

By default data is not stacked. If the `stacked` option of the value scale (y-axis on horizontal chart) is `true`, positive and negative values are stacked separately. Additionally a `stack` option can be defined per dataset to further divide into stack groups [more...](../general/data-structures/#dataset-configuration).
For some charts, you might want to stack positive and negative values together. That can be achieved by specifying `stacked: 'single'`.

## Callbacks

There are a number of config callbacks that can be used to change parameters in the scale at different points in the update process. The options are supplied at the top level of the axis options.

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/charts/area.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Both [line](./line.mdx) and [radar](./radar.mdx) charts support a `fill` option
| Relative dataset index | `string` | `'-1'`, `'-2'`, `'+1'`, ... |
| Boundary | `string` | `'start'`, `'end'`, `'origin'` |
| Disabled <sup>1</sup> | `boolean` | `false` |
| Stacked value below <sup>4</sup> | `string` | `'stack'` |
| Stacked value below | `string` | `'stack'` |
| Axis value | `object` | `{ value: number; }` |

> <sup>1</sup> for backward compatibility, `fill: true` is equivalent to `fill: 'origin'`<br/>
Expand Down
172 changes: 92 additions & 80 deletions samples/charts/area/line-stacked.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
<canvas id="canvas"></canvas>
</div>
<br>
<div style="width:75%;">
<canvas id="single"></canvas>
</div>
<br>
<br>
<button id="randomizeData">Randomize Data</button>
<button id="addDataset">Add Dataset</button>
Expand All @@ -27,81 +31,84 @@
<button id="removeData">Remove Data</button>
<script>
var MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
var config = {
var data = {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label: 'My First dataset',
borderColor: window.chartColors.red,
backgroundColor: window.chartColors.red,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
],
fill: true
}, {
label: 'My Second dataset',
borderColor: window.chartColors.blue,
backgroundColor: window.chartColors.blue,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
],
fill: true
}, {
label: 'My Third dataset',
borderColor: window.chartColors.green,
backgroundColor: window.chartColors.green,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
],
fill: true
}, {
label: 'My Fourth dataset',
borderColor: window.chartColors.yellow,
backgroundColor: window.chartColors.yellow,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
],
fill: true
}]
};
var config = (stacked) => ({
type: 'line',
data: {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label: 'My First dataset',
borderColor: window.chartColors.red,
backgroundColor: window.chartColors.red,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
],
fill: true
}, {
label: 'My Second dataset',
borderColor: window.chartColors.blue,
backgroundColor: window.chartColors.blue,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
],
fill: true
}, {
label: 'My Third dataset',
borderColor: window.chartColors.green,
backgroundColor: window.chartColors.green,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
],
fill: true
}, {
label: 'My Fourth dataset',
borderColor: window.chartColors.yellow,
backgroundColor: window.chartColors.yellow,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
],
fill: true
}]
},
data,
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Chart.js Line Chart - Stacked Area'
text: stacked === true ? 'Chart.js Line Chart - Stacked Area' : 'Same data, stacked=\'single\'',
},
tooltip: {
mode: 'index',
}
},
hover: {
mode: 'index'
interaction: {
mode: 'nearest',
axis: 'x',
intersect: false
},
scales: {
x: {
Expand All @@ -111,77 +118,82 @@
}
},
y: {
stacked: true,
stacked,
title: {
display: true,
text: 'Value'
}
}
}
}
};
});

window.onload = function() {
var ctx = document.getElementById('canvas').getContext('2d');
window.myLine = new Chart(ctx, config);
window.myLine = new Chart('canvas', config(true));
window.myLine2 = new Chart('single', config('single'));
};

document.getElementById('randomizeData').addEventListener('click', function() {
config.data.datasets.forEach(function(dataset) {
data.datasets.forEach(function(dataset) {
dataset.data = dataset.data.map(function() {
return randomScalingFactor();
});

});

window.myLine.update();
window.myLine2.update();
});

var colorNames = Object.keys(window.chartColors);
document.getElementById('addDataset').addEventListener('click', function() {
var colorName = colorNames[config.data.datasets.length % colorNames.length];
var colorName = colorNames[data.datasets.length % colorNames.length];
var newColor = window.chartColors[colorName];
var newDataset = {
label: 'Dataset ' + config.data.datasets.length,
label: 'Dataset ' + data.datasets.length,
borderColor: newColor,
backgroundColor: newColor,
data: [],
};

for (var index = 0; index < config.data.labels.length; ++index) {
for (var index = 0; index < data.labels.length; ++index) {
newDataset.data.push(randomScalingFactor());
}

config.data.datasets.push(newDataset);
data.datasets.push(newDataset);
window.myLine.update();
window.myLine2.update();
});

document.getElementById('addData').addEventListener('click', function() {
if (config.data.datasets.length > 0) {
var month = MONTHS[config.data.labels.length % MONTHS.length];
config.data.labels.push(month);
if (data.datasets.length > 0) {
var month = MONTHS[data.labels.length % MONTHS.length];
data.labels.push(month);

config.data.datasets.forEach(function(dataset) {
data.datasets.forEach(function(dataset) {
dataset.data.push(randomScalingFactor());
});

window.myLine.update();
window.myLine2.update();
}
});

document.getElementById('removeDataset').addEventListener('click', function() {
config.data.datasets.splice(0, 1);
data.datasets.splice(0, 1);
window.myLine.update();
window.myLine2.update();
});

document.getElementById('removeData').addEventListener('click', function() {
config.data.labels.splice(-1, 1); // remove the label first
data.labels.splice(-1, 1); // remove the label first

config.data.datasets.forEach(function(dataset) {
data.datasets.forEach(function(dataset) {
dataset.data.pop();
});

window.myLine.update();
window.myLine2.update();
});
</script>
</body>
Expand Down
5 changes: 2 additions & 3 deletions src/controllers/controller.bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -401,15 +401,14 @@ export default class BarController extends DatasetController {
*/
_calculateBarValuePixels(index) {
const me = this;
const meta = me._cachedMeta;
const vScale = meta.vScale;
const {vScale, _stacked} = me._cachedMeta;
const {base: baseValue, minBarLength} = me.options;
const parsed = me.getParsed(index);
const custom = parsed._custom;
const floating = isFloatBar(custom);
let value = parsed[vScale.axis];
let start = 0;
let length = meta._stacked ? me.applyStack(vScale, parsed) : value;
let length = _stacked ? me.applyStack(vScale, parsed, _stacked) : value;
let head, size;

if (length !== value) {
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/controller.line.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export default class LineController extends DatasetController {
const parsed = me.getParsed(i);
const properties = directUpdate ? point : {};
const x = properties.x = xScale.getPixelForValue(parsed.x, i);
const y = properties.y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(_stacked ? me.applyStack(yScale, parsed) : parsed.y, i);
const y = properties.y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(_stacked ? me.applyStack(yScale, parsed, _stacked) : parsed.y, i);
properties.skip = isNaN(x) || isNaN(y);
properties.stop = i > 0 && (parsed.x - prevParsed.x) > maxGapLength;

Expand Down
13 changes: 7 additions & 6 deletions src/core/core.datasetController.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ function getSortedDatasetIndices(chart, filterVisible) {
return keys;
}

function applyStack(stack, value, dsIndex, allOther) {
function applyStack(stack, value, dsIndex, options) {
const keys = stack.keys;
const singleMode = options.mode === 'single';
let i, ilen, datasetIndex, otherValue;

if (value === null) {
Expand All @@ -77,13 +78,13 @@ function applyStack(stack, value, dsIndex, allOther) {
for (i = 0, ilen = keys.length; i < ilen; ++i) {
datasetIndex = +keys[i];
if (datasetIndex === dsIndex) {
if (allOther) {
if (options.all) {
continue;
}
break;
}
otherValue = stack.values[datasetIndex];
if (isFinite(otherValue) && (value === 0 || sign(value) === sign(otherValue))) {
if (isFinite(otherValue) && (singleMode || (value === 0 || sign(value) === sign(otherValue)))) {
value += otherValue;
}
}
Expand Down Expand Up @@ -517,15 +518,15 @@ export default class DatasetController {
/**
* @protected
*/
applyStack(scale, parsed) {
applyStack(scale, parsed, mode) {
const chart = this.chart;
const meta = this._cachedMeta;
const value = parsed[scale.axis];
const stack = {
keys: getSortedDatasetIndices(chart, true),
values: parsed._stacks[scale.axis]
};
return applyStack(stack, value, meta.index);
return applyStack(stack, value, meta.index, {mode});
}

/**
Expand All @@ -541,7 +542,7 @@ export default class DatasetController {
// in addition to the stacked value
range.min = Math.min(range.min, value);
range.max = Math.max(range.max, value);
value = applyStack(stack, parsedValue, this._cachedMeta.index, true);
value = applyStack(stack, parsedValue, this._cachedMeta.index, {all: true});
}
range.min = Math.min(range.min, value);
range.max = Math.max(range.max, value);
Expand Down
Loading