chart-js-rails 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 324f6cbaa02d1840c03f3a0bd9da0d9cb3e98190
4
- data.tar.gz: 5fea40ac0c15963b27f5bf618fa5a1b3abe986d9
3
+ metadata.gz: 27e48fe398bd9a47f7b4f80d154f5eacc3db0b8e
4
+ data.tar.gz: 518d5312ffed1c9181d6c9b11aa99f5091b8c495
5
5
  SHA512:
6
- metadata.gz: 0e74c8b56f192d5649211fe6d827170818671725632f44ee34a426c30795d4ba7baed84590dd54ad260619e9a80bbc3677ef64dc546f53a9fbb681419368478d
7
- data.tar.gz: 6835283ced33f03c9e6fe8e23d9d3a2e56b09affd1f61a26c5948e0ee2af39852e81df28493d5e93d4149f1cbfc9fb0fa9f1a3135cbdb1a0d5496504b57bdf68
6
+ metadata.gz: be9c00e5b1ebfc0fec07c0f30dfc3ffeaf24e80bf914e0491da764b992b4c065eb2dfb0b56e36258121145a01f12a1f575fc72b1faab58e33186b34319848d85
7
+ data.tar.gz: faace6ddb07125eb57fc87c1c7a6cf0f37216932609f75125ce0980b2cf354a1e10e872a9b50c5c99eb475a4c1747119519f6f4bff7b0ef55f580b6bdefb3485
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Chart.js for Rails
2
2
 
3
- Integrate Chart.js into Rails Asset Pipeline (current version is 2.4.0)
3
+ Integrate Chart.js into Rails Asset Pipeline (current version is 2.5.0)
4
4
 
5
5
  ## Installation
6
6
 
@@ -1,5 +1,5 @@
1
1
  module ChartJs
2
2
  module Rails
3
- VERSION = '0.1.1'.freeze
3
+ VERSION = '0.1.2'.freeze
4
4
  end
5
5
  end
@@ -1,9 +1,9 @@
1
1
  /*!
2
2
  * Chart.js
3
3
  * http://chartjs.org/
4
- * Version: 2.4.0
4
+ * Version: 2.5.0
5
5
  *
6
- * Copyright 2016 Nick Downie
6
+ * Copyright 2017 Nick Downie
7
7
  * Released under the MIT license
8
8
  * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md
9
9
  */
