diff --git a/docs/charts/bar.md b/docs/charts/bar.md index 02f080614a1..328b1df0e19 100644 --- a/docs/charts/bar.md +++ b/docs/charts/bar.md @@ -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. diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index 0736cf6e1e0..aa8b06a2cbc 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -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(); @@ -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) { @@ -309,8 +314,10 @@ 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; } } @@ -318,12 +325,12 @@ module.exports = DatasetController.extend({ } 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; @@ -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(); } } diff --git a/src/core/core.scale.js b/src/core/core.scale.js index cf1fb281e88..7fccd4d4a15 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -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()) { @@ -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 diff --git a/src/scales/scale.linear.js b/src/scales/scale.linear.js index a2199b66e78..c9ce8709ded 100644 --- a/src/scales/scale.linear.js +++ b/src/scales/scale.linear.js @@ -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; } }); } @@ -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; } }); } @@ -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 diff --git a/src/scales/scale.logarithmic.js b/src/scales/scale.logarithmic.js index fd67f0b19a4..3e9b2849f0b 100644 --- a/src/scales/scale.logarithmic.js +++ b/src/scales/scale.logarithmic.js @@ -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; }); } }); @@ -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; } }); } @@ -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) { diff --git a/test/fixtures/controller.bar/floatBar/float-bar-horizontal.json b/test/fixtures/controller.bar/floatBar/float-bar-horizontal.json new file mode 100644 index 00000000000..c54dea9ce96 --- /dev/null +++ b/test/fixtures/controller.bar/floatBar/float-bar-horizontal.json @@ -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 + } + } +} diff --git a/test/fixtures/controller.bar/floatBar/float-bar-horizontal.png b/test/fixtures/controller.bar/floatBar/float-bar-horizontal.png new file mode 100644 index 00000000000..3293dc8877f Binary files /dev/null and b/test/fixtures/controller.bar/floatBar/float-bar-horizontal.png differ diff --git a/test/fixtures/controller.bar/floatBar/float-bar-stacked-horizontal.json b/test/fixtures/controller.bar/floatBar/float-bar-stacked-horizontal.json new file mode 100644 index 00000000000..8322ef472e4 --- /dev/null +++ b/test/fixtures/controller.bar/floatBar/float-bar-stacked-horizontal.json @@ -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 + } + } +} diff --git a/test/fixtures/controller.bar/floatBar/float-bar-stacked-horizontal.png b/test/fixtures/controller.bar/floatBar/float-bar-stacked-horizontal.png new file mode 100644 index 00000000000..5df57310985 Binary files /dev/null and b/test/fixtures/controller.bar/floatBar/float-bar-stacked-horizontal.png differ diff --git a/test/fixtures/controller.bar/floatBar/float-bar-stacked.json b/test/fixtures/controller.bar/floatBar/float-bar-stacked.json new file mode 100644 index 00000000000..d91b61f1b58 --- /dev/null +++ b/test/fixtures/controller.bar/floatBar/float-bar-stacked.json @@ -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 + } + } +} diff --git a/test/fixtures/controller.bar/floatBar/float-bar-stacked.png b/test/fixtures/controller.bar/floatBar/float-bar-stacked.png new file mode 100644 index 00000000000..c9c02ea2f7b Binary files /dev/null and b/test/fixtures/controller.bar/floatBar/float-bar-stacked.png differ diff --git a/test/fixtures/controller.bar/floatBar/float-bar.json b/test/fixtures/controller.bar/floatBar/float-bar.json new file mode 100644 index 00000000000..caeaa239668 --- /dev/null +++ b/test/fixtures/controller.bar/floatBar/float-bar.json @@ -0,0 +1,41 @@ +{ + "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, + "ticks": { + "min": -8, + "max": 12 + } + }], + "yAxes": [{ + "display": false + }] + } + } + }, + "debug": false, + "options": { + "canvas": { + "height": 256, + "width": 512 + } + } +} diff --git a/test/fixtures/controller.bar/floatBar/float-bar.png b/test/fixtures/controller.bar/floatBar/float-bar.png new file mode 100644 index 00000000000..eaff55a63ed Binary files /dev/null and b/test/fixtures/controller.bar/floatBar/float-bar.png differ