Skip to content

Commit

Permalink
Add support for floating bar chart ([start, end]) (chartjs#6056)
Browse files Browse the repository at this point in the history
  • Loading branch information
gwyneblaidd authored and simonbrunel committed May 21, 2019
1 parent d1b1141 commit 0470ce1
Show file tree
Hide file tree
Showing 13 changed files with 259 additions and 40 deletions.
5 changes: 5 additions & 0 deletions docs/charts/bar.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,11 @@ You can also specify the dataset as x/y coordinates when using the [time scale](
data: [{x:'2016-12-25', y:20}, {x:'2016-12-26', y:10}]
```

You can also specify the dataset for a bar chart as arrays of two numbers. This will force rendering of bars with gaps between them (floating-bars). First and second numbers in array will correspond the start and the end point of a bar respectively.
```javascript
data: [[5,6], [-3,-6]]
```

## Stacked Bar Chart

Bar charts can be configured into stacked bar charts by changing the settings on the X and Y axes to enable stacking. Stacked bar charts can be used to show how one data series is made up of a number of smaller pieces.
Expand Down
24 changes: 16 additions & 8 deletions src/controllers/controller.bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ module.exports = DatasetController.extend({
label: me.chart.data.labels[index]
};

if (helpers.isArray(dataset.data[index])) {
rectangle._model.borderSkipped = null;
}

me._updateElementGeometry(rectangle, index, reset);

rectangle.pivot();
Expand Down Expand Up @@ -293,12 +297,13 @@ module.exports = DatasetController.extend({
var scale = me._getValueScale();
var isHorizontal = scale.isHorizontal();
var datasets = chart.data.datasets;
var value = +scale.getRightValue(datasets[datasetIndex].data[index]);
var value = scale._parseValue(datasets[datasetIndex].data[index]);
var minBarLength = scale.options.minBarLength;
var stacked = scale.options.stacked;
var stack = meta.stack;
var start = 0;
var i, imeta, ivalue, base, head, size;
var start = value.start === undefined ? 0 : value.max >= 0 && value.min >= 0 ? value.min : value.max;
var length = value.start === undefined ? value.end : value.max >= 0 && value.min >= 0 ? value.max - value.min : value.min - value.max;
var i, imeta, ivalue, base, head, size, stackLength;

if (stacked || (stacked === undefined && stack !== undefined)) {
for (i = 0; i < datasetIndex; ++i) {
Expand All @@ -309,21 +314,23 @@ module.exports = DatasetController.extend({
imeta.controller._getValueScaleId() === scale.id &&
chart.isDatasetVisible(i)) {

ivalue = +scale.getRightValue(datasets[i].data[index]);
if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) {
stackLength = scale._parseValue(datasets[i].data[index]);
ivalue = stackLength.start === undefined ? stackLength.end : stackLength.min >= 0 && stackLength.max >= 0 ? stackLength.max : stackLength.min;

if ((value.min < 0 && ivalue < 0) || (value.max >= 0 && ivalue > 0)) {
start += ivalue;
}
}
}
}

base = scale.getPixelForValue(start);
head = scale.getPixelForValue(start + value);
head = scale.getPixelForValue(start + length);
size = head - base;

if (minBarLength !== undefined && Math.abs(size) < minBarLength) {
size = minBarLength;
if (value >= 0 && !isHorizontal || value < 0 && isHorizontal) {
if (length >= 0 && !isHorizontal || length < 0 && isHorizontal) {
head = base - minBarLength;
} else {
head = base + minBarLength;
Expand Down Expand Up @@ -374,7 +381,8 @@ module.exports = DatasetController.extend({
helpers.canvas.clipArea(chart.ctx, chart.chartArea);

for (; i < ilen; ++i) {
if (!isNaN(scale.getRightValue(dataset.data[i]))) {
var val = scale._parseValue(dataset.data[i]);
if (!isNaN(val.min) && !isNaN(val.max)) {
rects[i].draw();
}
}
Expand Down
40 changes: 40 additions & 0 deletions src/core/core.scale.js
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,7 @@ var Scale = Element.extend({
if ((typeof rawValue === 'number' || rawValue instanceof Number) && !isFinite(rawValue)) {
return NaN;
}

// If it is in fact an object, dive in one more level
if (rawValue) {
if (this.isHorizontal()) {
Expand All @@ -601,6 +602,45 @@ var Scale = Element.extend({
return rawValue;
},

/**
* @private
*/
_parseValue: function(value) {
var start, end, min, max;

if (helpers.isArray(value)) {
start = +this.getRightValue(value[0]);
end = +this.getRightValue(value[1]);
min = Math.min(start, end);
max = Math.max(start, end);
} else {
value = +this.getRightValue(value);
start = undefined;
end = value;
min = value;
max = value;
}

return {
min: min,
max: max,
start: start,
end: end
};
},

/**
* @private
*/
_getScaleLabel: function(rawValue) {
var v = this._parseValue(rawValue);
if (v.start !== undefined) {
return '[' + v.start + ', ' + v.end + ']';
}

return +this.getRightValue(rawValue);
},

/**
* Used to get the value to display in the tooltip for the data at the given index
* @param index
Expand Down
34 changes: 18 additions & 16 deletions src/scales/scale.linear.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,20 +70,25 @@ module.exports = LinearScaleBase.extend({

if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
helpers.each(dataset.data, function(rawValue, index) {
var value = +me.getRightValue(rawValue);
if (isNaN(value) || meta.data[index].hidden) {
var value = me._parseValue(rawValue);

if (isNaN(value.min) || isNaN(value.max) || meta.data[index].hidden) {
return;
}

positiveValues[index] = positiveValues[index] || 0;
negativeValues[index] = negativeValues[index] || 0;

if (value.min === 0 && !opts.ticks.beginAtZero) {
value.min = value.max;
}

if (opts.relativePoints) {
positiveValues[index] = 100;
} else if (value < 0) {
negativeValues[index] += value;
} else if (value.min < 0 || value.max < 0) {
negativeValues[index] += value.min;
} else {
positiveValues[index] += value;
positiveValues[index] += value.max;
}
});
}
Expand All @@ -102,21 +107,18 @@ module.exports = LinearScaleBase.extend({
var meta = chart.getDatasetMeta(datasetIndex);
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
helpers.each(dataset.data, function(rawValue, index) {
var value = +me.getRightValue(rawValue);
if (isNaN(value) || meta.data[index].hidden) {
var value = me._parseValue(rawValue);

if (isNaN(value.min) || isNaN(value.max) || meta.data[index].hidden) {
return;
}

if (me.min === null) {
me.min = value;
} else if (value < me.min) {
me.min = value;
if (me.min === null || value.min < me.min) {
me.min = value.min;
}

if (me.max === null) {
me.max = value;
} else if (value > me.max) {
me.max = value;
if (me.max === null || me.max < value.max) {
me.max = value.max;
}
});
}
Expand Down Expand Up @@ -151,7 +153,7 @@ module.exports = LinearScaleBase.extend({
},

getLabelForIndex: function(index, datasetIndex) {
return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]);
},

// Utils
Expand Down
28 changes: 12 additions & 16 deletions src/scales/scale.logarithmic.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,13 @@ module.exports = Scale.extend({

helpers.each(dataset.data, function(rawValue, index) {
var values = valuesPerStack[key];
var value = +me.getRightValue(rawValue);
var value = me._parseValue(rawValue);
// invalid, hidden and negative values are ignored
if (isNaN(value) || meta.data[index].hidden || value < 0) {
if (isNaN(value.min) || isNaN(value.max) || meta.data[index].hidden || value.min < 0 || value.max < 0) {
return;
}
values[index] = values[index] || 0;
values[index] += value;
values[index] += value.max;
});
}
});
Expand All @@ -143,26 +143,22 @@ module.exports = Scale.extend({
var meta = chart.getDatasetMeta(datasetIndex);
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
helpers.each(dataset.data, function(rawValue, index) {
var value = +me.getRightValue(rawValue);
var value = me._parseValue(rawValue);
// invalid, hidden and negative values are ignored
if (isNaN(value) || meta.data[index].hidden || value < 0) {
if (isNaN(value.min) || isNaN(value.max) || meta.data[index].hidden || value.min < 0 || value.max < 0) {
return;
}

if (me.min === null) {
me.min = value;
} else if (value < me.min) {
me.min = value;
if (me.min === null || value.min < me.min) {
me.min = value.min;
}

if (me.max === null) {
me.max = value;
} else if (value > me.max) {
me.max = value;
if (me.max === null || me.max < value.max) {
me.max = value.max;
}

if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) {
me.minNotZero = value;
if (value.min !== 0 && (me.minNotZero === null || value.min < me.minNotZero)) {
me.minNotZero = value.min;
}
});
}
Expand Down Expand Up @@ -247,7 +243,7 @@ module.exports = Scale.extend({

// Get the correct tooltip label
getLabelForIndex: function(index, datasetIndex) {
return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]);
},

getPixelForTick: function(index) {
Expand Down
41 changes: 41 additions & 0 deletions test/fixtures/controller.bar/floatBar/float-bar-horizontal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"config": {
"type": "horizontalBar",
"data": {
"labels": ["2030", "2034", "2038", "2042"],
"datasets": [{
"backgroundColor": "#FF6384",
"data": [11, [6,2], [-4,-7], -2]
}, {
"backgroundColor": "#36A2EB",
"data": [[1,2], [3,4], [-2,-3], [1,4]]
}, {
"backgroundColor": "#FFCE56",
"data": [[0,1], [1,2], [-2,-1], [1,-7]]
}]
},
"options": {
"title": false,
"legend": false,
"scales": {
"xAxes": [{
"display": false,
"ticks": {
"min": -8,
"max": 12
}
}],
"yAxes": [{
"display": false
}]
}
}
},
"debug": false,
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"config": {
"type": "horizontalBar",
"data": {
"labels": ["2030", "2034", "2038", "2042"],
"datasets": [{
"backgroundColor": "#FF6384",
"data": [11, [6,2], [-4,-7], -2]
}, {
"backgroundColor": "#36A2EB",
"data": [[1,2], [3,4], [-2,-3], [1,4]]
}, {
"backgroundColor": "#FFCE56",
"data": [[0,1], [1,2], [-2,-1], [1,-7]]
}]
},
"options": {
"title": false,
"legend": false,
"scales": {
"xAxes": [{
"display": false,
"stacked": true
}],
"yAxes": [{
"display": false,
"stacked": true,
"ticks": {
"min": -8,
"max": 12
}
}]
}
}
},
"debug": false,
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 43 additions & 0 deletions test/fixtures/controller.bar/floatBar/float-bar-stacked.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"config": {
"type": "bar",
"data": {
"labels": ["2030", "2034", "2038", "2042"],
"datasets": [{
"backgroundColor": "#FF6384",
"data": [11, [6,2], [-4,-7], -2]
}, {
"backgroundColor": "#36A2EB",
"data": [[1,2], [3,4], [-2,-3], [1,4]]
}, {
"backgroundColor": "#FFCE56",
"data": [[0,1], [1,2], [-2,-1], [1,-7]]
}]
},
"options": {
"title": false,
"legend": false,
"scales": {
"xAxes": [{
"display": false,
"stacked": true,
"ticks": {
"min": -8,
"max": 12
}
}],
"yAxes": [{
"display": false,
"stacked": true
}]
}
}
},
"debug": false,
"options": {
"canvas": {
"height": 256,
"width": 512
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 0470ce1

Please sign in to comment.