@@ -1668,14 +1668,15 @@ module.exports = {
1668
1668
  var Chart = require(28)();
1669
1669
 
1670
1670
  require(26)(Chart);
1671
+ require(42)(Chart);
1671
1672
  require(22)(Chart);
1673
+ require(31)(Chart);
1672
1674
  require(25)(Chart);
1673
1675
  require(21)(Chart);
1674
1676
  require(23)(Chart);
1675
1677
  require(24)(Chart);
1676
1678
  require(29)(Chart);
1677
1679
  require(33)(Chart);
1678
- require(31)(Chart);
1679
1680
  require(34)(Chart);
1680
1681
  require(32)(Chart);
1681
1682
  require(35)(Chart);
@@ -1688,12 +1689,12 @@ require(38)(Chart);
1688
1689
  require(39)(Chart);
1689
1690
  require(40)(Chart);
1690
1691
 
1692
+ require(45)(Chart);
1691
1693
  require(43)(Chart);
1692
- require(41)(Chart);
1693
- require(42)(Chart);
1694
1694
  require(44)(Chart);
1695
- require(45)(Chart);
1696
1695
  require(46)(Chart);
1696
+ require(47)(Chart);
1697
+ require(48)(Chart);
1697
1698
 
1698
1699
  // Controllers must be loaded after elements
1699
1700
  // See Chart.core.datasetController.dataElementType
@@ -1714,7 +1715,7 @@ require(14)(Chart);
1714
1715
 
1715
1716
  window.Chart = module.exports = Chart;
1716
1717
 
1717
- },{"10":10,"11":11,"12":12,"13":13,"14":14,"15":15,"16":16,"17":17,"18":18,"19":19,"20":20,"21":21,"22":22,"23":23,"24":24,"25":25,"26":26,"27":27,"28":28,"29":29,"30":30,"31":31,"32":32,"33":33,"34":34,"35":35,"36":36,"37":37,"38":38,"39":39,"40":40,"41":41,"42":42,"43":43,"44":44,"45":45,"46":46,"8":8,"9":9}],8:[function(require,module,exports){
1718
+ },{"10":10,"11":11,"12":12,"13":13,"14":14,"15":15,"16":16,"17":17,"18":18,"19":19,"20":20,"21":21,"22":22,"23":23,"24":24,"25":25,"26":26,"27":27,"28":28,"29":29,"30":30,"31":31,"32":32,"33":33,"34":34,"35":35,"36":36,"37":37,"38":38,"39":39,"40":40,"42":42,"43":43,"44":44,"45":45,"46":46,"47":47,"48":48,"8":8,"9":9}],8:[function(require,module,exports){
1718
1719
  'use strict';
1719
1720
 
1720
1721
  module.exports = function(Chart) {
@@ -1878,21 +1879,33 @@ module.exports = function(Chart) {
1878
1879
  initialize: function(chart, datasetIndex) {
1879
1880
  Chart.DatasetController.prototype.initialize.call(this, chart, datasetIndex);
1880
1881
 
1882
+ var me = this;
1883
+ var meta = me.getMeta();
1884
+ var dataset = me.getDataset();
1885
+
1886
+ meta.stack = dataset.stack;
1881
1887
  // Use this to indicate that this is a bar dataset.
1882
- this.getMeta().bar = true;
1888
+ meta.bar = true;
1883
1889
  },
1884
1890
 
1885
- // Get the number of datasets that display bars. We use this to correctly calculate the bar width
1886
- getBarCount: function() {
1891
+ // Correctly calculate the bar width accounting for stacks and the fact that not all bars are visible
1892
+ getStackCount: function() {
1887
1893
  var me = this;
1888
- var barCount = 0;
1894
+ var meta = me.getMeta();
1895
+ var yScale = me.getScaleForId(meta.yAxisID);
1896
+
1897
+ var stacks = [];
1889
1898
  helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) {
1890
- var meta = me.chart.getDatasetMeta(datasetIndex);
1891
- if (meta.bar && me.chart.isDatasetVisible(datasetIndex)) {
1892
- ++barCount;
1899
+ var dsMeta = me.chart.getDatasetMeta(datasetIndex);
1900
+ if (dsMeta.bar && me.chart.isDatasetVisible(datasetIndex) &&
1901
+ (yScale.options.stacked === false ||
1902
+ (yScale.options.stacked === true && stacks.indexOf(dsMeta.stack) === -1) ||
1903
+ (yScale.options.stacked === undefined && (dsMeta.stack === undefined || stacks.indexOf(dsMeta.stack) === -1)))) {
1904
+ stacks.push(dsMeta.stack);
1893
1905
  }
1894
1906
  }, me);
1895
- return barCount;
1907
+
1908
+ return stacks.length;
1896
1909
  },
1897
1910
 
1898
1911
  update: function(reset) {
@@ -1917,7 +1930,7 @@ module.exports = function(Chart) {
1917
1930
  rectangle._datasetIndex = me.index;
1918
1931
  rectangle._index = index;
1919
1932
 
1920
- var ruler = me.getRuler(index);
1933
+ var ruler = me.getRuler(index); // The index argument for compatible
1921
1934
  rectangle._model = {
1922
1935
  x: me.calculateBarX(index, me.index, ruler),
1923
1936
  y: reset ? scaleBase : me.calculateBarY(index, me.index),
@@ -1927,6 +1940,7 @@ module.exports = function(Chart) {
1927
1940
  datasetLabel: dataset.label,
1928
1941
 
1929
1942
  // Appearance
1943
+ horizontal: false,
1930
1944
  base: reset ? scaleBase : me.calculateBarBase(me.index, index),
1931
1945
  width: me.calculateBarWidth(ruler),
1932
1946
  backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor),
@@ -1942,9 +1956,11 @@ module.exports = function(Chart) {
1942
1956
  var me = this;
1943
1957
  var meta = me.getMeta();
1944
1958
  var yScale = me.getScaleForId(meta.yAxisID);
1945
- var base = 0;
1959
+ var base = yScale.getBaseValue();
1960
+ var original = base;
1946
1961
 
1947
- if (yScale.options.stacked) {
1962
+ if ((yScale.options.stacked === true) ||
1963
+ (yScale.options.stacked === undefined && meta.stack !== undefined)) {
1948
1964
  var chart = me.chart;
1949
1965
  var datasets = chart.data.datasets;
1950
1966
  var value = Number(datasets[datasetIndex].data[index]);
@@ -1952,9 +1968,10 @@ module.exports = function(Chart) {
1952
1968
  for (var i = 0; i < datasetIndex; i++) {
1953
1969
  var currentDs = datasets[i];
1954
1970
  var currentDsMeta = chart.getDatasetMeta(i);
1955
- if (currentDsMeta.bar && currentDsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) {
1971
+ if (currentDsMeta.bar && currentDsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i) &&
1972
+ meta.stack === currentDsMeta.stack) {
1956
1973
  var currentVal = Number(currentDs.data[index]);
1957
- base += value < 0 ? Math.min(currentVal, 0) : Math.max(currentVal, 0);
1974
+ base += value < 0 ? Math.min(currentVal, original) : Math.max(currentVal, original);
1958
1975
  }
1959
1976
  }
1960
1977
 
@@ -1964,34 +1981,22 @@ module.exports = function(Chart) {
1964
1981
  return yScale.getBasePixel();
1965
1982
  },
1966
1983
 
1967
- getRuler: function(index) {
1984
+ getRuler: function() {
1968
1985
  var me = this;
1969
1986
  var meta = me.getMeta();
1970
1987
  var xScale = me.getScaleForId(meta.xAxisID);
1971
- var datasetCount = me.getBarCount();
1972
-
1973
- var tickWidth;
1988
+ var stackCount = me.getStackCount();
1974
1989
 
1975
- if (xScale.options.type === 'category') {
1976
- tickWidth = xScale.getPixelForTick(index + 1) - xScale.getPixelForTick(index);
1977
- } else {
1978
- // Average width
1979
- tickWidth = xScale.width / xScale.ticks.length;
1980
- }
1990
+ var tickWidth = xScale.width / xScale.ticks.length;
1981
1991
  var categoryWidth = tickWidth * xScale.options.categoryPercentage;
1982
1992
  var categorySpacing = (tickWidth - (tickWidth * xScale.options.categoryPercentage)) / 2;
1983
- var fullBarWidth = categoryWidth / datasetCount;
1984
-
1985
- if (xScale.ticks.length !== me.chart.data.labels.length) {
1986
- var perc = xScale.ticks.length / me.chart.data.labels.length;
1987
- fullBarWidth = fullBarWidth * perc;
1988
- }
1993
+ var fullBarWidth = categoryWidth / stackCount;
1989
1994
 
1990
1995
  var barWidth = fullBarWidth * xScale.options.barPercentage;
1991
1996
  var barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage);
1992
1997
 
1993
1998
  return {
1994
- datasetCount: datasetCount,
1999
+ stackCount: stackCount,
1995
2000
  tickWidth: tickWidth,
1996
2001
  categoryWidth: categoryWidth,
1997
2002
  categorySpacing: categorySpacing,
@@ -2002,46 +2007,50 @@ module.exports = function(Chart) {
2002
2007
  },
2003
2008
 
2004
2009
  calculateBarWidth: function(ruler) {
2005
- var xScale = this.getScaleForId(this.getMeta().xAxisID);
2010
+ var me = this;
2011
+ var meta = me.getMeta();
2012
+ var xScale = me.getScaleForId(meta.xAxisID);
2006
2013
  if (xScale.options.barThickness) {
2007
2014
  return xScale.options.barThickness;
2008
2015
  }
2009
- return xScale.options.stacked ? ruler.categoryWidth : ruler.barWidth;
2016
+ return ruler.barWidth;
2010
2017
  },
2011
2018
 
2012
- // Get bar index from the given dataset index accounting for the fact that not all bars are visible
2013
- getBarIndex: function(datasetIndex) {
2014
- var barIndex = 0;
2015
- var meta, j;
2019
+ // Get stack index from the given dataset index accounting for stacks and the fact that not all bars are visible
2020
+ getStackIndex: function(datasetIndex) {
2021
+ var me = this;
2022
+ var meta = me.chart.getDatasetMeta(datasetIndex);
2023
+ var yScale = me.getScaleForId(meta.yAxisID);
2024
+ var dsMeta, j;
2025
+ var stacks = [meta.stack];
2016
2026
 
2017
2027
  for (j = 0; j < datasetIndex; ++j) {
2018
- meta = this.chart.getDatasetMeta(j);
2019
- if (meta.bar && this.chart.isDatasetVisible(j)) {
2020
- ++barIndex;
2028
+ dsMeta = this.chart.getDatasetMeta(j);
2029
+ if (dsMeta.bar && this.chart.isDatasetVisible(j) &&
2030
+ (yScale.options.stacked === false ||
2031
+ (yScale.options.stacked === true && stacks.indexOf(dsMeta.stack) === -1) ||
2032
+ (yScale.options.stacked === undefined && (dsMeta.stack === undefined || stacks.indexOf(dsMeta.stack) === -1)))) {
2033
+ stacks.push(dsMeta.stack);
2021
2034
  }
2022
2035
  }
2023
2036
 
2024
- return barIndex;
2037
+ return stacks.length - 1;
2025
2038
  },
2026
2039
 
2027
2040
  calculateBarX: function(index, datasetIndex, ruler) {
2028
2041
  var me = this;
2029
2042
  var meta = me.getMeta();
2030
2043
  var xScale = me.getScaleForId(meta.xAxisID);
2031
- var barIndex = me.getBarIndex(datasetIndex);
2044
+ var stackIndex = me.getStackIndex(datasetIndex);
2032
2045
  var leftTick = xScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo);
2033
2046
  leftTick -= me.chart.isCombo ? (ruler.tickWidth / 2) : 0;
2034
2047
 
2035
- if (xScale.options.stacked) {
2036
- return leftTick + (ruler.categoryWidth / 2) + ruler.categorySpacing;
2037
- }
2038
-
2039
2048
  return leftTick +
2040
2049
  (ruler.barWidth / 2) +
2041
2050
  ruler.categorySpacing +
2042
- (ruler.barWidth * barIndex) +
2051
+ (ruler.barWidth * stackIndex) +
2043
2052
  (ruler.barSpacing / 2) +
2044
- (ruler.barSpacing * barIndex);
2053
+ (ruler.barSpacing * stackIndex);
2045
2054
  },
2046
2055
 
2047
2056
  calculateBarY: function(index, datasetIndex) {
@@ -2050,15 +2059,17 @@ module.exports = function(Chart) {
2050
2059
  var yScale = me.getScaleForId(meta.yAxisID);
2051
2060
  var value = Number(me.getDataset().data[index]);
2052
2061
 
2053
- if (yScale.options.stacked) {
2054
-
2055
- var sumPos = 0,
2056
- sumNeg = 0;
2062
+ if (yScale.options.stacked ||
2063
+ (yScale.options.stacked === undefined && meta.stack !== undefined)) {
2064
+ var base = yScale.getBaseValue();
2065
+ var sumPos = base,
2066
+ sumNeg = base;
2057
2067
 
2058
2068
  for (var i = 0; i < datasetIndex; i++) {
2059
2069
  var ds = me.chart.data.datasets[i];
2060
2070
  var dsMeta = me.chart.getDatasetMeta(i);
2061
- if (dsMeta.bar && dsMeta.yAxisID === yScale.id && me.chart.isDatasetVisible(i)) {
2071
+ if (dsMeta.bar && dsMeta.yAxisID === yScale.id && me.chart.isDatasetVisible(i) &&
2072
+ meta.stack === dsMeta.stack) {
2062
2073
  var stackedVal = Number(ds.data[index]);
2063
2074
  if (stackedVal < 0) {
2064
2075
  sumNeg += stackedVal || 0;
@@ -2084,12 +2095,14 @@ module.exports = function(Chart) {
2084
2095
  var dataset = me.getDataset();
2085
2096
  var i, len;
2086
2097
 
2098
+ Chart.canvasHelpers.clipArea(me.chart.chart.ctx, me.chart.chartArea);
2087
2099
  for (i = 0, len = metaData.length; i < len; ++i) {
2088
2100
  var d = dataset.data[i];
2089
2101
  if (d !== null && d !== undefined && !isNaN(d)) {
2090
2102
  metaData[i].transition(easingDecimal).draw();
2091
2103
  }
2092
2104
  }
2105
+ Chart.canvasHelpers.unclipArea(me.chart.chart.ctx);
2093
2106
  },
2094
2107
 
2095
2108
  setHoverStyle: function(rectangle) {
@@ -2174,6 +2187,27 @@ module.exports = function(Chart) {
2174
2187
  };
2175
2188
 
2176
2189
  Chart.controllers.horizontalBar = Chart.controllers.bar.extend({
2190
+
2191
+ // Correctly calculate the bar width accounting for stacks and the fact that not all bars are visible
2192
+ getStackCount: function() {
2193
+ var me = this;
2194
+ var meta = me.getMeta();
2195
+ var xScale = me.getScaleForId(meta.xAxisID);
2196
+
2197
+ var stacks = [];
2198
+ helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) {
2199
+ var dsMeta = me.chart.getDatasetMeta(datasetIndex);
2200
+ if (dsMeta.bar && me.chart.isDatasetVisible(datasetIndex) &&
2201
+ (xScale.options.stacked === false ||
2202
+ (xScale.options.stacked === true && stacks.indexOf(dsMeta.stack) === -1) ||
2203
+ (xScale.options.stacked === undefined && (dsMeta.stack === undefined || stacks.indexOf(dsMeta.stack) === -1)))) {
2204
+ stacks.push(dsMeta.stack);
2205
+ }
2206
+ }, me);
2207
+
2208
+ return stacks.length;
2209
+ },
2210
+
2177
2211
  updateElement: function(rectangle, index, reset) {
2178
2212
  var me = this;
2179
2213
  var meta = me.getMeta();
@@ -2189,7 +2223,7 @@ module.exports = function(Chart) {
2189
2223
  rectangle._datasetIndex = me.index;
2190
2224
  rectangle._index = index;
2191
2225
 
2192
- var ruler = me.getRuler(index);
2226
+ var ruler = me.getRuler(index); // The index argument for compatible
2193
2227
  rectangle._model = {
2194
2228
  x: reset ? scaleBase : me.calculateBarX(index, me.index),
2195
2229
  y: me.calculateBarY(index, me.index, ruler),
@@ -2199,6 +2233,7 @@ module.exports = function(Chart) {
2199
2233
  datasetLabel: dataset.label,
2200
2234
 
2201
2235
  // Appearance
2236
+ horizontal: true,
2202
2237
  base: reset ? scaleBase : me.calculateBarBase(me.index, index),
2203
2238
  height: me.calculateBarHeight(ruler),
2204
2239
  backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor),
@@ -2206,62 +2241,6 @@ module.exports = function(Chart) {
2206
2241
  borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor),
2207
2242
  borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth)
2208
2243
  };
2209
- rectangle.draw = function() {
2210
- var ctx = this._chart.ctx;
2211
- var vm = this._view;
2212
-
2213
- var halfHeight = vm.height / 2,
2214
- topY = vm.y - halfHeight,
2215
- bottomY = vm.y + halfHeight,
2216
- right = vm.base - (vm.base - vm.x),
2217
- halfStroke = vm.borderWidth / 2;
2218
-
2219
- // Canvas doesn't allow us to stroke inside the width so we can
2220
- // adjust the sizes to fit if we're setting a stroke on the line
2221
- if (vm.borderWidth) {
2222
- topY += halfStroke;
2223
- bottomY -= halfStroke;
2224
- right += halfStroke;
2225
- }
2226
-
2227
- ctx.beginPath();
2228
-
2229
- ctx.fillStyle = vm.backgroundColor;
2230
- ctx.strokeStyle = vm.borderColor;
2231
- ctx.lineWidth = vm.borderWidth;
2232
-
2233
- // Corner points, from bottom-left to bottom-right clockwise
2234
- // | 1 2 |
2235
- // | 0 3 |
2236
- var corners = [
2237
- [vm.base, bottomY],
2238
- [vm.base, topY],
2239
- [right, topY],
2240
- [right, bottomY]
2241
- ];
2242
-
2243
- // Find first (starting) corner with fallback to 'bottom'
2244
- var borders = ['bottom', 'left', 'top', 'right'];
2245
- var startCorner = borders.indexOf(vm.borderSkipped, 0);
2246
- if (startCorner === -1) {
2247
- startCorner = 0;
2248
- }
2249
-
2250
- function cornerAt(cornerIndex) {
2251
- return corners[(startCorner + cornerIndex) % 4];
2252
- }
2253
-
2254
- // Draw rectangle from 'startCorner'
2255
- ctx.moveTo.apply(ctx, cornerAt(0));
2256
- for (var i = 1; i < 4; i++) {
2257
- ctx.lineTo.apply(ctx, cornerAt(i));
2258
- }
2259
-
2260
- ctx.fill();
2261
- if (vm.borderWidth) {
2262
- ctx.stroke();
2263
- }
2264
- };
2265
2244
 
2266
2245
  rectangle.pivot();
2267
2246
  },
@@ -2270,9 +2249,11 @@ module.exports = function(Chart) {
2270
2249
  var me = this;
2271
2250
  var meta = me.getMeta();
2272
2251
  var xScale = me.getScaleForId(meta.xAxisID);
2273
- var base = 0;
2252
+ var base = xScale.getBaseValue();
2253
+ var originalBase = base;
2274
2254
 
2275
- if (xScale.options.stacked) {
2255
+ if (xScale.options.stacked ||
2256
+ (xScale.options.stacked === undefined && meta.stack !== undefined)) {
2276
2257
  var chart = me.chart;
2277
2258
  var datasets = chart.data.datasets;
2278
2259
  var value = Number(datasets[datasetIndex].data[index]);
@@ -2280,9 +2261,10 @@ module.exports = function(Chart) {
2280
2261
  for (var i = 0; i < datasetIndex; i++) {
2281
2262
  var currentDs = datasets[i];
2282
2263
  var currentDsMeta = chart.getDatasetMeta(i);
2283
- if (currentDsMeta.bar && currentDsMeta.xAxisID === xScale.id && chart.isDatasetVisible(i)) {
2264
+ if (currentDsMeta.bar && currentDsMeta.xAxisID === xScale.id && chart.isDatasetVisible(i) &&
2265
+ meta.stack === currentDsMeta.stack) {
2284
2266
  var currentVal = Number(currentDs.data[index]);
2285
- base += value < 0 ? Math.min(currentVal, 0) : Math.max(currentVal, 0);
2267
+ base += value < 0 ? Math.min(currentVal, originalBase) : Math.max(currentVal, originalBase);
2286
2268
  }
2287
2269
  }
2288
2270
 
@@ -2292,33 +2274,22 @@ module.exports = function(Chart) {
2292
2274
  return xScale.getBasePixel();
2293
2275
  },
2294
2276
 
2295
- getRuler: function(index) {
2277
+ getRuler: function() {
2296
2278
  var me = this;
2297
2279
  var meta = me.getMeta();
2298
2280
  var yScale = me.getScaleForId(meta.yAxisID);
2299
- var datasetCount = me.getBarCount();
2281
+ var stackCount = me.getStackCount();
2300
2282
 
2301
- var tickHeight;
2302
- if (yScale.options.type === 'category') {
2303
- tickHeight = yScale.getPixelForTick(index + 1) - yScale.getPixelForTick(index);
2304
- } else {
2305
- // Average width
2306
- tickHeight = yScale.width / yScale.ticks.length;
2307
- }
2283
+ var tickHeight = yScale.height / yScale.ticks.length;
2308
2284
  var categoryHeight = tickHeight * yScale.options.categoryPercentage;
2309
2285
  var categorySpacing = (tickHeight - (tickHeight * yScale.options.categoryPercentage)) / 2;
2310
- var fullBarHeight = categoryHeight / datasetCount;
2311
-
2312
- if (yScale.ticks.length !== me.chart.data.labels.length) {
2313
- var perc = yScale.ticks.length / me.chart.data.labels.length;
2314
- fullBarHeight = fullBarHeight * perc;
2315
- }
2286
+ var fullBarHeight = categoryHeight / stackCount;
2316
2287
 
2317
2288
  var barHeight = fullBarHeight * yScale.options.barPercentage;
2318
2289
  var barSpacing = fullBarHeight - (fullBarHeight * yScale.options.barPercentage);
2319
2290
 
2320
2291
  return {
2321
- datasetCount: datasetCount,
2292
+ stackCount: stackCount,
2322
2293
  tickHeight: tickHeight,
2323
2294
  categoryHeight: categoryHeight,
2324
2295
  categorySpacing: categorySpacing,
@@ -2330,11 +2301,33 @@ module.exports = function(Chart) {
2330
2301
 
2331
2302
  calculateBarHeight: function(ruler) {
2332
2303
  var me = this;
2333
- var yScale = me.getScaleForId(me.getMeta().yAxisID);
2304
+ var meta = me.getMeta();
2305
+ var yScale = me.getScaleForId(meta.yAxisID);
2334
2306
  if (yScale.options.barThickness) {
2335
2307
  return yScale.options.barThickness;
2336
2308
  }
2337
- return yScale.options.stacked ? ruler.categoryHeight : ruler.barHeight;
2309
+ return ruler.barHeight;
2310
+ },
2311
+
2312
+ // Get stack index from the given dataset index accounting for stacks and the fact that not all bars are visible
2313
+ getStackIndex: function(datasetIndex) {
2314
+ var me = this;
2315
+ var meta = me.chart.getDatasetMeta(datasetIndex);
2316
+ var xScale = me.getScaleForId(meta.xAxisID);
2317
+ var dsMeta, j;
2318
+ var stacks = [meta.stack];
2319
+
2320
+ for (j = 0; j < datasetIndex; ++j) {
2321
+ dsMeta = this.chart.getDatasetMeta(j);
2322
+ if (dsMeta.bar && this.chart.isDatasetVisible(j) &&
2323
+ (xScale.options.stacked === false ||
2324
+ (xScale.options.stacked === true && stacks.indexOf(dsMeta.stack) === -1) ||
2325
+ (xScale.options.stacked === undefined && (dsMeta.stack === undefined || stacks.indexOf(dsMeta.stack) === -1)))) {
2326
+ stacks.push(dsMeta.stack);
2327
+ }
2328
+ }
2329
+
2330
+ return stacks.length - 1;
2338
2331
  },
2339
2332
 
2340
2333
  calculateBarX: function(index, datasetIndex) {
@@ -2343,15 +2336,17 @@ module.exports = function(Chart) {
2343
2336
  var xScale = me.getScaleForId(meta.xAxisID);
2344
2337
  var value = Number(me.getDataset().data[index]);
2345
2338
 
2346
- if (xScale.options.stacked) {
2347
-
2348
- var sumPos = 0,
2349
- sumNeg = 0;
2339
+ if (xScale.options.stacked ||
2340
+ (xScale.options.stacked === undefined && meta.stack !== undefined)) {
2341
+ var base = xScale.getBaseValue();
2342
+ var sumPos = base,
2343
+ sumNeg = base;
2350
2344
 
2351
2345
  for (var i = 0; i < datasetIndex; i++) {
2352
2346
  var ds = me.chart.data.datasets[i];
2353
2347
  var dsMeta = me.chart.getDatasetMeta(i);
2354
- if (dsMeta.bar && dsMeta.xAxisID === xScale.id && me.chart.isDatasetVisible(i)) {
2348
+ if (dsMeta.bar && dsMeta.xAxisID === xScale.id && me.chart.isDatasetVisible(i) &&
2349
+ meta.stack === dsMeta.stack) {
2355
2350
  var stackedVal = Number(ds.data[index]);
2356
2351
  if (stackedVal < 0) {
2357
2352
  sumNeg += stackedVal || 0;
@@ -2374,20 +2369,16 @@ module.exports = function(Chart) {
2374
2369
  var me = this;
2375
2370
  var meta = me.getMeta();
2376
2371
  var yScale = me.getScaleForId(meta.yAxisID);
2377
- var barIndex = me.getBarIndex(datasetIndex);
2372
+ var stackIndex = me.getStackIndex(datasetIndex);
2378
2373
  var topTick = yScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo);
2379
2374
  topTick -= me.chart.isCombo ? (ruler.tickHeight / 2) : 0;
2380
2375
 
2381
- if (yScale.options.stacked) {
2382
- return topTick + (ruler.categoryHeight / 2) + ruler.categorySpacing;
2383
- }
2384
-
2385
2376
  return topTick +
2386
2377
  (ruler.barHeight / 2) +
2387
2378
  ruler.categorySpacing +
2388
- (ruler.barHeight * barIndex) +
2379
+ (ruler.barHeight * stackIndex) +
2389
2380
  (ruler.barSpacing / 2) +
2390
- (ruler.barSpacing * barIndex);
2381
+ (ruler.barSpacing * stackIndex);
2391
2382
  }
2392
2383
  });
2393
2384
  };
@@ -2702,7 +2693,7 @@ module.exports = function(Chart) {
2702
2693
 
2703
2694
  chart.borderWidth = me.getMaxBorderWidth(meta.data);
2704
2695
  chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0);
2705
- chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 1, 0);
2696
+ chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0);
2706
2697
  chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
2707
2698
  chart.offsetX = offset.x * chart.outerRadius;
2708
2699
  chart.offsetY = offset.y * chart.outerRadius;
@@ -2710,7 +2701,7 @@ module.exports = function(Chart) {
2710
2701
  meta.total = me.calculateTotal();
2711
2702
 
2712
2703
  me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index));
2713
- me.innerRadius = me.outerRadius - chart.radiusLength;
2704
+ me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0);
2714
2705
 
2715
2706
  helpers.each(meta.data, function(arc, index) {
2716
2707
  me.updateElement(arc, index, reset);
@@ -2963,11 +2954,11 @@ module.exports = function(Chart) {
2963
2954
  var dataset = this.getDataset();
2964
2955
  var custom = point.custom || {};
2965
2956
 
2966
- if (custom.borderWidth) {
2957
+ if (!isNaN(custom.borderWidth)) {
2967
2958
  borderWidth = custom.borderWidth;
2968
- } else if (dataset.pointBorderWidth) {
2959
+ } else if (!isNaN(dataset.pointBorderWidth)) {
2969
2960
  borderWidth = helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth);
2970
- } else if (dataset.borderWidth) {
2961
+ } else if (!isNaN(dataset.borderWidth)) {
2971
2962
  borderWidth = dataset.borderWidth;
2972
2963
  }
2973
2964
 
@@ -3116,14 +3107,16 @@ module.exports = function(Chart) {
3116
3107
  points[i].transition(easingDecimal);
3117
3108
  }
3118
3109
 
3110
+ Chart.canvasHelpers.clipArea(me.chart.chart.ctx, me.chart.chartArea);
3119
3111
  // Transition and Draw the line
3120
3112
  if (lineEnabled(me.getDataset(), me.chart.options)) {
3121
3113
  meta.dataset.transition(easingDecimal).draw();
3122
3114
  }
3115
+ Chart.canvasHelpers.unclipArea(me.chart.chart.ctx);
3123
3116
 
3124
3117
  // Draw the points
3125
3118
  for (i=0, ilen=points.length; i<ilen; ++i) {
3126
- points[i].draw();
3119
+ points[i].draw(me.chart.chartArea);
3127
3120
  }
3128
3121
  },
3129
3122
 
@@ -3475,7 +3468,7 @@ module.exports = function(Chart) {
3475
3468
  y: reset ? scale.yCenter : pointPosition.y,
3476
3469
 
3477
3470
  // Appearance
3478
- tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.tension, me.chart.options.elements.line.tension),
3471
+ tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension),
3479
3472
  radius: custom.radius ? custom.radius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius),
3480
3473
  backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor),
3481
3474
  borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor),
@@ -3747,6 +3740,14 @@ module.exports = function(Chart) {
3747
3740
  ctx.fillRect(x - size, y - size, 2 * size, 2 * size);
3748
3741
  ctx.strokeRect(x - size, y - size, 2 * size, 2 * size);
3749
3742
  break;
3743
+ case 'rectRounded':
3744
+ var offset = radius / Math.SQRT2;
3745
+ var leftX = x - offset;
3746
+ var topY = y - offset;
3747
+ var sideSize = Math.SQRT2 * radius;
3748
+ Chart.helpers.drawRoundedRectangle(ctx, leftX, topY, sideSize, sideSize, radius / 2);
3749
+ ctx.fill();
3750
+ break;
3750
3751
  case 'rectRot':
3751
3752
  size = 1 / Math.SQRT2 * radius;
3752
3753
  ctx.beginPath();
@@ -3805,6 +3806,18 @@ module.exports = function(Chart) {
3805
3806
 
3806
3807
  ctx.stroke();
3807
3808
  };
3809
+
3810
+ helpers.clipArea = function(ctx, clipArea) {
3811
+ ctx.save();
3812
+ ctx.beginPath();
3813
+ ctx.rect(clipArea.left, clipArea.top, clipArea.right - clipArea.left, clipArea.bottom - clipArea.top);
3814
+ ctx.clip();
3815
+ };
3816
+
3817
+ helpers.unclipArea = function(ctx) {
3818
+ ctx.restore();
3819
+ };
3820
+
3808
3821
  };
3809
3822
 
3810
3823
  },{}],23:[function(require,module,exports){
@@ -3813,6 +3826,8 @@ module.exports = function(Chart) {
3813
3826
  module.exports = function(Chart) {
3814
3827
 
3815
3828
  var helpers = Chart.helpers;
3829
+ var plugins = Chart.plugins;
3830
+ var platform = Chart.platform;
3816
3831
 
3817
3832
  // Create a dictionary of chart types, to allow for extension of existing types
3818
3833
  Chart.types = {};
@@ -3824,140 +3839,6 @@ module.exports = function(Chart) {
3824
3839
  // Controllers available for dataset visualization eg. bar, line, slice, etc.
3825
3840
  Chart.controllers = {};
3826
3841
 
3827
- /**
3828
- * The "used" size is the final value of a dimension property after all calculations have
3829
- * been performed. This method uses the computed style of `element` but returns undefined
3830
- * if the computed style is not expressed in pixels. That can happen in some cases where
3831
- * `element` has a size relative to its parent and this last one is not yet displayed,
3832
- * for example because of `display: none` on a parent node.
3833
- * TODO(SB) Move this method in the upcoming core.platform class.
3834
- * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
3835
- * @returns {Number} Size in pixels or undefined if unknown.
3836
- */
3837
- function readUsedSize(element, property) {
3838
- var value = helpers.getStyle(element, property);
3839
- var matches = value && value.match(/(\d+)px/);
3840
- return matches? Number(matches[1]) : undefined;
3841
- }
3842
-
3843
- /**
3844
- * Initializes the canvas style and render size without modifying the canvas display size,
3845
- * since responsiveness is handled by the controller.resize() method. The config is used
3846
- * to determine the aspect ratio to apply in case no explicit height has been specified.
3847
- * TODO(SB) Move this method in the upcoming core.platform class.
3848
- */
3849
- function initCanvas(canvas, config) {
3850
- var style = canvas.style;
3851
-
3852
- // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it
3853
- // returns null or '' if no explicit value has been set to the canvas attribute.
3854
- var renderHeight = canvas.getAttribute('height');
3855
- var renderWidth = canvas.getAttribute('width');
3856
-
3857
- // Chart.js modifies some canvas values that we want to restore on destroy
3858
- canvas._chartjs = {
3859
- initial: {
3860
- height: renderHeight,
3861
- width: renderWidth,
3862
- style: {
3863
- display: style.display,
3864
- height: style.height,
3865
- width: style.width
3866
- }
3867
- }
3868
- };
3869
-
3870
- // Force canvas to display as block to avoid extra space caused by inline
3871
- // elements, which would interfere with the responsive resize process.
3872
- // https://github.com/chartjs/Chart.js/issues/2538
3873
- style.display = style.display || 'block';
3874
-
3875
- if (renderWidth === null || renderWidth === '') {
3876
- var displayWidth = readUsedSize(canvas, 'width');
3877
- if (displayWidth !== undefined) {
3878
- canvas.width = displayWidth;
3879
- }
3880
- }
3881
-
3882
- if (renderHeight === null || renderHeight === '') {
3883
- if (canvas.style.height === '') {
3884
- // If no explicit render height and style height, let's apply the aspect ratio,
3885
- // which one can be specified by the user but also by charts as default option
3886
- // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2.
3887
- canvas.height = canvas.width / (config.options.aspectRatio || 2);
3888
- } else {
3889
- var displayHeight = readUsedSize(canvas, 'height');
3890
- if (displayWidth !== undefined) {
3891
- canvas.height = displayHeight;
3892
- }
3893
- }
3894
- }
3895
-
3896
- return canvas;
3897
- }
3898
-
3899
- /**
3900
- * Restores the canvas initial state, such as render/display sizes and style.
3901
- * TODO(SB) Move this method in the upcoming core.platform class.
3902
- */
3903
- function releaseCanvas(canvas) {
3904
- if (!canvas._chartjs) {
3905
- return;
3906
- }
3907
-
3908
- var initial = canvas._chartjs.initial;
3909
- ['height', 'width'].forEach(function(prop) {
3910
- var value = initial[prop];
3911
- if (value === undefined || value === null) {
3912
- canvas.removeAttribute(prop);
3913
- } else {
3914
- canvas.setAttribute(prop, value);
3915
- }
3916
- });
3917
-
3918
- helpers.each(initial.style || {}, function(value, key) {
3919
- canvas.style[key] = value;
3920
- });
3921
-
3922
- // The canvas render size might have been changed (and thus the state stack discarded),
3923
- // we can't use save() and restore() to restore the initial state. So make sure that at
3924
- // least the canvas context is reset to the default state by setting the canvas width.
3925
- // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html
3926
- canvas.width = canvas.width;
3927
-
3928
- delete canvas._chartjs;
3929
- }
3930
-
3931
- /**
3932
- * TODO(SB) Move this method in the upcoming core.platform class.
3933
- */
3934
- function acquireContext(item, config) {
3935
- if (typeof item === 'string') {
3936
- item = document.getElementById(item);
3937
- } else if (item.length) {
3938
- // Support for array based queries (such as jQuery)
3939
- item = item[0];
3940
- }
3941
-
3942
- if (item && item.canvas) {
3943
- // Support for any object associated to a canvas (including a context2d)
3944
- item = item.canvas;
3945
- }
3946
-
3947
- if (item instanceof HTMLCanvasElement) {
3948
- // To prevent canvas fingerprinting, some add-ons undefine the getContext
3949
- // method, for example: https://github.com/kkapsner/CanvasBlocker
3950
- // https://github.com/chartjs/Chart.js/issues/2807
3951
- var context = item.getContext && item.getContext('2d');
3952
- if (context instanceof CanvasRenderingContext2D) {
3953
- initCanvas(item, config);
3954
- return context;
3955
- }
3956
- }
3957
-
3958
- return null;
3959
- }
3960
-
3961
3842
  /**
3962
3843
  * Initializes the given config with global and chart default values.
3963
3844
  */
@@ -3978,6 +3859,26 @@ module.exports = function(Chart) {
3978
3859
  return config;
3979
3860
  }
3980
3861
 
3862
+ /**
3863
+ * Updates the config of the chart
3864
+ * @param chart {Chart.Controller} chart to update the options for
3865
+ */
3866
+ function updateConfig(chart) {
3867
+ var newOptions = chart.options;
3868
+
3869
+ // Update Scale(s) with options
3870
+ if (newOptions.scale) {
3871
+ chart.scale.options = newOptions.scale;
3872
+ } else if (newOptions.scales) {
3873
+ newOptions.scales.xAxes.concat(newOptions.scales.yAxes).forEach(function(scaleOptions) {
3874
+ chart.scales[scaleOptions.id].options = scaleOptions;
3875
+ });
3876
+ }
3877
+
3878
+ // Tooltip
3879
+ chart.tooltip._options = newOptions.tooltips;
3880
+ }
3881
+
3981
3882
  /**
3982
3883
  * @class Chart.Controller
3983
3884
  * The main controller of a chart.
@@ -3987,7 +3888,7 @@ module.exports = function(Chart) {
3987
3888
 
3988
3889
  config = initConfig(config);
3989
3890
 
3990
- var context = acquireContext(item, config);
3891
+ var context = platform.acquireContext(item, config);
3991
3892
  var canvas = context && context.canvas;
3992
3893
  var height = canvas && canvas.height;
3993
3894
  var width = canvas && canvas.width;
@@ -4023,47 +3924,35 @@ module.exports = function(Chart) {
4023
3924
  return me;
4024
3925
  }
4025
3926
 
4026
- helpers.retinaScale(instance);
4027
-
4028
- // Responsiveness is currently based on the use of an iframe, however this method causes
4029
- // performance issues and could be troublesome when used with ad blockers. So make sure
4030
- // that the user is still able to create a chart without iframe when responsive is false.
4031
- // See https://github.com/chartjs/Chart.js/issues/2210
4032
- if (me.options.responsive) {
4033
- helpers.addResizeListener(canvas.parentNode, function() {
4034
- me.resize();
4035
- });
4036
-
4037
- // Initial resize before chart draws (must be silent to preserve initial animations).
4038
- me.resize(true);
4039
- }
4040
-
4041
3927
  me.initialize();
3928
+ me.update();
4042
3929
 
4043
3930
  return me;
4044
3931
  };
4045
3932
 
4046
- helpers.extend(Chart.Controller.prototype, /** @lends Chart.Controller */ {
3933
+ helpers.extend(Chart.Controller.prototype, /** @lends Chart.Controller.prototype */ {
4047
3934
  initialize: function() {
4048
3935
  var me = this;
4049
3936
 
4050
3937
  // Before init plugin notification
4051
- Chart.plugins.notify('beforeInit', [me]);
3938
+ plugins.notify(me, 'beforeInit');
3939
+
3940
+ helpers.retinaScale(me.chart);
4052
3941
 
4053
3942
  me.bindEvents();
4054
3943
 
4055
- // Make sure controllers are built first so that each dataset is bound to an axis before the scales
4056
- // are built
3944
+ if (me.options.responsive) {
3945
+ // Initial resize before chart draws (must be silent to preserve initial animations).
3946
+ me.resize(true);
3947
+ }
3948
+
3949
+ // Make sure scales have IDs and are built before we build any controllers.
4057
3950
  me.ensureScalesHaveIDs();
4058
- me.buildOrUpdateControllers();
4059
3951
  me.buildScales();
4060
- me.updateLayout();
4061
- me.resetElements();
4062
3952
  me.initToolTip();
4063
- me.update();
4064
3953
 
4065
3954
  // After init plugin notification
4066
- Chart.plugins.notify('afterInit', [me]);
3955
+ plugins.notify(me, 'afterInit');
4067
3956
 
4068
3957
  return me;
4069
3958
  },
@@ -4102,16 +3991,16 @@ module.exports = function(Chart) {
4102
3991
 
4103
3992
  helpers.retinaScale(chart);
4104
3993
 
4105
- // Notify any plugins about the resize
4106
- var newSize = {width: newWidth, height: newHeight};
4107
- Chart.plugins.notify('resize', [me, newSize]);
3994
+ if (!silent) {
3995
+ // Notify any plugins about the resize
3996
+ var newSize = {width: newWidth, height: newHeight};
3997
+ plugins.notify(me, 'resize', [newSize]);
4108
3998
 
4109
- // Notify of resize
4110
- if (me.options.onResize) {
4111
- me.options.onResize(me, newSize);
4112
- }
3999
+ // Notify of resize
4000
+ if (me.options.onResize) {
4001
+ me.options.onResize(me, newSize);
4002
+ }
4113
4003
 
4114
- if (!silent) {
4115
4004
  me.stop();
4116
4005
  me.update(me.options.responsiveAnimationDuration);
4117
4006
  }
@@ -4187,10 +4076,6 @@ module.exports = function(Chart) {
4187
4076
  Chart.scaleService.addScalesToLayout(this);
4188
4077
  },
4189
4078
 
4190
- updateLayout: function() {
4191
- Chart.layoutService.update(this, this.chart.width, this.chart.height);
4192
- },
4193
-
4194
4079
  buildOrUpdateControllers: function() {
4195
4080
  var me = this;
4196
4081
  var types = [];
@@ -4226,7 +4111,6 @@ module.exports = function(Chart) {
4226
4111
 
4227
4112
  /**
4228
4113
  * Reset the elements of all datasets
4229
- * @method resetElements
4230
4114
  * @private
4231
4115
  */
4232
4116
  resetElements: function() {
@@ -4238,7 +4122,6 @@ module.exports = function(Chart) {
4238
4122
 
4239
4123
  /**
4240
4124
  * Resets the chart back to it's state before the initial animation
4241
- * @method reset
4242
4125
  */
4243
4126
  reset: function() {
4244
4127
  this.resetElements();
@@ -4247,7 +4130,12 @@ module.exports = function(Chart) {
4247
4130
 
4248
4131
  update: function(animationDuration, lazy) {
4249
4132
  var me = this;
4250
- Chart.plugins.notify('beforeUpdate', [me]);
4133
+
4134
+ updateConfig(me);
4135
+
4136
+ if (plugins.notify(me, 'beforeUpdate') === false) {
4137
+ return;
4138
+ }
4251
4139
 
4252
4140
  // In case the entire data object changed
4253
4141
  me.tooltip._data = me.data;
@@ -4260,10 +4148,7 @@ module.exports = function(Chart) {
4260
4148
  me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements();
4261
4149
  }, me);
4262
4150
 
4263
- Chart.layoutService.update(me, me.chart.width, me.chart.height);
4264
-
4265
- // Apply changes to the datasets that require the scales to have been calculated i.e BorderColor changes
4266
- Chart.plugins.notify('afterScaleUpdate', [me]);
4151
+ me.updateLayout();
4267
4152
 
4268
4153
  // Can only reset the new controllers after the scales have been updated
4269
4154
  helpers.each(newControllers, function(controller) {
@@ -4273,7 +4158,7 @@ module.exports = function(Chart) {
4273
4158
  me.updateDatasets();
4274
4159
 
4275
4160
  // Do this before render so that any plugins that need final scale updates can use it
4276
- Chart.plugins.notify('afterUpdate', [me]);
4161
+ plugins.notify(me, 'afterUpdate');
4277
4162
 
4278
4163
  if (me._bufferedRender) {
4279
4164
  me._bufferedRequest = {
@@ -4286,51 +4171,64 @@ module.exports = function(Chart) {
4286
4171
  },
4287
4172
 
4288
4173
  /**
4289
- * @method beforeDatasetsUpdate
4290
- * @description Called before all datasets are updated. If a plugin returns false,
4291
- * the datasets update will be cancelled until another chart update is triggered.
4292
- * @param {Object} instance the chart instance being updated.
4293
- * @returns {Boolean} false to cancel the datasets update.
4294
- * @memberof Chart.PluginBase
4295
- * @since version 2.1.5
4296
- * @instance
4174
+ * Updates the chart layout unless a plugin returns `false` to the `beforeLayout`
4175
+ * hook, in which case, plugins will not be called on `afterLayout`.
4176
+ * @private
4297
4177
  */
4178
+ updateLayout: function() {
4179
+ var me = this;
4298
4180
 
4299
- /**
4300
- * @method afterDatasetsUpdate
4301
- * @description Called after all datasets have been updated. Note that this
4302
- * extension will not be called if the datasets update has been cancelled.
4303
- * @param {Object} instance the chart instance being updated.
4304
- * @memberof Chart.PluginBase
4305
- * @since version 2.1.5
4306
- * @instance
4307
- */
4181
+ if (plugins.notify(me, 'beforeLayout') === false) {
4182
+ return;
4183
+ }
4184
+
4185
+ Chart.layoutService.update(this, this.chart.width, this.chart.height);
4186
+
4187
+ /**
4188
+ * Provided for backward compatibility, use `afterLayout` instead.
4189
+ * @method IPlugin#afterScaleUpdate
4190
+ * @deprecated since version 2.5.0
4191
+ * @todo remove at version 3
4192
+ */
4193
+ plugins.notify(me, 'afterScaleUpdate');
4194
+ plugins.notify(me, 'afterLayout');
4195
+ },
4308
4196
 
4309
4197
  /**
4310
- * Updates all datasets unless a plugin returns false to the beforeDatasetsUpdate
4311
- * extension, in which case no datasets will be updated and the afterDatasetsUpdate
4312
- * notification will be skipped.
4313
- * @protected
4314
- * @instance
4198
+ * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate`
4199
+ * hook, in which case, plugins will not be called on `afterDatasetsUpdate`.
4200
+ * @private
4315
4201
  */
4316
4202
  updateDatasets: function() {
4317
4203
  var me = this;
4318
- var i, ilen;
4319
4204
 
4320
- if (Chart.plugins.notify('beforeDatasetsUpdate', [me])) {
4321
- for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
4322
- me.getDatasetMeta(i).controller.update();
4323
- }
4205
+ if (plugins.notify(me, 'beforeDatasetsUpdate') === false) {
4206
+ return;
4207
+ }
4324
4208
 
4325
- Chart.plugins.notify('afterDatasetsUpdate', [me]);
4209
+ for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
4210
+ me.getDatasetMeta(i).controller.update();
4326
4211
  }
4212
+
4213
+ plugins.notify(me, 'afterDatasetsUpdate');
4327
4214
  },
4328
4215
 
4329
4216
  render: function(duration, lazy) {
4330
4217
  var me = this;
4331
- Chart.plugins.notify('beforeRender', [me]);
4218
+
4219
+ if (plugins.notify(me, 'beforeRender') === false) {
4220
+ return;
4221
+ }
4332
4222
 
4333
4223
  var animationOptions = me.options.animation;
4224
+ var onComplete = function() {
4225
+ plugins.notify(me, 'afterRender');
4226
+ var callback = animationOptions && animationOptions.onComplete;
4227
+ if (callback && callback.call) {
4228
+ callback.call(me);
4229
+ }
4230
+ };
4231
+
4334
4232
  if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) {
4335
4233
  var animation = new Chart.Animation();
4336
4234
  animation.numSteps = (duration || animationOptions.duration) / 16.66; // 60 fps
@@ -4347,53 +4245,72 @@ module.exports = function(Chart) {
4347
4245
 
4348
4246
  // user events
4349
4247
  animation.onAnimationProgress = animationOptions.onProgress;
4350
- animation.onAnimationComplete = animationOptions.onComplete;
4248
+ animation.onAnimationComplete = onComplete;
4351
4249
 
4352
4250
  Chart.animationService.addAnimation(me, animation, duration, lazy);
4353
4251
  } else {
4354
4252
  me.draw();
4355
- if (animationOptions && animationOptions.onComplete && animationOptions.onComplete.call) {
4356
- animationOptions.onComplete.call(me);
4357
- }
4253
+ onComplete();
4358
4254
  }
4255
+
4359
4256
  return me;
4360
4257
  },
4361
4258
 
4362
- draw: function(ease) {
4259
+ draw: function(easingValue) {
4363
4260
  var me = this;
4364
- var easingDecimal = ease || 1;
4261
+
4365
4262
  me.clear();
4366
4263
 
4367
- Chart.plugins.notify('beforeDraw', [me, easingDecimal]);
4264
+ if (easingValue === undefined || easingValue === null) {
4265
+ easingValue = 1;
4266
+ }
4267
+
4268
+ if (plugins.notify(me, 'beforeDraw', [easingValue]) === false) {
4269
+ return;
4270
+ }
4368
4271
 
4369
4272
  // Draw all the scales
4370
4273
  helpers.each(me.boxes, function(box) {
4371
4274
  box.draw(me.chartArea);
4372
4275
  }, me);
4276
+
4373
4277
  if (me.scale) {
4374
4278
  me.scale.draw();
4375
4279
  }
4376
4280
 
4377
- Chart.plugins.notify('beforeDatasetsDraw', [me, easingDecimal]);
4378
-
4379
- // Draw each dataset via its respective controller (reversed to support proper line stacking)
4380
- helpers.each(me.data.datasets, function(dataset, datasetIndex) {
4381
- if (me.isDatasetVisible(datasetIndex)) {
4382
- me.getDatasetMeta(datasetIndex).controller.draw(ease);
4383
- }
4384
- }, me, true);
4385
-
4386
- Chart.plugins.notify('afterDatasetsDraw', [me, easingDecimal]);
4281
+ me.drawDatasets(easingValue);
4387
4282
 
4388
4283
  // Finally draw the tooltip
4389
- me.tooltip.transition(easingDecimal).draw();
4284
+ me.tooltip.transition(easingValue).draw();
4390
4285
 
4391
- Chart.plugins.notify('afterDraw', [me, easingDecimal]);
4286
+ plugins.notify(me, 'afterDraw', [easingValue]);
4392
4287
  },
4393
4288
 
4394
- // Get the single element that was clicked on
4395
- // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
4396
- getElementAtEvent: function(e) {
4289
+ /**
4290
+ * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw`
4291
+ * hook, in which case, plugins will not be called on `afterDatasetsDraw`.
4292
+ * @private
4293
+ */
4294
+ drawDatasets: function(easingValue) {
4295
+ var me = this;
4296
+
4297
+ if (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) {
4298
+ return;
4299
+ }
4300
+
4301
+ // Draw each dataset via its respective controller (reversed to support proper line stacking)
4302
+ helpers.each(me.data.datasets, function(dataset, datasetIndex) {
4303
+ if (me.isDatasetVisible(datasetIndex)) {
4304
+ me.getDatasetMeta(datasetIndex).controller.draw(easingValue);
4305
+ }
4306
+ }, me, true);
4307
+
4308
+ plugins.notify(me, 'afterDatasetsDraw', [easingValue]);
4309
+ },
4310
+
4311
+ // Get the single element that was clicked on
4312
+ // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
4313
+ getElementAtEvent: function(e) {
4397
4314
  return Chart.Interaction.modes.single(this, e);
4398
4315
  },
4399
4316
 
@@ -4415,7 +4332,7 @@ module.exports = function(Chart) {
4415
4332
  },
4416
4333
 
4417
4334
  getDatasetAtEvent: function(e) {
4418
- return Chart.Interaction.modes.dataset(this, e);
4335
+ return Chart.Interaction.modes.dataset(this, e, {intersect: true});
4419
4336
  },
4420
4337
 
4421
4338
  getDatasetMeta: function(datasetIndex) {
@@ -4480,15 +4397,14 @@ module.exports = function(Chart) {
4480
4397
  }
4481
4398
 
4482
4399
  if (canvas) {
4483
- helpers.unbindEvents(me, me.events);
4484
- helpers.removeResizeListener(canvas.parentNode);
4400
+ me.unbindEvents();
4485
4401
  helpers.clear(me.chart);
4486
- releaseCanvas(canvas);
4402
+ platform.releaseContext(me.chart.ctx);
4487
4403
  me.chart.canvas = null;
4488
4404
  me.chart.ctx = null;
4489
4405
  }
4490
4406
 
4491
- Chart.plugins.notify('destroy', [me]);
4407
+ plugins.notify(me, 'destroy');
4492
4408
 
4493
4409
  delete Chart.instances[me.id];
4494
4410
  },
@@ -4508,10 +4424,48 @@ module.exports = function(Chart) {
4508
4424
  me.tooltip.initialize();
4509
4425
  },
4510
4426
 
4427
+ /**
4428
+ * @private
4429
+ */
4511
4430
  bindEvents: function() {
4512
4431
  var me = this;
4513
- helpers.bindEvents(me, me.options.events, function(evt) {
4514
- me.eventHandler(evt);
4432
+ var listeners = me._listeners = {};
4433
+ var listener = function() {
4434
+ me.eventHandler.apply(me, arguments);
4435
+ };
4436
+
4437
+ helpers.each(me.options.events, function(type) {
4438
+ platform.addEventListener(me, type, listener);
4439
+ listeners[type] = listener;
4440
+ });
4441
+
4442
+ // Responsiveness is currently based on the use of an iframe, however this method causes
4443
+ // performance issues and could be troublesome when used with ad blockers. So make sure
4444
+ // that the user is still able to create a chart without iframe when responsive is false.
4445
+ // See https://github.com/chartjs/Chart.js/issues/2210
4446
+ if (me.options.responsive) {
4447
+ listener = function() {
4448
+ me.resize();
4449
+ };
4450
+
4451
+ platform.addEventListener(me, 'resize', listener);
4452
+ listeners.resize = listener;
4453
+ }
4454
+ },
4455
+
4456
+ /**
4457
+ * @private
4458
+ */
4459
+ unbindEvents: function() {
4460
+ var me = this;
4461
+ var listeners = me._listeners;
4462
+ if (!listeners) {
4463
+ return;
4464
+ }
4465
+
4466
+ delete me._listeners;
4467
+ helpers.each(listeners, function(listener, type) {
4468
+ platform.removeEventListener(me, type, listener);
4515
4469
  });
4516
4470
  },
4517
4471
 
@@ -4527,20 +4481,26 @@ module.exports = function(Chart) {
4527
4481
  }
4528
4482
  },
4529
4483
 
4484
+ /**
4485
+ * @private
4486
+ */
4530
4487
  eventHandler: function(e) {
4531
4488
  var me = this;
4532
- var legend = me.legend;
4533
4489
  var tooltip = me.tooltip;
4534
- var hoverOptions = me.options.hover;
4490
+
4491
+ if (plugins.notify(me, 'beforeEvent', [e]) === false) {
4492
+ return;
4493
+ }
4535
4494
 
4536
4495
  // Buffer any update calls so that renders do not occur
4537
4496
  me._bufferedRender = true;
4538
4497
  me._bufferedRequest = null;
4539
4498
 
4540
4499
  var changed = me.handleEvent(e);
4541
- changed |= legend && legend.handleEvent(e);
4542
4500
  changed |= tooltip && tooltip.handleEvent(e);
4543
4501
 
4502
+ plugins.notify(me, 'afterEvent', [e]);
4503
+
4544
4504
  var bufferedRequest = me._bufferedRequest;
4545
4505
  if (bufferedRequest) {
4546
4506
  // If we have an update that was triggered, we need to do a normal render
@@ -4551,7 +4511,7 @@ module.exports = function(Chart) {
4551
4511
 
4552
4512
  // We only need to render at this point. Updating will cause scales to be
4553
4513
  // recomputed generating flicker & using more memory than necessary.
4554
- me.render(hoverOptions.animationDuration, true);
4514
+ me.render(me.options.hover.animationDuration, true);
4555
4515
  }
4556
4516
 
4557
4517
  me._bufferedRender = false;
@@ -4563,7 +4523,7 @@ module.exports = function(Chart) {
4563
4523
  /**
4564
4524
  * Handle an event
4565
4525
  * @private
4566
- * param e {Event} the event to handle
4526
+ * @param {IEvent} event the event to handle
4567
4527
  * @return {Boolean} true if the chart needs to re-render
4568
4528
  */
4569
4529
  handleEvent: function(e) {
@@ -4583,12 +4543,14 @@ module.exports = function(Chart) {
4583
4543
 
4584
4544
  // On Hover hook
4585
4545
  if (hoverOptions.onHover) {
4586
- hoverOptions.onHover.call(me, me.active);
4546
+ // Need to call with native event here to not break backwards compatibility
4547
+ hoverOptions.onHover.call(me, e.native, me.active);
4587
4548
  }
4588
4549
 
4589
4550
  if (e.type === 'mouseup' || e.type === 'click') {
4590
4551
  if (options.onClick) {
4591
- options.onClick.call(me, e, me.active);
4552
+ // Use e.native here for backwards compatibility
4553
+ options.onClick.call(me, e.native, me.active);
4592
4554
  }
4593
4555
  }
4594
4556
 
@@ -5261,6 +5223,10 @@ module.exports = function(Chart) {
5261
5223
  helpers.almostEquals = function(x, y, epsilon) {
5262
5224
  return Math.abs(x - y) < epsilon;
5263
5225
  };
5226
+ helpers.almostWhole = function(x, epsilon) {
5227
+ var rounded = Math.round(x);
5228
+ return (((rounded - epsilon) < x) && ((rounded + epsilon) > x));
5229
+ };
5264
5230
  helpers.max = function(array) {
5265
5231
  return array.reduce(function(max, value) {
5266
5232
  if (!isNaN(value)) {
@@ -5385,7 +5351,10 @@ module.exports = function(Chart) {
5385
5351
  pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
5386
5352
  pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
5387
5353
  if (pointAfter && !pointAfter.model.skip) {
5388
- pointCurrent.deltaK = (pointAfter.model.y - pointCurrent.model.y) / (pointAfter.model.x - pointCurrent.model.x);
5354
+ var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x);
5355
+
5356
+ // In the case of two points that appear at the same x pixel, slopeDeltaX is 0
5357
+ pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0;
5389
5358
  }
5390
5359
 
5391
5360
  if (!pointBefore || pointBefore.model.skip) {
@@ -5695,16 +5664,6 @@ module.exports = function(Chart) {
5695
5664
  return window.setTimeout(callback, 1000 / 60);
5696
5665
  };
5697
5666
  }());
5698
- helpers.cancelAnimFrame = (function() {
5699
- return window.cancelAnimationFrame ||
5700
- window.webkitCancelAnimationFrame ||
5701
- window.mozCancelAnimationFrame ||
5702
- window.oCancelAnimationFrame ||
5703
- window.msCancelAnimationFrame ||
5704
- function(callback) {
5705
- return window.clearTimeout(callback, 1000 / 60);
5706
- };
5707
- }());
5708
5667
  // -- DOM methods
5709
5668
  helpers.getRelativePosition = function(evt, chart) {
5710
5669
  var mouseX, mouseY;
@@ -5761,23 +5720,6 @@ module.exports = function(Chart) {
5761
5720
  node['on' + eventType] = helpers.noop;
5762
5721
  }
5763
5722
  };
5764
- helpers.bindEvents = function(chartInstance, arrayOfEvents, handler) {
5765
- // Create the events object if it's not already present
5766
- var events = chartInstance.events = chartInstance.events || {};
5767
-
5768
- helpers.each(arrayOfEvents, function(eventName) {
5769
- events[eventName] = function() {
5770
- handler.apply(chartInstance, arguments);
5771
- };
5772
- helpers.addEvent(chartInstance.chart.canvas, eventName, events[eventName]);
5773
- });
5774
- };
5775
- helpers.unbindEvents = function(chartInstance, arrayOfEvents) {
5776
- var canvas = chartInstance.chart.canvas;
5777
- helpers.each(arrayOfEvents, function(handler, eventName) {
5778
- helpers.removeEvent(canvas, eventName, handler);
5779
- });
5780
- };
5781
5723
 
5782
5724
  // Private helper function to convert max-width/max-height values that may be percentages into a number
5783
5725
  function parseMaxStyle(styleValue, node, parentProperty) {
@@ -5968,73 +5910,6 @@ module.exports = function(Chart) {
5968
5910
 
5969
5911
  return color(c);
5970
5912
  };
5971
- helpers.addResizeListener = function(node, callback) {
5972
- var iframe = document.createElement('iframe');
5973
- iframe.className = 'chartjs-hidden-iframe';
5974
- iframe.style.cssText =
5975
- 'display:block;'+
5976
- 'overflow:hidden;'+
5977
- 'border:0;'+
5978
- 'margin:0;'+
5979
- 'top:0;'+
5980
- 'left:0;'+
5981
- 'bottom:0;'+
5982
- 'right:0;'+
5983
- 'height:100%;'+
5984
- 'width:100%;'+
5985
- 'position:absolute;'+
5986
- 'pointer-events:none;'+
5987
- 'z-index:-1;';
5988
-
5989
- // Prevent the iframe to gain focus on tab.
5990
- // https://github.com/chartjs/Chart.js/issues/3090
5991
- iframe.tabIndex = -1;
5992
-
5993
- // Let's keep track of this added iframe and thus avoid DOM query when removing it.
5994
- var stub = node._chartjs = {
5995
- resizer: iframe,
5996
- ticking: false
5997
- };
5998
-
5999
- // Throttle the callback notification until the next animation frame.
6000
- var notify = function() {
6001
- if (!stub.ticking) {
6002
- stub.ticking = true;
6003
- helpers.requestAnimFrame.call(window, function() {
6004
- if (stub.resizer) {
6005
- stub.ticking = false;
6006
- return callback();
6007
- }
6008
- });
6009
- }
6010
- };
6011
-
6012
- // If the iframe is re-attached to the DOM, the resize listener is removed because the
6013
- // content is reloaded, so make sure to install the handler after the iframe is loaded.
6014
- // https://github.com/chartjs/Chart.js/issues/3521
6015
- helpers.addEvent(iframe, 'load', function() {
6016
- helpers.addEvent(iframe.contentWindow || iframe, 'resize', notify);
6017
-
6018
- // The iframe size might have changed while loading, which can also
6019
- // happen if the size has been changed while detached from the DOM.
6020
- notify();
6021
- });
6022
-
6023
- node.insertBefore(iframe, node.firstChild);
6024
- };
6025
- helpers.removeResizeListener = function(node) {
6026
- if (!node || !node._chartjs) {
6027
- return;
6028
- }
6029
-
6030
- var iframe = node._chartjs.resizer;
6031
- if (iframe) {
6032
- iframe.parentNode.removeChild(iframe);
6033
- node._chartjs.resizer = null;
6034
- }
6035
-
6036
- delete node._chartjs;
6037
- };
6038
5913
  helpers.isArray = Array.isArray?
6039
5914
  function(obj) {
6040
5915
  return Array.isArray(obj);
@@ -6085,6 +5960,23 @@ module.exports = function(Chart) {
6085
5960
  module.exports = function(Chart) {
6086
5961
  var helpers = Chart.helpers;
6087
5962
 
5963
+ /**
5964
+ * Helper function to get relative position for an event
5965
+ * @param {Event|IEvent} event - The event to get the position for
5966
+ * @param {Chart} chart - The chart
5967
+ * @returns {Point} the event position
5968
+ */
5969
+ function getRelativePosition(e, chart) {
5970
+ if (e.native) {
5971
+ return {
5972
+ x: e.x,
5973
+ y: e.y
5974
+ };
5975
+ }
5976
+
5977
+ return helpers.getRelativePosition(e, chart);
5978
+ }
5979
+
6088
5980
  /**
6089
5981
  * Helper function to traverse all of the visible elements in the chart
6090
5982
  * @param chart {chart} the chart
@@ -6164,7 +6056,7 @@ module.exports = function(Chart) {
6164
6056
  }
6165
6057
 
6166
6058
  function indexMode(chart, e, options) {
6167
- var position = helpers.getRelativePosition(e, chart.chart);
6059
+ var position = getRelativePosition(e, chart.chart);
6168
6060
  var distanceMetric = function(pt1, pt2) {
6169
6061
  return Math.abs(pt1.x - pt2.x);
6170
6062
  };
@@ -6200,14 +6092,14 @@ module.exports = function(Chart) {
6200
6092
  */
6201
6093
 
6202
6094
  /**
6203
- * @namespace Chart.Interaction
6204
6095
  * Contains interaction related functions
6096
+ * @namespace Chart.Interaction
6205
6097
  */
6206
6098
  Chart.Interaction = {
6207
6099
  // Helper function for different modes
6208
6100
  modes: {
6209
6101
  single: function(chart, e) {
6210
- var position = helpers.getRelativePosition(e, chart.chart);
6102
+ var position = getRelativePosition(e, chart.chart);
6211
6103
  var elements = [];
6212
6104
 
6213
6105
  parseVisibleItems(chart, function(element) {
@@ -6248,7 +6140,7 @@ module.exports = function(Chart) {
6248
6140
  * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6249
6141
  */
6250
6142
  dataset: function(chart, e, options) {
6251
- var position = helpers.getRelativePosition(e, chart.chart);
6143
+ var position = getRelativePosition(e, chart.chart);
6252
6144
  var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false);
6253
6145
 
6254
6146
  if (items.length > 0) {
@@ -6275,7 +6167,7 @@ module.exports = function(Chart) {
6275
6167
  * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6276
6168
  */
6277
6169
  point: function(chart, e) {
6278
- var position = helpers.getRelativePosition(e, chart.chart);
6170
+ var position = getRelativePosition(e, chart.chart);
6279
6171
  return getIntersectItems(chart, position);
6280
6172
  },
6281
6173
 
@@ -6288,7 +6180,7 @@ module.exports = function(Chart) {
6288
6180
  * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6289
6181
  */
6290
6182
  nearest: function(chart, e, options) {
6291
- var position = helpers.getRelativePosition(e, chart.chart);
6183
+ var position = getRelativePosition(e, chart.chart);
6292
6184
  var nearestItems = getNearestItems(chart, position, options.intersect);
6293
6185
 
6294
6186
  // We have multiple items at the same distance from the event. Now sort by smallest
@@ -6320,7 +6212,7 @@ module.exports = function(Chart) {
6320
6212
  * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6321
6213
  */
6322
6214
  x: function(chart, e, options) {
6323
- var position = helpers.getRelativePosition(e, chart.chart);
6215
+ var position = getRelativePosition(e, chart.chart);
6324
6216
  var items = [];
6325
6217
  var intersectsItem = false;
6326
6218
 
@@ -6351,7 +6243,7 @@ module.exports = function(Chart) {
6351
6243
  * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6352
6244
  */
6353
6245
  y: function(chart, e, options) {
6354
- var position = helpers.getRelativePosition(e, chart.chart);
6246
+ var position = getRelativePosition(e, chart.chart);
6355
6247
  var items = [];
6356
6248
  var intersectsItem = false;
6357
6249
 
@@ -6585,15 +6477,36 @@ module.exports = function(Chart) {
6585
6477
  minBoxSizes.push({
6586
6478
  horizontal: isHorizontal,
6587
6479
  minSize: minSize,
6588
- box: box
6480
+ box: box,
6589
6481
  });
6590
6482
  }
6591
6483
 
6592
6484
  helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize);
6593
6485
 
6486
+ // If a horizontal box has padding, we move the left boxes over to avoid ugly charts (see issue #2478)
6487
+ var maxHorizontalLeftPadding = 0;
6488
+ var maxHorizontalRightPadding = 0;
6489
+ var maxVerticalTopPadding = 0;
6490
+ var maxVerticalBottomPadding = 0;
6491
+
6492
+ helpers.each(topBoxes.concat(bottomBoxes), function(horizontalBox) {
6493
+ if (horizontalBox.getPadding) {
6494
+ var boxPadding = horizontalBox.getPadding();
6495
+ maxHorizontalLeftPadding = Math.max(maxHorizontalLeftPadding, boxPadding.left);
6496
+ maxHorizontalRightPadding = Math.max(maxHorizontalRightPadding, boxPadding.right);
6497
+ }
6498
+ });
6499
+
6500
+ helpers.each(leftBoxes.concat(rightBoxes), function(verticalBox) {
6501
+ if (verticalBox.getPadding) {
6502
+ var boxPadding = verticalBox.getPadding();
6503
+ maxVerticalTopPadding = Math.max(maxVerticalTopPadding, boxPadding.top);
6504
+ maxVerticalBottomPadding = Math.max(maxVerticalBottomPadding, boxPadding.bottom);
6505
+ }
6506
+ });
6507
+
6594
6508
  // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could
6595
6509
  // be if the axes are drawn at their minimum sizes.
6596
-
6597
6510
  // Steps 5 & 6
6598
6511
  var totalLeftBoxesWidth = leftPadding;
6599
6512
  var totalRightBoxesWidth = rightPadding;
@@ -6609,8 +6522,8 @@ module.exports = function(Chart) {
6609
6522
  if (minBoxSize) {
6610
6523
  if (box.isHorizontal()) {
6611
6524
  var scaleMargin = {
6612
- left: totalLeftBoxesWidth,
6613
- right: totalRightBoxesWidth,
6525
+ left: Math.max(totalLeftBoxesWidth, maxHorizontalLeftPadding),
6526
+ right: Math.max(totalRightBoxesWidth, maxHorizontalRightPadding),
6614
6527
  top: 0,
6615
6528
  bottom: 0
6616
6529
  };
@@ -6688,6 +6601,15 @@ module.exports = function(Chart) {
6688
6601
  totalBottomBoxesHeight += box.height;
6689
6602
  });
6690
6603
 
6604
+ // We may be adding some padding to account for rotated x axis labels
6605
+ var leftPaddingAddition = Math.max(maxHorizontalLeftPadding - totalLeftBoxesWidth, 0);
6606
+ totalLeftBoxesWidth += leftPaddingAddition;
6607
+ totalRightBoxesWidth += Math.max(maxHorizontalRightPadding - totalRightBoxesWidth, 0);
6608
+
6609
+ var topPaddingAddition = Math.max(maxVerticalTopPadding - totalTopBoxesHeight, 0);
6610
+ totalTopBoxesHeight += topPaddingAddition;
6611
+ totalBottomBoxesHeight += Math.max(maxVerticalBottomPadding - totalBottomBoxesHeight, 0);
6612
+
6691
6613
  // Figure out if our chart area changed. This would occur if the dataset layout label rotation
6692
6614
  // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
6693
6615
  // without calling `fit` again
@@ -6720,8 +6642,8 @@ module.exports = function(Chart) {
6720
6642
  }
6721
6643
 
6722
6644
  // Step 7 - Position the boxes
6723
- var left = leftPadding;
6724
- var top = topPadding;
6645
+ var left = leftPadding + leftPaddingAddition;
6646
+ var top = topPadding + topPaddingAddition;
6725
6647
 
6726
6648
  function placeBox(box) {
6727
6649
  if (box.isHorizontal()) {
@@ -6940,10 +6862,20 @@ module.exports = function(Chart) {
6940
6862
  beforeBuildLabels: noop,
6941
6863
  buildLabels: function() {
6942
6864
  var me = this;
6943
- me.legendItems = me.options.labels.generateLabels.call(me, me.chart);
6865
+ var labelOpts = me.options.labels;
6866
+ var legendItems = labelOpts.generateLabels.call(me, me.chart);
6867
+
6868
+ if (labelOpts.filter) {
6869
+ legendItems = legendItems.filter(function(item) {
6870
+ return labelOpts.filter(item, me.chart.data);
6871
+ });
6872
+ }
6873
+
6944
6874
  if (me.options.reverse) {
6945
- me.legendItems.reverse();
6875
+ legendItems.reverse();
6946
6876
  }
6877
+
6878
+ me.legendItems = legendItems;
6947
6879
  },
6948
6880
  afterBuildLabels: noop,
6949
6881
 
@@ -7182,7 +7114,7 @@ module.exports = function(Chart) {
7182
7114
  }
7183
7115
  } else if (y + itemHeight > me.bottom) {
7184
7116
  x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
7185
- y = cursor.y = me.top;
7117
+ y = cursor.y = me.top + labelOpts.padding;
7186
7118
  cursor.line++;
7187
7119
  }
7188
7120
 
@@ -7207,7 +7139,7 @@ module.exports = function(Chart) {
7207
7139
  /**
7208
7140
  * Handle an event
7209
7141
  * @private
7210
- * @param e {Event} the event to handle
7142
+ * @param {IEvent} event - The event to handle
7211
7143
  * @return {Boolean} true if a change occured
7212
7144
  */
7213
7145
  handleEvent: function(e) {
@@ -7228,9 +7160,9 @@ module.exports = function(Chart) {
7228
7160
  return;
7229
7161
  }
7230
7162
 
7231
- var position = helpers.getRelativePosition(e, me.chart.chart),
7232
- x = position.x,
7233
- y = position.y;
7163
+ // Chart event already has relative position in it
7164
+ var x = e.x,
7165
+ y = e.y;
7234
7166
 
7235
7167
  if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
7236
7168
  // See if we are touching one of the dataset boxes
@@ -7241,11 +7173,13 @@ module.exports = function(Chart) {
7241
7173
  if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
7242
7174
  // Touching an element
7243
7175
  if (type === 'click') {
7244
- opts.onClick.call(me, e, me.legendItems[i]);
7176
+ // use e.native for backwards compatibility
7177
+ opts.onClick.call(me, e.native, me.legendItems[i]);
7245
7178
  changed = true;
7246
7179
  break;
7247
7180
  } else if (type === 'mousemove') {
7248
- opts.onHover.call(me, e, me.legendItems[i]);
7181
+ // use e.native for backwards compatibility
7182
+ opts.onHover.call(me, e.native, me.legendItems[i]);
7249
7183
  changed = true;
7250
7184
  break;
7251
7185
  }
@@ -7257,20 +7191,45 @@ module.exports = function(Chart) {
7257
7191
  }
7258
7192
  });
7259
7193
 
7194
+ function createNewLegendAndAttach(chartInstance, legendOpts) {
7195
+ var legend = new Chart.Legend({
7196
+ ctx: chartInstance.chart.ctx,
7197
+ options: legendOpts,
7198
+ chart: chartInstance
7199
+ });
7200
+ chartInstance.legend = legend;
7201
+ Chart.layoutService.addBox(chartInstance, legend);
7202
+ }
7203
+
7260
7204
  // Register the legend plugin
7261
7205
  Chart.plugins.register({
7262
7206
  beforeInit: function(chartInstance) {
7263
- var opts = chartInstance.options;
7264
- var legendOpts = opts.legend;
7207
+ var legendOpts = chartInstance.options.legend;
7265
7208
 
7266
7209
  if (legendOpts) {
7267
- chartInstance.legend = new Chart.Legend({
7268
- ctx: chartInstance.chart.ctx,
7269
- options: legendOpts,
7270
- chart: chartInstance
7271
- });
7210
+ createNewLegendAndAttach(chartInstance, legendOpts);
7211
+ }
7212
+ },
7213
+ beforeUpdate: function(chartInstance) {
7214
+ var legendOpts = chartInstance.options.legend;
7272
7215
 
7273
- Chart.layoutService.addBox(chartInstance, chartInstance.legend);
7216
+ if (legendOpts) {
7217
+ legendOpts = helpers.configMerge(Chart.defaults.global.legend, legendOpts);
7218
+
7219
+ if (chartInstance.legend) {
7220
+ chartInstance.legend.options = legendOpts;
7221
+ } else {
7222
+ createNewLegendAndAttach(chartInstance, legendOpts);
7223
+ }
7224
+ } else {
7225
+ Chart.layoutService.removeBox(chartInstance, chartInstance.legend);
7226
+ delete chartInstance.legend;
7227
+ }
7228
+ },
7229
+ afterEvent: function(chartInstance, e) {
7230
+ var legend = chartInstance.legend;
7231
+ if (legend) {
7232
+ legend.handleEvent(e);
7274
7233
  }
7275
7234
  }
7276
7235
  });
@@ -7281,7 +7240,9 @@ module.exports = function(Chart) {
7281
7240
 
7282
7241
  module.exports = function(Chart) {
7283
7242
 
7284
- var noop = Chart.helpers.noop;
7243
+ var helpers = Chart.helpers;
7244
+
7245
+ Chart.defaults.global.plugins = {};
7285
7246
 
7286
7247
  /**
7287
7248
  * The plugin service singleton
@@ -7289,8 +7250,20 @@ module.exports = function(Chart) {
7289
7250
  * @since 2.1.0
7290
7251
  */
7291
7252
  Chart.plugins = {
7253
+ /**
7254
+ * Globally registered plugins.
7255
+ * @private
7256
+ */
7292
7257
  _plugins: [],
7293
7258
 
7259
+ /**
7260
+ * This identifier is used to invalidate the descriptors cache attached to each chart
7261
+ * when a global plugin is registered or unregistered. In this case, the cache ID is
7262
+ * incremented and descriptors are regenerated during following API calls.
7263
+ * @private
7264
+ */
7265
+ _cacheId: 0,
7266
+
7294
7267
  /**
7295
7268
  * Registers the given plugin(s) if not already registered.
7296
7269
  * @param {Array|Object} plugins plugin instance(s).
@@ -7302,6 +7275,8 @@ module.exports = function(Chart) {
7302
7275
  p.push(plugin);
7303
7276
  }
7304
7277
  });
7278
+
7279
+ this._cacheId++;
7305
7280
  },
7306
7281
 
7307
7282
  /**
@@ -7316,6 +7291,8 @@ module.exports = function(Chart) {
7316
7291
  p.splice(idx, 1);
7317
7292
  }
7318
7293
  });
7294
+
7295
+ this._cacheId++;
7319
7296
  },
7320
7297
 
7321
7298
  /**
@@ -7324,6 +7301,7 @@ module.exports = function(Chart) {
7324
7301
  */
7325
7302
  clear: function() {
7326
7303
  this._plugins = [];
7304
+ this._cacheId++;
7327
7305
  },
7328
7306
 
7329
7307
  /**
@@ -7345,66 +7323,243 @@ module.exports = function(Chart) {
7345
7323
  },
7346
7324
 
7347
7325
  /**
7348
- * Calls registered plugins on the specified extension, with the given args. This
7349
- * method immediately returns as soon as a plugin explicitly returns false. The
7326
+ * Calls enabled plugins for `chart` on the specified hook and with the given args.
7327
+ * This method immediately returns as soon as a plugin explicitly returns false. The
7350
7328
  * returned value can be used, for instance, to interrupt the current action.
7351
- * @param {String} extension the name of the plugin method to call (e.g. 'beforeUpdate').
7352
- * @param {Array} [args] extra arguments to apply to the extension call.
7329
+ * @param {Object} chart - The chart instance for which plugins should be called.
7330
+ * @param {String} hook - The name of the plugin method to call (e.g. 'beforeUpdate').
7331
+ * @param {Array} [args] - Extra arguments to apply to the hook call.
7353
7332
  * @returns {Boolean} false if any of the plugins return false, else returns true.
7354
7333
  */
7355
- notify: function(extension, args) {
7356
- var plugins = this._plugins;
7357
- var ilen = plugins.length;
7358
- var i, plugin;
7334
+ notify: function(chart, hook, args) {
7335
+ var descriptors = this.descriptors(chart);
7336
+ var ilen = descriptors.length;
7337
+ var i, descriptor, plugin, params, method;
7359
7338
 
7360
7339
  for (i=0; i<ilen; ++i) {
7361
- plugin = plugins[i];
7362
- if (typeof plugin[extension] === 'function') {
7363
- if (plugin[extension].apply(plugin, args || []) === false) {
7340
+ descriptor = descriptors[i];
7341
+ plugin = descriptor.plugin;
7342
+ method = plugin[hook];
7343
+ if (typeof method === 'function') {
7344
+ params = [chart].concat(args || []);
7345
+ params.push(descriptor.options);
7346
+ if (method.apply(plugin, params) === false) {
7364
7347
  return false;
7365
7348
  }
7366
7349
  }
7367
7350
  }
7368
7351
 
7369
7352
  return true;
7370
- }
7371
- };
7353
+ },
7372
7354
 
7373
- /**
7374
- * Plugin extension methods.
7375
- * @interface Chart.PluginBase
7376
- * @since 2.1.0
7377
- */
7378
- Chart.PluginBase = Chart.Element.extend({
7379
- // Called at start of chart init
7380
- beforeInit: noop,
7355
+ /**
7356
+ * Returns descriptors of enabled plugins for the given chart.
7357
+ * @returns {Array} [{ plugin, options }]
7358
+ * @private
7359
+ */
7360
+ descriptors: function(chart) {
7361
+ var cache = chart._plugins || (chart._plugins = {});
7362
+ if (cache.id === this._cacheId) {
7363
+ return cache.descriptors;
7364
+ }
7381
7365
 
7382
- // Called at end of chart init
7383
- afterInit: noop,
7366
+ var plugins = [];
7367
+ var descriptors = [];
7368
+ var config = (chart && chart.config) || {};
7369
+ var defaults = Chart.defaults.global.plugins;
7370
+ var options = (config.options && config.options.plugins) || {};
7384
7371
 
7385
- // Called at start of update
7386
- beforeUpdate: noop,
7372
+ this._plugins.concat(config.plugins || []).forEach(function(plugin) {
7373
+ var idx = plugins.indexOf(plugin);
7374
+ if (idx !== -1) {
7375
+ return;
7376
+ }
7387
7377
 
7388
- // Called at end of update
7389
- afterUpdate: noop,
7378
+ var id = plugin.id;
7379
+ var opts = options[id];
7380
+ if (opts === false) {
7381
+ return;
7382
+ }
7390
7383
 
7391
- // Called at start of draw
7392
- beforeDraw: noop,
7384
+ if (opts === true) {
7385
+ opts = helpers.clone(defaults[id]);
7386
+ }
7393
7387
 
7394
- // Called at end of draw
7395
- afterDraw: noop,
7388
+ plugins.push(plugin);
7389
+ descriptors.push({
7390
+ plugin: plugin,
7391
+ options: opts || {}
7392
+ });
7393
+ });
7396
7394
 
7397
- // Called during destroy
7398
- destroy: noop
7399
- });
7395
+ cache.descriptors = descriptors;
7396
+ cache.id = this._cacheId;
7397
+ return descriptors;
7398
+ }
7399
+ };
7400
+
7401
+ /**
7402
+ * Plugin extension hooks.
7403
+ * @interface IPlugin
7404
+ * @since 2.1.0
7405
+ */
7406
+ /**
7407
+ * @method IPlugin#beforeInit
7408
+ * @desc Called before initializing `chart`.
7409
+ * @param {Chart.Controller} chart - The chart instance.
7410
+ * @param {Object} options - The plugin options.
7411
+ */
7412
+ /**
7413
+ * @method IPlugin#afterInit
7414
+ * @desc Called after `chart` has been initialized and before the first update.
7415
+ * @param {Chart.Controller} chart - The chart instance.
7416
+ * @param {Object} options - The plugin options.
7417
+ */
7418
+ /**
7419
+ * @method IPlugin#beforeUpdate
7420
+ * @desc Called before updating `chart`. If any plugin returns `false`, the update
7421
+ * is cancelled (and thus subsequent render(s)) until another `update` is triggered.
7422
+ * @param {Chart.Controller} chart - The chart instance.
7423
+ * @param {Object} options - The plugin options.
7424
+ * @returns {Boolean} `false` to cancel the chart update.
7425
+ */
7426
+ /**
7427
+ * @method IPlugin#afterUpdate
7428
+ * @desc Called after `chart` has been updated and before rendering. Note that this
7429
+ * hook will not be called if the chart update has been previously cancelled.
7430
+ * @param {Chart.Controller} chart - The chart instance.
7431
+ * @param {Object} options - The plugin options.
7432
+ */
7433
+ /**
7434
+ * @method IPlugin#beforeDatasetsUpdate
7435
+ * @desc Called before updating the `chart` datasets. If any plugin returns `false`,
7436
+ * the datasets update is cancelled until another `update` is triggered.
7437
+ * @param {Chart.Controller} chart - The chart instance.
7438
+ * @param {Object} options - The plugin options.
7439
+ * @returns {Boolean} false to cancel the datasets update.
7440
+ * @since version 2.1.5
7441
+ */
7442
+ /**
7443
+ * @method IPlugin#afterDatasetsUpdate
7444
+ * @desc Called after the `chart` datasets have been updated. Note that this hook
7445
+ * will not be called if the datasets update has been previously cancelled.
7446
+ * @param {Chart.Controller} chart - The chart instance.
7447
+ * @param {Object} options - The plugin options.
7448
+ * @since version 2.1.5
7449
+ */
7450
+ /**
7451
+ * @method IPlugin#beforeLayout
7452
+ * @desc Called before laying out `chart`. If any plugin returns `false`,
7453
+ * the layout update is cancelled until another `update` is triggered.
7454
+ * @param {Chart.Controller} chart - The chart instance.
7455
+ * @param {Object} options - The plugin options.
7456
+ * @returns {Boolean} `false` to cancel the chart layout.
7457
+ */
7458
+ /**
7459
+ * @method IPlugin#afterLayout
7460
+ * @desc Called after the `chart` has been layed out. Note that this hook will not
7461
+ * be called if the layout update has been previously cancelled.
7462
+ * @param {Chart.Controller} chart - The chart instance.
7463
+ * @param {Object} options - The plugin options.
7464
+ */
7465
+ /**
7466
+ * @method IPlugin#beforeRender
7467
+ * @desc Called before rendering `chart`. If any plugin returns `false`,
7468
+ * the rendering is cancelled until another `render` is triggered.
7469
+ * @param {Chart.Controller} chart - The chart instance.
7470
+ * @param {Object} options - The plugin options.
7471
+ * @returns {Boolean} `false` to cancel the chart rendering.
7472
+ */
7473
+ /**
7474
+ * @method IPlugin#afterRender
7475
+ * @desc Called after the `chart` has been fully rendered (and animation completed). Note
7476
+ * that this hook will not be called if the rendering has been previously cancelled.
7477
+ * @param {Chart.Controller} chart - The chart instance.
7478
+ * @param {Object} options - The plugin options.
7479
+ */
7480
+ /**
7481
+ * @method IPlugin#beforeDraw
7482
+ * @desc Called before drawing `chart` at every animation frame specified by the given
7483
+ * easing value. If any plugin returns `false`, the frame drawing is cancelled until
7484
+ * another `render` is triggered.
7485
+ * @param {Chart.Controller} chart - The chart instance.
7486
+ * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
7487
+ * @param {Object} options - The plugin options.
7488
+ * @returns {Boolean} `false` to cancel the chart drawing.
7489
+ */
7490
+ /**
7491
+ * @method IPlugin#afterDraw
7492
+ * @desc Called after the `chart` has been drawn for the specific easing value. Note
7493
+ * that this hook will not be called if the drawing has been previously cancelled.
7494
+ * @param {Chart.Controller} chart - The chart instance.
7495
+ * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
7496
+ * @param {Object} options - The plugin options.
7497
+ */
7498
+ /**
7499
+ * @method IPlugin#beforeDatasetsDraw
7500
+ * @desc Called before drawing the `chart` datasets. If any plugin returns `false`,
7501
+ * the datasets drawing is cancelled until another `render` is triggered.
7502
+ * @param {Chart.Controller} chart - The chart instance.
7503
+ * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
7504
+ * @param {Object} options - The plugin options.
7505
+ * @returns {Boolean} `false` to cancel the chart datasets drawing.
7506
+ */
7507
+ /**
7508
+ * @method IPlugin#afterDatasetsDraw
7509
+ * @desc Called after the `chart` datasets have been drawn. Note that this hook
7510
+ * will not be called if the datasets drawing has been previously cancelled.
7511
+ * @param {Chart.Controller} chart - The chart instance.
7512
+ * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
7513
+ * @param {Object} options - The plugin options.
7514
+ */
7515
+ /**
7516
+ * @method IPlugin#beforeEvent
7517
+ * @desc Called before processing the specified `event`. If any plugin returns `false`,
7518
+ * the event will be discarded.
7519
+ * @param {Chart.Controller} chart - The chart instance.
7520
+ * @param {IEvent} event - The event object.
7521
+ * @param {Object} options - The plugin options.
7522
+ */
7523
+ /**
7524
+ * @method IPlugin#afterEvent
7525
+ * @desc Called after the `event` has been consumed. Note that this hook
7526
+ * will not be called if the `event` has been previously discarded.
7527
+ * @param {Chart.Controller} chart - The chart instance.
7528
+ * @param {IEvent} event - The event object.
7529
+ * @param {Object} options - The plugin options.
7530
+ */
7531
+ /**
7532
+ * @method IPlugin#resize
7533
+ * @desc Called after the chart as been resized.
7534
+ * @param {Chart.Controller} chart - The chart instance.
7535
+ * @param {Number} size - The new canvas display size (eq. canvas.style width & height).
7536
+ * @param {Object} options - The plugin options.
7537
+ */
7538
+ /**
7539
+ * @method IPlugin#destroy
7540
+ * @desc Called after the chart as been destroyed.
7541
+ * @param {Chart.Controller} chart - The chart instance.
7542
+ * @param {Object} options - The plugin options.
7543
+ */
7400
7544
 
7401
7545
  /**
7402
7546
  * Provided for backward compatibility, use Chart.plugins instead
7403
7547
  * @namespace Chart.pluginService
7404
7548
  * @deprecated since version 2.1.5
7405
- * @todo remove me at version 3
7549
+ * @todo remove at version 3
7550
+ * @private
7406
7551
  */
7407
7552
  Chart.pluginService = Chart.plugins;
7553
+
7554
+ /**
7555
+ * Provided for backward compatibility, inheriting from Chart.PlugingBase has no
7556
+ * effect, instead simply create/register plugins via plain JavaScript objects.
7557
+ * @interface Chart.PluginBase
7558
+ * @deprecated since version 2.5.0
7559
+ * @todo remove at version 3
7560
+ * @private
7561
+ */
7562
+ Chart.PluginBase = helpers.inherits({});
7408
7563
  };
7409
7564
 
7410
7565
  },{}],32:[function(require,module,exports){
@@ -7449,7 +7604,7 @@ module.exports = function(Chart) {
7449
7604
  minRotation: 0,
7450
7605
  maxRotation: 50,
7451
7606
  mirror: false,
7452
- padding: 10,
7607
+ padding: 0,
7453
7608
  reverse: false,
7454
7609
  display: true,
7455
7610
  autoSkip: true,
@@ -7460,9 +7615,45 @@ module.exports = function(Chart) {
7460
7615
  }
7461
7616
  };
7462
7617
 
7618
+ function computeTextSize(context, tick, font) {
7619
+ return helpers.isArray(tick) ?
7620
+ helpers.longestText(context, font, tick) :
7621
+ context.measureText(tick).width;
7622
+ }
7623
+
7624
+ function parseFontOptions(options) {
7625
+ var getValueOrDefault = helpers.getValueOrDefault;
7626
+ var globalDefaults = Chart.defaults.global;
7627
+ var size = getValueOrDefault(options.fontSize, globalDefaults.defaultFontSize);
7628
+ var style = getValueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle);
7629
+ var family = getValueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily);
7630
+
7631
+ return {
7632
+ size: size,
7633
+ style: style,
7634
+ family: family,
7635
+ font: helpers.fontString(size, style, family)
7636
+ };
7637
+ }
7638
+
7463
7639
  Chart.Scale = Chart.Element.extend({
7640
+ /**
7641
+ * Get the padding needed for the scale
7642
+ * @method getPadding
7643
+ * @private
7644
+ * @returns {Padding} the necessary padding
7645
+ */
7646
+ getPadding: function() {
7647
+ var me = this;
7648
+ return {
7649
+ left: me.paddingLeft || 0,
7650
+ top: me.paddingTop || 0,
7651
+ right: me.paddingRight || 0,
7652
+ bottom: me.paddingBottom || 0
7653
+ };
7654
+ },
7464
7655
 
7465
- // These methods are ordered by lifecycle. Utilities then follow.
7656
+ // These methods are ordered by lifecyle. Utilities then follow.
7466
7657
  // Any function defined here is inherited by all scale types.
7467
7658
  // Any function can be extended by the scale type
7468
7659
 
@@ -7484,6 +7675,7 @@ module.exports = function(Chart) {
7484
7675
  top: 0,
7485
7676
  bottom: 0
7486
7677
  }, margins);
7678
+ me.longestTextCache = me.longestTextCache || {};
7487
7679
 
7488
7680
  // Dimensions
7489
7681
  me.beforeSetDimensions();
@@ -7592,72 +7784,42 @@ module.exports = function(Chart) {
7592
7784
  calculateTickRotation: function() {
7593
7785
  var me = this;
7594
7786
  var context = me.ctx;
7595
- var globalDefaults = Chart.defaults.global;
7596
- var optionTicks = me.options.ticks;
7787
+ var tickOpts = me.options.ticks;
7597
7788
 
7598
7789
  // Get the width of each grid by calculating the difference
7599
7790
  // between x offsets between 0 and 1.
7600
- var tickFontSize = helpers.getValueOrDefault(optionTicks.fontSize, globalDefaults.defaultFontSize);
7601
- var tickFontStyle = helpers.getValueOrDefault(optionTicks.fontStyle, globalDefaults.defaultFontStyle);
7602
- var tickFontFamily = helpers.getValueOrDefault(optionTicks.fontFamily, globalDefaults.defaultFontFamily);
7603
- var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
7604
- context.font = tickLabelFont;
7791
+ var tickFont = parseFontOptions(tickOpts);
7792
+ context.font = tickFont.font;
7605
7793
 
7606
- var firstWidth = context.measureText(me.ticks[0]).width;
7607
- var lastWidth = context.measureText(me.ticks[me.ticks.length - 1]).width;
7608
- var firstRotated;
7794
+ var labelRotation = tickOpts.minRotation || 0;
7609
7795
 
7610
- me.labelRotation = optionTicks.minRotation || 0;
7611
- me.paddingRight = 0;
7612
- me.paddingLeft = 0;
7796
+ if (me.options.display && me.isHorizontal()) {
7797
+ var originalLabelWidth = helpers.longestText(context, tickFont.font, me.ticks, me.longestTextCache);
7798
+ var labelWidth = originalLabelWidth;
7799
+ var cosRotation;
7800
+ var sinRotation;
7613
7801
 
7614
- if (me.options.display) {
7615
- if (me.isHorizontal()) {
7616
- me.paddingRight = lastWidth / 2 + 3;
7617
- me.paddingLeft = firstWidth / 2 + 3;
7802
+ // Allow 3 pixels x2 padding either side for label readability
7803
+ var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6;
7618
7804
 
7619
- if (!me.longestTextCache) {
7620
- me.longestTextCache = {};
7621
- }
7622
- var originalLabelWidth = helpers.longestText(context, tickLabelFont, me.ticks, me.longestTextCache);
7623
- var labelWidth = originalLabelWidth;
7624
- var cosRotation;
7625
- var sinRotation;
7626
-
7627
- // Allow 3 pixels x2 padding either side for label readability
7628
- // only the index matters for a dataset scale, but we want a consistent interface between scales
7629
- var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6;
7630
-
7631
- // Max label rotation can be set or default to 90 - also act as a loop counter
7632
- while (labelWidth > tickWidth && me.labelRotation < optionTicks.maxRotation) {
7633
- cosRotation = Math.cos(helpers.toRadians(me.labelRotation));
7634
- sinRotation = Math.sin(helpers.toRadians(me.labelRotation));
7635
-
7636
- firstRotated = cosRotation * firstWidth;
7637
-
7638
- // We're right aligning the text now.
7639
- if (firstRotated + tickFontSize / 2 > me.yLabelWidth) {
7640
- me.paddingLeft = firstRotated + tickFontSize / 2;
7641
- }
7642
-
7643
- me.paddingRight = tickFontSize / 2;
7644
-
7645
- if (sinRotation * originalLabelWidth > me.maxHeight) {
7646
- // go back one step
7647
- me.labelRotation--;
7648
- break;
7649
- }
7805
+ // Max label rotation can be set or default to 90 - also act as a loop counter
7806
+ while (labelWidth > tickWidth && labelRotation < tickOpts.maxRotation) {
7807
+ var angleRadians = helpers.toRadians(labelRotation);
7808
+ cosRotation = Math.cos(angleRadians);
7809
+ sinRotation = Math.sin(angleRadians);
7650
7810
 
7651
- me.labelRotation++;
7652
- labelWidth = cosRotation * originalLabelWidth;
7811
+ if (sinRotation * originalLabelWidth > me.maxHeight) {
7812
+ // go back one step
7813
+ labelRotation--;
7814
+ break;
7653
7815
  }
7816
+
7817
+ labelRotation++;
7818
+ labelWidth = cosRotation * originalLabelWidth;
7654
7819
  }
7655
7820
  }
7656
7821
 
7657
- if (me.margins) {
7658
- me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0);
7659
- me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0);
7660
- }
7822
+ me.labelRotation = labelRotation;
7661
7823
  },
7662
7824
  afterCalculateTickRotation: function() {
7663
7825
  helpers.callCallback(this.options.afterCalculateTickRotation, [this]);
@@ -7677,20 +7839,14 @@ module.exports = function(Chart) {
7677
7839
  };
7678
7840
 
7679
7841
  var opts = me.options;
7680
- var globalDefaults = Chart.defaults.global;
7681
7842
  var tickOpts = opts.ticks;
7682
7843
  var scaleLabelOpts = opts.scaleLabel;
7683
7844
  var gridLineOpts = opts.gridLines;
7684
7845
  var display = opts.display;
7685
7846
  var isHorizontal = me.isHorizontal();
7686
7847
 
7687
- var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
7688
- var tickFontStyle = helpers.getValueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle);
7689
- var tickFontFamily = helpers.getValueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily);
7690
- var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
7691
-
7692
- var scaleLabelFontSize = helpers.getValueOrDefault(scaleLabelOpts.fontSize, globalDefaults.defaultFontSize);
7693
-
7848
+ var tickFont = parseFontOptions(tickOpts);
7849
+ var scaleLabelFontSize = parseFontOptions(scaleLabelOpts).size * 1.5;
7694
7850
  var tickMarkLength = opts.gridLines.tickMarkLength;
7695
7851
 
7696
7852
  // Width
@@ -7711,78 +7867,84 @@ module.exports = function(Chart) {
7711
7867
  // Are we showing a title for the scale?
7712
7868
  if (scaleLabelOpts.display && display) {
7713
7869
  if (isHorizontal) {
7714
- minSize.height += (scaleLabelFontSize * 1.5);
7870
+ minSize.height += scaleLabelFontSize;
7715
7871
  } else {
7716
- minSize.width += (scaleLabelFontSize * 1.5);
7872
+ minSize.width += scaleLabelFontSize;
7717
7873
  }
7718
7874
  }
7719
7875
 
7876
+ // Don't bother fitting the ticks if we are not showing them
7720
7877
  if (tickOpts.display && display) {
7721
- // Don't bother fitting the ticks if we are not showing them
7722
- if (!me.longestTextCache) {
7723
- me.longestTextCache = {};
7724
- }
7725
-
7726
- var largestTextWidth = helpers.longestText(me.ctx, tickLabelFont, me.ticks, me.longestTextCache);
7878
+ var largestTextWidth = helpers.longestText(me.ctx, tickFont.font, me.ticks, me.longestTextCache);
7727
7879
  var tallestLabelHeightInLines = helpers.numberOfLabelLines(me.ticks);
7728
- var lineSpace = tickFontSize * 0.5;
7880
+ var lineSpace = tickFont.size * 0.5;
7729
7881
 
7730
7882
  if (isHorizontal) {
7731
7883
  // A horizontal axis is more constrained by the height.
7732
7884
  me.longestLabelWidth = largestTextWidth;
7733
7885
 
7886
+ var angleRadians = helpers.toRadians(me.labelRotation);
7887
+ var cosRotation = Math.cos(angleRadians);
7888
+ var sinRotation = Math.sin(angleRadians);
7889
+
7734
7890
  // TODO - improve this calculation
7735
- var labelHeight = (Math.sin(helpers.toRadians(me.labelRotation)) * me.longestLabelWidth) + (tickFontSize * tallestLabelHeightInLines) + (lineSpace * tallestLabelHeightInLines);
7891
+ var labelHeight = (sinRotation * largestTextWidth)
7892
+ + (tickFont.size * tallestLabelHeightInLines)
7893
+ + (lineSpace * tallestLabelHeightInLines);
7736
7894
 
7737
7895
  minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight);
7738
- me.ctx.font = tickLabelFont;
7896
+ me.ctx.font = tickFont.font;
7739
7897
 
7740
- var firstLabelWidth = me.ctx.measureText(me.ticks[0]).width;
7741
- var lastLabelWidth = me.ctx.measureText(me.ticks[me.ticks.length - 1]).width;
7898
+ var firstTick = me.ticks[0];
7899
+ var firstLabelWidth = computeTextSize(me.ctx, firstTick, tickFont.font);
7900
+
7901
+ var lastTick = me.ticks[me.ticks.length - 1];
7902
+ var lastLabelWidth = computeTextSize(me.ctx, lastTick, tickFont.font);
7742
7903
 
7743
7904
  // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned which means that the right padding is dominated
7744
7905
  // by the font height
7745
- var cosRotation = Math.cos(helpers.toRadians(me.labelRotation));
7746
- var sinRotation = Math.sin(helpers.toRadians(me.labelRotation));
7747
- me.paddingLeft = me.labelRotation !== 0 ? (cosRotation * firstLabelWidth) + 3 : firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges
7748
- me.paddingRight = me.labelRotation !== 0 ? (sinRotation * (tickFontSize / 2)) + 3 : lastLabelWidth / 2 + 3; // when rotated
7906
+ if (me.labelRotation !== 0) {
7907
+ me.paddingLeft = opts.position === 'bottom'? (cosRotation * firstLabelWidth) + 3: (cosRotation * lineSpace) + 3; // add 3 px to move away from canvas edges
7908
+ me.paddingRight = opts.position === 'bottom'? (cosRotation * lineSpace) + 3: (cosRotation * lastLabelWidth) + 3;
7909
+ } else {
7910
+ me.paddingLeft = firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges
7911
+ me.paddingRight = lastLabelWidth / 2 + 3;
7912
+ }
7749
7913
  } else {
7750
7914
  // A vertical axis is more constrained by the width. Labels are the dominant factor here, so get that length first
7751
- var maxLabelWidth = me.maxWidth - minSize.width;
7752
-
7753
7915
  // Account for padding
7754
- var mirror = tickOpts.mirror;
7755
- if (!mirror) {
7756
- largestTextWidth += me.options.ticks.padding;
7757
- } else {
7758
- // If mirrored text is on the inside so don't expand
7759
- largestTextWidth = 0;
7760
- }
7761
7916
 
7762
- if (largestTextWidth < maxLabelWidth) {
7763
- // We don't need all the room
7764
- minSize.width += largestTextWidth;
7917
+ if (tickOpts.mirror) {
7918
+ largestTextWidth = 0;
7765
7919
  } else {
7766
- // Expand to max size
7767
- minSize.width = me.maxWidth;
7920
+ largestTextWidth += me.options.ticks.padding;
7768
7921
  }
7769
-
7770
- me.paddingTop = tickFontSize / 2;
7771
- me.paddingBottom = tickFontSize / 2;
7922
+ minSize.width += largestTextWidth;
7923
+ me.paddingTop = tickFont.size / 2;
7924
+ me.paddingBottom = tickFont.size / 2;
7772
7925
  }
7773
7926
  }
7774
7927
 
7928
+ me.handleMargins();
7929
+
7930
+ me.width = minSize.width;
7931
+ me.height = minSize.height;
7932
+ },
7933
+
7934
+ /**
7935
+ * Handle margins and padding interactions
7936
+ * @private
7937
+ */
7938
+ handleMargins: function() {
7939
+ var me = this;
7775
7940
  if (me.margins) {
7776
7941
  me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0);
7777
7942
  me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0);
7778
7943
  me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0);
7779
7944
  me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0);
7780
7945
  }
7781
-
7782
- me.width = minSize.width;
7783
- me.height = minSize.height;
7784
-
7785
7946
  },
7947
+
7786
7948
  afterFit: function() {
7787
7949
  helpers.callCallback(this.options.afterFit, [this]);
7788
7950
  },
@@ -7862,15 +8024,18 @@ module.exports = function(Chart) {
7862
8024
  },
7863
8025
 
7864
8026
  getBasePixel: function() {
8027
+ return this.getPixelForValue(this.getBaseValue());
8028
+ },
8029
+
8030
+ getBaseValue: function() {
7865
8031
  var me = this;
7866
8032
  var min = me.min;
7867
8033
  var max = me.max;
7868
8034
 
7869
- return me.getPixelForValue(
7870
- me.beginAtZero? 0:
8035
+ return me.beginAtZero ? 0:
7871
8036
  min < 0 && max < 0? max :
7872
8037
  min > 0 && max > 0? min :
7873
- 0);
8038
+ 0;
7874
8039
  },
7875
8040
 
7876
8041
  // Actually draw the scale on the canvas
@@ -7900,19 +8065,14 @@ module.exports = function(Chart) {
7900
8065
  }
7901
8066
 
7902
8067
  var tickFontColor = helpers.getValueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor);
7903
- var tickFontSize = helpers.getValueOrDefault(optionTicks.fontSize, globalDefaults.defaultFontSize);
7904
- var tickFontStyle = helpers.getValueOrDefault(optionTicks.fontStyle, globalDefaults.defaultFontStyle);
7905
- var tickFontFamily = helpers.getValueOrDefault(optionTicks.fontFamily, globalDefaults.defaultFontFamily);
7906
- var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
7907
- var tl = gridLines.tickMarkLength;
8068
+ var tickFont = parseFontOptions(optionTicks);
8069
+
8070
+ var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0;
7908
8071
  var borderDash = helpers.getValueOrDefault(gridLines.borderDash, globalDefaults.borderDash);
7909
8072
  var borderDashOffset = helpers.getValueOrDefault(gridLines.borderDashOffset, globalDefaults.borderDashOffset);
7910
8073
 
7911
8074
  var scaleLabelFontColor = helpers.getValueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor);
7912
- var scaleLabelFontSize = helpers.getValueOrDefault(scaleLabel.fontSize, globalDefaults.defaultFontSize);
7913
- var scaleLabelFontStyle = helpers.getValueOrDefault(scaleLabel.fontStyle, globalDefaults.defaultFontStyle);
7914
- var scaleLabelFontFamily = helpers.getValueOrDefault(scaleLabel.fontFamily, globalDefaults.defaultFontFamily);
7915
- var scaleLabelFont = helpers.fontString(scaleLabelFontSize, scaleLabelFontStyle, scaleLabelFontFamily);
8075
+ var scaleLabelFont = parseFontOptions(scaleLabel);
7916
8076
 
7917
8077
  var labelRotationRadians = helpers.toRadians(me.labelRotation);
7918
8078
  var cosRotation = Math.cos(labelRotationRadians);
@@ -7988,15 +8148,21 @@ module.exports = function(Chart) {
7988
8148
  var textBaseline = 'middle';
7989
8149
 
7990
8150
  if (isHorizontal) {
7991
- if (!isRotated) {
7992
- textBaseline = options.position === 'top' ? 'bottom' : 'top';
7993
- }
7994
8151
 
7995
- textAlign = isRotated ? 'right' : 'center';
8152
+ if (options.position === 'bottom') {
8153
+ // bottom
8154
+ textBaseline = !isRotated? 'top':'middle';
8155
+ textAlign = !isRotated? 'center': 'right';
8156
+ labelY = me.top + tl;
8157
+ } else {
8158
+ // top
8159
+ textBaseline = !isRotated? 'bottom':'middle';
8160
+ textAlign = !isRotated? 'center': 'left';
8161
+ labelY = me.bottom - tl;
8162
+ }
7996
8163
 
7997
8164
  var xLineValue = me.getPixelForTick(index) + helpers.aliasPixel(lineWidth); // xvalues for grid lines
7998
8165
  labelX = me.getPixelForTick(index, gridLines.offsetGridLines) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option)
7999
- labelY = (isRotated) ? me.top + 12 : options.position === 'top' ? me.bottom - tl : me.top + tl;
8000
8166
 
8001
8167
  tx1 = tx2 = x1 = x2 = xLineValue;
8002
8168
  ty1 = yTickStart;
@@ -8004,23 +8170,20 @@ module.exports = function(Chart) {
8004
8170
  y1 = chartArea.top;
8005
8171
  y2 = chartArea.bottom;
8006
8172
  } else {
8007
- if (options.position === 'left') {
8008
- if (optionTicks.mirror) {
8009
- labelX = me.right + optionTicks.padding;
8010
- textAlign = 'left';
8011
- } else {
8012
- labelX = me.right - optionTicks.padding;
8013
- textAlign = 'right';
8014
- }
8015
- // right side
8016
- } else if (optionTicks.mirror) {
8017
- labelX = me.left - optionTicks.padding;
8018
- textAlign = 'right';
8173
+ var isLeft = options.position === 'left';
8174
+ var tickPadding = optionTicks.padding;
8175
+ var labelXOffset;
8176
+
8177
+ if (optionTicks.mirror) {
8178
+ textAlign = isLeft ? 'left' : 'right';
8179
+ labelXOffset = tickPadding;
8019
8180
  } else {
8020
- labelX = me.left + optionTicks.padding;
8021
- textAlign = 'left';
8181
+ textAlign = isLeft ? 'right' : 'left';
8182
+ labelXOffset = tl + tickPadding;
8022
8183
  }
8023
8184
 
8185
+ labelX = isLeft ? me.right - labelXOffset : me.left + labelXOffset;
8186
+
8024
8187
  var yLineValue = me.getPixelForTick(index); // xvalues for grid lines
8025
8188
  yLineValue += helpers.aliasPixel(lineWidth);
8026
8189
  labelY = me.getPixelForTick(index, gridLines.offsetGridLines);
@@ -8085,17 +8248,17 @@ module.exports = function(Chart) {
8085
8248
  context.save();
8086
8249
  context.translate(itemToDraw.labelX, itemToDraw.labelY);
8087
8250
  context.rotate(itemToDraw.rotation);
8088
- context.font = tickLabelFont;
8251
+ context.font = tickFont.font;
8089
8252
  context.textBaseline = itemToDraw.textBaseline;
8090
8253
  context.textAlign = itemToDraw.textAlign;
8091
8254
 
8092
8255
  var label = itemToDraw.label;
8093
8256
  if (helpers.isArray(label)) {
8094
- for (var i = 0, y = -(label.length - 1)*tickFontSize*0.75; i < label.length; ++i) {
8257
+ for (var i = 0, y = 0; i < label.length; ++i) {
8095
8258
  // We just make sure the multiline element is a string here..
8096
8259
  context.fillText('' + label[i], 0, y);
8097
8260
  // apply same lineSpacing as calculated @ L#320
8098
- y += (tickFontSize * 1.5);
8261
+ y += (tickFont.size * 1.5);
8099
8262
  }
8100
8263
  } else {
8101
8264
  context.fillText(label, 0, 0);
@@ -8112,10 +8275,10 @@ module.exports = function(Chart) {
8112
8275
 
8113
8276
  if (isHorizontal) {
8114
8277
  scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width
8115
- scaleLabelY = options.position === 'bottom' ? me.bottom - (scaleLabelFontSize / 2) : me.top + (scaleLabelFontSize / 2);
8278
+ scaleLabelY = options.position === 'bottom' ? me.bottom - (scaleLabelFont.size / 2) : me.top + (scaleLabelFont.size / 2);
8116
8279
  } else {
8117
8280
  var isLeft = options.position === 'left';
8118
- scaleLabelX = isLeft ? me.left + (scaleLabelFontSize / 2) : me.right - (scaleLabelFontSize / 2);
8281
+ scaleLabelX = isLeft ? me.left + (scaleLabelFont.size / 2) : me.right - (scaleLabelFont.size / 2);
8119
8282
  scaleLabelY = me.top + ((me.bottom - me.top) / 2);
8120
8283
  rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI;
8121
8284
  }
@@ -8126,7 +8289,7 @@ module.exports = function(Chart) {
8126
8289
  context.textAlign = 'center';
8127
8290
  context.textBaseline = 'middle';
8128
8291
  context.fillStyle = scaleLabelFontColor; // render in correct colour
8129
- context.font = scaleLabelFont;
8292
+ context.font = scaleLabelFont.font;
8130
8293
  context.fillText(scaleLabel.labelString, 0, 0);
8131
8294
  context.restore();
8132
8295
  }
@@ -8272,8 +8435,8 @@ module.exports = function(Chart) {
8272
8435
 
8273
8436
  // If min, max and stepSize is set and they make an evenly spaced scale use it.
8274
8437
  if (generationOptions.min && generationOptions.max && generationOptions.stepSize) {
8275
- var minMaxDeltaDivisibleByStepSize = ((generationOptions.max - generationOptions.min) % generationOptions.stepSize) === 0;
8276
- if (minMaxDeltaDivisibleByStepSize) {
8438
+ // If very close to our whole number, use it.
8439
+ if (helpers.almostWhole((generationOptions.max - generationOptions.min) / generationOptions.stepSize, spacing / 1000)) {
8277
8440
  niceMin = generationOptions.min;
8278
8441
  niceMax = generationOptions.max;
8279
8442
  }
@@ -8314,27 +8477,33 @@ module.exports = function(Chart) {
8314
8477
  // the graph
8315
8478
  var tickVal = getValueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min))));
8316
8479
 
8317
- while (tickVal < dataRange.max) {
8318
- ticks.push(tickVal);
8480
+ var endExp = Math.floor(helpers.log10(dataRange.max));
8481
+ var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp));
8482
+ var exp;
8483
+ var significand;
8319
8484
 
8320
- var exp;
8321
- var significand;
8485
+ if (tickVal === 0) {
8486
+ exp = Math.floor(helpers.log10(dataRange.minNotZero));
8487
+ significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp));
8322
8488
 
8323
- if (tickVal === 0) {
8324
- exp = Math.floor(helpers.log10(dataRange.minNotZero));
8325
- significand = Math.round(dataRange.minNotZero / Math.pow(10, exp));
8326
- } else {
8327
- exp = Math.floor(helpers.log10(tickVal));
8328
- significand = Math.floor(tickVal / Math.pow(10, exp)) + 1;
8329
- }
8489
+ ticks.push(tickVal);
8490
+ tickVal = significand * Math.pow(10, exp);
8491
+ } else {
8492
+ exp = Math.floor(helpers.log10(tickVal));
8493
+ significand = Math.floor(tickVal / Math.pow(10, exp));
8494
+ }
8495
+
8496
+ do {
8497
+ ticks.push(tickVal);
8330
8498
 
8499
+ ++significand;
8331
8500
  if (significand === 10) {
8332
8501
  significand = 1;
8333
8502
  ++exp;
8334
8503
  }
8335
8504
 
8336
8505
  tickVal = significand * Math.pow(10, exp);
8337
- }
8506
+ } while (exp < endExp || (exp === endExp && significand < endSignificand));
8338
8507
 
8339
8508
  var lastTick = getValueOrDefault(generationOptions.max, tickVal);
8340
8509
  ticks.push(lastTick);
@@ -8431,7 +8600,6 @@ module.exports = function(Chart) {
8431
8600
  initialize: function(config) {
8432
8601
  var me = this;
8433
8602
  helpers.extend(me, config);
8434
- me.options = helpers.configMerge(Chart.defaults.global.title, config.options);
8435
8603
 
8436
8604
  // Contains hit boxes for each dataset (in dataset order)
8437
8605
  me.legendHitBoxes = [];
@@ -8439,12 +8607,7 @@ module.exports = function(Chart) {
8439
8607
 
8440
8608
  // These methods are ordered by lifecycle. Utilities then follow.
8441
8609
 
8442
- beforeUpdate: function() {
8443
- var chartOpts = this.chart.options;
8444
- if (chartOpts && chartOpts.title) {
8445
- this.options = helpers.configMerge(Chart.defaults.global.title, chartOpts.title);
8446
- }
8447
- },
8610
+ beforeUpdate: noop,
8448
8611
  update: function(maxWidth, maxHeight, margins) {
8449
8612
  var me = this;
8450
8613
 
@@ -8596,20 +8759,39 @@ module.exports = function(Chart) {
8596
8759
  }
8597
8760
  });
8598
8761
 
8762
+ function createNewTitleBlockAndAttach(chartInstance, titleOpts) {
8763
+ var title = new Chart.Title({
8764
+ ctx: chartInstance.chart.ctx,
8765
+ options: titleOpts,
8766
+ chart: chartInstance
8767
+ });
8768
+ chartInstance.titleBlock = title;
8769
+ Chart.layoutService.addBox(chartInstance, title);
8770
+ }
8771
+
8599
8772
  // Register the title plugin
8600
8773
  Chart.plugins.register({
8601
8774
  beforeInit: function(chartInstance) {
8602
- var opts = chartInstance.options;
8603
- var titleOpts = opts.title;
8775
+ var titleOpts = chartInstance.options.title;
8604
8776
 
8605
8777
  if (titleOpts) {
8606
- chartInstance.titleBlock = new Chart.Title({
8607
- ctx: chartInstance.chart.ctx,
8608
- options: titleOpts,
8609
- chart: chartInstance
8610
- });
8778
+ createNewTitleBlockAndAttach(chartInstance, titleOpts);
8779
+ }
8780
+ },
8781
+ beforeUpdate: function(chartInstance) {
8782
+ var titleOpts = chartInstance.options.title;
8611
8783
 
8612
- Chart.layoutService.addBox(chartInstance, chartInstance.titleBlock);
8784
+ if (titleOpts) {
8785
+ titleOpts = helpers.configMerge(Chart.defaults.global.title, titleOpts);
8786
+
8787
+ if (chartInstance.titleBlock) {
8788
+ chartInstance.titleBlock.options = titleOpts;
8789
+ } else {
8790
+ createNewTitleBlockAndAttach(chartInstance, titleOpts);
8791
+ }
8792
+ } else {
8793
+ Chart.layoutService.removeBox(chartInstance, chartInstance.titleBlock);
8794
+ delete chartInstance.titleBlock;
8613
8795
  }
8614
8796
  }
8615
8797
  });
@@ -9381,7 +9563,7 @@ module.exports = function(Chart) {
9381
9563
  /**
9382
9564
  * Handle an event
9383
9565
  * @private
9384
- * @param e {Event} the event to handle
9566
+ * @param {IEvent} event - The event to handle
9385
9567
  * @returns {Boolean} true if the tooltip changed
9386
9568
  */
9387
9569
  handleEvent: function(e) {
@@ -9403,7 +9585,10 @@ module.exports = function(Chart) {
9403
9585
  me._lastActive = me._active;
9404
9586
 
9405
9587
  if (options.enabled || options.custom) {
9406
- me._eventPosition = helpers.getRelativePosition(e, me._chart);
9588
+ me._eventPosition = {
9589
+ x: e.x,
9590
+ y: e.y
9591
+ };
9407
9592
 
9408
9593
  var model = me._model;
9409
9594
  me.update(true);
@@ -9845,13 +10030,17 @@ module.exports = function(Chart) {
9845
10030
  padding: vm.radius + vm.borderWidth
9846
10031
  };
9847
10032
  },
9848
- draw: function() {
10033
+ draw: function(chartArea) {
9849
10034
  var vm = this._view;
10035
+ var model = this._model;
9850
10036
  var ctx = this._chart.ctx;
9851
10037
  var pointStyle = vm.pointStyle;
9852
10038
  var radius = vm.radius;
9853
10039
  var x = vm.x;
9854
10040
  var y = vm.y;
10041
+ var color = Chart.helpers.color;
10042
+ var errMargin = 1.01; // 1.01 is margin for Accumulated error. (Especially Edge, IE.)
10043
+ var ratio = 0;
9855
10044
 
9856
10045
  if (vm.skip) {
9857
10046
  return;
@@ -9861,6 +10050,24 @@ module.exports = function(Chart) {
9861
10050
  ctx.lineWidth = helpers.getValueOrDefault(vm.borderWidth, globalOpts.elements.point.borderWidth);
9862
10051
  ctx.fillStyle = vm.backgroundColor || defaultColor;
9863
10052
 
10053
+ // Cliping for Points.
10054
+ // going out from inner charArea?
10055
+ if ((chartArea !== undefined) && ((model.x < chartArea.left) || (chartArea.right*errMargin < model.x) || (model.y < chartArea.top) || (chartArea.bottom*errMargin < model.y))) {
10056
+ // Point fade out
10057
+ if (model.x < chartArea.left) {
10058
+ ratio = (x - model.x) / (chartArea.left - model.x);
10059
+ } else if (chartArea.right*errMargin < model.x) {
10060
+ ratio = (model.x - x) / (model.x - chartArea.right);
10061
+ } else if (model.y < chartArea.top) {
10062
+ ratio = (y - model.y) / (chartArea.top - model.y);
10063
+ } else if (chartArea.bottom*errMargin < model.y) {
10064
+ ratio = (model.y - y) / (model.y - chartArea.bottom);
10065
+ }
10066
+ ratio = Math.round(ratio*100) / 100;
10067
+ ctx.strokeStyle = color(ctx.strokeStyle).alpha(ratio).rgbString();
10068
+ ctx.fillStyle = color(ctx.fillStyle).alpha(ratio).rgbString();
10069
+ }
10070
+
9864
10071
  Chart.canvasHelpers.drawPoint(ctx, pointStyle, radius, x, y);
9865
10072
  }
9866
10073
  });
@@ -9922,39 +10129,71 @@ module.exports = function(Chart) {
9922
10129
  draw: function() {
9923
10130
  var ctx = this._chart.ctx;
9924
10131
  var vm = this._view;
9925
-
9926
- var halfWidth = vm.width / 2,
9927
- leftX = vm.x - halfWidth,
9928
- rightX = vm.x + halfWidth,
9929
- top = vm.base - (vm.base - vm.y),
9930
- halfStroke = vm.borderWidth / 2;
10132
+ var left, right, top, bottom, signX, signY, borderSkipped;
10133
+ var borderWidth = vm.borderWidth;
10134
+
10135
+ if (!vm.horizontal) {
10136
+ // bar
10137
+ left = vm.x - vm.width / 2;
10138
+ right = vm.x + vm.width / 2;
10139
+ top = vm.y;
10140
+ bottom = vm.base;
10141
+ signX = 1;
10142
+ signY = bottom > top? 1: -1;
10143
+ borderSkipped = vm.borderSkipped || 'bottom';
10144
+ } else {
10145
+ // horizontal bar
10146
+ left = vm.base;
10147
+ right = vm.x;
10148
+ top = vm.y - vm.height / 2;
10149
+ bottom = vm.y + vm.height / 2;
10150
+ signX = right > left? 1: -1;
10151
+ signY = 1;
10152
+ borderSkipped = vm.borderSkipped || 'left';
10153
+ }
9931
10154
 
9932
10155
  // Canvas doesn't allow us to stroke inside the width so we can
9933
10156
  // adjust the sizes to fit if we're setting a stroke on the line
9934
- if (vm.borderWidth) {
9935
- leftX += halfStroke;
9936
- rightX -= halfStroke;
9937
- top += halfStroke;
10157
+ if (borderWidth) {
10158
+ // borderWidth shold be less than bar width and bar height.
10159
+ var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom));
10160
+ borderWidth = borderWidth > barSize? barSize: borderWidth;
10161
+ var halfStroke = borderWidth / 2;
10162
+ // Adjust borderWidth when bar top position is near vm.base(zero).
10163
+ var borderLeft = left + (borderSkipped !== 'left'? halfStroke * signX: 0);
10164
+ var borderRight = right + (borderSkipped !== 'right'? -halfStroke * signX: 0);
10165
+ var borderTop = top + (borderSkipped !== 'top'? halfStroke * signY: 0);
10166
+ var borderBottom = bottom + (borderSkipped !== 'bottom'? -halfStroke * signY: 0);
10167
+ // not become a vertical line?
10168
+ if (borderLeft !== borderRight) {
10169
+ top = borderTop;
10170
+ bottom = borderBottom;
10171
+ }
10172
+ // not become a horizontal line?
10173
+ if (borderTop !== borderBottom) {
10174
+ left = borderLeft;
10175
+ right = borderRight;
10176
+ }
9938
10177
  }
9939
10178
 
9940
10179
  ctx.beginPath();
9941
10180
  ctx.fillStyle = vm.backgroundColor;
9942
10181
  ctx.strokeStyle = vm.borderColor;
9943
- ctx.lineWidth = vm.borderWidth;
10182
+ ctx.lineWidth = borderWidth;
9944
10183
 
9945
10184
  // Corner points, from bottom-left to bottom-right clockwise
9946
10185
  // | 1 2 |
9947
10186
  // | 0 3 |
9948
10187
  var corners = [
9949
- [leftX, vm.base],
9950
- [leftX, top],
9951
- [rightX, top],
9952
- [rightX, vm.base]
10188
+ [left, bottom],
10189
+ [left, top],
10190
+ [right, top],
10191
+ [right, bottom]
9953
10192
  ];
9954
10193
 
9955
10194
  // Find first (starting) corner with fallback to 'bottom'
9956
10195
  var borders = ['bottom', 'left', 'top', 'right'];
9957
- var startCorner = borders.indexOf(vm.borderSkipped, 0);
10196
+ var startCorner = borders.indexOf(borderSkipped, 0);
9958
10197
  if (startCorner === -1) {
9959
10198
  startCorner = 0;
9960
10199
  }
@@ -9973,7 +10212,7 @@ module.exports = function(Chart) {
9973
10212
  }
9974
10213
 
9975
10214
  ctx.fill();
9976
- if (vm.borderWidth) {
10215
+ if (borderWidth) {
9977
10216
  ctx.stroke();
9978
10217
  }
9979
10218
  },
@@ -10047,6 +10286,356 @@ module.exports = function(Chart) {
10047
10286
  },{}],41:[function(require,module,exports){
10048
10287
  'use strict';
10049
10288
 
10289
+ // Chart.Platform implementation for targeting a web browser
10290
+ module.exports = function(Chart) {
10291
+ var helpers = Chart.helpers;
10292
+
10293
+ // DOM event types -> Chart.js event types.
10294
+ // Note: only events with different types are mapped.
10295
+ // https://developer.mozilla.org/en-US/docs/Web/Events
10296
+ var eventTypeMap = {
10297
+ // Touch events
10298
+ touchstart: 'mousedown',
10299
+ touchmove: 'mousemove',
10300
+ touchend: 'mouseup',
10301
+
10302
+ // Pointer events
10303
+ pointerenter: 'mouseenter',
10304
+ pointerdown: 'mousedown',
10305
+ pointermove: 'mousemove',
10306
+ pointerup: 'mouseup',
10307
+ pointerleave: 'mouseout',
10308
+ pointerout: 'mouseout'
10309
+ };
10310
+
10311
+ /**
10312
+ * The "used" size is the final value of a dimension property after all calculations have
10313
+ * been performed. This method uses the computed style of `element` but returns undefined
10314
+ * if the computed style is not expressed in pixels. That can happen in some cases where
10315
+ * `element` has a size relative to its parent and this last one is not yet displayed,
10316
+ * for example because of `display: none` on a parent node.
10317
+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
10318
+ * @returns {Number} Size in pixels or undefined if unknown.
10319
+ */
10320
+ function readUsedSize(element, property) {
10321
+ var value = helpers.getStyle(element, property);
10322
+ var matches = value && value.match(/(\d+)px/);
10323
+ return matches? Number(matches[1]) : undefined;
10324
+ }
10325
+
10326
+ /**
10327
+ * Initializes the canvas style and render size without modifying the canvas display size,
10328
+ * since responsiveness is handled by the controller.resize() method. The config is used
10329
+ * to determine the aspect ratio to apply in case no explicit height has been specified.
10330
+ */
10331
+ function initCanvas(canvas, config) {
10332
+ var style = canvas.style;
10333
+
10334
+ // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it
10335
+ // returns null or '' if no explicit value has been set to the canvas attribute.
10336
+ var renderHeight = canvas.getAttribute('height');
10337
+ var renderWidth = canvas.getAttribute('width');
10338
+
10339
+ // Chart.js modifies some canvas values that we want to restore on destroy
10340
+ canvas._chartjs = {
10341
+ initial: {
10342
+ height: renderHeight,
10343
+ width: renderWidth,
10344
+ style: {
10345
+ display: style.display,
10346
+ height: style.height,
10347
+ width: style.width
10348
+ }
10349
+ }
10350
+ };
10351
+
10352
+ // Force canvas to display as block to avoid extra space caused by inline
10353
+ // elements, which would interfere with the responsive resize process.
10354
+ // https://github.com/chartjs/Chart.js/issues/2538
10355
+ style.display = style.display || 'block';
10356
+
10357
+ if (renderWidth === null || renderWidth === '') {
10358
+ var displayWidth = readUsedSize(canvas, 'width');
10359
+ if (displayWidth !== undefined) {
10360
+ canvas.width = displayWidth;
10361
+ }
10362
+ }
10363
+
10364
+ if (renderHeight === null || renderHeight === '') {
10365
+ if (canvas.style.height === '') {
10366
+ // If no explicit render height and style height, let's apply the aspect ratio,
10367
+ // which one can be specified by the user but also by charts as default option
10368
+ // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2.
10369
+ canvas.height = canvas.width / (config.options.aspectRatio || 2);
10370
+ } else {
10371
+ var displayHeight = readUsedSize(canvas, 'height');
10372
+ if (displayWidth !== undefined) {
10373
+ canvas.height = displayHeight;
10374
+ }
10375
+ }
10376
+ }
10377
+
10378
+ return canvas;
10379
+ }
10380
+
10381
+ function createEvent(type, chart, x, y, native) {
10382
+ return {
10383
+ type: type,
10384
+ chart: chart,
10385
+ native: native || null,
10386
+ x: x !== undefined? x : null,
10387
+ y: y !== undefined? y : null,
10388
+ };
10389
+ }
10390
+
10391
+ function fromNativeEvent(event, chart) {
10392
+ var type = eventTypeMap[event.type] || event.type;
10393
+ var pos = helpers.getRelativePosition(event, chart);
10394
+ return createEvent(type, chart, pos.x, pos.y, event);
10395
+ }
10396
+
10397
+ function createResizer(handler) {
10398
+ var iframe = document.createElement('iframe');
10399
+ iframe.className = 'chartjs-hidden-iframe';
10400
+ iframe.style.cssText =
10401
+ 'display:block;'+
10402
+ 'overflow:hidden;'+
10403
+ 'border:0;'+
10404
+ 'margin:0;'+
10405
+ 'top:0;'+
10406
+ 'left:0;'+
10407
+ 'bottom:0;'+
10408
+ 'right:0;'+
10409
+ 'height:100%;'+
10410
+ 'width:100%;'+
10411
+ 'position:absolute;'+
10412
+ 'pointer-events:none;'+
10413
+ 'z-index:-1;';
10414
+
10415
+ // Prevent the iframe to gain focus on tab.
10416
+ // https://github.com/chartjs/Chart.js/issues/3090
10417
+ iframe.tabIndex = -1;
10418
+
10419
+ // If the iframe is re-attached to the DOM, the resize listener is removed because the
10420
+ // content is reloaded, so make sure to install the handler after the iframe is loaded.
10421
+ // https://github.com/chartjs/Chart.js/issues/3521
10422
+ helpers.addEvent(iframe, 'load', function() {
10423
+ helpers.addEvent(iframe.contentWindow || iframe, 'resize', handler);
10424
+
10425
+ // The iframe size might have changed while loading, which can also
10426
+ // happen if the size has been changed while detached from the DOM.
10427
+ handler();
10428
+ });
10429
+
10430
+ return iframe;
10431
+ }
10432
+
10433
+ function addResizeListener(node, listener, chart) {
10434
+ var stub = node._chartjs = {
10435
+ ticking: false
10436
+ };
10437
+
10438
+ // Throttle the callback notification until the next animation frame.
10439
+ var notify = function() {
10440
+ if (!stub.ticking) {
10441
+ stub.ticking = true;
10442
+ helpers.requestAnimFrame.call(window, function() {
10443
+ if (stub.resizer) {
10444
+ stub.ticking = false;
10445
+ return listener(createEvent('resize', chart));
10446
+ }
10447
+ });
10448
+ }
10449
+ };
10450
+
10451
+ // Let's keep track of this added iframe and thus avoid DOM query when removing it.
10452
+ stub.resizer = createResizer(notify);
10453
+
10454
+ node.insertBefore(stub.resizer, node.firstChild);
10455
+ }
10456
+
10457
+ function removeResizeListener(node) {
10458
+ if (!node || !node._chartjs) {
10459
+ return;
10460
+ }
10461
+
10462
+ var resizer = node._chartjs.resizer;
10463
+ if (resizer) {
10464
+ resizer.parentNode.removeChild(resizer);
10465
+ node._chartjs.resizer = null;
10466
+ }
10467
+
10468
+ delete node._chartjs;
10469
+ }
10470
+
10471
+ return {
10472
+ acquireContext: function(item, config) {
10473
+ if (typeof item === 'string') {
10474
+ item = document.getElementById(item);
10475
+ } else if (item.length) {
10476
+ // Support for array based queries (such as jQuery)
10477
+ item = item[0];
10478
+ }
10479
+
10480
+ if (item && item.canvas) {
10481
+ // Support for any object associated to a canvas (including a context2d)
10482
+ item = item.canvas;
10483
+ }
10484
+
10485
+ if (item instanceof HTMLCanvasElement) {
10486
+ // To prevent canvas fingerprinting, some add-ons undefine the getContext
10487
+ // method, for example: https://github.com/kkapsner/CanvasBlocker
10488
+ // https://github.com/chartjs/Chart.js/issues/2807
10489
+ var context = item.getContext && item.getContext('2d');
10490
+ if (context instanceof CanvasRenderingContext2D) {
10491
+ initCanvas(item, config);
10492
+ return context;
10493
+ }
10494
+ }
10495
+
10496
+ return null;
10497
+ },
10498
+
10499
+ releaseContext: function(context) {
10500
+ var canvas = context.canvas;
10501
+ if (!canvas._chartjs) {
10502
+ return;
10503
+ }
10504
+
10505
+ var initial = canvas._chartjs.initial;
10506
+ ['height', 'width'].forEach(function(prop) {
10507
+ var value = initial[prop];
10508
+ if (value === undefined || value === null) {
10509
+ canvas.removeAttribute(prop);
10510
+ } else {
10511
+ canvas.setAttribute(prop, value);
10512
+ }
10513
+ });
10514
+
10515
+ helpers.each(initial.style || {}, function(value, key) {
10516
+ canvas.style[key] = value;
10517
+ });
10518
+
10519
+ // The canvas render size might have been changed (and thus the state stack discarded),
10520
+ // we can't use save() and restore() to restore the initial state. So make sure that at
10521
+ // least the canvas context is reset to the default state by setting the canvas width.
10522
+ // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html
10523
+ canvas.width = canvas.width;
10524
+
10525
+ delete canvas._chartjs;
10526
+ },
10527
+
10528
+ addEventListener: function(chart, type, listener) {
10529
+ var canvas = chart.chart.canvas;
10530
+ if (type === 'resize') {
10531
+ // Note: the resize event is not supported on all browsers.
10532
+ addResizeListener(canvas.parentNode, listener, chart.chart);
10533
+ return;
10534
+ }
10535
+
10536
+ var stub = listener._chartjs || (listener._chartjs = {});
10537
+ var proxies = stub.proxies || (stub.proxies = {});
10538
+ var proxy = proxies[chart.id + '_' + type] = function(event) {
10539
+ listener(fromNativeEvent(event, chart.chart));
10540
+ };
10541
+
10542
+ helpers.addEvent(canvas, type, proxy);
10543
+ },
10544
+
10545
+ removeEventListener: function(chart, type, listener) {
10546
+ var canvas = chart.chart.canvas;
10547
+ if (type === 'resize') {
10548
+ // Note: the resize event is not supported on all browsers.
10549
+ removeResizeListener(canvas.parentNode, listener);
10550
+ return;
10551
+ }
10552
+
10553
+ var stub = listener._chartjs || {};
10554
+ var proxies = stub.proxies || {};
10555
+ var proxy = proxies[chart.id + '_' + type];
10556
+ if (!proxy) {
10557
+ return;
10558
+ }
10559
+
10560
+ helpers.removeEvent(canvas, type, proxy);
10561
+ }
10562
+ };
10563
+ };
10564
+
10565
+ },{}],42:[function(require,module,exports){
10566
+ 'use strict';
10567
+
10568
+ // By default, select the browser (DOM) platform.
10569
+ // @TODO Make possible to select another platform at build time.
10570
+ var implementation = require(41);
10571
+
10572
+ module.exports = function(Chart) {
10573
+ /**
10574
+ * @namespace Chart.platform
10575
+ * @see https://chartjs.gitbooks.io/proposals/content/Platform.html
10576
+ * @since 2.4.0
10577
+ */
10578
+ Chart.platform = {
10579
+ /**
10580
+ * Called at chart construction time, returns a context2d instance implementing
10581
+ * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}.
10582
+ * @param {*} item - The native item from which to acquire context (platform specific)
10583
+ * @param {Object} options - The chart options
10584
+ * @returns {CanvasRenderingContext2D} context2d instance
10585
+ */
10586
+ acquireContext: function() {},
10587
+
10588
+ /**
10589
+ * Called at chart destruction time, releases any resources associated to the context
10590
+ * previously returned by the acquireContext() method.
10591
+ * @param {CanvasRenderingContext2D} context - The context2d instance
10592
+ * @returns {Boolean} true if the method succeeded, else false
10593
+ */
10594
+ releaseContext: function() {},
10595
+
10596
+ /**
10597
+ * Registers the specified listener on the given chart.
10598
+ * @param {Chart} chart - Chart from which to listen for event
10599
+ * @param {String} type - The ({@link IEvent}) type to listen for
10600
+ * @param {Function} listener - Receives a notification (an object that implements
10601
+ * the {@link IEvent} interface) when an event of the specified type occurs.
10602
+ */
10603
+ addEventListener: function() {},
10604
+
10605
+ /**
10606
+ * Removes the specified listener previously registered with addEventListener.
10607
+ * @param {Chart} chart -Chart from which to remove the listener
10608
+ * @param {String} type - The ({@link IEvent}) type to remove
10609
+ * @param {Function} listener - The listener function to remove from the event target.
10610
+ */
10611
+ removeEventListener: function() {}
10612
+ };
10613
+
10614
+ /**
10615
+ * @interface IPlatform
10616
+ * Allows abstracting platform dependencies away from the chart
10617
+ * @borrows Chart.platform.acquireContext as acquireContext
10618
+ * @borrows Chart.platform.releaseContext as releaseContext
10619
+ * @borrows Chart.platform.addEventListener as addEventListener
10620
+ * @borrows Chart.platform.removeEventListener as removeEventListener
10621
+ */
10622
+
10623
+ /**
10624
+ * @interface IEvent
10625
+ * @prop {String} type - The event type name, possible values are:
10626
+ * 'contextmenu', 'mouseenter', 'mousedown', 'mousemove', 'mouseup', 'mouseout',
10627
+ * 'click', 'dblclick', 'keydown', 'keypress', 'keyup' and 'resize'
10628
+ * @prop {*} native - The original native event (null for emulated events, e.g. 'resize')
10629
+ * @prop {Number} x - The mouse x position, relative to the canvas (null for incompatible events)
10630
+ * @prop {Number} y - The mouse y position, relative to the canvas (null for incompatible events)
10631
+ */
10632
+
10633
+ Chart.helpers.extend(Chart.platform, implementation(Chart));
10634
+ };
10635
+
10636
+ },{"41":41}],43:[function(require,module,exports){
10637
+ 'use strict';
10638
+
10050
10639
  module.exports = function(Chart) {
10051
10640
 
10052
10641
  var helpers = Chart.helpers;
@@ -10101,10 +10690,10 @@ module.exports = function(Chart) {
10101
10690
  var data = me.chart.data;
10102
10691
  var isHorizontal = me.isHorizontal();
10103
10692
 
10104
- if ((data.xLabels && isHorizontal) || (data.yLabels && !isHorizontal)) {
10693
+ if (data.yLabels && !isHorizontal) {
10105
10694
  return me.getRightValue(data.datasets[datasetIndex].data[index]);
10106
10695
  }
10107
- return me.ticks[index];
10696
+ return me.ticks[index - me.minIndex];
10108
10697
  },
10109
10698
 
10110
10699
  // Used to get data value locations. Value can either be an index or a numerical value
@@ -10120,9 +10709,8 @@ module.exports = function(Chart) {
10120
10709
  }
10121
10710
 
10122
10711
  if (me.isHorizontal()) {
10123
- var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
10124
- var valueWidth = innerWidth / offsetAmt;
10125
- var widthOffset = (valueWidth * (index - me.minIndex)) + me.paddingLeft;
10712
+ var valueWidth = me.width / offsetAmt;
10713
+ var widthOffset = (valueWidth * (index - me.minIndex));
10126
10714
 
10127
10715
  if (me.options.gridLines.offsetGridLines && includeOffset || me.maxIndex === me.minIndex && includeOffset) {
10128
10716
  widthOffset += (valueWidth / 2);
@@ -10130,9 +10718,8 @@ module.exports = function(Chart) {
10130
10718
 
10131
10719
  return me.left + Math.round(widthOffset);
10132
10720
  }
10133
- var innerHeight = me.height - (me.paddingTop + me.paddingBottom);
10134
- var valueHeight = innerHeight / offsetAmt;
10135
- var heightOffset = (valueHeight * (index - me.minIndex)) + me.paddingTop;
10721
+ var valueHeight = me.height / offsetAmt;
10722
+ var heightOffset = (valueHeight * (index - me.minIndex));
10136
10723
 
10137
10724
  if (me.options.gridLines.offsetGridLines && includeOffset) {
10138
10725
  heightOffset += (valueHeight / 2);
@@ -10148,15 +10735,13 @@ module.exports = function(Chart) {
10148
10735
  var value;
10149
10736
  var offsetAmt = Math.max((me.ticks.length - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
10150
10737
  var horz = me.isHorizontal();
10151
- var innerDimension = horz ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom);
10152
- var valueDimension = innerDimension / offsetAmt;
10738
+ var valueDimension = (horz ? me.width : me.height) / offsetAmt;
10153
10739
 
10154
10740
  pixel -= horz ? me.left : me.top;
10155
10741
 
10156
10742
  if (me.options.gridLines.offsetGridLines) {
10157
10743
  pixel -= (valueDimension / 2);
10158
10744
  }
10159
- pixel -= horz ? me.paddingLeft : me.paddingTop;
10160
10745
 
10161
10746
  if (pixel <= 0) {
10162
10747
  value = 0;
@@ -10175,7 +10760,7 @@ module.exports = function(Chart) {
10175
10760
 
10176
10761
  };
10177
10762
 
10178
- },{}],42:[function(require,module,exports){
10763
+ },{}],44:[function(require,module,exports){
10179
10764
  'use strict';
10180
10765
 
10181
10766
  module.exports = function(Chart) {
@@ -10206,21 +10791,43 @@ module.exports = function(Chart) {
10206
10791
  me.min = null;
10207
10792
  me.max = null;
10208
10793
 
10209
- if (opts.stacked) {
10210
- var valuesPerType = {};
10794
+ var hasStacks = opts.stacked;
10795
+ if (hasStacks === undefined) {
10796
+ helpers.each(datasets, function(dataset, datasetIndex) {
10797
+ if (hasStacks) {
10798
+ return;
10799
+ }
10800
+
10801
+ var meta = chart.getDatasetMeta(datasetIndex);
10802
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
10803
+ meta.stack !== undefined) {
10804
+ hasStacks = true;
10805
+ }
10806
+ });
10807
+ }
10808
+
10809
+ if (opts.stacked || hasStacks) {
10810
+ var valuesPerStack = {};
10211
10811
 
10212
10812
  helpers.each(datasets, function(dataset, datasetIndex) {
10213
10813
  var meta = chart.getDatasetMeta(datasetIndex);
10214
- if (valuesPerType[meta.type] === undefined) {
10215
- valuesPerType[meta.type] = {
10814
+ var key = [
10815
+ meta.type,
10816
+ // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
10817
+ ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
10818
+ meta.stack
10819
+ ].join('.');
10820
+
10821
+ if (valuesPerStack[key] === undefined) {
10822
+ valuesPerStack[key] = {
10216
10823
  positiveValues: [],
10217
10824
  negativeValues: []
10218
10825
  };
10219
10826
  }
10220
10827
 
10221
10828
  // Store these per type
10222
- var positiveValues = valuesPerType[meta.type].positiveValues;
10223
- var negativeValues = valuesPerType[meta.type].negativeValues;
10829
+ var positiveValues = valuesPerStack[key].positiveValues;
10830
+ var negativeValues = valuesPerStack[key].negativeValues;
10224
10831
 
10225
10832
  if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
10226
10833
  helpers.each(dataset.data, function(rawValue, index) {
@@ -10243,7 +10850,7 @@ module.exports = function(Chart) {
10243
10850
  }
10244
10851
  });
10245
10852
 
10246
- helpers.each(valuesPerType, function(valuesForType) {
10853
+ helpers.each(valuesPerStack, function(valuesForType) {
10247
10854
  var values = valuesForType.positiveValues.concat(valuesForType.negativeValues);
10248
10855
  var minVal = helpers.min(values);
10249
10856
  var maxVal = helpers.max(values);
@@ -10310,31 +10917,25 @@ module.exports = function(Chart) {
10310
10917
  // This must be called after fit has been run so that
10311
10918
  // this.left, this.top, this.right, and this.bottom have been defined
10312
10919
  var me = this;
10313
- var paddingLeft = me.paddingLeft;
10314
- var paddingBottom = me.paddingBottom;
10315
10920
  var start = me.start;
10316
10921
 
10317
10922
  var rightValue = +me.getRightValue(value);
10318
10923
  var pixel;
10319
- var innerDimension;
10320
10924
  var range = me.end - start;
10321
10925
 
10322
10926
  if (me.isHorizontal()) {
10323
- innerDimension = me.width - (paddingLeft + me.paddingRight);
10324
- pixel = me.left + (innerDimension / range * (rightValue - start));
10325
- return Math.round(pixel + paddingLeft);
10927
+ pixel = me.left + (me.width / range * (rightValue - start));
10928
+ return Math.round(pixel);
10326
10929
  }
10327
- innerDimension = me.height - (me.paddingTop + paddingBottom);
10328
- pixel = (me.bottom - paddingBottom) - (innerDimension / range * (rightValue - start));
10930
+
10931
+ pixel = me.bottom - (me.height / range * (rightValue - start));
10329
10932
  return Math.round(pixel);
10330
10933
  },
10331
10934
  getValueForPixel: function(pixel) {
10332
10935
  var me = this;
10333
10936
  var isHorizontal = me.isHorizontal();
10334
- var paddingLeft = me.paddingLeft;
10335
- var paddingBottom = me.paddingBottom;
10336
- var innerDimension = isHorizontal ? me.width - (paddingLeft + me.paddingRight) : me.height - (me.paddingTop + paddingBottom);
10337
- var offset = (isHorizontal ? pixel - me.left - paddingLeft : me.bottom - paddingBottom - pixel) / innerDimension;
10937
+ var innerDimension = isHorizontal ? me.width : me.height;
10938
+ var offset = (isHorizontal ? pixel - me.left : me.bottom - pixel) / innerDimension;
10338
10939
  return me.start + ((me.end - me.start) * offset);
10339
10940
  },
10340
10941
  getPixelForTick: function(index) {
@@ -10345,7 +10946,7 @@ module.exports = function(Chart) {
10345
10946
 
10346
10947
  };
10347
10948
 
10348
- },{}],43:[function(require,module,exports){
10949
+ },{}],45:[function(require,module,exports){
10349
10950
  'use strict';
10350
10951
 
10351
10952
  module.exports = function(Chart) {
@@ -10445,7 +11046,7 @@ module.exports = function(Chart) {
10445
11046
  });
10446
11047
  };
10447
11048
 
10448
- },{}],44:[function(require,module,exports){
11049
+ },{}],46:[function(require,module,exports){
10449
11050
  'use strict';
10450
11051
 
10451
11052
  module.exports = function(Chart) {
@@ -10480,18 +11081,40 @@ module.exports = function(Chart) {
10480
11081
  me.max = null;
10481
11082
  me.minNotZero = null;
10482
11083
 
10483
- if (opts.stacked) {
10484
- var valuesPerType = {};
11084
+ var hasStacks = opts.stacked;
11085
+ if (hasStacks === undefined) {
11086
+ helpers.each(datasets, function(dataset, datasetIndex) {
11087
+ if (hasStacks) {
11088
+ return;
11089
+ }
11090
+
11091
+ var meta = chart.getDatasetMeta(datasetIndex);
11092
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
11093
+ meta.stack !== undefined) {
11094
+ hasStacks = true;
11095
+ }
11096
+ });
11097
+ }
11098
+
11099
+ if (opts.stacked || hasStacks) {
11100
+ var valuesPerStack = {};
10485
11101
 
10486
11102
  helpers.each(datasets, function(dataset, datasetIndex) {
10487
11103
  var meta = chart.getDatasetMeta(datasetIndex);
11104
+ var key = [
11105
+ meta.type,
11106
+ // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
11107
+ ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
11108
+ meta.stack
11109
+ ].join('.');
11110
+
10488
11111
  if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
10489
- if (valuesPerType[meta.type] === undefined) {
10490
- valuesPerType[meta.type] = [];
11112
+ if (valuesPerStack[key] === undefined) {
11113
+ valuesPerStack[key] = [];
10491
11114
  }
10492
11115
 
10493
11116
  helpers.each(dataset.data, function(rawValue, index) {
10494
- var values = valuesPerType[meta.type];
11117
+ var values = valuesPerStack[key];
10495
11118
  var value = +me.getRightValue(rawValue);
10496
11119
  if (isNaN(value) || meta.data[index].hidden) {
10497
11120
  return;
@@ -10509,7 +11132,7 @@ module.exports = function(Chart) {
10509
11132
  }
10510
11133
  });
10511
11134
 
10512
- helpers.each(valuesPerType, function(valuesForType) {
11135
+ helpers.each(valuesPerStack, function(valuesForType) {
10513
11136
  var minVal = helpers.min(valuesForType);
10514
11137
  var maxVal = helpers.max(valuesForType);
10515
11138
  me.min = me.min === null ? minVal : Math.min(me.min, minVal);
@@ -10610,46 +11233,42 @@ module.exports = function(Chart) {
10610
11233
  var start = me.start;
10611
11234
  var newVal = +me.getRightValue(value);
10612
11235
  var range;
10613
- var paddingTop = me.paddingTop;
10614
- var paddingBottom = me.paddingBottom;
10615
- var paddingLeft = me.paddingLeft;
10616
11236
  var opts = me.options;
10617
11237
  var tickOpts = opts.ticks;
10618
11238
 
10619
11239
  if (me.isHorizontal()) {
10620
11240
  range = helpers.log10(me.end) - helpers.log10(start); // todo: if start === 0
10621
11241
  if (newVal === 0) {
10622
- pixel = me.left + paddingLeft;
11242
+ pixel = me.left;
10623
11243
  } else {
10624
- innerDimension = me.width - (paddingLeft + me.paddingRight);
11244
+ innerDimension = me.width;
10625
11245
  pixel = me.left + (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
10626
- pixel += paddingLeft;
10627
11246
  }
10628
11247
  } else {
10629
11248
  // Bottom - top since pixels increase downward on a screen
10630
- innerDimension = me.height - (paddingTop + paddingBottom);
11249
+ innerDimension = me.height;
10631
11250
  if (start === 0 && !tickOpts.reverse) {
10632
11251
  range = helpers.log10(me.end) - helpers.log10(me.minNotZero);
10633
11252
  if (newVal === start) {
10634
- pixel = me.bottom - paddingBottom;
11253
+ pixel = me.bottom;
10635
11254
  } else if (newVal === me.minNotZero) {
10636
- pixel = me.bottom - paddingBottom - innerDimension * 0.02;
11255
+ pixel = me.bottom - innerDimension * 0.02;
10637
11256
  } else {
10638
- pixel = me.bottom - paddingBottom - innerDimension * 0.02 - (innerDimension * 0.98/ range * (helpers.log10(newVal)-helpers.log10(me.minNotZero)));
11257
+ pixel = me.bottom - innerDimension * 0.02 - (innerDimension * 0.98/ range * (helpers.log10(newVal)-helpers.log10(me.minNotZero)));
10639
11258
  }
10640
11259
  } else if (me.end === 0 && tickOpts.reverse) {
10641
11260
  range = helpers.log10(me.start) - helpers.log10(me.minNotZero);
10642
11261
  if (newVal === me.end) {
10643
- pixel = me.top + paddingTop;
11262
+ pixel = me.top;
10644
11263
  } else if (newVal === me.minNotZero) {
10645
- pixel = me.top + paddingTop + innerDimension * 0.02;
11264
+ pixel = me.top + innerDimension * 0.02;
10646
11265
  } else {
10647
- pixel = me.top + paddingTop + innerDimension * 0.02 + (innerDimension * 0.98/ range * (helpers.log10(newVal)-helpers.log10(me.minNotZero)));
11266
+ pixel = me.top + innerDimension * 0.02 + (innerDimension * 0.98/ range * (helpers.log10(newVal)-helpers.log10(me.minNotZero)));
10648
11267
  }
10649
11268
  } else {
10650
11269
  range = helpers.log10(me.end) - helpers.log10(start);
10651
- innerDimension = me.height - (paddingTop + paddingBottom);
10652
- pixel = (me.bottom - paddingBottom) - (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
11270
+ innerDimension = me.height;
11271
+ pixel = me.bottom - (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
10653
11272
  }
10654
11273
  }
10655
11274
  return pixel;
@@ -10660,11 +11279,11 @@ module.exports = function(Chart) {
10660
11279
  var value, innerDimension;
10661
11280
 
10662
11281
  if (me.isHorizontal()) {
10663
- innerDimension = me.width - (me.paddingLeft + me.paddingRight);
10664
- value = me.start * Math.pow(10, (pixel - me.left - me.paddingLeft) * range / innerDimension);
11282
+ innerDimension = me.width;
11283
+ value = me.start * Math.pow(10, (pixel - me.left) * range / innerDimension);
10665
11284
  } else { // todo: if start === 0
10666
- innerDimension = me.height - (me.paddingTop + me.paddingBottom);
10667
- value = Math.pow(10, (me.bottom - me.paddingBottom - pixel) * range / innerDimension) / me.start;
11285
+ innerDimension = me.height;
11286
+ value = Math.pow(10, (me.bottom - pixel) * range / innerDimension) / me.start;
10668
11287
  }
10669
11288
  return value;
10670
11289
  }
@@ -10673,7 +11292,7 @@ module.exports = function(Chart) {
10673
11292
 
10674
11293
  };
10675
11294
 
10676
- },{}],45:[function(require,module,exports){
11295
+ },{}],47:[function(require,module,exports){
10677
11296
  'use strict';
10678
11297
 
10679
11298
  module.exports = function(Chart) {
@@ -10723,10 +11342,266 @@ module.exports = function(Chart) {
10723
11342
  }
10724
11343
  };
10725
11344
 
11345
+ function getValueCount(scale) {
11346
+ return !scale.options.lineArc ? scale.chart.data.labels.length : 0;
11347
+ }
11348
+
11349
+ function getPointLabelFontOptions(scale) {
11350
+ var pointLabelOptions = scale.options.pointLabels;
11351
+ var fontSize = helpers.getValueOrDefault(pointLabelOptions.fontSize, globalDefaults.defaultFontSize);
11352
+ var fontStyle = helpers.getValueOrDefault(pointLabelOptions.fontStyle, globalDefaults.defaultFontStyle);
11353
+ var fontFamily = helpers.getValueOrDefault(pointLabelOptions.fontFamily, globalDefaults.defaultFontFamily);
11354
+ var font = helpers.fontString(fontSize, fontStyle, fontFamily);
11355
+
11356
+ return {
11357
+ size: fontSize,
11358
+ style: fontStyle,
11359
+ family: fontFamily,
11360
+ font: font
11361
+ };
11362
+ }
11363
+
11364
+ function measureLabelSize(ctx, fontSize, label) {
11365
+ if (helpers.isArray(label)) {
11366
+ return {
11367
+ w: helpers.longestText(ctx, ctx.font, label),
11368
+ h: (label.length * fontSize) + ((label.length - 1) * 1.5 * fontSize)
11369
+ };
11370
+ }
11371
+
11372
+ return {
11373
+ w: ctx.measureText(label).width,
11374
+ h: fontSize
11375
+ };
11376
+ }
11377
+
11378
+ function determineLimits(angle, pos, size, min, max) {
11379
+ if (angle === min || angle === max) {
11380
+ return {
11381
+ start: pos - (size / 2),
11382
+ end: pos + (size / 2)
11383
+ };
11384
+ } else if (angle < min || angle > max) {
11385
+ return {
11386
+ start: pos - size - 5,
11387
+ end: pos
11388
+ };
11389
+ }
11390
+
11391
+ return {
11392
+ start: pos,
11393
+ end: pos + size + 5
11394
+ };
11395
+ }
11396
+
11397
+ /**
11398
+ * Helper function to fit a radial linear scale with point labels
11399
+ */
11400
+ function fitWithPointLabels(scale) {
11401
+ /*
11402
+ * Right, this is really confusing and there is a lot of maths going on here
11403
+ * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
11404
+ *
11405
+ * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
11406
+ *
11407
+ * Solution:
11408
+ *
11409
+ * We assume the radius of the polygon is half the size of the canvas at first
11410
+ * at each index we check if the text overlaps.
11411
+ *
11412
+ * Where it does, we store that angle and that index.
11413
+ *
11414
+ * After finding the largest index and angle we calculate how much we need to remove
11415
+ * from the shape radius to move the point inwards by that x.
11416
+ *
11417
+ * We average the left and right distances to get the maximum shape radius that can fit in the box
11418
+ * along with labels.
11419
+ *
11420
+ * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
11421
+ * on each side, removing that from the size, halving it and adding the left x protrusion width.
11422
+ *
11423
+ * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
11424
+ * and position it in the most space efficient manner
11425
+ *
11426
+ * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
11427
+ */
11428
+
11429
+ var plFont = getPointLabelFontOptions(scale);
11430
+
11431
+ // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
11432
+ // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
11433
+ var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2);
11434
+ var furthestLimits = {
11435
+ l: scale.width,
11436
+ r: 0,
11437
+ t: scale.height,
11438
+ b: 0
11439
+ };
11440
+ var furthestAngles = {};
11441
+ var i;
11442
+ var textSize;
11443
+ var pointPosition;
11444
+
11445
+ scale.ctx.font = plFont.font;
11446
+ scale._pointLabelSizes = [];
11447
+
11448
+ var valueCount = getValueCount(scale);
11449
+ for (i = 0; i < valueCount; i++) {
11450
+ pointPosition = scale.getPointPosition(i, largestPossibleRadius);
11451
+ textSize = measureLabelSize(scale.ctx, plFont.size, scale.pointLabels[i] || '');
11452
+ scale._pointLabelSizes[i] = textSize;
11453
+
11454
+ // Add quarter circle to make degree 0 mean top of circle
11455
+ var angleRadians = scale.getIndexAngle(i);
11456
+ var angle = helpers.toDegrees(angleRadians) % 360;
11457
+ var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
11458
+ var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
11459
+
11460
+ if (hLimits.start < furthestLimits.l) {
11461
+ furthestLimits.l = hLimits.start;
11462
+ furthestAngles.l = angleRadians;
11463
+ }
11464
+
11465
+ if (hLimits.end > furthestLimits.r) {
11466
+ furthestLimits.r = hLimits.end;
11467
+ furthestAngles.r = angleRadians;
11468
+ }
11469
+
11470
+ if (vLimits.start < furthestLimits.t) {
11471
+ furthestLimits.t = vLimits.start;
11472
+ furthestAngles.t = angleRadians;
11473
+ }
11474
+
11475
+ if (vLimits.end > furthestLimits.b) {
11476
+ furthestLimits.b = vLimits.end;
11477
+ furthestAngles.b = angleRadians;
11478
+ }
11479
+ }
11480
+
11481
+ scale.setReductions(largestPossibleRadius, furthestLimits, furthestAngles);
11482
+ }
11483
+
11484
+ /**
11485
+ * Helper function to fit a radial linear scale with no point labels
11486
+ */
11487
+ function fit(scale) {
11488
+ var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2);
11489
+ scale.drawingArea = Math.round(largestPossibleRadius);
11490
+ scale.setCenterPoint(0, 0, 0, 0);
11491
+ }
11492
+
11493
+ function getTextAlignForAngle(angle) {
11494
+ if (angle === 0 || angle === 180) {
11495
+ return 'center';
11496
+ } else if (angle < 180) {
11497
+ return 'left';
11498
+ }
11499
+
11500
+ return 'right';
11501
+ }
11502
+
11503
+ function fillText(ctx, text, position, fontSize) {
11504
+ if (helpers.isArray(text)) {
11505
+ var y = position.y;
11506
+ var spacing = 1.5 * fontSize;
11507
+
11508
+ for (var i = 0; i < text.length; ++i) {
11509
+ ctx.fillText(text[i], position.x, y);
11510
+ y+= spacing;
11511
+ }
11512
+ } else {
11513
+ ctx.fillText(text, position.x, position.y);
11514
+ }
11515
+ }
11516
+
11517
+ function adjustPointPositionForLabelHeight(angle, textSize, position) {
11518
+ if (angle === 90 || angle === 270) {
11519
+ position.y -= (textSize.h / 2);
11520
+ } else if (angle > 270 || angle < 90) {
11521
+ position.y -= textSize.h;
11522
+ }
11523
+ }
11524
+
11525
+ function drawPointLabels(scale) {
11526
+ var ctx = scale.ctx;
11527
+ var getValueOrDefault = helpers.getValueOrDefault;
11528
+ var opts = scale.options;
11529
+ var angleLineOpts = opts.angleLines;
11530
+ var pointLabelOpts = opts.pointLabels;
11531
+
11532
+ ctx.lineWidth = angleLineOpts.lineWidth;
11533
+ ctx.strokeStyle = angleLineOpts.color;
11534
+
11535
+ var outerDistance = scale.getDistanceFromCenterForValue(opts.reverse ? scale.min : scale.max);
11536
+
11537
+ // Point Label Font
11538
+ var plFont = getPointLabelFontOptions(scale);
11539
+
11540
+ ctx.textBaseline = 'top';
11541
+
11542
+ for (var i = getValueCount(scale) - 1; i >= 0; i--) {
11543
+ if (angleLineOpts.display) {
11544
+ var outerPosition = scale.getPointPosition(i, outerDistance);
11545
+ ctx.beginPath();
11546
+ ctx.moveTo(scale.xCenter, scale.yCenter);
11547
+ ctx.lineTo(outerPosition.x, outerPosition.y);
11548
+ ctx.stroke();
11549
+ ctx.closePath();
11550
+ }
11551
+ // Extra 3px out for some label spacing
11552
+ var pointLabelPosition = scale.getPointPosition(i, outerDistance + 5);
11553
+
11554
+ // Keep this in loop since we may support array properties here
11555
+ var pointLabelFontColor = getValueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor);
11556
+ ctx.font = plFont.font;
11557
+ ctx.fillStyle = pointLabelFontColor;
11558
+
11559
+ var angleRadians = scale.getIndexAngle(i);
11560
+ var angle = helpers.toDegrees(angleRadians);
11561
+ ctx.textAlign = getTextAlignForAngle(angle);
11562
+ adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition);
11563
+ fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.size);
11564
+ }
11565
+ }
11566
+
11567
+ function drawRadiusLine(scale, gridLineOpts, radius, index) {
11568
+ var ctx = scale.ctx;
11569
+ ctx.strokeStyle = helpers.getValueAtIndexOrDefault(gridLineOpts.color, index - 1);
11570
+ ctx.lineWidth = helpers.getValueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1);
11571
+
11572
+ if (scale.options.lineArc) {
11573
+ // Draw circular arcs between the points
11574
+ ctx.beginPath();
11575
+ ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2);
11576
+ ctx.closePath();
11577
+ ctx.stroke();
11578
+ } else {
11579
+ // Draw straight lines connecting each index
11580
+ var valueCount = getValueCount(scale);
11581
+
11582
+ if (valueCount === 0) {
11583
+ return;
11584
+ }
11585
+
11586
+ ctx.beginPath();
11587
+ var pointPosition = scale.getPointPosition(0, radius);
11588
+ ctx.moveTo(pointPosition.x, pointPosition.y);
11589
+
11590
+ for (var i = 1; i < valueCount; i++) {
11591
+ pointPosition = scale.getPointPosition(i, radius);
11592
+ ctx.lineTo(pointPosition.x, pointPosition.y);
11593
+ }
11594
+
11595
+ ctx.closePath();
11596
+ ctx.stroke();
11597
+ }
11598
+ }
11599
+
11600
+ function numberOrZero(param) {
11601
+ return helpers.isNumber(param) ? param : 0;
11602
+ }
11603
+
10726
11604
  var LinearRadialScale = Chart.LinearScaleBase.extend({
10727
- getValueCount: function() {
10728
- return this.chart.data.labels.length;
10729
- },
10730
11605
  setDimensions: function() {
10731
11606
  var me = this;
10732
11607
  var opts = me.options;
@@ -10744,9 +11619,8 @@ module.exports = function(Chart) {
10744
11619
  determineDataLimits: function() {
10745
11620
  var me = this;
10746
11621
  var chart = me.chart;
10747
- me.min = null;
10748
- me.max = null;
10749
-
11622
+ var min = Number.POSITIVE_INFINITY;
11623
+ var max = Number.NEGATIVE_INFINITY;
10750
11624
 
10751
11625
  helpers.each(chart.data.datasets, function(dataset, datasetIndex) {
10752
11626
  if (chart.isDatasetVisible(datasetIndex)) {
@@ -10758,21 +11632,15 @@ module.exports = function(Chart) {
10758
11632
  return;
10759
11633
  }
10760
11634
 
10761
- if (me.min === null) {
10762
- me.min = value;
10763
- } else if (value < me.min) {
10764
- me.min = value;
10765
- }
10766
-
10767
- if (me.max === null) {
10768
- me.max = value;
10769
- } else if (value > me.max) {
10770
- me.max = value;
10771
- }
11635
+ min = Math.min(value, min);
11636
+ max = Math.max(value, max);
10772
11637
  });
10773
11638
  }
10774
11639
  });
10775
11640
 
11641
+ me.min = (min === Number.POSITIVE_INFINITY ? 0 : min);
11642
+ me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max);
11643
+
10776
11644
  // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
10777
11645
  me.handleTickRangeOptions();
10778
11646
  },
@@ -10792,122 +11660,46 @@ module.exports = function(Chart) {
10792
11660
  return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
10793
11661
  },
10794
11662
  fit: function() {
10795
- /*
10796
- * Right, this is really confusing and there is a lot of maths going on here
10797
- * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
10798
- *
10799
- * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
10800
- *
10801
- * Solution:
10802
- *
10803
- * We assume the radius of the polygon is half the size of the canvas at first
10804
- * at each index we check if the text overlaps.
10805
- *
10806
- * Where it does, we store that angle and that index.
10807
- *
10808
- * After finding the largest index and angle we calculate how much we need to remove
10809
- * from the shape radius to move the point inwards by that x.
10810
- *
10811
- * We average the left and right distances to get the maximum shape radius that can fit in the box
10812
- * along with labels.
10813
- *
10814
- * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
10815
- * on each side, removing that from the size, halving it and adding the left x protrusion width.
10816
- *
10817
- * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
10818
- * and position it in the most space efficient manner
10819
- *
10820
- * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
10821
- */
10822
-
10823
- var pointLabels = this.options.pointLabels;
10824
- var pointLabelFontSize = helpers.getValueOrDefault(pointLabels.fontSize, globalDefaults.defaultFontSize);
10825
- var pointLabeFontStyle = helpers.getValueOrDefault(pointLabels.fontStyle, globalDefaults.defaultFontStyle);
10826
- var pointLabeFontFamily = helpers.getValueOrDefault(pointLabels.fontFamily, globalDefaults.defaultFontFamily);
10827
- var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily);
10828
-
10829
- // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
10830
- // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
10831
- var largestPossibleRadius = helpers.min([(this.height / 2 - pointLabelFontSize - 5), this.width / 2]),
10832
- pointPosition,
10833
- i,
10834
- textWidth,
10835
- halfTextWidth,
10836
- furthestRight = this.width,
10837
- furthestRightIndex,
10838
- furthestRightAngle,
10839
- furthestLeft = 0,
10840
- furthestLeftIndex,
10841
- furthestLeftAngle,
10842
- xProtrusionLeft,
10843
- xProtrusionRight,
10844
- radiusReductionRight,
10845
- radiusReductionLeft;
10846
- this.ctx.font = pointLabeFont;
10847
-
10848
- for (i = 0; i < this.getValueCount(); i++) {
10849
- // 5px to space the text slightly out - similar to what we do in the draw function.
10850
- pointPosition = this.getPointPosition(i, largestPossibleRadius);
10851
- textWidth = this.ctx.measureText(this.pointLabels[i] ? this.pointLabels[i] : '').width + 5;
10852
-
10853
- // Add quarter circle to make degree 0 mean top of circle
10854
- var angleRadians = this.getIndexAngle(i) + (Math.PI / 2);
10855
- var angle = (angleRadians * 360 / (2 * Math.PI)) % 360;
10856
-
10857
- if (angle === 0 || angle === 180) {
10858
- // At angle 0 and 180, we're at exactly the top/bottom
10859
- // of the radar chart, so text will be aligned centrally, so we'll half it and compare
10860
- // w/left and right text sizes
10861
- halfTextWidth = textWidth / 2;
10862
- if (pointPosition.x + halfTextWidth > furthestRight) {
10863
- furthestRight = pointPosition.x + halfTextWidth;
10864
- furthestRightIndex = i;
10865
- }
10866
- if (pointPosition.x - halfTextWidth < furthestLeft) {
10867
- furthestLeft = pointPosition.x - halfTextWidth;
10868
- furthestLeftIndex = i;
10869
- }
10870
- } else if (angle < 180) {
10871
- // Less than half the values means we'll left align the text
10872
- if (pointPosition.x + textWidth > furthestRight) {
10873
- furthestRight = pointPosition.x + textWidth;
10874
- furthestRightIndex = i;
10875
- }
10876
- // More than half the values means we'll right align the text
10877
- } else if (pointPosition.x - textWidth < furthestLeft) {
10878
- furthestLeft = pointPosition.x - textWidth;
10879
- furthestLeftIndex = i;
10880
- }
11663
+ if (this.options.lineArc) {
11664
+ fit(this);
11665
+ } else {
11666
+ fitWithPointLabels(this);
10881
11667
  }
10882
-
10883
- xProtrusionLeft = furthestLeft;
10884
- xProtrusionRight = Math.ceil(furthestRight - this.width);
10885
-
10886
- furthestRightAngle = this.getIndexAngle(furthestRightIndex);
10887
- furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
10888
-
10889
- radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2);
10890
- radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2);
10891
-
10892
- // Ensure we actually need to reduce the size of the chart
10893
- radiusReductionRight = (helpers.isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
10894
- radiusReductionLeft = (helpers.isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
10895
-
10896
- this.drawingArea = Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2);
10897
- this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
10898
11668
  },
10899
- setCenterPoint: function(leftMovement, rightMovement) {
11669
+ /**
11670
+ * Set radius reductions and determine new radius and center point
11671
+ * @private
11672
+ */
11673
+ setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) {
11674
+ var me = this;
11675
+ var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l);
11676
+ var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r);
11677
+ var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t);
11678
+ var radiusReductionBottom = -Math.max(furthestLimits.b - me.height, 0) / Math.cos(furthestAngles.b);
11679
+
11680
+ radiusReductionLeft = numberOrZero(radiusReductionLeft);
11681
+ radiusReductionRight = numberOrZero(radiusReductionRight);
11682
+ radiusReductionTop = numberOrZero(radiusReductionTop);
11683
+ radiusReductionBottom = numberOrZero(radiusReductionBottom);
11684
+
11685
+ me.drawingArea = Math.min(
11686
+ Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2),
11687
+ Math.round(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2));
11688
+ me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom);
11689
+ },
11690
+ setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) {
10900
11691
  var me = this;
10901
11692
  var maxRight = me.width - rightMovement - me.drawingArea,
10902
- maxLeft = leftMovement + me.drawingArea;
11693
+ maxLeft = leftMovement + me.drawingArea,
11694
+ maxTop = topMovement + me.drawingArea,
11695
+ maxBottom = me.height - bottomMovement - me.drawingArea;
10903
11696
 
10904
11697
  me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left);
10905
- // Always vertically in the centre as the text height doesn't change
10906
- me.yCenter = Math.round((me.height / 2) + me.top);
11698
+ me.yCenter = Math.round(((maxTop + maxBottom) / 2) + me.top);
10907
11699
  },
10908
11700
 
10909
11701
  getIndexAngle: function(index) {
10910
- var angleMultiplier = (Math.PI * 2) / this.getValueCount();
11702
+ var angleMultiplier = (Math.PI * 2) / getValueCount(this);
10911
11703
  var startAngle = this.chart.options && this.chart.options.startAngle ?
10912
11704
  this.chart.options.startAngle :
10913
11705
  0;
@@ -10915,7 +11707,7 @@ module.exports = function(Chart) {
10915
11707
  var startAngleRadians = startAngle * Math.PI * 2 / 360;
10916
11708
 
10917
11709
  // Start from the top instead of right, so remove a quarter of the circle
10918
- return index * angleMultiplier - (Math.PI / 2) + startAngleRadians;
11710
+ return index * angleMultiplier + startAngleRadians;
10919
11711
  },
10920
11712
  getDistanceFromCenterForValue: function(value) {
10921
11713
  var me = this;
@@ -10933,7 +11725,7 @@ module.exports = function(Chart) {
10933
11725
  },
10934
11726
  getPointPosition: function(index, distanceFromCenter) {
10935
11727
  var me = this;
10936
- var thisAngle = me.getIndexAngle(index);
11728
+ var thisAngle = me.getIndexAngle(index) - (Math.PI / 2);
10937
11729
  return {
10938
11730
  x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter,
10939
11731
  y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter
@@ -10960,8 +11752,6 @@ module.exports = function(Chart) {
10960
11752
  var opts = me.options;
10961
11753
  var gridLineOpts = opts.gridLines;
10962
11754
  var tickOpts = opts.ticks;
10963
- var angleLineOpts = opts.angleLines;
10964
- var pointLabelOpts = opts.pointLabels;
10965
11755
  var getValueOrDefault = helpers.getValueOrDefault;
10966
11756
 
10967
11757
  if (opts.display) {
@@ -10981,29 +11771,7 @@ module.exports = function(Chart) {
10981
11771
 
10982
11772
  // Draw circular lines around the scale
10983
11773
  if (gridLineOpts.display && index !== 0) {
10984
- ctx.strokeStyle = helpers.getValueAtIndexOrDefault(gridLineOpts.color, index - 1);
10985
- ctx.lineWidth = helpers.getValueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1);
10986
-
10987
- if (opts.lineArc) {
10988
- // Draw circular arcs between the points
10989
- ctx.beginPath();
10990
- ctx.arc(me.xCenter, me.yCenter, yCenterOffset, 0, Math.PI * 2);
10991
- ctx.closePath();
10992
- ctx.stroke();
10993
- } else {
10994
- // Draw straight lines connecting each index
10995
- ctx.beginPath();
10996
- for (var i = 0; i < me.getValueCount(); i++) {
10997
- var pointPosition = me.getPointPosition(i, yCenterOffset);
10998
- if (i === 0) {
10999
- ctx.moveTo(pointPosition.x, pointPosition.y);
11000
- } else {
11001
- ctx.lineTo(pointPosition.x, pointPosition.y);
11002
- }
11003
- }
11004
- ctx.closePath();
11005
- ctx.stroke();
11006
- }
11774
+ drawRadiusLine(me, gridLineOpts, yCenterOffset, index);
11007
11775
  }
11008
11776
 
11009
11777
  if (tickOpts.display) {
@@ -11030,59 +11798,7 @@ module.exports = function(Chart) {
11030
11798
  });
11031
11799
 
11032
11800
  if (!opts.lineArc) {
11033
- ctx.lineWidth = angleLineOpts.lineWidth;
11034
- ctx.strokeStyle = angleLineOpts.color;
11035
-
11036
- var outerDistance = me.getDistanceFromCenterForValue(opts.reverse ? me.min : me.max);
11037
-
11038
- // Point Label Font
11039
- var pointLabelFontSize = getValueOrDefault(pointLabelOpts.fontSize, globalDefaults.defaultFontSize);
11040
- var pointLabeFontStyle = getValueOrDefault(pointLabelOpts.fontStyle, globalDefaults.defaultFontStyle);
11041
- var pointLabeFontFamily = getValueOrDefault(pointLabelOpts.fontFamily, globalDefaults.defaultFontFamily);
11042
- var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily);
11043
-
11044
- for (var i = me.getValueCount() - 1; i >= 0; i--) {
11045
- if (angleLineOpts.display) {
11046
- var outerPosition = me.getPointPosition(i, outerDistance);
11047
- ctx.beginPath();
11048
- ctx.moveTo(me.xCenter, me.yCenter);
11049
- ctx.lineTo(outerPosition.x, outerPosition.y);
11050
- ctx.stroke();
11051
- ctx.closePath();
11052
- }
11053
- // Extra 3px out for some label spacing
11054
- var pointLabelPosition = me.getPointPosition(i, outerDistance + 5);
11055
-
11056
- // Keep this in loop since we may support array properties here
11057
- var pointLabelFontColor = getValueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor);
11058
- ctx.font = pointLabeFont;
11059
- ctx.fillStyle = pointLabelFontColor;
11060
-
11061
- var pointLabels = me.pointLabels;
11062
-
11063
- // Add quarter circle to make degree 0 mean top of circle
11064
- var angleRadians = this.getIndexAngle(i) + (Math.PI / 2);
11065
- var angle = (angleRadians * 360 / (2 * Math.PI)) % 360;
11066
-
11067
- if (angle === 0 || angle === 180) {
11068
- ctx.textAlign = 'center';
11069
- } else if (angle < 180) {
11070
- ctx.textAlign = 'left';
11071
- } else {
11072
- ctx.textAlign = 'right';
11073
- }
11074
-
11075
- // Set the correct text baseline based on outer positioning
11076
- if (angle === 90 || angle === 270) {
11077
- ctx.textBaseline = 'middle';
11078
- } else if (angle > 270 || angle < 90) {
11079
- ctx.textBaseline = 'bottom';
11080
- } else {
11081
- ctx.textBaseline = 'top';
11082
- }
11083
-
11084
- ctx.fillText(pointLabels[i] ? pointLabels[i] : '', pointLabelPosition.x, pointLabelPosition.y);
11085
- }
11801
+ drawPointLabels(me);
11086
11802
  }
11087
11803
  }
11088
11804
  }
@@ -11091,7 +11807,7 @@ module.exports = function(Chart) {
11091
11807
 
11092
11808
  };
11093
11809
 
11094
- },{}],46:[function(require,module,exports){
11810
+ },{}],48:[function(require,module,exports){
11095
11811
  /* global window: false */
11096
11812
  'use strict';
11097
11813
 
@@ -11334,7 +12050,7 @@ module.exports = function(Chart) {
11334
12050
  me.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, 1);
11335
12051
  } else {
11336
12052
  // Determine the smallest needed unit of the time
11337
- var innerWidth = me.isHorizontal() ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom);
12053
+ var innerWidth = me.isHorizontal() ? me.width : me.height;
11338
12054
 
11339
12055
  // Crude approximation of what the label length might be
11340
12056
  var tempFirstLabel = me.tickFormatFunction(me.firstTick, 0, []);
@@ -11418,7 +12134,7 @@ module.exports = function(Chart) {
11418
12134
  me.ticks.push(me.firstTick.clone());
11419
12135
 
11420
12136
  // For every unit in between the first and last moment, create a moment and add it to the ticks tick
11421
- for (var i = 1; i <= me.scaleSizeInUnits; ++i) {
12137
+ for (var i = me.unitScale; i <= me.scaleSizeInUnits; i += me.unitScale) {
11422
12138
  var newTick = roundedStart.clone().add(i, me.tickUnit);
11423
12139
 
11424
12140
  // Are we greater than the max time
@@ -11426,9 +12142,7 @@ module.exports = function(Chart) {
11426
12142
  break;
11427
12143
  }
11428
12144
 
11429
- if (i % me.unitScale === 0) {
11430
- me.ticks.push(newTick);
11431
- }
12145
+ me.ticks.push(newTick);
11432
12146
  }
11433
12147
 
11434
12148
  // Always show the right tick
@@ -11454,9 +12168,10 @@ module.exports = function(Chart) {
11454
12168
  getLabelForIndex: function(index, datasetIndex) {
11455
12169
  var me = this;
11456
12170
  var label = me.chart.data.labels && index < me.chart.data.labels.length ? me.chart.data.labels[index] : '';
12171
+ var value = me.chart.data.datasets[datasetIndex].data[index];
11457
12172
 
11458
- if (typeof me.chart.data.datasets[datasetIndex].data[0] === 'object') {
11459
- label = me.getRightValue(me.chart.data.datasets[datasetIndex].data[index]);
12173
+ if (value !== null && typeof value === 'object') {
12174
+ label = me.getRightValue(value);
11460
12175
  }
11461
12176
 
11462
12177
  // Format nicely
@@ -11503,14 +12218,11 @@ module.exports = function(Chart) {
11503
12218
  var decimal = offset !== 0 ? offset / me.scaleSizeInUnits : offset;
11504
12219
 
11505
12220
  if (me.isHorizontal()) {
11506
- var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
11507
- var valueOffset = (innerWidth * decimal) + me.paddingLeft;
11508
-
12221
+ var valueOffset = (me.width * decimal);
11509
12222
  return me.left + Math.round(valueOffset);
11510
12223
  }
11511
- var innerHeight = me.height - (me.paddingTop + me.paddingBottom);
11512
- var heightOffset = (innerHeight * decimal) + me.paddingTop;
11513
12224
 
12225
+ var heightOffset = (me.height * decimal);
11514
12226
  return me.top + Math.round(heightOffset);
11515
12227
  }
11516
12228
  },
@@ -11519,8 +12231,8 @@ module.exports = function(Chart) {
11519
12231
  },
11520
12232
  getValueForPixel: function(pixel) {
11521
12233
  var me = this;
11522
- var innerDimension = me.isHorizontal() ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom);
11523
- var offset = (pixel - (me.isHorizontal() ? me.left + me.paddingLeft : me.top + me.paddingTop)) / innerDimension;
12234
+ var innerDimension = me.isHorizontal() ? me.width : me.height;
12235
+ var offset = (pixel - (me.isHorizontal() ? me.left : me.top)) / innerDimension;
11524
12236
  offset *= me.scaleSizeInUnits;
11525
12237
  return me.firstTick.clone().add(moment.duration(offset, me.tickUnit).asSeconds(), 'seconds');
11526
12238
  },