diff --git a/docs/charts/bar.md b/docs/charts/bar.md index 3f6e5cdd202..086c8ce646a 100644 --- a/docs/charts/bar.md +++ b/docs/charts/bar.md @@ -16,7 +16,7 @@ A bar chart provides a way of showing data values represented as vertical bars. ], "datasets": [{ "label": "My First Dataset", - "data": [65, 59, 80, 81, 56, 55, 40], + "data": [65, 59, 80, 81, 56, 55, 40, [10, 20]], "fill": false, "backgroundColor": [ "rgba(255, 99, 132, 0.2)", @@ -108,6 +108,7 @@ In general, this does not need to be changed except when creating chart types that derive from a bar chart. Options are: + * `'bottom'` * `'left'` * `'top'` @@ -194,10 +195,12 @@ Sample: |==============| ## Data Structure -The `data` property of a dataset for a bar chart is specified as an array of numbers. Each point in the data array corresponds to the label at the same index on the x axis. + +The `data` property of a dataset for a bar chart is specified as a an array of numbers or arrays. Each point in the data array corresponds to the label at the same index on the x axis. +For floating bar chart you can use array for a specific Y value passing two numbers. First one will be used as min value and second one as max value for a bar. ```javascript -data: [20, 10] +data: [20, 10, [10, 20]] ``` You can also specify the dataset as x/y coordinates when using the [time scale](../axes/cartesian/time.md#time-cartesian-axis). @@ -242,7 +245,7 @@ A horizontal bar chart is a variation on a vertical bar chart. It is sometimes u "labels": ["January", "February", "March", "April", "May", "June", "July"], "datasets": [{ "label": "My First Dataset", - "data": [65, 59, 80, 81, 56, 55, 40], + "data": [65, 59, 80, 81, 56, 55, 40, [10, 20]], "fill": false, "backgroundColor": [ "rgba(255, 99, 132, 0.2)", diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index aef53b68afe..e96f6b92919 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -149,6 +149,11 @@ module.exports = DatasetController.extend({ var dataset = me.getDataset(); var options = me._resolveElementOptions(rectangle, index); + // float-bar support, if y arguments are array lets override rectangles styles, assigning no skippingBorder + if (helpers.isArray(dataset.data[index])) { + options.borderSkipped = null; + } + rectangle._xScale = me.getScaleForId(meta.xAxisID); rectangle._yScale = me.getScaleForId(meta.yAxisID); rectangle._datasetIndex = me.index; @@ -285,12 +290,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.max >= 0 && value.min >= 0 ? value.min : value.max; + var yValue = value.max >= 0 && value.min >= 0 ? value.max - value.min : value.min - value.max; + var i, imeta, ivalue, base, head, size, yStackValue; if (stacked || (stacked === undefined && stack !== undefined)) { for (i = 0; i < datasetIndex; ++i) { @@ -301,8 +307,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)) { + yStackValue = scale._parseValue(datasets[i].data[index]); + ivalue = yStackValue.min >= 0 && yStackValue.max >= 0 ? yStackValue.max : yStackValue.min; + + if ((value.min < 0 && ivalue < 0) || (value.max >= 0 && ivalue >= 0)) { start += ivalue; } } @@ -310,7 +318,7 @@ module.exports = DatasetController.extend({ } base = scale.getPixelForValue(start); - head = scale.getPixelForValue(start + value); + head = scale.getPixelForValue(start + yValue); size = head - base; if (minBarLength !== undefined && Math.abs(size) < minBarLength) { @@ -365,8 +373,10 @@ module.exports = DatasetController.extend({ helpers.canvas.clipArea(chart.ctx, chart.chartArea); + // float-bar support, if y arguments are array function will use bottom value as bar start point for (; i < ilen; ++i) { - if (!isNaN(scale.getRightValue(dataset.data[i]))) { + var val = scale._parseValue(dataset.data[i]); + if (!isNaN(val.start) && !isNaN(val.end)) { rects[i].draw(); } } diff --git a/src/core/core.scale.js b/src/core/core.scale.js index ff73d98115f..861f91c1a79 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -522,6 +522,12 @@ module.exports = Element.extend({ if ((typeof rawValue === 'number' || rawValue instanceof Number) && !isFinite(rawValue)) { return NaN; } + + // Float-bar support. Handling arrays + if (helpers.isArray(rawValue)) { + return [this.getRightValue(rawValue[0]), this.getRightValue(rawValue[1])]; + } + // If it is in fact an object, dive in one more level if (rawValue) { if (this.isHorizontal()) { @@ -537,6 +543,38 @@ module.exports = Element.extend({ return rawValue; }, + _parseValue: function(raw) { + var value = +this.getRightValue(raw); + var start, end, isarr; + + if (helpers.isArray(value)) { + start = value[0]; + end = value[1]; + isarr = true; + } else { + start = 0; + end = value; + isarr = false; + } + + return { + min: Math.min(start, end), + max: Math.max(start, end), + start: start, + end: end, + isarr: isarr + }; + }, + + getScaleLabel: function(rawValue) { + var v = this._parseValue(rawValue); + if (v.issar === true) { + return v.min === v.max ? v.min : v.min + ' ; ' + v.max; + } + + 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/elements/element.rectangle.js b/src/elements/element.rectangle.js index fe3702cda4e..c406a62b70e 100644 --- a/src/elements/element.rectangle.js +++ b/src/elements/element.rectangle.js @@ -123,8 +123,13 @@ module.exports = Element.extend({ // Find first (starting) corner with fallback to 'bottom' var borders = ['bottom', 'left', 'top', 'right']; var startCorner = borders.indexOf(borderSkipped, 0); - if (startCorner === -1) { + if (borderSkipped === null) { startCorner = 0; + } else { + startCorner = borders.indexOf(borderSkipped, 0); + if (startCorner === -1) { + startCorner = 0; + } } function cornerAt(index) { @@ -135,7 +140,9 @@ module.exports = Element.extend({ var corner = cornerAt(0); ctx.moveTo(corner[0], corner[1]); - for (var i = 1; i < 4; i++) { + var cornersCount = borderSkipped === null ? 4 : 3; + + for (var i = 1; i <= cornersCount; i++) { corner = cornerAt(i); ctx.lineTo(corner[0], corner[1]); } diff --git a/src/platforms/platform.dom.js b/src/platforms/platform.dom.js index 01a46174d7f..053db20d2e2 100644 --- a/src/platforms/platform.dom.js +++ b/src/platforms/platform.dom.js @@ -124,11 +124,11 @@ var supportsEventListenerOptions = (function() { // https://github.com/chartjs/Chart.js/issues/4287 var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false; -function addEventListener(node, type, listener) { +function addListener(node, type, listener) { node.addEventListener(type, listener, eventListenerOptions); } -function removeEventListener(node, type, listener) { +function removeListener(node, type, listener) { node.removeEventListener(type, listener, eventListenerOptions); } @@ -223,8 +223,8 @@ function createResizer(handler) { handler(); }; - addEventListener(expand, 'scroll', onScroll.bind(expand, 'expand')); - addEventListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink')); + addListener(expand, 'scroll', onScroll.bind(expand, 'expand')); + addListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink')); return resizer; } @@ -239,7 +239,7 @@ function watchForRender(node, handler) { }; helpers.each(ANIMATION_START_EVENTS, function(type) { - addEventListener(node, type, proxy); + addListener(node, type, proxy); }); // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class @@ -258,7 +258,7 @@ function unwatchForRender(node) { if (proxy) { helpers.each(ANIMATION_START_EVENTS, function(type) { - removeEventListener(node, type, proxy); + removeListener(node, type, proxy); }); delete expando.renderProxy; @@ -429,7 +429,7 @@ module.exports = { listener(fromNativeEvent(event, chart)); }; - addEventListener(canvas, type, proxy); + addListener(canvas, type, proxy); }, removeEventListener: function(chart, type, listener) { @@ -447,7 +447,7 @@ module.exports = { return; } - removeEventListener(canvas, type, proxy); + removeListener(canvas, type, proxy); } }; @@ -462,7 +462,7 @@ module.exports = { * @todo remove at version 3 * @private */ -helpers.addEvent = addEventListener; +helpers.addEvent = addListener; /** * Provided for backward compatibility, use EventTarget.removeEventListener instead. @@ -473,4 +473,4 @@ helpers.addEvent = addEventListener; * @todo remove at version 3 * @private */ -helpers.removeEvent = removeEventListener; +helpers.removeEvent = removeListener; diff --git a/src/scales/scale.linear.js b/src/scales/scale.linear.js index 41a4e4168a3..df7b828e471 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,28 @@ 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 (value.isarr === false) { + value.min = value.end; + value.max = value.end; + } + + if (me.min === null) { - me.min = value; - } else if (value < me.min) { - me.min = value; + me.min = value.min; + } else if (value.min < me.min) { + me.min = value.min; } if (me.max === null) { - me.max = value; - } else if (value > me.max) { - me.max = value; + me.max = value.max; + } else if (value.max > me.max) { + me.max = value.max; } }); } @@ -151,7 +163,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 91f90845da3..7e5b1cbe750 100644 --- a/src/scales/scale.logarithmic.js +++ b/src/scales/scale.logarithmic.js @@ -113,13 +113,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.max < 0 || value.max < 0) { return; } values[index] = values[index] || 0; - values[index] += value; + values[index] += value.max; }); } }); @@ -138,26 +138,31 @@ 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 (value.isarr === false) { + value.min = value.end; + value.max = value.end; + } + if (me.min === null) { - me.min = value; - } else if (value < me.min) { - me.min = value; + me.min = value.min; + } else if (value.min < me.min) { + me.min = value.min; } if (me.max === null) { - me.max = value; - } else if (value > me.max) { - me.max = value; + me.max = value.max; + } else if (value.max > me.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; } }); } @@ -242,7 +247,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) {