chart-js-rails 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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
  },