Skip to content

Add support for floating bar chart ([start, end]) #6056

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 21, 2019
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
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 @@ -162,6 +162,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 @@ -285,12 +289,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 @@ -301,21 +306,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 @@ -366,7 +373,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 @@ -523,6 +523,7 @@ module.exports = 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 @@ -538,6 +539,45 @@ module.exports = 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