Skip to content

Instantly share code, notes, and snippets.

@mehak-sachdeva
Last active September 23, 2021 13:36
  • Star 2 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save mehak-sachdeva/503588cc042fd4b5500128034beae71a to your computer and use it in GitHub Desktop.
Interactive Charts

##Link to the gist: http://bit.ly/CartoCampII

Inspiration: http://www.nytimes.com/elections/2016/national-results-map

http://chriswhong.github.io/nycSchools/

##Data file to use

Collection and wrangling of data: http://bit.ly/dataelection

We will create the visualization using GeoJSON which is a format for encoding a variety of geographic data structure. It follows the following structure:

{
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [125.6, 10.1]
        },
        "properties": {
          "name": "Dinagat Islands"
        }
}

SQL API to call our compiled and cleaned data (obtained from json) : "https://mehak-carto.cartodb.com/api/v2/sql?q=SELECT%20*%20FROM%20finalest%20&format=geojson&filename=finalest"

##Displaying the Basemap Tiles

var map = L.map('map').setView([39.0119, -98.4842], 5);
			
		L.tileLayer('http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {
			attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="https://carto.com/attributions">CARTO</a>'
		}).addTo(map);

You can also create your custom basemaps in Mapbox and access them here or also bring in Stamen tiles instead.

##Adding Colors

We create a symbolic map with a gradient of colors reflecting the fraction of votes for the Democratic Party in the Primary elections 2016 for each county in the country. For this we create a function that defines the colors for each vote_fraction bracket according to our predefined brackets.


	function getColor(b) {
			return b <= 0.0 & b> -0.25 ? '#85c4c9' :
                   b <= -0.25 & b > -0.5 ? '#4f90a6':
                   b <= -0.5 & b> -0.75 ? '#3b738f':
                   b <= -0.75 & b>= -1.0 ? '#2a5674':
			       b > 0.0 & b <= 0.25 ? '#facba6' :
                   b > 0.25 & b <= 0.5 ? '#f8b58b':
                   b > 0.5 & b <= 0.75 ? '#f2855d':
                   b > 0.75 & b <= 1.0 ? '#eb4a40':
			    			'grey' ;
		}

Since this function takes in the fraction votes parameter, while defining the overall style function for the feature of our GeoJSON data, we access the feature.properties.bernie_fraction while calling the getColor function.

	function style(feature) {
			return {
				weight: 0.6,
				opacity: 0.4,
				color: 'white',
				fillOpacity: 0.8,
				fillColor: getColor(feature.properties.frac_tot)
			};
		}

##Adding Interaction while Hover

We next create a function highlightFeature() that takes in the event object and highlights the target layer (i.e. the layer over which the user hovers) with the styling properties mentioned in the options. We use the setStyle(), a pre-defined method in Leaflet. We also make sure, that the highlights of the hovered feature do not clash with the existing layer features by the bringToFront() method. Since, this method has issues with Internet Explorer and Mobile Opera we want to make sure that we do not apply this on either of those platforms by this condition if (!L.Browser.ie && !L.Browser.opera).

function highlightFeature(e) {
			var layer = e.target;

			layer.setStyle({
				weight: 3,
				color: 'grey',
				dashArray: '',
				fillOpacity: 0.7
			});

			if (!L.Browser.ie && !L.Browser.opera) {
				layer.bringToFront();
			}

			info.update(layer.feature.properties);
		}

To define what happens when the mouse is not hovering over any of the counties, we define the following function:

var geojson;

		function resetHighlight(e) {
			geojson.resetStyle(e.target);
			info.update();
		}

The resetStyle method in leaflet helps reset the style of the target feature to the original GeoJSON style, particularly useful in post-hover cases like this. We also use the info.update() function within our functions defining interaction which is defined as follows:

var info = L.control();

		info.onAdd = function(map) {
			this._div = L.DomUtil.create('div', 'info');
			this.update();
			return this._div;
		};
        
		info.update = function(props) {
            if (props) {
                if (props.party == 'Democratic') {
                    var labels = ['Hillary Clinton', 'Bernie Sanders'];
                    var data = [props.clinton_1746_president, props.sanders_1445_president];
                    console.log('labels', labels, 'data', data);
                    var dems = '<h4>US Primary Election Data 2016</h4>' +  '<br />' + (props ? props.name + ' County' + '<br />' + '<br />' + '<b>Democratic Party Winner: ' + props.d_winner + '</b><br />'+ 'Margin of Victory (%): ' + props.d_margin_pc.toFixed(2):'mehak');
                    dems += '<canvas id="myChart" width="10" height="10"></canvas>';
                    this._div.innerHTML = dems;
                    newChart(labels, data);
                } else {
                    var labels = ['Trump', 'Cruz','Kasich','Rubio'];
                    var data = [props.trump_8639_president, props.cruz_61815_president, props.kasich_36679_president, props.rubio_53044_president];
                    var reps = '<h4>US Primary Election Data 2016</h4>' +  '<br />' + (props ? props.name + ' County' + '<br />' + '<br />' + '<b>Republican Party Winner: ' + props.r_winner + '</b><br />'+ 'Margin of Victory (%): ' + props.r_margin_pc.toFixed(2):'andy');
                    reps += '<canvas id="myChart" width="10" height="10"></canvas>';
                    this._div.innerHTML = reps;
                    newChart(labels, data);
                }
            }

                console.log('props:', props);
		};

		info.addTo(map);

This creates a custom control to update the information of each county as the user hovers over it. We also use an info.onAdd function that adds the information on the map as the layers are loaded.

info.onAdd = function (map) {
			this._div = L.DomUtil.create('div', 'info');
			this.update();
			return this._div;
		};

The L.DomUtil.create() creates a DOM node (div element here) and assigns it to the class 'info'. This element is added at the time of map loading on the map using info.addTo(map);

##Charts

http://www.chartjs.org/docs/

var newChart = function(labels, data) {
        var dataLength = labels ? labels.length : 0;
        console.log
        console.log('we\'re in newChart', labels, data);
        var backgroundColors = ['rgba(235,127,134, 0.9)',
                                'rgba(206,102,147, 0.9)',
                                'rgba(129,55,83, 0.9)',
                                'rgba(211,156,131, 0.9)',
                                'rgba(153, 102, 255, 0.9)',
                                'rgba(255, 159, 64, 0.9)'];
        var colors = [];
        for (var i = 0; i < dataLength; i++) {
            colors.push(backgroundColors[i]);
        };
        console.log('newChart colors', colors);
        var ctx = document.getElementById("myChart");
        var myChart = new Chart(ctx, {
                type: 'bar',
                data: {
                    labels: labels,
                    datasets: [{
                        label: '# of Votes',
                        data: data,
                        backgroundColor: colors,
                        borderColor: colors,
                        borderWidth: 1
                    }]
                },
                options: {
                    scales: {
                        yAxes: [{
                            ticks: {
                                beginAtZero:true
                            }
                        }]
                    }
                }
            });
    };

##Putting it all together!

Next, we use the onEachFeature option that will be called on each created feature layer to define what happens on mouseover, mouseout and click events. And finally, adding the GeoJSON with the defined style and onEachFeature option, we add the layer and its interactivity to the map using addTo(Map).

function onEachFeature(feature, layer) {
			console.log('onEachFeature was entered');
			layer.on({
				mouseover: highlightFeature,
				mouseout: resetHighlight,
				click: zoomToFeature
			});
		}

		$.getJSON ("https://mehak-carto.cartodb.com/api/v2/sql?q=SELECT%20*%20FROM%20primary_results_transposed_simplified%20&format=geojson&filename=primary_results_transposed_simplified", function(data) {
			console.log('geojson retrieved');
			geojson = L.geoJson(data, {
				style: style,
				onEachFeature: onEachFeature
			}).addTo(map);
		});

##Adding Legend to the Map

Next we construct and add the legend to the map.

	var legend = L.control({position: 'bottomright'});

		legend.onAdd = function (map) {

			var div = L.DomUtil.create('div', 'info legend'),
				grades = [100, 75, 50, 25, 0, 25, 50, 75, 100],
				labels = ['<strong>Party Bent</strong>'],
				from, to;
			var x=1;
            var y=1;
			for (var i = 0; i < grades.length - 1; i++) {
				from = grades[i];
				to = grades[i + 1];
                y++;

				labels.push(
					'<i style="background:' + getColor(x,x-0.25) + '"></i> ' + from + (' to ' + to)
                    );
					x-=0.25;
			}

			div.innerHTML = labels.join('<br>');
			return div;
		};

		legend.addTo(map);
		

In this function, the DomUtil option creates a div element within the info legend class and assigns the range to the grade array. Using the loop, we assign the grade brackets to the colors by the getColor function. Finally, the labels.join() option adds format to the legend labels and it is finally added to the map with addTo(map).

/*!
* Chart.js
* http://chartjs.org/
* Version: 2.3.0
*
* Copyright 2016 Nick Downie
* Released under the MIT license
* https://github.com/chartjs/Chart.js/blob/master/LICENSE.md
*/
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Chart = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
},{}],2:[function(require,module,exports){
/* MIT license */
var colorNames = require(6);
module.exports = {
getRgba: getRgba,
getHsla: getHsla,
getRgb: getRgb,
getHsl: getHsl,
getHwb: getHwb,
getAlpha: getAlpha,
hexString: hexString,
rgbString: rgbString,
rgbaString: rgbaString,
percentString: percentString,
percentaString: percentaString,
hslString: hslString,
hslaString: hslaString,
hwbString: hwbString,
keyword: keyword
}
function getRgba(string) {
if (!string) {
return;
}
var abbr = /^#([a-fA-F0-9]{3})$/,
hex = /^#([a-fA-F0-9]{6})$/,
rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/,
per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/,
keyword = /(\w+)/;
var rgb = [0, 0, 0],
a = 1,
match = string.match(abbr);
if (match) {
match = match[1];
for (var i = 0; i < rgb.length; i++) {
rgb[i] = parseInt(match[i] + match[i], 16);
}
}
else if (match = string.match(hex)) {
match = match[1];
for (var i = 0; i < rgb.length; i++) {
rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16);
}
}
else if (match = string.match(rgba)) {
for (var i = 0; i < rgb.length; i++) {
rgb[i] = parseInt(match[i + 1]);
}
a = parseFloat(match[4]);
}
else if (match = string.match(per)) {
for (var i = 0; i < rgb.length; i++) {
rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55);
}
a = parseFloat(match[4]);
}
else if (match = string.match(keyword)) {
if (match[1] == "transparent") {
return [0, 0, 0, 0];
}
rgb = colorNames[match[1]];
if (!rgb) {
return;
}
}
for (var i = 0; i < rgb.length; i++) {
rgb[i] = scale(rgb[i], 0, 255);
}
if (!a && a != 0) {
a = 1;
}
else {
a = scale(a, 0, 1);
}
rgb[3] = a;
return rgb;
}
function getHsla(string) {
if (!string) {
return;
}
var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
var match = string.match(hsl);
if (match) {
var alpha = parseFloat(match[4]);
var h = scale(parseInt(match[1]), 0, 360),
s = scale(parseFloat(match[2]), 0, 100),
l = scale(parseFloat(match[3]), 0, 100),
a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
return [h, s, l, a];
}
}
function getHwb(string) {
if (!string) {
return;
}
var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
var match = string.match(hwb);
if (match) {
var alpha = parseFloat(match[4]);
var h = scale(parseInt(match[1]), 0, 360),
w = scale(parseFloat(match[2]), 0, 100),
b = scale(parseFloat(match[3]), 0, 100),
a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
return [h, w, b, a];
}
}
function getRgb(string) {
var rgba = getRgba(string);
return rgba && rgba.slice(0, 3);
}
function getHsl(string) {
var hsla = getHsla(string);
return hsla && hsla.slice(0, 3);
}
function getAlpha(string) {
var vals = getRgba(string);
if (vals) {
return vals[3];
}
else if (vals = getHsla(string)) {
return vals[3];
}
else if (vals = getHwb(string)) {
return vals[3];
}
}
// generators
function hexString(rgb) {
return "#" + hexDouble(rgb[0]) + hexDouble(rgb[1])
+ hexDouble(rgb[2]);
}
function rgbString(rgba, alpha) {
if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
return rgbaString(rgba, alpha);
}
return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")";
}
function rgbaString(rgba, alpha) {
if (alpha === undefined) {
alpha = (rgba[3] !== undefined ? rgba[3] : 1);
}
return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2]
+ ", " + alpha + ")";
}
function percentString(rgba, alpha) {
if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
return percentaString(rgba, alpha);
}
var r = Math.round(rgba[0]/255 * 100),
g = Math.round(rgba[1]/255 * 100),
b = Math.round(rgba[2]/255 * 100);
return "rgb(" + r + "%, " + g + "%, " + b + "%)";
}
function percentaString(rgba, alpha) {
var r = Math.round(rgba[0]/255 * 100),
g = Math.round(rgba[1]/255 * 100),
b = Math.round(rgba[2]/255 * 100);
return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")";
}
function hslString(hsla, alpha) {
if (alpha < 1 || (hsla[3] && hsla[3] < 1)) {
return hslaString(hsla, alpha);
}
return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)";
}
function hslaString(hsla, alpha) {
if (alpha === undefined) {
alpha = (hsla[3] !== undefined ? hsla[3] : 1);
}
return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, "
+ alpha + ")";
}
// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax
// (hwb have alpha optional & 1 is default value)
function hwbString(hwb, alpha) {
if (alpha === undefined) {
alpha = (hwb[3] !== undefined ? hwb[3] : 1);
}
return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%"
+ (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")";
}
function keyword(rgb) {
return reverseNames[rgb.slice(0, 3)];
}
// helpers
function scale(num, min, max) {
return Math.min(Math.max(min, num), max);
}
function hexDouble(num) {
var str = num.toString(16).toUpperCase();
return (str.length < 2) ? "0" + str : str;
}
//create a list of reverse color names
var reverseNames = {};
for (var name in colorNames) {
reverseNames[colorNames[name]] = name;
}
},{"6":6}],3:[function(require,module,exports){
/* MIT license */
var convert = require(5);
var string = require(2);
var Color = function (obj) {
if (obj instanceof Color) {
return obj;
}
if (!(this instanceof Color)) {
return new Color(obj);
}
this.values = {
rgb: [0, 0, 0],
hsl: [0, 0, 0],
hsv: [0, 0, 0],
hwb: [0, 0, 0],
cmyk: [0, 0, 0, 0],
alpha: 1
};
// parse Color() argument
var vals;
if (typeof obj === 'string') {
vals = string.getRgba(obj);
if (vals) {
this.setValues('rgb', vals);
} else if (vals = string.getHsla(obj)) {
this.setValues('hsl', vals);
} else if (vals = string.getHwb(obj)) {
this.setValues('hwb', vals);
} else {
throw new Error('Unable to parse color from string "' + obj + '"');
}
} else if (typeof obj === 'object') {
vals = obj;
if (vals.r !== undefined || vals.red !== undefined) {
this.setValues('rgb', vals);
} else if (vals.l !== undefined || vals.lightness !== undefined) {
this.setValues('hsl', vals);
} else if (vals.v !== undefined || vals.value !== undefined) {
this.setValues('hsv', vals);
} else if (vals.w !== undefined || vals.whiteness !== undefined) {
this.setValues('hwb', vals);
} else if (vals.c !== undefined || vals.cyan !== undefined) {
this.setValues('cmyk', vals);
} else {
throw new Error('Unable to parse color from object ' + JSON.stringify(obj));
}
}
};
Color.prototype = {
rgb: function () {
return this.setSpace('rgb', arguments);
},
hsl: function () {
return this.setSpace('hsl', arguments);
},
hsv: function () {
return this.setSpace('hsv', arguments);
},
hwb: function () {
return this.setSpace('hwb', arguments);
},
cmyk: function () {
return this.setSpace('cmyk', arguments);
},
rgbArray: function () {
return this.values.rgb;
},
hslArray: function () {
return this.values.hsl;
},
hsvArray: function () {
return this.values.hsv;
},
hwbArray: function () {
var values = this.values;
if (values.alpha !== 1) {
return values.hwb.concat([values.alpha]);
}
return values.hwb;
},
cmykArray: function () {
return this.values.cmyk;
},
rgbaArray: function () {
var values = this.values;
return values.rgb.concat([values.alpha]);
},
hslaArray: function () {
var values = this.values;
return values.hsl.concat([values.alpha]);
},
alpha: function (val) {
if (val === undefined) {
return this.values.alpha;
}
this.setValues('alpha', val);
return this;
},
red: function (val) {
return this.setChannel('rgb', 0, val);
},
green: function (val) {
return this.setChannel('rgb', 1, val);
},
blue: function (val) {
return this.setChannel('rgb', 2, val);
},
hue: function (val) {
if (val) {
val %= 360;
val = val < 0 ? 360 + val : val;
}
return this.setChannel('hsl', 0, val);
},
saturation: function (val) {
return this.setChannel('hsl', 1, val);
},
lightness: function (val) {
return this.setChannel('hsl', 2, val);
},
saturationv: function (val) {
return this.setChannel('hsv', 1, val);
},
whiteness: function (val) {
return this.setChannel('hwb', 1, val);
},
blackness: function (val) {
return this.setChannel('hwb', 2, val);
},
value: function (val) {
return this.setChannel('hsv', 2, val);
},
cyan: function (val) {
return this.setChannel('cmyk', 0, val);
},
magenta: function (val) {
return this.setChannel('cmyk', 1, val);
},
yellow: function (val) {
return this.setChannel('cmyk', 2, val);
},
black: function (val) {
return this.setChannel('cmyk', 3, val);
},
hexString: function () {
return string.hexString(this.values.rgb);
},
rgbString: function () {
return string.rgbString(this.values.rgb, this.values.alpha);
},
rgbaString: function () {
return string.rgbaString(this.values.rgb, this.values.alpha);
},
percentString: function () {
return string.percentString(this.values.rgb, this.values.alpha);
},
hslString: function () {
return string.hslString(this.values.hsl, this.values.alpha);
},
hslaString: function () {
return string.hslaString(this.values.hsl, this.values.alpha);
},
hwbString: function () {
return string.hwbString(this.values.hwb, this.values.alpha);
},
keyword: function () {
return string.keyword(this.values.rgb, this.values.alpha);
},
rgbNumber: function () {
var rgb = this.values.rgb;
return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
},
luminosity: function () {
// http://www.w3.org/TR/WCAG20/#relativeluminancedef
var rgb = this.values.rgb;
var lum = [];
for (var i = 0; i < rgb.length; i++) {
var chan = rgb[i] / 255;
lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4);
}
return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
},
contrast: function (color2) {
// http://www.w3.org/TR/WCAG20/#contrast-ratiodef
var lum1 = this.luminosity();
var lum2 = color2.luminosity();
if (lum1 > lum2) {
return (lum1 + 0.05) / (lum2 + 0.05);
}
return (lum2 + 0.05) / (lum1 + 0.05);
},
level: function (color2) {
var contrastRatio = this.contrast(color2);
if (contrastRatio >= 7.1) {
return 'AAA';
}
return (contrastRatio >= 4.5) ? 'AA' : '';
},
dark: function () {
// YIQ equation from http://24ways.org/2010/calculating-color-contrast
var rgb = this.values.rgb;
var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
return yiq < 128;
},
light: function () {
return !this.dark();
},
negate: function () {
var rgb = [];
for (var i = 0; i < 3; i++) {
rgb[i] = 255 - this.values.rgb[i];
}
this.setValues('rgb', rgb);
return this;
},
lighten: function (ratio) {
var hsl = this.values.hsl;
hsl[2] += hsl[2] * ratio;
this.setValues('hsl', hsl);
return this;
},
darken: function (ratio) {
var hsl = this.values.hsl;
hsl[2] -= hsl[2] * ratio;
this.setValues('hsl', hsl);
return this;
},
saturate: function (ratio) {
var hsl = this.values.hsl;
hsl[1] += hsl[1] * ratio;
this.setValues('hsl', hsl);
return this;
},
desaturate: function (ratio) {
var hsl = this.values.hsl;
hsl[1] -= hsl[1] * ratio;
this.setValues('hsl', hsl);
return this;
},
whiten: function (ratio) {
var hwb = this.values.hwb;
hwb[1] += hwb[1] * ratio;
this.setValues('hwb', hwb);
return this;
},
blacken: function (ratio) {
var hwb = this.values.hwb;
hwb[2] += hwb[2] * ratio;
this.setValues('hwb', hwb);
return this;
},
greyscale: function () {
var rgb = this.values.rgb;
// http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;
this.setValues('rgb', [val, val, val]);
return this;
},
clearer: function (ratio) {
var alpha = this.values.alpha;
this.setValues('alpha', alpha - (alpha * ratio));
return this;
},
opaquer: function (ratio) {
var alpha = this.values.alpha;
this.setValues('alpha', alpha + (alpha * ratio));
return this;
},
rotate: function (degrees) {
var hsl = this.values.hsl;
var hue = (hsl[0] + degrees) % 360;
hsl[0] = hue < 0 ? 360 + hue : hue;
this.setValues('hsl', hsl);
return this;
},
/**
* Ported from sass implementation in C
* https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209
*/
mix: function (mixinColor, weight) {
var color1 = this;
var color2 = mixinColor;
var p = weight === undefined ? 0.5 : weight;
var w = 2 * p - 1;
var a = color1.alpha() - color2.alpha();
var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
var w2 = 1 - w1;
return this
.rgb(
w1 * color1.red() + w2 * color2.red(),
w1 * color1.green() + w2 * color2.green(),
w1 * color1.blue() + w2 * color2.blue()
)
.alpha(color1.alpha() * p + color2.alpha() * (1 - p));
},
toJSON: function () {
return this.rgb();
},
clone: function () {
// NOTE(SB): using node-clone creates a dependency to Buffer when using browserify,
// making the final build way to big to embed in Chart.js. So let's do it manually,
// assuming that values to clone are 1 dimension arrays containing only numbers,
// except 'alpha' which is a number.
var result = new Color();
var source = this.values;
var target = result.values;
var value, type;
for (var prop in source) {
if (source.hasOwnProperty(prop)) {
value = source[prop];
type = ({}).toString.call(value);
if (type === '[object Array]') {
target[prop] = value.slice(0);
} else if (type === '[object Number]') {
target[prop] = value;
} else {
console.error('unexpected color value:', value);
}
}
}
return result;
}
};
Color.prototype.spaces = {
rgb: ['red', 'green', 'blue'],
hsl: ['hue', 'saturation', 'lightness'],
hsv: ['hue', 'saturation', 'value'],
hwb: ['hue', 'whiteness', 'blackness'],
cmyk: ['cyan', 'magenta', 'yellow', 'black']
};
Color.prototype.maxes = {
rgb: [255, 255, 255],
hsl: [360, 100, 100],
hsv: [360, 100, 100],
hwb: [360, 100, 100],
cmyk: [100, 100, 100, 100]
};
Color.prototype.getValues = function (space) {
var values = this.values;
var vals = {};
for (var i = 0; i < space.length; i++) {
vals[space.charAt(i)] = values[space][i];
}
if (values.alpha !== 1) {
vals.a = values.alpha;
}
// {r: 255, g: 255, b: 255, a: 0.4}
return vals;
};
Color.prototype.setValues = function (space, vals) {
var values = this.values;
var spaces = this.spaces;
var maxes = this.maxes;
var alpha = 1;
var i;
if (space === 'alpha') {
alpha = vals;
} else if (vals.length) {
// [10, 10, 10]
values[space] = vals.slice(0, space.length);
alpha = vals[space.length];
} else if (vals[space.charAt(0)] !== undefined) {
// {r: 10, g: 10, b: 10}
for (i = 0; i < space.length; i++) {
values[space][i] = vals[space.charAt(i)];
}
alpha = vals.a;
} else if (vals[spaces[space][0]] !== undefined) {
// {red: 10, green: 10, blue: 10}
var chans = spaces[space];
for (i = 0; i < space.length; i++) {
values[space][i] = vals[chans[i]];
}
alpha = vals.alpha;
}
values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha)));
if (space === 'alpha') {
return false;
}
var capped;
// cap values of the space prior converting all values
for (i = 0; i < space.length; i++) {
capped = Math.max(0, Math.min(maxes[space][i], values[space][i]));
values[space][i] = Math.round(capped);
}
// convert to all the other color spaces
for (var sname in spaces) {
if (sname !== space) {
values[sname] = convert[space][sname](values[space]);
}
}
return true;
};
Color.prototype.setSpace = function (space, args) {
var vals = args[0];
if (vals === undefined) {
// color.rgb()
return this.getValues(space);
}
// color.rgb(10, 10, 10)
if (typeof vals === 'number') {
vals = Array.prototype.slice.call(args);
}
this.setValues(space, vals);
return this;
};
Color.prototype.setChannel = function (space, index, val) {
var svalues = this.values[space];
if (val === undefined) {
// color.red()
return svalues[index];
} else if (val === svalues[index]) {
// color.red(color.red())
return this;
}
// color.red(100)
svalues[index] = val;
this.setValues(space, svalues);
return this;
};
if (typeof window !== 'undefined') {
window.Color = Color;
}
module.exports = Color;
},{"2":2,"5":5}],4:[function(require,module,exports){
/* MIT license */
module.exports = {
rgb2hsl: rgb2hsl,
rgb2hsv: rgb2hsv,
rgb2hwb: rgb2hwb,
rgb2cmyk: rgb2cmyk,
rgb2keyword: rgb2keyword,
rgb2xyz: rgb2xyz,
rgb2lab: rgb2lab,
rgb2lch: rgb2lch,
hsl2rgb: hsl2rgb,
hsl2hsv: hsl2hsv,
hsl2hwb: hsl2hwb,
hsl2cmyk: hsl2cmyk,
hsl2keyword: hsl2keyword,
hsv2rgb: hsv2rgb,
hsv2hsl: hsv2hsl,
hsv2hwb: hsv2hwb,
hsv2cmyk: hsv2cmyk,
hsv2keyword: hsv2keyword,
hwb2rgb: hwb2rgb,
hwb2hsl: hwb2hsl,
hwb2hsv: hwb2hsv,
hwb2cmyk: hwb2cmyk,
hwb2keyword: hwb2keyword,
cmyk2rgb: cmyk2rgb,
cmyk2hsl: cmyk2hsl,
cmyk2hsv: cmyk2hsv,
cmyk2hwb: cmyk2hwb,
cmyk2keyword: cmyk2keyword,
keyword2rgb: keyword2rgb,
keyword2hsl: keyword2hsl,
keyword2hsv: keyword2hsv,
keyword2hwb: keyword2hwb,
keyword2cmyk: keyword2cmyk,
keyword2lab: keyword2lab,
keyword2xyz: keyword2xyz,
xyz2rgb: xyz2rgb,
xyz2lab: xyz2lab,
xyz2lch: xyz2lch,
lab2xyz: lab2xyz,
lab2rgb: lab2rgb,
lab2lch: lab2lch,
lch2lab: lch2lab,
lch2xyz: lch2xyz,
lch2rgb: lch2rgb
}
function rgb2hsl(rgb) {
var r = rgb[0]/255,
g = rgb[1]/255,
b = rgb[2]/255,
min = Math.min(r, g, b),
max = Math.max(r, g, b),
delta = max - min,
h, s, l;
if (max == min)
h = 0;
else if (r == max)
h = (g - b) / delta;
else if (g == max)
h = 2 + (b - r) / delta;
else if (b == max)
h = 4 + (r - g)/ delta;
h = Math.min(h * 60, 360);
if (h < 0)
h += 360;
l = (min + max) / 2;
if (max == min)
s = 0;
else if (l <= 0.5)
s = delta / (max + min);
else
s = delta / (2 - max - min);
return [h, s * 100, l * 100];
}
function rgb2hsv(rgb) {
var r = rgb[0],
g = rgb[1],
b = rgb[2],
min = Math.min(r, g, b),
max = Math.max(r, g, b),
delta = max - min,
h, s, v;
if (max == 0)
s = 0;
else
s = (delta/max * 1000)/10;
if (max == min)
h = 0;
else if (r == max)
h = (g - b) / delta;
else if (g == max)
h = 2 + (b - r) / delta;
else if (b == max)
h = 4 + (r - g) / delta;
h = Math.min(h * 60, 360);
if (h < 0)
h += 360;
v = ((max / 255) * 1000) / 10;
return [h, s, v];
}
function rgb2hwb(rgb) {
var r = rgb[0],
g = rgb[1],
b = rgb[2],
h = rgb2hsl(rgb)[0],
w = 1/255 * Math.min(r, Math.min(g, b)),
b = 1 - 1/255 * Math.max(r, Math.max(g, b));
return [h, w * 100, b * 100];
}
function rgb2cmyk(rgb) {
var r = rgb[0] / 255,
g = rgb[1] / 255,
b = rgb[2] / 255,
c, m, y, k;
k = Math.min(1 - r, 1 - g, 1 - b);
c = (1 - r - k) / (1 - k) || 0;
m = (1 - g - k) / (1 - k) || 0;
y = (1 - b - k) / (1 - k) || 0;
return [c * 100, m * 100, y * 100, k * 100];
}
function rgb2keyword(rgb) {
return reverseKeywords[JSON.stringify(rgb)];
}
function rgb2xyz(rgb) {
var r = rgb[0] / 255,
g = rgb[1] / 255,
b = rgb[2] / 255;
// assume sRGB
r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);
var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
return [x * 100, y *100, z * 100];
}
function rgb2lab(rgb) {
var xyz = rgb2xyz(rgb),
x = xyz[0],
y = xyz[1],
z = xyz[2],
l, a, b;
x /= 95.047;
y /= 100;
z /= 108.883;
x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
l = (116 * y) - 16;
a = 500 * (x - y);
b = 200 * (y - z);
return [l, a, b];
}
function rgb2lch(args) {
return lab2lch(rgb2lab(args));
}
function hsl2rgb(hsl) {
var h = hsl[0] / 360,
s = hsl[1] / 100,
l = hsl[2] / 100,
t1, t2, t3, rgb, val;
if (s == 0) {
val = l * 255;
return [val, val, val];
}
if (l < 0.5)
t2 = l * (1 + s);
else
t2 = l + s - l * s;
t1 = 2 * l - t2;
rgb = [0, 0, 0];
for (var i = 0; i < 3; i++) {
t3 = h + 1 / 3 * - (i - 1);
t3 < 0 && t3++;
t3 > 1 && t3--;
if (6 * t3 < 1)
val = t1 + (t2 - t1) * 6 * t3;
else if (2 * t3 < 1)
val = t2;
else if (3 * t3 < 2)
val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
else
val = t1;
rgb[i] = val * 255;
}
return rgb;
}
function hsl2hsv(hsl) {
var h = hsl[0],
s = hsl[1] / 100,
l = hsl[2] / 100,
sv, v;
if(l === 0) {
// no need to do calc on black
// also avoids divide by 0 error
return [0, 0, 0];
}
l *= 2;
s *= (l <= 1) ? l : 2 - l;
v = (l + s) / 2;
sv = (2 * s) / (l + s);
return [h, sv * 100, v * 100];
}
function hsl2hwb(args) {
return rgb2hwb(hsl2rgb(args));
}
function hsl2cmyk(args) {
return rgb2cmyk(hsl2rgb(args));
}
function hsl2keyword(args) {
return rgb2keyword(hsl2rgb(args));
}
function hsv2rgb(hsv) {
var h = hsv[0] / 60,
s = hsv[1] / 100,
v = hsv[2] / 100,
hi = Math.floor(h) % 6;
var f = h - Math.floor(h),
p = 255 * v * (1 - s),
q = 255 * v * (1 - (s * f)),
t = 255 * v * (1 - (s * (1 - f))),
v = 255 * v;
switch(hi) {
case 0:
return [v, t, p];
case 1:
return [q, v, p];
case 2:
return [p, v, t];
case 3:
return [p, q, v];
case 4:
return [t, p, v];
case 5:
return [v, p, q];
}
}
function hsv2hsl(hsv) {
var h = hsv[0],
s = hsv[1] / 100,
v = hsv[2] / 100,
sl, l;
l = (2 - s) * v;
sl = s * v;
sl /= (l <= 1) ? l : 2 - l;
sl = sl || 0;
l /= 2;
return [h, sl * 100, l * 100];
}
function hsv2hwb(args) {
return rgb2hwb(hsv2rgb(args))
}
function hsv2cmyk(args) {
return rgb2cmyk(hsv2rgb(args));
}
function hsv2keyword(args) {
return rgb2keyword(hsv2rgb(args));
}
// http://dev.w3.org/csswg/css-color/#hwb-to-rgb
function hwb2rgb(hwb) {
var h = hwb[0] / 360,
wh = hwb[1] / 100,
bl = hwb[2] / 100,
ratio = wh + bl,
i, v, f, n;
// wh + bl cant be > 1
if (ratio > 1) {
wh /= ratio;
bl /= ratio;
}
i = Math.floor(6 * h);
v = 1 - bl;
f = 6 * h - i;
if ((i & 0x01) != 0) {
f = 1 - f;
}
n = wh + f * (v - wh); // linear interpolation
switch (i) {
default:
case 6:
case 0: r = v; g = n; b = wh; break;
case 1: r = n; g = v; b = wh; break;
case 2: r = wh; g = v; b = n; break;
case 3: r = wh; g = n; b = v; break;
case 4: r = n; g = wh; b = v; break;
case 5: r = v; g = wh; b = n; break;
}
return [r * 255, g * 255, b * 255];
}
function hwb2hsl(args) {
return rgb2hsl(hwb2rgb(args));
}
function hwb2hsv(args) {
return rgb2hsv(hwb2rgb(args));
}
function hwb2cmyk(args) {
return rgb2cmyk(hwb2rgb(args));
}
function hwb2keyword(args) {
return rgb2keyword(hwb2rgb(args));
}
function cmyk2rgb(cmyk) {
var c = cmyk[0] / 100,
m = cmyk[1] / 100,
y = cmyk[2] / 100,
k = cmyk[3] / 100,
r, g, b;
r = 1 - Math.min(1, c * (1 - k) + k);
g = 1 - Math.min(1, m * (1 - k) + k);
b = 1 - Math.min(1, y * (1 - k) + k);
return [r * 255, g * 255, b * 255];
}
function cmyk2hsl(args) {
return rgb2hsl(cmyk2rgb(args));
}
function cmyk2hsv(args) {
return rgb2hsv(cmyk2rgb(args));
}
function cmyk2hwb(args) {
return rgb2hwb(cmyk2rgb(args));
}
function cmyk2keyword(args) {
return rgb2keyword(cmyk2rgb(args));
}
function xyz2rgb(xyz) {
var x = xyz[0] / 100,
y = xyz[1] / 100,
z = xyz[2] / 100,
r, g, b;
r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
// assume sRGB
r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)
: r = (r * 12.92);
g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)
: g = (g * 12.92);
b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)
: b = (b * 12.92);
r = Math.min(Math.max(0, r), 1);
g = Math.min(Math.max(0, g), 1);
b = Math.min(Math.max(0, b), 1);
return [r * 255, g * 255, b * 255];
}
function xyz2lab(xyz) {
var x = xyz[0],
y = xyz[1],
z = xyz[2],
l, a, b;
x /= 95.047;
y /= 100;
z /= 108.883;
x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
l = (116 * y) - 16;
a = 500 * (x - y);
b = 200 * (y - z);
return [l, a, b];
}
function xyz2lch(args) {
return lab2lch(xyz2lab(args));
}
function lab2xyz(lab) {
var l = lab[0],
a = lab[1],
b = lab[2],
x, y, z, y2;
if (l <= 8) {
y = (l * 100) / 903.3;
y2 = (7.787 * (y / 100)) + (16 / 116);
} else {
y = 100 * Math.pow((l + 16) / 116, 3);
y2 = Math.pow(y / 100, 1/3);
}
x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3);
z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3);
return [x, y, z];
}
function lab2lch(lab) {
var l = lab[0],
a = lab[1],
b = lab[2],
hr, h, c;
hr = Math.atan2(b, a);
h = hr * 360 / 2 / Math.PI;
if (h < 0) {
h += 360;
}
c = Math.sqrt(a * a + b * b);
return [l, c, h];
}
function lab2rgb(args) {
return xyz2rgb(lab2xyz(args));
}
function lch2lab(lch) {
var l = lch[0],
c = lch[1],
h = lch[2],
a, b, hr;
hr = h / 360 * 2 * Math.PI;
a = c * Math.cos(hr);
b = c * Math.sin(hr);
return [l, a, b];
}
function lch2xyz(args) {
return lab2xyz(lch2lab(args));
}
function lch2rgb(args) {
return lab2rgb(lch2lab(args));
}
function keyword2rgb(keyword) {
return cssKeywords[keyword];
}
function keyword2hsl(args) {
return rgb2hsl(keyword2rgb(args));
}
function keyword2hsv(args) {
return rgb2hsv(keyword2rgb(args));
}
function keyword2hwb(args) {
return rgb2hwb(keyword2rgb(args));
}
function keyword2cmyk(args) {
return rgb2cmyk(keyword2rgb(args));
}
function keyword2lab(args) {
return rgb2lab(keyword2rgb(args));
}
function keyword2xyz(args) {
return rgb2xyz(keyword2rgb(args));
}
var cssKeywords = {
aliceblue: [240,248,255],
antiquewhite: [250,235,215],
aqua: [0,255,255],
aquamarine: [127,255,212],
azure: [240,255,255],
beige: [245,245,220],
bisque: [255,228,196],
black: [0,0,0],
blanchedalmond: [255,235,205],
blue: [0,0,255],
blueviolet: [138,43,226],
brown: [165,42,42],
burlywood: [222,184,135],
cadetblue: [95,158,160],
chartreuse: [127,255,0],
chocolate: [210,105,30],
coral: [255,127,80],
cornflowerblue: [100,149,237],
cornsilk: [255,248,220],
crimson: [220,20,60],
cyan: [0,255,255],
darkblue: [0,0,139],
darkcyan: [0,139,139],
darkgoldenrod: [184,134,11],
darkgray: [169,169,169],
darkgreen: [0,100,0],
darkgrey: [169,169,169],
darkkhaki: [189,183,107],
darkmagenta: [139,0,139],
darkolivegreen: [85,107,47],
darkorange: [255,140,0],
darkorchid: [153,50,204],
darkred: [139,0,0],
darksalmon: [233,150,122],
darkseagreen: [143,188,143],
darkslateblue: [72,61,139],
darkslategray: [47,79,79],
darkslategrey: [47,79,79],
darkturquoise: [0,206,209],
darkviolet: [148,0,211],
deeppink: [255,20,147],
deepskyblue: [0,191,255],
dimgray: [105,105,105],
dimgrey: [105,105,105],
dodgerblue: [30,144,255],
firebrick: [178,34,34],
floralwhite: [255,250,240],
forestgreen: [34,139,34],
fuchsia: [255,0,255],
gainsboro: [220,220,220],
ghostwhite: [248,248,255],
gold: [255,215,0],
goldenrod: [218,165,32],
gray: [128,128,128],
green: [0,128,0],
greenyellow: [173,255,47],
grey: [128,128,128],
honeydew: [240,255,240],
hotpink: [255,105,180],
indianred: [205,92,92],
indigo: [75,0,130],
ivory: [255,255,240],
khaki: [240,230,140],
lavender: [230,230,250],
lavenderblush: [255,240,245],
lawngreen: [124,252,0],
lemonchiffon: [255,250,205],
lightblue: [173,216,230],
lightcoral: [240,128,128],
lightcyan: [224,255,255],
lightgoldenrodyellow: [250,250,210],
lightgray: [211,211,211],
lightgreen: [144,238,144],
lightgrey: [211,211,211],
lightpink: [255,182,193],
lightsalmon: [255,160,122],
lightseagreen: [32,178,170],
lightskyblue: [135,206,250],
lightslategray: [119,136,153],
lightslategrey: [119,136,153],
lightsteelblue: [176,196,222],
lightyellow: [255,255,224],
lime: [0,255,0],
limegreen: [50,205,50],
linen: [250,240,230],
magenta: [255,0,255],
maroon: [128,0,0],
mediumaquamarine: [102,205,170],
mediumblue: [0,0,205],
mediumorchid: [186,85,211],
mediumpurple: [147,112,219],
mediumseagreen: [60,179,113],
mediumslateblue: [123,104,238],
mediumspringgreen: [0,250,154],
mediumturquoise: [72,209,204],
mediumvioletred: [199,21,133],
midnightblue: [25,25,112],
mintcream: [245,255,250],
mistyrose: [255,228,225],
moccasin: [255,228,181],
navajowhite: [255,222,173],
navy: [0,0,128],
oldlace: [253,245,230],
olive: [128,128,0],
olivedrab: [107,142,35],
orange: [255,165,0],
orangered: [255,69,0],
orchid: [218,112,214],
palegoldenrod: [238,232,170],
palegreen: [152,251,152],
paleturquoise: [175,238,238],
palevioletred: [219,112,147],
papayawhip: [255,239,213],
peachpuff: [255,218,185],
peru: [205,133,63],
pink: [255,192,203],
plum: [221,160,221],
powderblue: [176,224,230],
purple: [128,0,128],
rebeccapurple: [102, 51, 153],
red: [255,0,0],
rosybrown: [188,143,143],
royalblue: [65,105,225],
saddlebrown: [139,69,19],
salmon: [250,128,114],
sandybrown: [244,164,96],
seagreen: [46,139,87],
seashell: [255,245,238],
sienna: [160,82,45],
silver: [192,192,192],
skyblue: [135,206,235],
slateblue: [106,90,205],
slategray: [112,128,144],
slategrey: [112,128,144],
snow: [255,250,250],
springgreen: [0,255,127],
steelblue: [70,130,180],
tan: [210,180,140],
teal: [0,128,128],
thistle: [216,191,216],
tomato: [255,99,71],
turquoise: [64,224,208],
violet: [238,130,238],
wheat: [245,222,179],
white: [255,255,255],
whitesmoke: [245,245,245],
yellow: [255,255,0],
yellowgreen: [154,205,50]
};
var reverseKeywords = {};
for (var key in cssKeywords) {
reverseKeywords[JSON.stringify(cssKeywords[key])] = key;
}
},{}],5:[function(require,module,exports){
var conversions = require(4);
var convert = function() {
return new Converter();
}
for (var func in conversions) {
// export Raw versions
convert[func + "Raw"] = (function(func) {
// accept array or plain args
return function(arg) {
if (typeof arg == "number")
arg = Array.prototype.slice.call(arguments);
return conversions[func](arg);
}
})(func);
var pair = /(\w+)2(\w+)/.exec(func),
from = pair[1],
to = pair[2];
// export rgb2hsl and ["rgb"]["hsl"]
convert[from] = convert[from] || {};
convert[from][to] = convert[func] = (function(func) {
return function(arg) {
if (typeof arg == "number")
arg = Array.prototype.slice.call(arguments);
var val = conversions[func](arg);
if (typeof val == "string" || val === undefined)
return val; // keyword
for (var i = 0; i < val.length; i++)
val[i] = Math.round(val[i]);
return val;
}
})(func);
}
/* Converter does lazy conversion and caching */
var Converter = function() {
this.convs = {};
};
/* Either get the values for a space or
set the values for a space, depending on args */
Converter.prototype.routeSpace = function(space, args) {
var values = args[0];
if (values === undefined) {
// color.rgb()
return this.getValues(space);
}
// color.rgb(10, 10, 10)
if (typeof values == "number") {
values = Array.prototype.slice.call(args);
}
return this.setValues(space, values);
};
/* Set the values for a space, invalidating cache */
Converter.prototype.setValues = function(space, values) {
this.space = space;
this.convs = {};
this.convs[space] = values;
return this;
};
/* Get the values for a space. If there's already
a conversion for the space, fetch it, otherwise
compute it */
Converter.prototype.getValues = function(space) {
var vals = this.convs[space];
if (!vals) {
var fspace = this.space,
from = this.convs[fspace];
vals = convert[fspace][space](from);
this.convs[space] = vals;
}
return vals;
};
["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) {
Converter.prototype[space] = function(vals) {
return this.routeSpace(space, arguments);
}
});
module.exports = convert;
},{"4":4}],6:[function(require,module,exports){
module.exports = {
"aliceblue": [240, 248, 255],
"antiquewhite": [250, 235, 215],
"aqua": [0, 255, 255],
"aquamarine": [127, 255, 212],
"azure": [240, 255, 255],
"beige": [245, 245, 220],
"bisque": [255, 228, 196],
"black": [0, 0, 0],
"blanchedalmond": [255, 235, 205],
"blue": [0, 0, 255],
"blueviolet": [138, 43, 226],
"brown": [165, 42, 42],
"burlywood": [222, 184, 135],
"cadetblue": [95, 158, 160],
"chartreuse": [127, 255, 0],
"chocolate": [210, 105, 30],
"coral": [255, 127, 80],
"cornflowerblue": [100, 149, 237],
"cornsilk": [255, 248, 220],
"crimson": [220, 20, 60],
"cyan": [0, 255, 255],
"darkblue": [0, 0, 139],
"darkcyan": [0, 139, 139],
"darkgoldenrod": [184, 134, 11],
"darkgray": [169, 169, 169],
"darkgreen": [0, 100, 0],
"darkgrey": [169, 169, 169],
"darkkhaki": [189, 183, 107],
"darkmagenta": [139, 0, 139],
"darkolivegreen": [85, 107, 47],
"darkorange": [255, 140, 0],
"darkorchid": [153, 50, 204],
"darkred": [139, 0, 0],
"darksalmon": [233, 150, 122],
"darkseagreen": [143, 188, 143],
"darkslateblue": [72, 61, 139],
"darkslategray": [47, 79, 79],
"darkslategrey": [47, 79, 79],
"darkturquoise": [0, 206, 209],
"darkviolet": [148, 0, 211],
"deeppink": [255, 20, 147],
"deepskyblue": [0, 191, 255],
"dimgray": [105, 105, 105],
"dimgrey": [105, 105, 105],
"dodgerblue": [30, 144, 255],
"firebrick": [178, 34, 34],
"floralwhite": [255, 250, 240],
"forestgreen": [34, 139, 34],
"fuchsia": [255, 0, 255],
"gainsboro": [220, 220, 220],
"ghostwhite": [248, 248, 255],
"gold": [255, 215, 0],
"goldenrod": [218, 165, 32],
"gray": [128, 128, 128],
"green": [0, 128, 0],
"greenyellow": [173, 255, 47],
"grey": [128, 128, 128],
"honeydew": [240, 255, 240],
"hotpink": [255, 105, 180],
"indianred": [205, 92, 92],
"indigo": [75, 0, 130],
"ivory": [255, 255, 240],
"khaki": [240, 230, 140],
"lavender": [230, 230, 250],
"lavenderblush": [255, 240, 245],
"lawngreen": [124, 252, 0],
"lemonchiffon": [255, 250, 205],
"lightblue": [173, 216, 230],
"lightcoral": [240, 128, 128],
"lightcyan": [224, 255, 255],
"lightgoldenrodyellow": [250, 250, 210],
"lightgray": [211, 211, 211],
"lightgreen": [144, 238, 144],
"lightgrey": [211, 211, 211],
"lightpink": [255, 182, 193],
"lightsalmon": [255, 160, 122],
"lightseagreen": [32, 178, 170],
"lightskyblue": [135, 206, 250],
"lightslategray": [119, 136, 153],
"lightslategrey": [119, 136, 153],
"lightsteelblue": [176, 196, 222],
"lightyellow": [255, 255, 224],
"lime": [0, 255, 0],
"limegreen": [50, 205, 50],
"linen": [250, 240, 230],
"magenta": [255, 0, 255],
"maroon": [128, 0, 0],
"mediumaquamarine": [102, 205, 170],
"mediumblue": [0, 0, 205],
"mediumorchid": [186, 85, 211],
"mediumpurple": [147, 112, 219],
"mediumseagreen": [60, 179, 113],
"mediumslateblue": [123, 104, 238],
"mediumspringgreen": [0, 250, 154],
"mediumturquoise": [72, 209, 204],
"mediumvioletred": [199, 21, 133],
"midnightblue": [25, 25, 112],
"mintcream": [245, 255, 250],
"mistyrose": [255, 228, 225],
"moccasin": [255, 228, 181],
"navajowhite": [255, 222, 173],
"navy": [0, 0, 128],
"oldlace": [253, 245, 230],
"olive": [128, 128, 0],
"olivedrab": [107, 142, 35],
"orange": [255, 165, 0],
"orangered": [255, 69, 0],
"orchid": [218, 112, 214],
"palegoldenrod": [238, 232, 170],
"palegreen": [152, 251, 152],
"paleturquoise": [175, 238, 238],
"palevioletred": [219, 112, 147],
"papayawhip": [255, 239, 213],
"peachpuff": [255, 218, 185],
"peru": [205, 133, 63],
"pink": [255, 192, 203],
"plum": [221, 160, 221],
"powderblue": [176, 224, 230],
"purple": [128, 0, 128],
"rebeccapurple": [102, 51, 153],
"red": [255, 0, 0],
"rosybrown": [188, 143, 143],
"royalblue": [65, 105, 225],
"saddlebrown": [139, 69, 19],
"salmon": [250, 128, 114],
"sandybrown": [244, 164, 96],
"seagreen": [46, 139, 87],
"seashell": [255, 245, 238],
"sienna": [160, 82, 45],
"silver": [192, 192, 192],
"skyblue": [135, 206, 235],
"slateblue": [106, 90, 205],
"slategray": [112, 128, 144],
"slategrey": [112, 128, 144],
"snow": [255, 250, 250],
"springgreen": [0, 255, 127],
"steelblue": [70, 130, 180],
"tan": [210, 180, 140],
"teal": [0, 128, 128],
"thistle": [216, 191, 216],
"tomato": [255, 99, 71],
"turquoise": [64, 224, 208],
"violet": [238, 130, 238],
"wheat": [245, 222, 179],
"white": [255, 255, 255],
"whitesmoke": [245, 245, 245],
"yellow": [255, 255, 0],
"yellowgreen": [154, 205, 50]
};
},{}],7:[function(require,module,exports){
/**
* @namespace Chart
*/
var Chart = require(27)();
require(26)(Chart);
require(22)(Chart);
require(25)(Chart);
require(21)(Chart);
require(23)(Chart);
require(24)(Chart);
require(28)(Chart);
require(32)(Chart);
require(30)(Chart);
require(31)(Chart);
require(33)(Chart);
require(29)(Chart);
require(34)(Chart);
require(35)(Chart);
require(36)(Chart);
require(37)(Chart);
require(38)(Chart);
require(41)(Chart);
require(39)(Chart);
require(40)(Chart);
require(42)(Chart);
require(43)(Chart);
require(44)(Chart);
// Controllers must be loaded after elements
// See Chart.core.datasetController.dataElementType
require(15)(Chart);
require(16)(Chart);
require(17)(Chart);
require(18)(Chart);
require(19)(Chart);
require(20)(Chart);
require(8)(Chart);
require(9)(Chart);
require(10)(Chart);
require(11)(Chart);
require(12)(Chart);
require(13)(Chart);
require(14)(Chart);
window.Chart = module.exports = Chart;
},{"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,"8":8,"9":9}],8:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
Chart.Bar = function(context, config) {
config.type = 'bar';
return new Chart(context, config);
};
};
},{}],9:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
Chart.Bubble = function(context, config) {
config.type = 'bubble';
return new Chart(context, config);
};
};
},{}],10:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
Chart.Doughnut = function(context, config) {
config.type = 'doughnut';
return new Chart(context, config);
};
};
},{}],11:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
Chart.Line = function(context, config) {
config.type = 'line';
return new Chart(context, config);
};
};
},{}],12:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
Chart.PolarArea = function(context, config) {
config.type = 'polarArea';
return new Chart(context, config);
};
};
},{}],13:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
Chart.Radar = function(context, config) {
config.options = Chart.helpers.configMerge({aspectRatio: 1}, config.options);
config.type = 'radar';
return new Chart(context, config);
};
};
},{}],14:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var defaultConfig = {
hover: {
mode: 'single'
},
scales: {
xAxes: [{
type: 'linear', // scatter should not use a category axis
position: 'bottom',
id: 'x-axis-1' // need an ID so datasets can reference the scale
}],
yAxes: [{
type: 'linear',
position: 'left',
id: 'y-axis-1'
}]
},
tooltips: {
callbacks: {
title: function() {
// Title doesn't make sense for scatter since we format the data as a point
return '';
},
label: function(tooltipItem) {
return '(' + tooltipItem.xLabel + ', ' + tooltipItem.yLabel + ')';
}
}
}
};
// Register the default config for this type
Chart.defaults.scatter = defaultConfig;
// Scatter charts use line controllers
Chart.controllers.scatter = Chart.controllers.line;
Chart.Scatter = function(context, config) {
config.type = 'scatter';
return new Chart(context, config);
};
};
},{}],15:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers;
Chart.defaults.bar = {
hover: {
mode: 'label'
},
scales: {
xAxes: [{
type: 'category',
// Specific to Bar Controller
categoryPercentage: 0.8,
barPercentage: 0.9,
// grid line settings
gridLines: {
offsetGridLines: true
}
}],
yAxes: [{
type: 'linear'
}]
}
};
Chart.controllers.bar = Chart.DatasetController.extend({
dataElementType: Chart.elements.Rectangle,
initialize: function(chart, datasetIndex) {
Chart.DatasetController.prototype.initialize.call(this, chart, datasetIndex);
// Use this to indicate that this is a bar dataset.
this.getMeta().bar = true;
},
// Get the number of datasets that display bars. We use this to correctly calculate the bar width
getBarCount: function() {
var me = this;
var barCount = 0;
helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) {
var meta = me.chart.getDatasetMeta(datasetIndex);
if (meta.bar && me.chart.isDatasetVisible(datasetIndex)) {
++barCount;
}
}, me);
return barCount;
},
update: function(reset) {
var me = this;
helpers.each(me.getMeta().data, function(rectangle, index) {
me.updateElement(rectangle, index, reset);
}, me);
},
updateElement: function(rectangle, index, reset) {
var me = this;
var meta = me.getMeta();
var xScale = me.getScaleForId(meta.xAxisID);
var yScale = me.getScaleForId(meta.yAxisID);
var scaleBase = yScale.getBasePixel();
var rectangleElementOptions = me.chart.options.elements.rectangle;
var custom = rectangle.custom || {};
var dataset = me.getDataset();
helpers.extend(rectangle, {
// Utility
_xScale: xScale,
_yScale: yScale,
_datasetIndex: me.index,
_index: index,
// Desired view properties
_model: {
x: me.calculateBarX(index, me.index),
y: reset ? scaleBase : me.calculateBarY(index, me.index),
// Tooltip
label: me.chart.data.labels[index],
datasetLabel: dataset.label,
// Appearance
base: reset ? scaleBase : me.calculateBarBase(me.index, index),
width: me.calculateBarWidth(index),
backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor),
borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped,
borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor),
borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth)
}
});
rectangle.pivot();
},
calculateBarBase: function(datasetIndex, index) {
var me = this;
var meta = me.getMeta();
var yScale = me.getScaleForId(meta.yAxisID);
var base = 0;
if (yScale.options.stacked) {
var chart = me.chart;
var datasets = chart.data.datasets;
var value = Number(datasets[datasetIndex].data[index]);
for (var i = 0; i < datasetIndex; i++) {
var currentDs = datasets[i];
var currentDsMeta = chart.getDatasetMeta(i);
if (currentDsMeta.bar && currentDsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) {
var currentVal = Number(currentDs.data[index]);
base += value < 0 ? Math.min(currentVal, 0) : Math.max(currentVal, 0);
}
}
return yScale.getPixelForValue(base);
}
return yScale.getBasePixel();
},
getRuler: function(index) {
var me = this;
var meta = me.getMeta();
var xScale = me.getScaleForId(meta.xAxisID);
var datasetCount = me.getBarCount();
var tickWidth;
if (xScale.options.type === 'category') {
tickWidth = xScale.getPixelForTick(index + 1) - xScale.getPixelForTick(index);
} else {
// Average width
tickWidth = xScale.width / xScale.ticks.length;
}
var categoryWidth = tickWidth * xScale.options.categoryPercentage;
var categorySpacing = (tickWidth - (tickWidth * xScale.options.categoryPercentage)) / 2;
var fullBarWidth = categoryWidth / datasetCount;
if (xScale.ticks.length !== me.chart.data.labels.length) {
var perc = xScale.ticks.length / me.chart.data.labels.length;
fullBarWidth = fullBarWidth * perc;
}
var barWidth = fullBarWidth * xScale.options.barPercentage;
var barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage);
return {
datasetCount: datasetCount,
tickWidth: tickWidth,
categoryWidth: categoryWidth,
categorySpacing: categorySpacing,
fullBarWidth: fullBarWidth,
barWidth: barWidth,
barSpacing: barSpacing
};
},
calculateBarWidth: function(index) {
var xScale = this.getScaleForId(this.getMeta().xAxisID);
if (xScale.options.barThickness) {
return xScale.options.barThickness;
}
var ruler = this.getRuler(index);
return xScale.options.stacked ? ruler.categoryWidth : ruler.barWidth;
},
// Get bar index from the given dataset index accounting for the fact that not all bars are visible
getBarIndex: function(datasetIndex) {
var barIndex = 0;
var meta, j;
for (j = 0; j < datasetIndex; ++j) {
meta = this.chart.getDatasetMeta(j);
if (meta.bar && this.chart.isDatasetVisible(j)) {
++barIndex;
}
}
return barIndex;
},
calculateBarX: function(index, datasetIndex) {
var me = this;
var meta = me.getMeta();
var xScale = me.getScaleForId(meta.xAxisID);
var barIndex = me.getBarIndex(datasetIndex);
var ruler = me.getRuler(index);
var leftTick = xScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo);
leftTick -= me.chart.isCombo ? (ruler.tickWidth / 2) : 0;
if (xScale.options.stacked) {
return leftTick + (ruler.categoryWidth / 2) + ruler.categorySpacing;
}
return leftTick +
(ruler.barWidth / 2) +
ruler.categorySpacing +
(ruler.barWidth * barIndex) +
(ruler.barSpacing / 2) +
(ruler.barSpacing * barIndex);
},
calculateBarY: function(index, datasetIndex) {
var me = this;
var meta = me.getMeta();
var yScale = me.getScaleForId(meta.yAxisID);
var value = Number(me.getDataset().data[index]);
if (yScale.options.stacked) {
var sumPos = 0,
sumNeg = 0;
for (var i = 0; i < datasetIndex; i++) {
var ds = me.chart.data.datasets[i];
var dsMeta = me.chart.getDatasetMeta(i);
if (dsMeta.bar && dsMeta.yAxisID === yScale.id && me.chart.isDatasetVisible(i)) {
var stackedVal = Number(ds.data[index]);
if (stackedVal < 0) {
sumNeg += stackedVal || 0;
} else {
sumPos += stackedVal || 0;
}
}
}
if (value < 0) {
return yScale.getPixelForValue(sumNeg + value);
}
return yScale.getPixelForValue(sumPos + value);
}
return yScale.getPixelForValue(value);
},
draw: function(ease) {
var me = this;
var easingDecimal = ease || 1;
helpers.each(me.getMeta().data, function(rectangle, index) {
var d = me.getDataset().data[index];
if (d !== null && d !== undefined && !isNaN(d)) {
rectangle.transition(easingDecimal).draw();
}
}, me);
},
setHoverStyle: function(rectangle) {
var dataset = this.chart.data.datasets[rectangle._datasetIndex];
var index = rectangle._index;
var custom = rectangle.custom || {};
var model = rectangle._model;
model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor));
model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
},
removeHoverStyle: function(rectangle) {
var dataset = this.chart.data.datasets[rectangle._datasetIndex];
var index = rectangle._index;
var custom = rectangle.custom || {};
var model = rectangle._model;
var rectangleElementOptions = this.chart.options.elements.rectangle;
model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor);
model.borderColor = custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor);
model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth);
}
});
// including horizontalBar in the bar file, instead of a file of its own
// it extends bar (like pie extends doughnut)
Chart.defaults.horizontalBar = {
hover: {
mode: 'label'
},
scales: {
xAxes: [{
type: 'linear',
position: 'bottom'
}],
yAxes: [{
position: 'left',
type: 'category',
// Specific to Horizontal Bar Controller
categoryPercentage: 0.8,
barPercentage: 0.9,
// grid line settings
gridLines: {
offsetGridLines: true
}
}]
},
elements: {
rectangle: {
borderSkipped: 'left'
}
},
tooltips: {
callbacks: {
title: function(tooltipItems, data) {
// Pick first xLabel for now
var title = '';
if (tooltipItems.length > 0) {
if (tooltipItems[0].yLabel) {
title = tooltipItems[0].yLabel;
} else if (data.labels.length > 0 && tooltipItems[0].index < data.labels.length) {
title = data.labels[tooltipItems[0].index];
}
}
return title;
},
label: function(tooltipItem, data) {
var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || '';
return datasetLabel + ': ' + tooltipItem.xLabel;
}
}
}
};
Chart.controllers.horizontalBar = Chart.controllers.bar.extend({
updateElement: function(rectangle, index, reset) {
var me = this;
var meta = me.getMeta();
var xScale = me.getScaleForId(meta.xAxisID);
var yScale = me.getScaleForId(meta.yAxisID);
var scaleBase = xScale.getBasePixel();
var custom = rectangle.custom || {};
var dataset = me.getDataset();
var rectangleElementOptions = me.chart.options.elements.rectangle;
helpers.extend(rectangle, {
// Utility
_xScale: xScale,
_yScale: yScale,
_datasetIndex: me.index,
_index: index,
// Desired view properties
_model: {
x: reset ? scaleBase : me.calculateBarX(index, me.index),
y: me.calculateBarY(index, me.index),
// Tooltip
label: me.chart.data.labels[index],
datasetLabel: dataset.label,
// Appearance
base: reset ? scaleBase : me.calculateBarBase(me.index, index),
height: me.calculateBarHeight(index),
backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor),
borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped,
borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor),
borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth)
},
draw: function() {
var ctx = this._chart.ctx;
var vm = this._view;
var halfHeight = vm.height / 2,
topY = vm.y - halfHeight,
bottomY = vm.y + halfHeight,
right = vm.base - (vm.base - vm.x),
halfStroke = vm.borderWidth / 2;
// Canvas doesn't allow us to stroke inside the width so we can
// adjust the sizes to fit if we're setting a stroke on the line
if (vm.borderWidth) {
topY += halfStroke;
bottomY -= halfStroke;
right += halfStroke;
}
ctx.beginPath();
ctx.fillStyle = vm.backgroundColor;
ctx.strokeStyle = vm.borderColor;
ctx.lineWidth = vm.borderWidth;
// Corner points, from bottom-left to bottom-right clockwise
// | 1 2 |
// | 0 3 |
var corners = [
[vm.base, bottomY],
[vm.base, topY],
[right, topY],
[right, bottomY]
];
// Find first (starting) corner with fallback to 'bottom'
var borders = ['bottom', 'left', 'top', 'right'];
var startCorner = borders.indexOf(vm.borderSkipped, 0);
if (startCorner === -1) {
startCorner = 0;
}
function cornerAt(cornerIndex) {
return corners[(startCorner + cornerIndex) % 4];
}
// Draw rectangle from 'startCorner'
ctx.moveTo.apply(ctx, cornerAt(0));
for (var i = 1; i < 4; i++) {
ctx.lineTo.apply(ctx, cornerAt(i));
}
ctx.fill();
if (vm.borderWidth) {
ctx.stroke();
}
},
inRange: function(mouseX, mouseY) {
var vm = this._view;
var inRange = false;
if (vm) {
if (vm.x < vm.base) {
inRange = (mouseY >= vm.y - vm.height / 2 && mouseY <= vm.y + vm.height / 2) && (mouseX >= vm.x && mouseX <= vm.base);
} else {
inRange = (mouseY >= vm.y - vm.height / 2 && mouseY <= vm.y + vm.height / 2) && (mouseX >= vm.base && mouseX <= vm.x);
}
}
return inRange;
}
});
rectangle.pivot();
},
calculateBarBase: function(datasetIndex, index) {
var me = this;
var meta = me.getMeta();
var xScale = me.getScaleForId(meta.xAxisID);
var base = 0;
if (xScale.options.stacked) {
var chart = me.chart;
var datasets = chart.data.datasets;
var value = Number(datasets[datasetIndex].data[index]);
for (var i = 0; i < datasetIndex; i++) {
var currentDs = datasets[i];
var currentDsMeta = chart.getDatasetMeta(i);
if (currentDsMeta.bar && currentDsMeta.xAxisID === xScale.id && chart.isDatasetVisible(i)) {
var currentVal = Number(currentDs.data[index]);
base += value < 0 ? Math.min(currentVal, 0) : Math.max(currentVal, 0);
}
}
return xScale.getPixelForValue(base);
}
return xScale.getBasePixel();
},
getRuler: function(index) {
var me = this;
var meta = me.getMeta();
var yScale = me.getScaleForId(meta.yAxisID);
var datasetCount = me.getBarCount();
var tickHeight;
if (yScale.options.type === 'category') {
tickHeight = yScale.getPixelForTick(index + 1) - yScale.getPixelForTick(index);
} else {
// Average width
tickHeight = yScale.width / yScale.ticks.length;
}
var categoryHeight = tickHeight * yScale.options.categoryPercentage;
var categorySpacing = (tickHeight - (tickHeight * yScale.options.categoryPercentage)) / 2;
var fullBarHeight = categoryHeight / datasetCount;
if (yScale.ticks.length !== me.chart.data.labels.length) {
var perc = yScale.ticks.length / me.chart.data.labels.length;
fullBarHeight = fullBarHeight * perc;
}
var barHeight = fullBarHeight * yScale.options.barPercentage;
var barSpacing = fullBarHeight - (fullBarHeight * yScale.options.barPercentage);
return {
datasetCount: datasetCount,
tickHeight: tickHeight,
categoryHeight: categoryHeight,
categorySpacing: categorySpacing,
fullBarHeight: fullBarHeight,
barHeight: barHeight,
barSpacing: barSpacing
};
},
calculateBarHeight: function(index) {
var me = this;
var yScale = me.getScaleForId(me.getMeta().yAxisID);
if (yScale.options.barThickness) {
return yScale.options.barThickness;
}
var ruler = me.getRuler(index);
return yScale.options.stacked ? ruler.categoryHeight : ruler.barHeight;
},
calculateBarX: function(index, datasetIndex) {
var me = this;
var meta = me.getMeta();
var xScale = me.getScaleForId(meta.xAxisID);
var value = Number(me.getDataset().data[index]);
if (xScale.options.stacked) {
var sumPos = 0,
sumNeg = 0;
for (var i = 0; i < datasetIndex; i++) {
var ds = me.chart.data.datasets[i];
var dsMeta = me.chart.getDatasetMeta(i);
if (dsMeta.bar && dsMeta.xAxisID === xScale.id && me.chart.isDatasetVisible(i)) {
var stackedVal = Number(ds.data[index]);
if (stackedVal < 0) {
sumNeg += stackedVal || 0;
} else {
sumPos += stackedVal || 0;
}
}
}
if (value < 0) {
return xScale.getPixelForValue(sumNeg + value);
}
return xScale.getPixelForValue(sumPos + value);
}
return xScale.getPixelForValue(value);
},
calculateBarY: function(index, datasetIndex) {
var me = this;
var meta = me.getMeta();
var yScale = me.getScaleForId(meta.yAxisID);
var barIndex = me.getBarIndex(datasetIndex);
var ruler = me.getRuler(index);
var topTick = yScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo);
topTick -= me.chart.isCombo ? (ruler.tickHeight / 2) : 0;
if (yScale.options.stacked) {
return topTick + (ruler.categoryHeight / 2) + ruler.categorySpacing;
}
return topTick +
(ruler.barHeight / 2) +
ruler.categorySpacing +
(ruler.barHeight * barIndex) +
(ruler.barSpacing / 2) +
(ruler.barSpacing * barIndex);
}
});
};
},{}],16:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers;
Chart.defaults.bubble = {
hover: {
mode: 'single'
},
scales: {
xAxes: [{
type: 'linear', // bubble should probably use a linear scale by default
position: 'bottom',
id: 'x-axis-0' // need an ID so datasets can reference the scale
}],
yAxes: [{
type: 'linear',
position: 'left',
id: 'y-axis-0'
}]
},
tooltips: {
callbacks: {
title: function() {
// Title doesn't make sense for scatter since we format the data as a point
return '';
},
label: function(tooltipItem, data) {
var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || '';
var dataPoint = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
return datasetLabel + ': (' + dataPoint.x + ', ' + dataPoint.y + ', ' + dataPoint.r + ')';
}
}
}
};
Chart.controllers.bubble = Chart.DatasetController.extend({
dataElementType: Chart.elements.Point,
update: function(reset) {
var me = this;
var meta = me.getMeta();
var points = meta.data;
// Update Points
helpers.each(points, function(point, index) {
me.updateElement(point, index, reset);
});
},
updateElement: function(point, index, reset) {
var me = this;
var meta = me.getMeta();
var xScale = me.getScaleForId(meta.xAxisID);
var yScale = me.getScaleForId(meta.yAxisID);
var custom = point.custom || {};
var dataset = me.getDataset();
var data = dataset.data[index];
var pointElementOptions = me.chart.options.elements.point;
var dsIndex = me.index;
helpers.extend(point, {
// Utility
_xScale: xScale,
_yScale: yScale,
_datasetIndex: dsIndex,
_index: index,
// Desired view properties
_model: {
x: reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex, me.chart.isCombo),
y: reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex),
// Appearance
radius: reset ? 0 : custom.radius ? custom.radius : me.getRadius(data),
// Tooltip
hitRadius: custom.hitRadius ? custom.hitRadius : helpers.getValueAtIndexOrDefault(dataset.hitRadius, index, pointElementOptions.hitRadius)
}
});
// Trick to reset the styles of the point
Chart.DatasetController.prototype.removeHoverStyle.call(me, point, pointElementOptions);
var model = point._model;
model.skip = custom.skip ? custom.skip : (isNaN(model.x) || isNaN(model.y));
point.pivot();
},
getRadius: function(value) {
return value.r || this.chart.options.elements.point.radius;
},
setHoverStyle: function(point) {
var me = this;
Chart.DatasetController.prototype.setHoverStyle.call(me, point);
// Radius
var dataset = me.chart.data.datasets[point._datasetIndex];
var index = point._index;
var custom = point.custom || {};
var model = point._model;
model.radius = custom.hoverRadius ? custom.hoverRadius : (helpers.getValueAtIndexOrDefault(dataset.hoverRadius, index, me.chart.options.elements.point.hoverRadius)) + me.getRadius(dataset.data[index]);
},
removeHoverStyle: function(point) {
var me = this;
Chart.DatasetController.prototype.removeHoverStyle.call(me, point, me.chart.options.elements.point);
var dataVal = me.chart.data.datasets[point._datasetIndex].data[point._index];
var custom = point.custom || {};
var model = point._model;
model.radius = custom.radius ? custom.radius : me.getRadius(dataVal);
}
});
};
},{}],17:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers,
defaults = Chart.defaults;
defaults.doughnut = {
animation: {
// Boolean - Whether we animate the rotation of the Doughnut
animateRotate: true,
// Boolean - Whether we animate scaling the Doughnut from the centre
animateScale: false
},
aspectRatio: 1,
hover: {
mode: 'single'
},
legendCallback: function(chart) {
var text = [];
text.push('<ul class="' + chart.id + '-legend">');
var data = chart.data;
var datasets = data.datasets;
var labels = data.labels;
if (datasets.length) {
for (var i = 0; i < datasets[0].data.length; ++i) {
text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>');
if (labels[i]) {
text.push(labels[i]);
}
text.push('</li>');
}
}
text.push('</ul>');
return text.join('');
},
legend: {
labels: {
generateLabels: function(chart) {
var data = chart.data;
if (data.labels.length && data.datasets.length) {
return data.labels.map(function(label, i) {
var meta = chart.getDatasetMeta(0);
var ds = data.datasets[0];
var arc = meta.data[i];
var custom = arc && arc.custom || {};
var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault;
var arcOpts = chart.options.elements.arc;
var fill = custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
var stroke = custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
var bw = custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
return {
text: label,
fillStyle: fill,
strokeStyle: stroke,
lineWidth: bw,
hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
// Extra data used for toggling the correct item
index: i
};
});
}
return [];
}
},
onClick: function(e, legendItem) {
var index = legendItem.index;
var chart = this.chart;
var i, ilen, meta;
for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
meta = chart.getDatasetMeta(i);
// toggle visibility of index if exists
if (meta.data[index]) {
meta.data[index].hidden = !meta.data[index].hidden;
}
}
chart.update();
}
},
// The percentage of the chart that we cut out of the middle.
cutoutPercentage: 50,
// The rotation of the chart, where the first data arc begins.
rotation: Math.PI * -0.5,
// The total circumference of the chart.
circumference: Math.PI * 2.0,
// Need to override these to give a nice default
tooltips: {
callbacks: {
title: function() {
return '';
},
label: function(tooltipItem, data) {
return data.labels[tooltipItem.index] + ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
}
}
}
};
defaults.pie = helpers.clone(defaults.doughnut);
helpers.extend(defaults.pie, {
cutoutPercentage: 0
});
Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({
dataElementType: Chart.elements.Arc,
linkScales: helpers.noop,
// Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
getRingIndex: function(datasetIndex) {
var ringIndex = 0;
for (var j = 0; j < datasetIndex; ++j) {
if (this.chart.isDatasetVisible(j)) {
++ringIndex;
}
}
return ringIndex;
},
update: function(reset) {
var me = this;
var chart = me.chart,
chartArea = chart.chartArea,
opts = chart.options,
arcOpts = opts.elements.arc,
availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth,
availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth,
minSize = Math.min(availableWidth, availableHeight),
offset = {
x: 0,
y: 0
},
meta = me.getMeta(),
cutoutPercentage = opts.cutoutPercentage,
circumference = opts.circumference;
// If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc
if (circumference < Math.PI * 2.0) {
var startAngle = opts.rotation % (Math.PI * 2.0);
startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0);
var endAngle = startAngle + circumference;
var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)};
var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)};
var contains0 = (startAngle <= 0 && 0 <= endAngle) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle);
var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle);
var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle);
var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle);
var cutout = cutoutPercentage / 100.0;
var min = {x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout))};
var max = {x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout))};
var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5};
minSize = Math.min(availableWidth / size.width, availableHeight / size.height);
offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5};
}
chart.borderWidth = me.getMaxBorderWidth(meta.data);
chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0);
chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 1, 0);
chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
chart.offsetX = offset.x * chart.outerRadius;
chart.offsetY = offset.y * chart.outerRadius;
meta.total = me.calculateTotal();
me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index));
me.innerRadius = me.outerRadius - chart.radiusLength;
helpers.each(meta.data, function(arc, index) {
me.updateElement(arc, index, reset);
});
},
updateElement: function(arc, index, reset) {
var me = this;
var chart = me.chart,
chartArea = chart.chartArea,
opts = chart.options,
animationOpts = opts.animation,
centerX = (chartArea.left + chartArea.right) / 2,
centerY = (chartArea.top + chartArea.bottom) / 2,
startAngle = opts.rotation, // non reset case handled later
endAngle = opts.rotation, // non reset case handled later
dataset = me.getDataset(),
circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI)),
innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius,
outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius,
valueAtIndexOrDefault = helpers.getValueAtIndexOrDefault;
helpers.extend(arc, {
// Utility
_datasetIndex: me.index,
_index: index,
// Desired view properties
_model: {
x: centerX + chart.offsetX,
y: centerY + chart.offsetY,
startAngle: startAngle,
endAngle: endAngle,
circumference: circumference,
outerRadius: outerRadius,
innerRadius: innerRadius,
label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])
}
});
var model = arc._model;
// Resets the visual styles
this.removeHoverStyle(arc);
// Set correct angles if not resetting
if (!reset || !animationOpts.animateRotate) {
if (index === 0) {
model.startAngle = opts.rotation;
} else {
model.startAngle = me.getMeta().data[index - 1]._model.endAngle;
}
model.endAngle = model.startAngle + model.circumference;
}
arc.pivot();
},
removeHoverStyle: function(arc) {
Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
},
calculateTotal: function() {
var dataset = this.getDataset();
var meta = this.getMeta();
var total = 0;
var value;
helpers.each(meta.data, function(element, index) {
value = dataset.data[index];
if (!isNaN(value) && !element.hidden) {
total += Math.abs(value);
}
});
/* if (total === 0) {
total = NaN;
}*/
return total;
},
calculateCircumference: function(value) {
var total = this.getMeta().total;
if (total > 0 && !isNaN(value)) {
return (Math.PI * 2.0) * (value / total);
}
return 0;
},
// gets the max border or hover width to properly scale pie charts
getMaxBorderWidth: function(elements) {
var max = 0,
index = this.index,
length = elements.length,
borderWidth,
hoverWidth;
for (var i = 0; i < length; i++) {
borderWidth = elements[i]._model ? elements[i]._model.borderWidth : 0;
hoverWidth = elements[i]._chart ? elements[i]._chart.config.data.datasets[index].hoverBorderWidth : 0;
max = borderWidth > max ? borderWidth : max;
max = hoverWidth > max ? hoverWidth : max;
}
return max;
}
});
};
},{}],18:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers;
Chart.defaults.line = {
showLines: true,
spanGaps: false,
hover: {
mode: 'label'
},
scales: {
xAxes: [{
type: 'category',
id: 'x-axis-0'
}],
yAxes: [{
type: 'linear',
id: 'y-axis-0'
}]
}
};
function lineEnabled(dataset, options) {
return helpers.getValueOrDefault(dataset.showLine, options.showLines);
}
Chart.controllers.line = Chart.DatasetController.extend({
datasetElementType: Chart.elements.Line,
dataElementType: Chart.elements.Point,
addElementAndReset: function(index) {
var me = this;
var options = me.chart.options;
var meta = me.getMeta();
Chart.DatasetController.prototype.addElementAndReset.call(me, index);
// Make sure bezier control points are updated
if (lineEnabled(me.getDataset(), options) && meta.dataset._model.tension !== 0) {
me.updateBezierControlPoints();
}
},
update: function(reset) {
var me = this;
var meta = me.getMeta();
var line = meta.dataset;
var points = meta.data || [];
var options = me.chart.options;
var lineElementOptions = options.elements.line;
var scale = me.getScaleForId(meta.yAxisID);
var i, ilen, custom;
var dataset = me.getDataset();
var showLine = lineEnabled(dataset, options);
// Update Line
if (showLine) {
custom = line.custom || {};
// Compatibility: If the properties are defined with only the old name, use those values
if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
dataset.lineTension = dataset.tension;
}
// Utility
line._scale = scale;
line._datasetIndex = me.index;
// Data
line._children = points;
// Model
line._model = {
// Appearance
// The default behavior of lines is to break at null values, according
// to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158
// This option gives linse the ability to span gaps
spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps,
tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.lineTension, lineElementOptions.tension),
backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
steppedLine: custom.steppedLine ? custom.steppedLine : helpers.getValueOrDefault(dataset.steppedLine, lineElementOptions.stepped),
cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.getValueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode),
// Scale
scaleTop: scale.top,
scaleBottom: scale.bottom,
scaleZero: scale.getBasePixel()
};
line.pivot();
}
// Update Points
for (i=0, ilen=points.length; i<ilen; ++i) {
me.updateElement(points[i], i, reset);
}
if (showLine && line._model.tension !== 0) {
me.updateBezierControlPoints();
}
// Now pivot the point for animation
for (i=0, ilen=points.length; i<ilen; ++i) {
points[i].pivot();
}
},
getPointBackgroundColor: function(point, index) {
var backgroundColor = this.chart.options.elements.point.backgroundColor;
var dataset = this.getDataset();
var custom = point.custom || {};
if (custom.backgroundColor) {
backgroundColor = custom.backgroundColor;
} else if (dataset.pointBackgroundColor) {
backgroundColor = helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor);
} else if (dataset.backgroundColor) {
backgroundColor = dataset.backgroundColor;
}
return backgroundColor;
},
getPointBorderColor: function(point, index) {
var borderColor = this.chart.options.elements.point.borderColor;
var dataset = this.getDataset();
var custom = point.custom || {};
if (custom.borderColor) {
borderColor = custom.borderColor;
} else if (dataset.pointBorderColor) {
borderColor = helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor);
} else if (dataset.borderColor) {
borderColor = dataset.borderColor;
}
return borderColor;
},
getPointBorderWidth: function(point, index) {
var borderWidth = this.chart.options.elements.point.borderWidth;
var dataset = this.getDataset();
var custom = point.custom || {};
if (custom.borderWidth) {
borderWidth = custom.borderWidth;
} else if (dataset.pointBorderWidth) {
borderWidth = helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth);
} else if (dataset.borderWidth) {
borderWidth = dataset.borderWidth;
}
return borderWidth;
},
updateElement: function(point, index, reset) {
var me = this;
var meta = me.getMeta();
var custom = point.custom || {};
var dataset = me.getDataset();
var datasetIndex = me.index;
var value = dataset.data[index];
var yScale = me.getScaleForId(meta.yAxisID);
var xScale = me.getScaleForId(meta.xAxisID);
var pointOptions = me.chart.options.elements.point;
var x, y;
var labels = me.chart.data.labels || [];
var includeOffset = (labels.length === 1 || dataset.data.length === 1) || me.chart.isCombo;
// Compatibility: If the properties are defined with only the old name, use those values
if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
dataset.pointRadius = dataset.radius;
}
if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
dataset.pointHitRadius = dataset.hitRadius;
}
x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex, includeOffset);
y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex);
// Utility
point._xScale = xScale;
point._yScale = yScale;
point._datasetIndex = datasetIndex;
point._index = index;
// Desired view properties
point._model = {
x: x,
y: y,
skip: custom.skip || isNaN(x) || isNaN(y),
// Appearance
radius: custom.radius || helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius),
pointStyle: custom.pointStyle || helpers.getValueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle),
backgroundColor: me.getPointBackgroundColor(point, index),
borderColor: me.getPointBorderColor(point, index),
borderWidth: me.getPointBorderWidth(point, index),
tension: meta.dataset._model ? meta.dataset._model.tension : 0,
steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false,
// Tooltip
hitRadius: custom.hitRadius || helpers.getValueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius)
};
},
calculatePointY: function(value, index, datasetIndex) {
var me = this;
var chart = me.chart;
var meta = me.getMeta();
var yScale = me.getScaleForId(meta.yAxisID);
var sumPos = 0;
var sumNeg = 0;
var i, ds, dsMeta;
if (yScale.options.stacked) {
for (i = 0; i < datasetIndex; i++) {
ds = chart.data.datasets[i];
dsMeta = chart.getDatasetMeta(i);
if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) {
var stackedRightValue = Number(yScale.getRightValue(ds.data[index]));
if (stackedRightValue < 0) {
sumNeg += stackedRightValue || 0;
} else {
sumPos += stackedRightValue || 0;
}
}
}
var rightValue = Number(yScale.getRightValue(value));
if (rightValue < 0) {
return yScale.getPixelForValue(sumNeg + rightValue);
}
return yScale.getPixelForValue(sumPos + rightValue);
}
return yScale.getPixelForValue(value);
},
updateBezierControlPoints: function() {
var me = this;
var meta = me.getMeta();
var area = me.chart.chartArea;
var points = (meta.data || []);
var i, ilen, point, model, controlPoints;
// Only consider points that are drawn in case the spanGaps option is used
if (meta.dataset._model.spanGaps) {
points = points.filter(function(pt) {
return !pt._model.skip;
});
}
function capControlPoint(pt, min, max) {
return Math.max(Math.min(pt, max), min);
}
if (meta.dataset._model.cubicInterpolationMode === 'monotone') {
helpers.splineCurveMonotone(points);
} else {
for (i = 0, ilen = points.length; i < ilen; ++i) {
point = points[i];
model = point._model;
controlPoints = helpers.splineCurve(
helpers.previousItem(points, i)._model,
model,
helpers.nextItem(points, i)._model,
meta.dataset._model.tension
);
model.controlPointPreviousX = controlPoints.previous.x;
model.controlPointPreviousY = controlPoints.previous.y;
model.controlPointNextX = controlPoints.next.x;
model.controlPointNextY = controlPoints.next.y;
}
}
if (me.chart.options.elements.line.capBezierPoints) {
for (i = 0, ilen = points.length; i < ilen; ++i) {
model = points[i]._model;
model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right);
model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom);
model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right);
model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom);
}
}
},
draw: function(ease) {
var me = this;
var meta = me.getMeta();
var points = meta.data || [];
var easingDecimal = ease || 1;
var i, ilen;
// Transition Point Locations
for (i=0, ilen=points.length; i<ilen; ++i) {
points[i].transition(easingDecimal);
}
// Transition and Draw the line
if (lineEnabled(me.getDataset(), me.chart.options)) {
meta.dataset.transition(easingDecimal).draw();
}
// Draw the points
for (i=0, ilen=points.length; i<ilen; ++i) {
points[i].draw();
}
},
setHoverStyle: function(point) {
// Point
var dataset = this.chart.data.datasets[point._datasetIndex];
var index = point._index;
var custom = point.custom || {};
var model = point._model;
model.radius = custom.hoverRadius || helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
model.backgroundColor = custom.hoverBackgroundColor || helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
model.borderColor = custom.hoverBorderColor || helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
model.borderWidth = custom.hoverBorderWidth || helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
},
removeHoverStyle: function(point) {
var me = this;
var dataset = me.chart.data.datasets[point._datasetIndex];
var index = point._index;
var custom = point.custom || {};
var model = point._model;
// Compatibility: If the properties are defined with only the old name, use those values
if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
dataset.pointRadius = dataset.radius;
}
model.radius = custom.radius || helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, me.chart.options.elements.point.radius);
model.backgroundColor = me.getPointBackgroundColor(point, index);
model.borderColor = me.getPointBorderColor(point, index);
model.borderWidth = me.getPointBorderWidth(point, index);
}
});
};
},{}],19:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers;
Chart.defaults.polarArea = {
scale: {
type: 'radialLinear',
lineArc: true, // so that lines are circular
ticks: {
beginAtZero: true
}
},
// Boolean - Whether to animate the rotation of the chart
animation: {
animateRotate: true,
animateScale: true
},
startAngle: -0.5 * Math.PI,
aspectRatio: 1,
legendCallback: function(chart) {
var text = [];
text.push('<ul class="' + chart.id + '-legend">');
var data = chart.data;
var datasets = data.datasets;
var labels = data.labels;
if (datasets.length) {
for (var i = 0; i < datasets[0].data.length; ++i) {
text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '">');
if (labels[i]) {
text.push(labels[i]);
}
text.push('</span></li>');
}
}
text.push('</ul>');
return text.join('');
},
legend: {
labels: {
generateLabels: function(chart) {
var data = chart.data;
if (data.labels.length && data.datasets.length) {
return data.labels.map(function(label, i) {
var meta = chart.getDatasetMeta(0);
var ds = data.datasets[0];
var arc = meta.data[i];
var custom = arc.custom || {};
var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault;
var arcOpts = chart.options.elements.arc;
var fill = custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
var stroke = custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
var bw = custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
return {
text: label,
fillStyle: fill,
strokeStyle: stroke,
lineWidth: bw,
hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
// Extra data used for toggling the correct item
index: i
};
});
}
return [];
}
},
onClick: function(e, legendItem) {
var index = legendItem.index;
var chart = this.chart;
var i, ilen, meta;
for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
meta = chart.getDatasetMeta(i);
meta.data[index].hidden = !meta.data[index].hidden;
}
chart.update();
}
},
// Need to override these to give a nice default
tooltips: {
callbacks: {
title: function() {
return '';
},
label: function(tooltipItem, data) {
return data.labels[tooltipItem.index] + ': ' + tooltipItem.yLabel;
}
}
}
};
Chart.controllers.polarArea = Chart.DatasetController.extend({
dataElementType: Chart.elements.Arc,
linkScales: helpers.noop,
update: function(reset) {
var me = this;
var chart = me.chart;
var chartArea = chart.chartArea;
var meta = me.getMeta();
var opts = chart.options;
var arcOpts = opts.elements.arc;
var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0);
chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index);
me.innerRadius = me.outerRadius - chart.radiusLength;
meta.count = me.countVisibleElements();
helpers.each(meta.data, function(arc, index) {
me.updateElement(arc, index, reset);
});
},
updateElement: function(arc, index, reset) {
var me = this;
var chart = me.chart;
var dataset = me.getDataset();
var opts = chart.options;
var animationOpts = opts.animation;
var scale = chart.scale;
var getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault;
var labels = chart.data.labels;
var circumference = me.calculateCircumference(dataset.data[index]);
var centerX = scale.xCenter;
var centerY = scale.yCenter;
// If there is NaN data before us, we need to calculate the starting angle correctly.
// We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data
var visibleCount = 0;
var meta = me.getMeta();
for (var i = 0; i < index; ++i) {
if (!isNaN(dataset.data[i]) && !meta.data[i].hidden) {
++visibleCount;
}
}
// var negHalfPI = -0.5 * Math.PI;
var datasetStartAngle = opts.startAngle;
var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
var startAngle = datasetStartAngle + (circumference * visibleCount);
var endAngle = startAngle + (arc.hidden ? 0 : circumference);
var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
helpers.extend(arc, {
// Utility
_datasetIndex: me.index,
_index: index,
_scale: scale,
// Desired view properties
_model: {
x: centerX,
y: centerY,
innerRadius: 0,
outerRadius: reset ? resetRadius : distance,
startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle,
endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle,
label: getValueAtIndexOrDefault(labels, index, labels[index])
}
});
// Apply border and fill style
me.removeHoverStyle(arc);
arc.pivot();
},
removeHoverStyle: function(arc) {
Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
},
countVisibleElements: function() {
var dataset = this.getDataset();
var meta = this.getMeta();
var count = 0;
helpers.each(meta.data, function(element, index) {
if (!isNaN(dataset.data[index]) && !element.hidden) {
count++;
}
});
return count;
},
calculateCircumference: function(value) {
var count = this.getMeta().count;
if (count > 0 && !isNaN(value)) {
return (2 * Math.PI) / count;
}
return 0;
}
});
};
},{}],20:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers;
Chart.defaults.radar = {
scale: {
type: 'radialLinear'
},
elements: {
line: {
tension: 0 // no bezier in radar
}
}
};
Chart.controllers.radar = Chart.DatasetController.extend({
datasetElementType: Chart.elements.Line,
dataElementType: Chart.elements.Point,
linkScales: helpers.noop,
addElementAndReset: function(index) {
Chart.DatasetController.prototype.addElementAndReset.call(this, index);
// Make sure bezier control points are updated
this.updateBezierControlPoints();
},
update: function(reset) {
var me = this;
var meta = me.getMeta();
var line = meta.dataset;
var points = meta.data;
var custom = line.custom || {};
var dataset = me.getDataset();
var lineElementOptions = me.chart.options.elements.line;
var scale = me.chart.scale;
// Compatibility: If the properties are defined with only the old name, use those values
if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
dataset.lineTension = dataset.tension;
}
helpers.extend(meta.dataset, {
// Utility
_datasetIndex: me.index,
// Data
_children: points,
_loop: true,
// Model
_model: {
// Appearance
tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.lineTension, lineElementOptions.tension),
backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
// Scale
scaleTop: scale.top,
scaleBottom: scale.bottom,
scaleZero: scale.getBasePosition()
}
});
meta.dataset.pivot();
// Update Points
helpers.each(points, function(point, index) {
me.updateElement(point, index, reset);
}, me);
// Update bezier control points
me.updateBezierControlPoints();
},
updateElement: function(point, index, reset) {
var me = this;
var custom = point.custom || {};
var dataset = me.getDataset();
var scale = me.chart.scale;
var pointElementOptions = me.chart.options.elements.point;
var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]);
helpers.extend(point, {
// Utility
_datasetIndex: me.index,
_index: index,
_scale: scale,
// Desired view properties
_model: {
x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales
y: reset ? scale.yCenter : pointPosition.y,
// Appearance
tension: custom.tension ? custom.tension : helpers.getValueOrDefault(dataset.tension, me.chart.options.elements.line.tension),
radius: custom.radius ? custom.radius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius),
backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor),
borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor),
borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth),
pointStyle: custom.pointStyle ? custom.pointStyle : helpers.getValueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle),
// Tooltip
hitRadius: custom.hitRadius ? custom.hitRadius : helpers.getValueAtIndexOrDefault(dataset.hitRadius, index, pointElementOptions.hitRadius)
}
});
point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
},
updateBezierControlPoints: function() {
var chartArea = this.chart.chartArea;
var meta = this.getMeta();
helpers.each(meta.data, function(point, index) {
var model = point._model;
var controlPoints = helpers.splineCurve(
helpers.previousItem(meta.data, index, true)._model,
model,
helpers.nextItem(meta.data, index, true)._model,
model.tension
);
// Prevent the bezier going outside of the bounds of the graph
model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left);
model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top);
model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left);
model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top);
// Now pivot the point for animation
point.pivot();
});
},
draw: function(ease) {
var meta = this.getMeta();
var easingDecimal = ease || 1;
// Transition Point Locations
helpers.each(meta.data, function(point) {
point.transition(easingDecimal);
});
// Transition and Draw the line
meta.dataset.transition(easingDecimal).draw();
// Draw the points
helpers.each(meta.data, function(point) {
point.draw();
});
},
setHoverStyle: function(point) {
// Point
var dataset = this.chart.data.datasets[point._datasetIndex];
var custom = point.custom || {};
var index = point._index;
var model = point._model;
model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
},
removeHoverStyle: function(point) {
var dataset = this.chart.data.datasets[point._datasetIndex];
var custom = point.custom || {};
var index = point._index;
var model = point._model;
var pointElementOptions = this.chart.options.elements.point;
model.radius = custom.radius ? custom.radius : helpers.getValueAtIndexOrDefault(dataset.radius, index, pointElementOptions.radius);
model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor);
model.borderColor = custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor);
model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth);
}
});
};
},{}],21:[function(require,module,exports){
/* global window: false */
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers;
Chart.defaults.global.animation = {
duration: 1000,
easing: 'easeOutQuart',
onProgress: helpers.noop,
onComplete: helpers.noop
};
Chart.Animation = Chart.Element.extend({
currentStep: null, // the current animation step
numSteps: 60, // default number of steps
easing: '', // the easing to use for this animation
render: null, // render function used by the animation service
onAnimationProgress: null, // user specified callback to fire on each step of the animation
onAnimationComplete: null // user specified callback to fire when the animation finishes
});
Chart.animationService = {
frameDuration: 17,
animations: [],
dropFrames: 0,
request: null,
addAnimation: function(chartInstance, animationObject, duration, lazy) {
var me = this;
if (!lazy) {
chartInstance.animating = true;
}
for (var index = 0; index < me.animations.length; ++index) {
if (me.animations[index].chartInstance === chartInstance) {
// replacing an in progress animation
me.animations[index].animationObject = animationObject;
return;
}
}
me.animations.push({
chartInstance: chartInstance,
animationObject: animationObject
});
// If there are no animations queued, manually kickstart a digest, for lack of a better word
if (me.animations.length === 1) {
me.requestAnimationFrame();
}
},
// Cancel the animation for a given chart instance
cancelAnimation: function(chartInstance) {
var index = helpers.findIndex(this.animations, function(animationWrapper) {
return animationWrapper.chartInstance === chartInstance;
});
if (index !== -1) {
this.animations.splice(index, 1);
chartInstance.animating = false;
}
},
requestAnimationFrame: function() {
var me = this;
if (me.request === null) {
// Skip animation frame requests until the active one is executed.
// This can happen when processing mouse events, e.g. 'mousemove'
// and 'mouseout' events will trigger multiple renders.
me.request = helpers.requestAnimFrame.call(window, function() {
me.request = null;
me.startDigest();
});
}
},
startDigest: function() {
var me = this;
var startTime = Date.now();
var framesToDrop = 0;
if (me.dropFrames > 1) {
framesToDrop = Math.floor(me.dropFrames);
me.dropFrames = me.dropFrames % 1;
}
var i = 0;
while (i < me.animations.length) {
if (me.animations[i].animationObject.currentStep === null) {
me.animations[i].animationObject.currentStep = 0;
}
me.animations[i].animationObject.currentStep += 1 + framesToDrop;
if (me.animations[i].animationObject.currentStep > me.animations[i].animationObject.numSteps) {
me.animations[i].animationObject.currentStep = me.animations[i].animationObject.numSteps;
}
me.animations[i].animationObject.render(me.animations[i].chartInstance, me.animations[i].animationObject);
if (me.animations[i].animationObject.onAnimationProgress && me.animations[i].animationObject.onAnimationProgress.call) {
me.animations[i].animationObject.onAnimationProgress.call(me.animations[i].chartInstance, me.animations[i]);
}
if (me.animations[i].animationObject.currentStep === me.animations[i].animationObject.numSteps) {
if (me.animations[i].animationObject.onAnimationComplete && me.animations[i].animationObject.onAnimationComplete.call) {
me.animations[i].animationObject.onAnimationComplete.call(me.animations[i].chartInstance, me.animations[i]);
}
// executed the last frame. Remove the animation.
me.animations[i].chartInstance.animating = false;
me.animations.splice(i, 1);
} else {
++i;
}
}
var endTime = Date.now();
var dropFrames = (endTime - startTime) / me.frameDuration;
me.dropFrames += dropFrames;
// Do we have more stuff to animate?
if (me.animations.length > 0) {
me.requestAnimationFrame();
}
}
};
};
},{}],22:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
// Global Chart canvas helpers object for drawing items to canvas
var helpers = Chart.canvasHelpers = {};
helpers.drawPoint = function(ctx, pointStyle, radius, x, y) {
var type, edgeLength, xOffset, yOffset, height, size;
if (typeof pointStyle === 'object') {
type = pointStyle.toString();
if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
ctx.drawImage(pointStyle, x - pointStyle.width / 2, y - pointStyle.height / 2);
return;
}
}
if (isNaN(radius) || radius <= 0) {
return;
}
switch (pointStyle) {
// Default includes circle
default:
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
break;
case 'triangle':
ctx.beginPath();
edgeLength = 3 * radius / Math.sqrt(3);
height = edgeLength * Math.sqrt(3) / 2;
ctx.moveTo(x - edgeLength / 2, y + height / 3);
ctx.lineTo(x + edgeLength / 2, y + height / 3);
ctx.lineTo(x, y - 2 * height / 3);
ctx.closePath();
ctx.fill();
break;
case 'rect':
size = 1 / Math.SQRT2 * radius;
ctx.beginPath();
ctx.fillRect(x - size, y - size, 2 * size, 2 * size);
ctx.strokeRect(x - size, y - size, 2 * size, 2 * size);
break;
case 'rectRot':
size = 1 / Math.SQRT2 * radius;
ctx.beginPath();
ctx.moveTo(x - size, y);
ctx.lineTo(x, y + size);
ctx.lineTo(x + size, y);
ctx.lineTo(x, y - size);
ctx.closePath();
ctx.fill();
break;
case 'cross':
ctx.beginPath();
ctx.moveTo(x, y + radius);
ctx.lineTo(x, y - radius);
ctx.moveTo(x - radius, y);
ctx.lineTo(x + radius, y);
ctx.closePath();
break;
case 'crossRot':
ctx.beginPath();
xOffset = Math.cos(Math.PI / 4) * radius;
yOffset = Math.sin(Math.PI / 4) * radius;
ctx.moveTo(x - xOffset, y - yOffset);
ctx.lineTo(x + xOffset, y + yOffset);
ctx.moveTo(x - xOffset, y + yOffset);
ctx.lineTo(x + xOffset, y - yOffset);
ctx.closePath();
break;
case 'star':
ctx.beginPath();
ctx.moveTo(x, y + radius);
ctx.lineTo(x, y - radius);
ctx.moveTo(x - radius, y);
ctx.lineTo(x + radius, y);
xOffset = Math.cos(Math.PI / 4) * radius;
yOffset = Math.sin(Math.PI / 4) * radius;
ctx.moveTo(x - xOffset, y - yOffset);
ctx.lineTo(x + xOffset, y + yOffset);
ctx.moveTo(x - xOffset, y + yOffset);
ctx.lineTo(x + xOffset, y - yOffset);
ctx.closePath();
break;
case 'line':
ctx.beginPath();
ctx.moveTo(x - radius, y);
ctx.lineTo(x + radius, y);
ctx.closePath();
break;
case 'dash':
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + radius, y);
ctx.closePath();
break;
}
ctx.stroke();
};
};
},{}],23:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers;
// Create a dictionary of chart types, to allow for extension of existing types
Chart.types = {};
// Store a reference to each instance - allowing us to globally resize chart instances on window resize.
// Destroy method on the chart will remove the instance of the chart from this reference.
Chart.instances = {};
// Controllers available for dataset visualization eg. bar, line, slice, etc.
Chart.controllers = {};
/**
* @class Chart.Controller
* The main controller of a chart.
*/
Chart.Controller = function(instance) {
this.chart = instance;
this.config = instance.config;
this.options = this.config.options = helpers.configMerge(Chart.defaults.global, Chart.defaults[this.config.type], this.config.options || {});
this.id = helpers.uid();
Object.defineProperty(this, 'data', {
get: function() {
return this.config.data;
}
});
// Add the chart instance to the global namespace
Chart.instances[this.id] = this;
if (this.options.responsive) {
// Silent resize before chart draws
this.resize(true);
}
this.initialize();
return this;
};
helpers.extend(Chart.Controller.prototype, /** @lends Chart.Controller */ {
initialize: function() {
var me = this;
// Before init plugin notification
Chart.plugins.notify('beforeInit', [me]);
me.bindEvents();
// Make sure controllers are built first so that each dataset is bound to an axis before the scales
// are built
me.ensureScalesHaveIDs();
me.buildOrUpdateControllers();
me.buildScales();
me.updateLayout();
me.resetElements();
me.initToolTip();
me.update();
// After init plugin notification
Chart.plugins.notify('afterInit', [me]);
return me;
},
clear: function() {
helpers.clear(this.chart);
return this;
},
stop: function() {
// Stops any current animation loop occuring
Chart.animationService.cancelAnimation(this);
return this;
},
resize: function(silent) {
var me = this;
var chart = me.chart;
var canvas = chart.canvas;
var newWidth = helpers.getMaximumWidth(canvas);
var aspectRatio = chart.aspectRatio;
var newHeight = (me.options.maintainAspectRatio && isNaN(aspectRatio) === false && isFinite(aspectRatio) && aspectRatio !== 0) ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas);
var sizeChanged = chart.width !== newWidth || chart.height !== newHeight;
if (!sizeChanged) {
return me;
}
canvas.width = chart.width = newWidth;
canvas.height = chart.height = newHeight;
helpers.retinaScale(chart);
// Notify any plugins about the resize
var newSize = {width: newWidth, height: newHeight};
Chart.plugins.notify('resize', [me, newSize]);
// Notify of resize
if (me.options.onResize) {
me.options.onResize(me, newSize);
}
if (!silent) {
me.stop();
me.update(me.options.responsiveAnimationDuration);
}
return me;
},
ensureScalesHaveIDs: function() {
var options = this.options;
var scalesOptions = options.scales || {};
var scaleOptions = options.scale;
helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) {
xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index);
});
helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) {
yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index);
});
if (scaleOptions) {
scaleOptions.id = scaleOptions.id || 'scale';
}
},
/**
* Builds a map of scale ID to scale object for future lookup.
*/
buildScales: function() {
var me = this;
var options = me.options;
var scales = me.scales = {};
var items = [];
if (options.scales) {
items = items.concat(
(options.scales.xAxes || []).map(function(xAxisOptions) {
return {options: xAxisOptions, dtype: 'category'};
}),
(options.scales.yAxes || []).map(function(yAxisOptions) {
return {options: yAxisOptions, dtype: 'linear'};
})
);
}
if (options.scale) {
items.push({options: options.scale, dtype: 'radialLinear', isDefault: true});
}
helpers.each(items, function(item) {
var scaleOptions = item.options;
var scaleType = helpers.getValueOrDefault(scaleOptions.type, item.dtype);
var scaleClass = Chart.scaleService.getScaleConstructor(scaleType);
if (!scaleClass) {
return;
}
var scale = new scaleClass({
id: scaleOptions.id,
options: scaleOptions,
ctx: me.chart.ctx,
chart: me
});
scales[scale.id] = scale;
// TODO(SB): I think we should be able to remove this custom case (options.scale)
// and consider it as a regular scale part of the "scales"" map only! This would
// make the logic easier and remove some useless? custom code.
if (item.isDefault) {
me.scale = scale;
}
});
Chart.scaleService.addScalesToLayout(this);
},
updateLayout: function() {
Chart.layoutService.update(this, this.chart.width, this.chart.height);
},
buildOrUpdateControllers: function() {
var me = this;
var types = [];
var newControllers = [];
helpers.each(me.data.datasets, function(dataset, datasetIndex) {
var meta = me.getDatasetMeta(datasetIndex);
if (!meta.type) {
meta.type = dataset.type || me.config.type;
}
types.push(meta.type);
if (meta.controller) {
meta.controller.updateIndex(datasetIndex);
} else {
meta.controller = new Chart.controllers[meta.type](me, datasetIndex);
newControllers.push(meta.controller);
}
}, me);
if (types.length > 1) {
for (var i = 1; i < types.length; i++) {
if (types[i] !== types[i - 1]) {
me.isCombo = true;
break;
}
}
}
return newControllers;
},
resetElements: function() {
var me = this;
helpers.each(me.data.datasets, function(dataset, datasetIndex) {
me.getDatasetMeta(datasetIndex).controller.reset();
}, me);
},
update: function(animationDuration, lazy) {
var me = this;
Chart.plugins.notify('beforeUpdate', [me]);
// In case the entire data object changed
me.tooltip._data = me.data;
// Make sure dataset controllers are updated and new controllers are reset
var newControllers = me.buildOrUpdateControllers();
// Make sure all dataset controllers have correct meta data counts
helpers.each(me.data.datasets, function(dataset, datasetIndex) {
me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements();
}, me);
Chart.layoutService.update(me, me.chart.width, me.chart.height);
// Apply changes to the dataets that require the scales to have been calculated i.e BorderColor chages
Chart.plugins.notify('afterScaleUpdate', [me]);
// Can only reset the new controllers after the scales have been updated
helpers.each(newControllers, function(controller) {
controller.reset();
});
me.updateDatasets();
// Do this before render so that any plugins that need final scale updates can use it
Chart.plugins.notify('afterUpdate', [me]);
me.render(animationDuration, lazy);
},
/**
* @method beforeDatasetsUpdate
* @description Called before all datasets are updated. If a plugin returns false,
* the datasets update will be cancelled until another chart update is triggered.
* @param {Object} instance the chart instance being updated.
* @returns {Boolean} false to cancel the datasets update.
* @memberof Chart.PluginBase
* @since version 2.1.5
* @instance
*/
/**
* @method afterDatasetsUpdate
* @description Called after all datasets have been updated. Note that this
* extension will not be called if the datasets update has been cancelled.
* @param {Object} instance the chart instance being updated.
* @memberof Chart.PluginBase
* @since version 2.1.5
* @instance
*/
/**
* Updates all datasets unless a plugin returns false to the beforeDatasetsUpdate
* extension, in which case no datasets will be updated and the afterDatasetsUpdate
* notification will be skipped.
* @protected
* @instance
*/
updateDatasets: function() {
var me = this;
var i, ilen;
if (Chart.plugins.notify('beforeDatasetsUpdate', [me])) {
for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
me.getDatasetMeta(i).controller.update();
}
Chart.plugins.notify('afterDatasetsUpdate', [me]);
}
},
render: function(duration, lazy) {
var me = this;
Chart.plugins.notify('beforeRender', [me]);
var animationOptions = me.options.animation;
if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) {
var animation = new Chart.Animation();
animation.numSteps = (duration || animationOptions.duration) / 16.66; // 60 fps
animation.easing = animationOptions.easing;
// render function
animation.render = function(chartInstance, animationObject) {
var easingFunction = helpers.easingEffects[animationObject.easing];
var stepDecimal = animationObject.currentStep / animationObject.numSteps;
var easeDecimal = easingFunction(stepDecimal);
chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep);
};
// user events
animation.onAnimationProgress = animationOptions.onProgress;
animation.onAnimationComplete = animationOptions.onComplete;
Chart.animationService.addAnimation(me, animation, duration, lazy);
} else {
me.draw();
if (animationOptions && animationOptions.onComplete && animationOptions.onComplete.call) {
animationOptions.onComplete.call(me);
}
}
return me;
},
draw: function(ease) {
var me = this;
var easingDecimal = ease || 1;
me.clear();
Chart.plugins.notify('beforeDraw', [me, easingDecimal]);
// Draw all the scales
helpers.each(me.boxes, function(box) {
box.draw(me.chartArea);
}, me);
if (me.scale) {
me.scale.draw();
}
Chart.plugins.notify('beforeDatasetsDraw', [me, easingDecimal]);
// Draw each dataset via its respective controller (reversed to support proper line stacking)
helpers.each(me.data.datasets, function(dataset, datasetIndex) {
if (me.isDatasetVisible(datasetIndex)) {
me.getDatasetMeta(datasetIndex).controller.draw(ease);
}
}, me, true);
Chart.plugins.notify('afterDatasetsDraw', [me, easingDecimal]);
// Finally draw the tooltip
me.tooltip.transition(easingDecimal).draw();
Chart.plugins.notify('afterDraw', [me, easingDecimal]);
},
// Get the single element that was clicked on
// @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
getElementAtEvent: function(e) {
var me = this;
var eventPosition = helpers.getRelativePosition(e, me.chart);
var elementsArray = [];
helpers.each(me.data.datasets, function(dataset, datasetIndex) {
if (me.isDatasetVisible(datasetIndex)) {
var meta = me.getDatasetMeta(datasetIndex);
helpers.each(meta.data, function(element) {
if (element.inRange(eventPosition.x, eventPosition.y)) {
elementsArray.push(element);
return elementsArray;
}
});
}
});
return elementsArray.slice(0, 1);
},
getElementsAtEvent: function(e) {
var me = this;
var eventPosition = helpers.getRelativePosition(e, me.chart);
var elementsArray = [];
var found = function() {
if (me.data.datasets) {
for (var i = 0; i < me.data.datasets.length; i++) {
var meta = me.getDatasetMeta(i);
if (me.isDatasetVisible(i)) {
for (var j = 0; j < meta.data.length; j++) {
if (meta.data[j].inRange(eventPosition.x, eventPosition.y)) {
return meta.data[j];
}
}
}
}
}
}.call(me);
if (!found) {
return elementsArray;
}
helpers.each(me.data.datasets, function(dataset, datasetIndex) {
if (me.isDatasetVisible(datasetIndex)) {
var meta = me.getDatasetMeta(datasetIndex),
element = meta.data[found._index];
if (element && !element._view.skip) {
elementsArray.push(element);
}
}
}, me);
return elementsArray;
},
getElementsAtXAxis: function(e) {
var me = this;
var eventPosition = helpers.getRelativePosition(e, me.chart);
var elementsArray = [];
var found = function() {
if (me.data.datasets) {
for (var i = 0; i < me.data.datasets.length; i++) {
var meta = me.getDatasetMeta(i);
if (me.isDatasetVisible(i)) {
for (var j = 0; j < meta.data.length; j++) {
if (meta.data[j].inLabelRange(eventPosition.x, eventPosition.y)) {
return meta.data[j];
}
}
}
}
}
}.call(me);
if (!found) {
return elementsArray;
}
helpers.each(me.data.datasets, function(dataset, datasetIndex) {
if (me.isDatasetVisible(datasetIndex)) {
var meta = me.getDatasetMeta(datasetIndex);
var index = helpers.findIndex(meta.data, function(it) {
return found._model.x === it._model.x;
});
if (index !== -1 && !meta.data[index]._view.skip) {
elementsArray.push(meta.data[index]);
}
}
}, me);
return elementsArray;
},
getElementsAtEventForMode: function(e, mode) {
var me = this;
switch (mode) {
case 'single':
return me.getElementAtEvent(e);
case 'label':
return me.getElementsAtEvent(e);
case 'dataset':
return me.getDatasetAtEvent(e);
case 'x-axis':
return me.getElementsAtXAxis(e);
default:
return e;
}
},
getDatasetAtEvent: function(e) {
var elementsArray = this.getElementAtEvent(e);
if (elementsArray.length > 0) {
elementsArray = this.getDatasetMeta(elementsArray[0]._datasetIndex).data;
}
return elementsArray;
},
getDatasetMeta: function(datasetIndex) {
var me = this;
var dataset = me.data.datasets[datasetIndex];
if (!dataset._meta) {
dataset._meta = {};
}
var meta = dataset._meta[me.id];
if (!meta) {
meta = dataset._meta[me.id] = {
type: null,
data: [],
dataset: null,
controller: null,
hidden: null, // See isDatasetVisible() comment
xAxisID: null,
yAxisID: null
};
}
return meta;
},
getVisibleDatasetCount: function() {
var count = 0;
for (var i = 0, ilen = this.data.datasets.length; i<ilen; ++i) {
if (this.isDatasetVisible(i)) {
count++;
}
}
return count;
},
isDatasetVisible: function(datasetIndex) {
var meta = this.getDatasetMeta(datasetIndex);
// meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false,
// the dataset.hidden value is ignored, else if null, the dataset hidden state is returned.
return typeof meta.hidden === 'boolean'? !meta.hidden : !this.data.datasets[datasetIndex].hidden;
},
generateLegend: function() {
return this.options.legendCallback(this);
},
destroy: function() {
var me = this;
me.stop();
me.clear();
helpers.unbindEvents(me, me.events);
helpers.removeResizeListener(me.chart.canvas.parentNode);
// Reset canvas height/width attributes
var canvas = me.chart.canvas;
canvas.width = me.chart.width;
canvas.height = me.chart.height;
// if we scaled the canvas in response to a devicePixelRatio !== 1, we need to undo that transform here
if (me.chart.originalDevicePixelRatio !== undefined) {
me.chart.ctx.scale(1 / me.chart.originalDevicePixelRatio, 1 / me.chart.originalDevicePixelRatio);
}
// Reset to the old style since it may have been changed by the device pixel ratio changes
canvas.style.width = me.chart.originalCanvasStyleWidth;
canvas.style.height = me.chart.originalCanvasStyleHeight;
Chart.plugins.notify('destroy', [me]);
delete Chart.instances[me.id];
},
toBase64Image: function() {
return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
},
initToolTip: function() {
var me = this;
me.tooltip = new Chart.Tooltip({
_chart: me.chart,
_chartInstance: me,
_data: me.data,
_options: me.options.tooltips
}, me);
},
bindEvents: function() {
var me = this;
helpers.bindEvents(me, me.options.events, function(evt) {
me.eventHandler(evt);
});
},
updateHoverStyle: function(elements, mode, enabled) {
var method = enabled? 'setHoverStyle' : 'removeHoverStyle';
var element, i, ilen;
switch (mode) {
case 'single':
elements = [elements[0]];
break;
case 'label':
case 'dataset':
case 'x-axis':
// elements = elements;
break;
default:
// unsupported mode
return;
}
for (i=0, ilen=elements.length; i<ilen; ++i) {
element = elements[i];
if (element) {
this.getDatasetMeta(element._datasetIndex).controller[method](element);
}
}
},
eventHandler: function(e) {
var me = this;
var tooltip = me.tooltip;
var options = me.options || {};
var hoverOptions = options.hover;
var tooltipsOptions = options.tooltips;
me.lastActive = me.lastActive || [];
me.lastTooltipActive = me.lastTooltipActive || [];
// Find Active Elements for hover and tooltips
if (e.type === 'mouseout') {
me.active = [];
me.tooltipActive = [];
} else {
me.active = me.getElementsAtEventForMode(e, hoverOptions.mode);
me.tooltipActive = me.getElementsAtEventForMode(e, tooltipsOptions.mode);
}
// On Hover hook
if (hoverOptions.onHover) {
hoverOptions.onHover.call(me, me.active);
}
if (me.legend && me.legend.handleEvent) {
me.legend.handleEvent(e);
}
if (e.type === 'mouseup' || e.type === 'click') {
if (options.onClick) {
options.onClick.call(me, e, me.active);
}
}
// Remove styling for last active (even if it may still be active)
if (me.lastActive.length) {
me.updateHoverStyle(me.lastActive, hoverOptions.mode, false);
}
// Built in hover styling
if (me.active.length && hoverOptions.mode) {
me.updateHoverStyle(me.active, hoverOptions.mode, true);
}
// Built in Tooltips
if (tooltipsOptions.enabled || tooltipsOptions.custom) {
tooltip.initialize();
tooltip._active = me.tooltipActive;
tooltip.update(true);
}
// Hover animations
tooltip.pivot();
if (!me.animating) {
// If entering, leaving, or changing elements, animate the change via pivot
if (!helpers.arrayEquals(me.active, me.lastActive) ||
!helpers.arrayEquals(me.tooltipActive, me.lastTooltipActive)) {
me.stop();
if (tooltipsOptions.enabled || tooltipsOptions.custom) {
tooltip.update(true);
}
// We only need to render at this point. Updating will cause scales to be
// recomputed generating flicker & using more memory than necessary.
me.render(hoverOptions.animationDuration, true);
}
}
// Remember Last Actives
me.lastActive = me.active;
me.lastTooltipActive = me.tooltipActive;
return me;
}
});
};
},{}],24:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers;
var noop = helpers.noop;
// Base class for all dataset controllers (line, bar, etc)
Chart.DatasetController = function(chart, datasetIndex) {
this.initialize(chart, datasetIndex);
};
helpers.extend(Chart.DatasetController.prototype, {
/**
* Element type used to generate a meta dataset (e.g. Chart.element.Line).
* @type {Chart.core.element}
*/
datasetElementType: null,
/**
* Element type used to generate a meta data (e.g. Chart.element.Point).
* @type {Chart.core.element}
*/
dataElementType: null,
initialize: function(chart, datasetIndex) {
var me = this;
me.chart = chart;
me.index = datasetIndex;
me.linkScales();
me.addElements();
},
updateIndex: function(datasetIndex) {
this.index = datasetIndex;
},
linkScales: function() {
var me = this;
var meta = me.getMeta();
var dataset = me.getDataset();
if (meta.xAxisID === null) {
meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id;
}
if (meta.yAxisID === null) {
meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id;
}
},
getDataset: function() {
return this.chart.data.datasets[this.index];
},
getMeta: function() {
return this.chart.getDatasetMeta(this.index);
},
getScaleForId: function(scaleID) {
return this.chart.scales[scaleID];
},
reset: function() {
this.update(true);
},
createMetaDataset: function() {
var me = this;
var type = me.datasetElementType;
return type && new type({
_chart: me.chart.chart,
_datasetIndex: me.index
});
},
createMetaData: function(index) {
var me = this;
var type = me.dataElementType;
return type && new type({
_chart: me.chart.chart,
_datasetIndex: me.index,
_index: index
});
},
addElements: function() {
var me = this;
var meta = me.getMeta();
var data = me.getDataset().data || [];
var metaData = meta.data;
var i, ilen;
for (i=0, ilen=data.length; i<ilen; ++i) {
metaData[i] = metaData[i] || me.createMetaData(meta, i);
}
meta.dataset = meta.dataset || me.createMetaDataset();
},
addElementAndReset: function(index) {
var me = this;
var element = me.createMetaData(index);
me.getMeta().data.splice(index, 0, element);
me.updateElement(element, index, true);
},
buildOrUpdateElements: function() {
// Handle the number of data points changing
var meta = this.getMeta(),
md = meta.data,
numData = this.getDataset().data.length,
numMetaData = md.length;
// Make sure that we handle number of datapoints changing
if (numData < numMetaData) {
// Remove excess bars for data points that have been removed
md.splice(numData, numMetaData - numData);
} else if (numData > numMetaData) {
// Add new elements
for (var index = numMetaData; index < numData; ++index) {
this.addElementAndReset(index);
}
}
},
update: noop,
draw: function(ease) {
var easingDecimal = ease || 1;
helpers.each(this.getMeta().data, function(element) {
element.transition(easingDecimal).draw();
});
},
removeHoverStyle: function(element, elementOpts) {
var dataset = this.chart.data.datasets[element._datasetIndex],
index = element._index,
custom = element.custom || {},
valueOrDefault = helpers.getValueAtIndexOrDefault,
model = element._model;
model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor);
model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor);
model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth);
},
setHoverStyle: function(element) {
var dataset = this.chart.data.datasets[element._datasetIndex],
index = element._index,
custom = element.custom || {},
valueOrDefault = helpers.getValueAtIndexOrDefault,
getHoverColor = helpers.getHoverColor,
model = element._model;
model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor));
model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor));
model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
}
});
Chart.DatasetController.extend = helpers.inherits;
};
},{}],25:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers;
Chart.elements = {};
Chart.Element = function(configuration) {
helpers.extend(this, configuration);
this.initialize.apply(this, arguments);
};
helpers.extend(Chart.Element.prototype, {
initialize: function() {
this.hidden = false;
},
pivot: function() {
var me = this;
if (!me._view) {
me._view = helpers.clone(me._model);
}
me._start = helpers.clone(me._view);
return me;
},
transition: function(ease) {
var me = this;
if (!me._view) {
me._view = helpers.clone(me._model);
}
// No animation -> No Transition
if (ease === 1) {
me._view = me._model;
me._start = null;
return me;
}
if (!me._start) {
me.pivot();
}
helpers.each(me._model, function(value, key) {
if (key[0] === '_') {
// Only non-underscored properties
// Init if doesn't exist
} else if (!me._view.hasOwnProperty(key)) {
if (typeof value === 'number' && !isNaN(me._view[key])) {
me._view[key] = value * ease;
} else {
me._view[key] = value;
}
// No unnecessary computations
} else if (value === me._view[key]) {
// It's the same! Woohoo!
// Color transitions if possible
} else if (typeof value === 'string') {
try {
var color = helpers.color(me._model[key]).mix(helpers.color(me._start[key]), ease);
me._view[key] = color.rgbString();
} catch (err) {
me._view[key] = value;
}
// Number transitions
} else if (typeof value === 'number') {
var startVal = me._start[key] !== undefined && isNaN(me._start[key]) === false ? me._start[key] : 0;
me._view[key] = ((me._model[key] - startVal) * ease) + startVal;
// Everything else
} else {
me._view[key] = value;
}
}, me);
return me;
},
tooltipPosition: function() {
return {
x: this._model.x,
y: this._model.y
};
},
hasValue: function() {
return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y);
}
});
Chart.Element.extend = helpers.inherits;
};
},{}],26:[function(require,module,exports){
/* global window: false */
/* global document: false */
'use strict';
var color = require(3);
module.exports = function(Chart) {
// Global Chart helpers object for utility methods and classes
var helpers = Chart.helpers = {};
// -- Basic js utility methods
helpers.each = function(loopable, callback, self, reverse) {
// Check to see if null or undefined firstly.
var i, len;
if (helpers.isArray(loopable)) {
len = loopable.length;
if (reverse) {
for (i = len - 1; i >= 0; i--) {
callback.call(self, loopable[i], i);
}
} else {
for (i = 0; i < len; i++) {
callback.call(self, loopable[i], i);
}
}
} else if (typeof loopable === 'object') {
var keys = Object.keys(loopable);
len = keys.length;
for (i = 0; i < len; i++) {
callback.call(self, loopable[keys[i]], keys[i]);
}
}
};
helpers.clone = function(obj) {
var objClone = {};
helpers.each(obj, function(value, key) {
if (helpers.isArray(value)) {
objClone[key] = value.slice(0);
} else if (typeof value === 'object' && value !== null) {
objClone[key] = helpers.clone(value);
} else {
objClone[key] = value;
}
});
return objClone;
};
helpers.extend = function(base) {
var setFn = function(value, key) {
base[key] = value;
};
for (var i = 1, ilen = arguments.length; i < ilen; i++) {
helpers.each(arguments[i], setFn);
}
return base;
};
// Need a special merge function to chart configs since they are now grouped
helpers.configMerge = function(_base) {
var base = helpers.clone(_base);
helpers.each(Array.prototype.slice.call(arguments, 1), function(extension) {
helpers.each(extension, function(value, key) {
if (key === 'scales') {
// Scale config merging is complex. Add out own function here for that
base[key] = helpers.scaleMerge(base.hasOwnProperty(key) ? base[key] : {}, value);
} else if (key === 'scale') {
// Used in polar area & radar charts since there is only one scale
base[key] = helpers.configMerge(base.hasOwnProperty(key) ? base[key] : {}, Chart.scaleService.getScaleDefaults(value.type), value);
} else if (base.hasOwnProperty(key) && helpers.isArray(base[key]) && helpers.isArray(value)) {
// In this case we have an array of objects replacing another array. Rather than doing a strict replace,
// merge. This allows easy scale option merging
var baseArray = base[key];
helpers.each(value, function(valueObj, index) {
if (index < baseArray.length) {
if (typeof baseArray[index] === 'object' && baseArray[index] !== null && typeof valueObj === 'object' && valueObj !== null) {
// Two objects are coming together. Do a merge of them.
baseArray[index] = helpers.configMerge(baseArray[index], valueObj);
} else {
// Just overwrite in this case since there is nothing to merge
baseArray[index] = valueObj;
}
} else {
baseArray.push(valueObj); // nothing to merge
}
});
} else if (base.hasOwnProperty(key) && typeof base[key] === 'object' && base[key] !== null && typeof value === 'object') {
// If we are overwriting an object with an object, do a merge of the properties.
base[key] = helpers.configMerge(base[key], value);
} else {
// can just overwrite the value in this case
base[key] = value;
}
});
});
return base;
};
helpers.scaleMerge = function(_base, extension) {
var base = helpers.clone(_base);
helpers.each(extension, function(value, key) {
if (key === 'xAxes' || key === 'yAxes') {
// These properties are arrays of items
if (base.hasOwnProperty(key)) {
helpers.each(value, function(valueObj, index) {
var axisType = helpers.getValueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear');
var axisDefaults = Chart.scaleService.getScaleDefaults(axisType);
if (index >= base[key].length || !base[key][index].type) {
base[key].push(helpers.configMerge(axisDefaults, valueObj));
} else if (valueObj.type && valueObj.type !== base[key][index].type) {
// Type changed. Bring in the new defaults before we bring in valueObj so that valueObj can override the correct scale defaults
base[key][index] = helpers.configMerge(base[key][index], axisDefaults, valueObj);
} else {
// Type is the same
base[key][index] = helpers.configMerge(base[key][index], valueObj);
}
});
} else {
base[key] = [];
helpers.each(value, function(valueObj) {
var axisType = helpers.getValueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear');
base[key].push(helpers.configMerge(Chart.scaleService.getScaleDefaults(axisType), valueObj));
});
}
} else if (base.hasOwnProperty(key) && typeof base[key] === 'object' && base[key] !== null && typeof value === 'object') {
// If we are overwriting an object with an object, do a merge of the properties.
base[key] = helpers.configMerge(base[key], value);
} else {
// can just overwrite the value in this case
base[key] = value;
}
});
return base;
};
helpers.getValueAtIndexOrDefault = function(value, index, defaultValue) {
if (value === undefined || value === null) {
return defaultValue;
}
if (helpers.isArray(value)) {
return index < value.length ? value[index] : defaultValue;
}
return value;
};
helpers.getValueOrDefault = function(value, defaultValue) {
return value === undefined ? defaultValue : value;
};
helpers.indexOf = Array.prototype.indexOf?
function(array, item) {
return array.indexOf(item);
}:
function(array, item) {
for (var i = 0, ilen = array.length; i < ilen; ++i) {
if (array[i] === item) {
return i;
}
}
return -1;
};
helpers.where = function(collection, filterCallback) {
if (helpers.isArray(collection) && Array.prototype.filter) {
return collection.filter(filterCallback);
}
var filtered = [];
helpers.each(collection, function(item) {
if (filterCallback(item)) {
filtered.push(item);
}
});
return filtered;
};
helpers.findIndex = Array.prototype.findIndex?
function(array, callback, scope) {
return array.findIndex(callback, scope);
} :
function(array, callback, scope) {
scope = scope === undefined? array : scope;
for (var i = 0, ilen = array.length; i < ilen; ++i) {
if (callback.call(scope, array[i], i, array)) {
return i;
}
}
return -1;
};
helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
// Default to start of the array
if (startIndex === undefined || startIndex === null) {
startIndex = -1;
}
for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
var currentItem = arrayToSearch[i];
if (filterCallback(currentItem)) {
return currentItem;
}
}
};
helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
// Default to end of the array
if (startIndex === undefined || startIndex === null) {
startIndex = arrayToSearch.length;
}
for (var i = startIndex - 1; i >= 0; i--) {
var currentItem = arrayToSearch[i];
if (filterCallback(currentItem)) {
return currentItem;
}
}
};
helpers.inherits = function(extensions) {
// Basic javascript inheritance based on the model created in Backbone.js
var me = this;
var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() {
return me.apply(this, arguments);
};
var Surrogate = function() {
this.constructor = ChartElement;
};
Surrogate.prototype = me.prototype;
ChartElement.prototype = new Surrogate();
ChartElement.extend = helpers.inherits;
if (extensions) {
helpers.extend(ChartElement.prototype, extensions);
}
ChartElement.__super__ = me.prototype;
return ChartElement;
};
helpers.noop = function() {};
helpers.uid = (function() {
var id = 0;
return function() {
return id++;
};
}());
// -- Math methods
helpers.isNumber = function(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
};
helpers.almostEquals = function(x, y, epsilon) {
return Math.abs(x - y) < epsilon;
};
helpers.max = function(array) {
return array.reduce(function(max, value) {
if (!isNaN(value)) {
return Math.max(max, value);
}
return max;
}, Number.NEGATIVE_INFINITY);
};
helpers.min = function(array) {
return array.reduce(function(min, value) {
if (!isNaN(value)) {
return Math.min(min, value);
}
return min;
}, Number.POSITIVE_INFINITY);
};
helpers.sign = Math.sign?
function(x) {
return Math.sign(x);
} :
function(x) {
x = +x; // convert to a number
if (x === 0 || isNaN(x)) {
return x;
}
return x > 0 ? 1 : -1;
};
helpers.log10 = Math.log10?
function(x) {
return Math.log10(x);
} :
function(x) {
return Math.log(x) / Math.LN10;
};
helpers.toRadians = function(degrees) {
return degrees * (Math.PI / 180);
};
helpers.toDegrees = function(radians) {
return radians * (180 / Math.PI);
};
// Gets the angle from vertical upright to the point about a centre.
helpers.getAngleFromPoint = function(centrePoint, anglePoint) {
var distanceFromXCenter = anglePoint.x - centrePoint.x,
distanceFromYCenter = anglePoint.y - centrePoint.y,
radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
if (angle < (-0.5 * Math.PI)) {
angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
}
return {
angle: angle,
distance: radialDistanceFromCenter
};
};
helpers.aliasPixel = function(pixelWidth) {
return (pixelWidth % 2 === 0) ? 0 : 0.5;
};
helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) {
// Props to Rob Spencer at scaled innovation for his post on splining between points
// http://scaledinnovation.com/analytics/splines/aboutSplines.html
// This function must also respect "skipped" points
var previous = firstPoint.skip ? middlePoint : firstPoint,
current = middlePoint,
next = afterPoint.skip ? middlePoint : afterPoint;
var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2));
var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2));
var s01 = d01 / (d01 + d12);
var s12 = d12 / (d01 + d12);
// If all points are the same, s01 & s02 will be inf
s01 = isNaN(s01) ? 0 : s01;
s12 = isNaN(s12) ? 0 : s12;
var fa = t * s01; // scaling factor for triangle Ta
var fb = t * s12;
return {
previous: {
x: current.x - fa * (next.x - previous.x),
y: current.y - fa * (next.y - previous.y)
},
next: {
x: current.x + fb * (next.x - previous.x),
y: current.y + fb * (next.y - previous.y)
}
};
};
helpers.EPSILON = Number.EPSILON || 1e-14;
helpers.splineCurveMonotone = function(points) {
// This function calculates Bézier control points in a similar way than |splineCurve|,
// but preserves monotonicity of the provided data and ensures no local extremums are added
// between the dataset discrete points due to the interpolation.
// See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
var pointsWithTangents = (points || []).map(function(point) {
return {
model: point._model,
deltaK: 0,
mK: 0
};
});
// Calculate slopes (deltaK) and initialize tangents (mK)
var pointsLen = pointsWithTangents.length;
var i, pointBefore, pointCurrent, pointAfter;
for (i = 0; i < pointsLen; ++i) {
pointCurrent = pointsWithTangents[i];
if (pointCurrent.model.skip) {
continue;
}
pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
if (pointAfter && !pointAfter.model.skip) {
pointCurrent.deltaK = (pointAfter.model.y - pointCurrent.model.y) / (pointAfter.model.x - pointCurrent.model.x);
}
if (!pointBefore || pointBefore.model.skip) {
pointCurrent.mK = pointCurrent.deltaK;
} else if (!pointAfter || pointAfter.model.skip) {
pointCurrent.mK = pointBefore.deltaK;
} else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) {
pointCurrent.mK = 0;
} else {
pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2;
}
}
// Adjust tangents to ensure monotonic properties
var alphaK, betaK, tauK, squaredMagnitude;
for (i = 0; i < pointsLen - 1; ++i) {
pointCurrent = pointsWithTangents[i];
pointAfter = pointsWithTangents[i + 1];
if (pointCurrent.model.skip || pointAfter.model.skip) {
continue;
}
if (helpers.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) {
pointCurrent.mK = pointAfter.mK = 0;
continue;
}
alphaK = pointCurrent.mK / pointCurrent.deltaK;
betaK = pointAfter.mK / pointCurrent.deltaK;
squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
if (squaredMagnitude <= 9) {
continue;
}
tauK = 3 / Math.sqrt(squaredMagnitude);
pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK;
pointAfter.mK = betaK * tauK * pointCurrent.deltaK;
}
// Compute control points
var deltaX;
for (i = 0; i < pointsLen; ++i) {
pointCurrent = pointsWithTangents[i];
if (pointCurrent.model.skip) {
continue;
}
pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
if (pointBefore && !pointBefore.model.skip) {
deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3;
pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX;
pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK;
}
if (pointAfter && !pointAfter.model.skip) {
deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3;
pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX;
pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK;
}
}
};
helpers.nextItem = function(collection, index, loop) {
if (loop) {
return index >= collection.length - 1 ? collection[0] : collection[index + 1];
}
return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1];
};
helpers.previousItem = function(collection, index, loop) {
if (loop) {
return index <= 0 ? collection[collection.length - 1] : collection[index - 1];
}
return index <= 0 ? collection[0] : collection[index - 1];
};
// Implementation of the nice number algorithm used in determining where axis labels will go
helpers.niceNum = function(range, round) {
var exponent = Math.floor(helpers.log10(range));
var fraction = range / Math.pow(10, exponent);
var niceFraction;
if (round) {
if (fraction < 1.5) {
niceFraction = 1;
} else if (fraction < 3) {
niceFraction = 2;
} else if (fraction < 7) {
niceFraction = 5;
} else {
niceFraction = 10;
}
} else if (fraction <= 1.0) {
niceFraction = 1;
} else if (fraction <= 2) {
niceFraction = 2;
} else if (fraction <= 5) {
niceFraction = 5;
} else {
niceFraction = 10;
}
return niceFraction * Math.pow(10, exponent);
};
// Easing functions adapted from Robert Penner's easing equations
// http://www.robertpenner.com/easing/
var easingEffects = helpers.easingEffects = {
linear: function(t) {
return t;
},
easeInQuad: function(t) {
return t * t;
},
easeOutQuad: function(t) {
return -1 * t * (t - 2);
},
easeInOutQuad: function(t) {
if ((t /= 1 / 2) < 1) {
return 1 / 2 * t * t;
}
return -1 / 2 * ((--t) * (t - 2) - 1);
},
easeInCubic: function(t) {
return t * t * t;
},
easeOutCubic: function(t) {
return 1 * ((t = t / 1 - 1) * t * t + 1);
},
easeInOutCubic: function(t) {
if ((t /= 1 / 2) < 1) {
return 1 / 2 * t * t * t;
}
return 1 / 2 * ((t -= 2) * t * t + 2);
},
easeInQuart: function(t) {
return t * t * t * t;
},
easeOutQuart: function(t) {
return -1 * ((t = t / 1 - 1) * t * t * t - 1);
},
easeInOutQuart: function(t) {
if ((t /= 1 / 2) < 1) {
return 1 / 2 * t * t * t * t;
}
return -1 / 2 * ((t -= 2) * t * t * t - 2);
},
easeInQuint: function(t) {
return 1 * (t /= 1) * t * t * t * t;
},
easeOutQuint: function(t) {
return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
},
easeInOutQuint: function(t) {
if ((t /= 1 / 2) < 1) {
return 1 / 2 * t * t * t * t * t;
}
return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
},
easeInSine: function(t) {
return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
},
easeOutSine: function(t) {
return 1 * Math.sin(t / 1 * (Math.PI / 2));
},
easeInOutSine: function(t) {
return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
},
easeInExpo: function(t) {
return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
},
easeOutExpo: function(t) {
return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
},
easeInOutExpo: function(t) {
if (t === 0) {
return 0;
}
if (t === 1) {
return 1;
}
if ((t /= 1 / 2) < 1) {
return 1 / 2 * Math.pow(2, 10 * (t - 1));
}
return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
},
easeInCirc: function(t) {
if (t >= 1) {
return t;
}
return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
},
easeOutCirc: function(t) {
return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
},
easeInOutCirc: function(t) {
if ((t /= 1 / 2) < 1) {
return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
}
return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
},
easeInElastic: function(t) {
var s = 1.70158;
var p = 0;
var a = 1;
if (t === 0) {
return 0;
}
if ((t /= 1) === 1) {
return 1;
}
if (!p) {
p = 1 * 0.3;
}
if (a < Math.abs(1)) {
a = 1;
s = p / 4;
} else {
s = p / (2 * Math.PI) * Math.asin(1 / a);
}
return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
},
easeOutElastic: function(t) {
var s = 1.70158;
var p = 0;
var a = 1;
if (t === 0) {
return 0;
}
if ((t /= 1) === 1) {
return 1;
}
if (!p) {
p = 1 * 0.3;
}
if (a < Math.abs(1)) {
a = 1;
s = p / 4;
} else {
s = p / (2 * Math.PI) * Math.asin(1 / a);
}
return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
},
easeInOutElastic: function(t) {
var s = 1.70158;
var p = 0;
var a = 1;
if (t === 0) {
return 0;
}
if ((t /= 1 / 2) === 2) {
return 1;
}
if (!p) {
p = 1 * (0.3 * 1.5);
}
if (a < Math.abs(1)) {
a = 1;
s = p / 4;
} else {
s = p / (2 * Math.PI) * Math.asin(1 / a);
}
if (t < 1) {
return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
}
return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
},
easeInBack: function(t) {
var s = 1.70158;
return 1 * (t /= 1) * t * ((s + 1) * t - s);
},
easeOutBack: function(t) {
var s = 1.70158;
return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
},
easeInOutBack: function(t) {
var s = 1.70158;
if ((t /= 1 / 2) < 1) {
return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
}
return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
},
easeInBounce: function(t) {
return 1 - easingEffects.easeOutBounce(1 - t);
},
easeOutBounce: function(t) {
if ((t /= 1) < (1 / 2.75)) {
return 1 * (7.5625 * t * t);
} else if (t < (2 / 2.75)) {
return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
} else if (t < (2.5 / 2.75)) {
return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
}
return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
},
easeInOutBounce: function(t) {
if (t < 1 / 2) {
return easingEffects.easeInBounce(t * 2) * 0.5;
}
return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
}
};
// Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
helpers.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
return window.setTimeout(callback, 1000 / 60);
};
}());
helpers.cancelAnimFrame = (function() {
return window.cancelAnimationFrame ||
window.webkitCancelAnimationFrame ||
window.mozCancelAnimationFrame ||
window.oCancelAnimationFrame ||
window.msCancelAnimationFrame ||
function(callback) {
return window.clearTimeout(callback, 1000 / 60);
};
}());
// -- DOM methods
helpers.getRelativePosition = function(evt, chart) {
var mouseX, mouseY;
var e = evt.originalEvent || evt,
canvas = evt.currentTarget || evt.srcElement,
boundingRect = canvas.getBoundingClientRect();
var touches = e.touches;
if (touches && touches.length > 0) {
mouseX = touches[0].clientX;
mouseY = touches[0].clientY;
} else {
mouseX = e.clientX;
mouseY = e.clientY;
}
// Scale mouse coordinates into canvas coordinates
// by following the pattern laid out by 'jerryj' in the comments of
// http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left'));
var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top'));
var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right'));
var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom'));
var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight;
var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom;
// We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However
// the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here
mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio);
mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio);
return {
x: mouseX,
y: mouseY
};
};
helpers.addEvent = function(node, eventType, method) {
if (node.addEventListener) {
node.addEventListener(eventType, method);
} else if (node.attachEvent) {
node.attachEvent('on' + eventType, method);
} else {
node['on' + eventType] = method;
}
};
helpers.removeEvent = function(node, eventType, handler) {
if (node.removeEventListener) {
node.removeEventListener(eventType, handler, false);
} else if (node.detachEvent) {
node.detachEvent('on' + eventType, handler);
} else {
node['on' + eventType] = helpers.noop;
}
};
helpers.bindEvents = function(chartInstance, arrayOfEvents, handler) {
// Create the events object if it's not already present
var events = chartInstance.events = chartInstance.events || {};
helpers.each(arrayOfEvents, function(eventName) {
events[eventName] = function() {
handler.apply(chartInstance, arguments);
};
helpers.addEvent(chartInstance.chart.canvas, eventName, events[eventName]);
});
};
helpers.unbindEvents = function(chartInstance, arrayOfEvents) {
var canvas = chartInstance.chart.canvas;
helpers.each(arrayOfEvents, function(handler, eventName) {
helpers.removeEvent(canvas, eventName, handler);
});
};
// Private helper function to convert max-width/max-height values that may be percentages into a number
function parseMaxStyle(styleValue, node, parentProperty) {
var valueInPixels;
if (typeof(styleValue) === 'string') {
valueInPixels = parseInt(styleValue, 10);
if (styleValue.indexOf('%') !== -1) {
// percentage * size in dimension
valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
}
} else {
valueInPixels = styleValue;
}
return valueInPixels;
}
/**
* Returns if the given value contains an effective constraint.
* @private
*/
function isConstrainedValue(value) {
return value !== undefined && value !== null && value !== 'none';
}
// Private helper to get a constraint dimension
// @param domNode : the node to check the constraint on
// @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight)
// @param percentageProperty : property of parent to use when calculating width as a percentage
// @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser
function getConstraintDimension(domNode, maxStyle, percentageProperty) {
var view = document.defaultView;
var parentNode = domNode.parentNode;
var constrainedNode = view.getComputedStyle(domNode)[maxStyle];
var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle];
var hasCNode = isConstrainedValue(constrainedNode);
var hasCContainer = isConstrainedValue(constrainedContainer);
var infinity = Number.POSITIVE_INFINITY;
if (hasCNode || hasCContainer) {
return Math.min(
hasCNode? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity,
hasCContainer? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity);
}
return 'none';
}
// returns Number or undefined if no constraint
helpers.getConstraintWidth = function(domNode) {
return getConstraintDimension(domNode, 'max-width', 'clientWidth');
};
// returns Number or undefined if no constraint
helpers.getConstraintHeight = function(domNode) {
return getConstraintDimension(domNode, 'max-height', 'clientHeight');
};
helpers.getMaximumWidth = function(domNode) {
var container = domNode.parentNode;
var paddingLeft = parseInt(helpers.getStyle(container, 'padding-left'), 10);
var paddingRight = parseInt(helpers.getStyle(container, 'padding-right'), 10);
var w = container.clientWidth - paddingLeft - paddingRight;
var cw = helpers.getConstraintWidth(domNode);
return isNaN(cw)? w : Math.min(w, cw);
};
helpers.getMaximumHeight = function(domNode) {
var container = domNode.parentNode;
var paddingTop = parseInt(helpers.getStyle(container, 'padding-top'), 10);
var paddingBottom = parseInt(helpers.getStyle(container, 'padding-bottom'), 10);
var h = container.clientHeight - paddingTop - paddingBottom;
var ch = helpers.getConstraintHeight(domNode);
return isNaN(ch)? h : Math.min(h, ch);
};
helpers.getStyle = function(el, property) {
return el.currentStyle ?
el.currentStyle[property] :
document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
};
helpers.retinaScale = function(chart) {
var ctx = chart.ctx;
var canvas = chart.canvas;
var width = canvas.width;
var height = canvas.height;
var pixelRatio = chart.currentDevicePixelRatio = window.devicePixelRatio || 1;
if (pixelRatio !== 1) {
canvas.height = height * pixelRatio;
canvas.width = width * pixelRatio;
ctx.scale(pixelRatio, pixelRatio);
// Store the device pixel ratio so that we can go backwards in `destroy`.
// The devicePixelRatio changes with zoom, so there are no guarantees that it is the same
// when destroy is called
chart.originalDevicePixelRatio = chart.originalDevicePixelRatio || pixelRatio;
}
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
};
// -- Canvas methods
helpers.clear = function(chart) {
chart.ctx.clearRect(0, 0, chart.width, chart.height);
};
helpers.fontString = function(pixelSize, fontStyle, fontFamily) {
return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
};
helpers.longestText = function(ctx, font, arrayOfThings, cache) {
cache = cache || {};
var data = cache.data = cache.data || {};
var gc = cache.garbageCollect = cache.garbageCollect || [];
if (cache.font !== font) {
data = cache.data = {};
gc = cache.garbageCollect = [];
cache.font = font;
}
ctx.font = font;
var longest = 0;
helpers.each(arrayOfThings, function(thing) {
// Undefined strings and arrays should not be measured
if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) {
longest = helpers.measureText(ctx, data, gc, longest, thing);
} else if (helpers.isArray(thing)) {
// if it is an array lets measure each element
// to do maybe simplify this function a bit so we can do this more recursively?
helpers.each(thing, function(nestedThing) {
// Undefined strings and arrays should not be measured
if (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) {
longest = helpers.measureText(ctx, data, gc, longest, nestedThing);
}
});
}
});
var gcLen = gc.length / 2;
if (gcLen > arrayOfThings.length) {
for (var i = 0; i < gcLen; i++) {
delete data[gc[i]];
}
gc.splice(0, gcLen);
}
return longest;
};
helpers.measureText = function(ctx, data, gc, longest, string) {
var textWidth = data[string];
if (!textWidth) {
textWidth = data[string] = ctx.measureText(string).width;
gc.push(string);
}
if (textWidth > longest) {
longest = textWidth;
}
return longest;
};
helpers.numberOfLabelLines = function(arrayOfThings) {
var numberOfLines = 1;
helpers.each(arrayOfThings, function(thing) {
if (helpers.isArray(thing)) {
if (thing.length > numberOfLines) {
numberOfLines = thing.length;
}
}
});
return numberOfLines;
};
helpers.drawRoundedRectangle = function(ctx, x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
};
helpers.color = function(c) {
if (!color) {
console.error('Color.js not found!');
return c;
}
/* global CanvasGradient */
if (c instanceof CanvasGradient) {
return color(Chart.defaults.global.defaultColor);
}
return color(c);
};
helpers.addResizeListener = function(node, callback) {
// Hide an iframe before the node
var hiddenIframe = document.createElement('iframe');
var hiddenIframeClass = 'chartjs-hidden-iframe';
if (hiddenIframe.classlist) {
// can use classlist
hiddenIframe.classlist.add(hiddenIframeClass);
} else {
hiddenIframe.setAttribute('class', hiddenIframeClass);
}
// Set the style
hiddenIframe.tabIndex = -1;
var style = hiddenIframe.style;
style.width = '100%';
style.display = 'block';
style.border = 0;
style.height = 0;
style.margin = 0;
style.position = 'absolute';
style.left = 0;
style.right = 0;
style.top = 0;
style.bottom = 0;
// Insert the iframe so that contentWindow is available
node.insertBefore(hiddenIframe, node.firstChild);
(hiddenIframe.contentWindow || hiddenIframe).onresize = function() {
if (callback) {
return callback();
}
};
};
helpers.removeResizeListener = function(node) {
var hiddenIframe = node.querySelector('.chartjs-hidden-iframe');
// Remove the resize detect iframe
if (hiddenIframe) {
hiddenIframe.parentNode.removeChild(hiddenIframe);
}
};
helpers.isArray = Array.isArray?
function(obj) {
return Array.isArray(obj);
} :
function(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
};
// ! @see http://stackoverflow.com/a/14853974
helpers.arrayEquals = function(a0, a1) {
var i, ilen, v0, v1;
if (!a0 || !a1 || a0.length !== a1.length) {
return false;
}
for (i = 0, ilen=a0.length; i < ilen; ++i) {
v0 = a0[i];
v1 = a1[i];
if (v0 instanceof Array && v1 instanceof Array) {
if (!helpers.arrayEquals(v0, v1)) {
return false;
}
} else if (v0 !== v1) {
// NOTE: two different object instances will never be equal: {x:20} != {x:20}
return false;
}
}
return true;
};
helpers.callCallback = function(fn, args, _tArg) {
if (fn && typeof fn.call === 'function') {
fn.apply(_tArg, args);
}
};
helpers.getHoverColor = function(colorValue) {
/* global CanvasPattern */
return (colorValue instanceof CanvasPattern) ?
colorValue :
helpers.color(colorValue).saturate(0.5).darken(0.1).rgbString();
};
};
},{"3":3}],27:[function(require,module,exports){
'use strict';
module.exports = function() {
// Occupy the global variable of Chart, and create a simple base class
var Chart = function(context, config) {
var me = this;
var helpers = Chart.helpers;
me.config = config || {
data: {
datasets: []
}
};
// Support a jQuery'd canvas element
if (context.length && context[0].getContext) {
context = context[0];
}
// Support a canvas domnode
if (context.getContext) {
context = context.getContext('2d');
}
me.ctx = context;
me.canvas = context.canvas;
context.canvas.style.display = context.canvas.style.display || 'block';
// Figure out what the size of the chart will be.
// If the canvas has a specified width and height, we use those else
// we look to see if the canvas node has a CSS width and height.
// If there is still no height, fill the parent container
me.width = context.canvas.width || parseInt(helpers.getStyle(context.canvas, 'width'), 10) || helpers.getMaximumWidth(context.canvas);
me.height = context.canvas.height || parseInt(helpers.getStyle(context.canvas, 'height'), 10) || helpers.getMaximumHeight(context.canvas);
me.aspectRatio = me.width / me.height;
if (isNaN(me.aspectRatio) || isFinite(me.aspectRatio) === false) {
// If the canvas has no size, try and figure out what the aspect ratio will be.
// Some charts prefer square canvases (pie, radar, etc). If that is specified, use that
// else use the canvas default ratio of 2
me.aspectRatio = config.aspectRatio !== undefined ? config.aspectRatio : 2;
}
// Store the original style of the element so we can set it back
me.originalCanvasStyleWidth = context.canvas.style.width;
me.originalCanvasStyleHeight = context.canvas.style.height;
// High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
helpers.retinaScale(me);
me.controller = new Chart.Controller(me);
// Always bind this so that if the responsive state changes we still work
helpers.addResizeListener(context.canvas.parentNode, function() {
if (me.controller && me.controller.config.options.responsive) {
me.controller.resize();
}
});
return me.controller ? me.controller : me;
};
// Globally expose the defaults to allow for user updating/changing
Chart.defaults = {
global: {
responsive: true,
responsiveAnimationDuration: 0,
maintainAspectRatio: true,
events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
hover: {
onHover: null,
mode: 'single',
animationDuration: 400
},
onClick: null,
defaultColor: 'rgba(0,0,0,0.1)',
defaultFontColor: '#666',
defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
defaultFontSize: 12,
defaultFontStyle: 'normal',
showLines: true,
// Element defaults defined in element extensions
elements: {},
// Legend callback string
legendCallback: function(chart) {
var text = [];
text.push('<ul class="' + chart.id + '-legend">');
for (var i = 0; i < chart.data.datasets.length; i++) {
text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '"></span>');
if (chart.data.datasets[i].label) {
text.push(chart.data.datasets[i].label);
}
text.push('</li>');
}
text.push('</ul>');
return text.join('');
}
}
};
Chart.Chart = Chart;
return Chart;
};
},{}],28:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers;
// The layout service is very self explanatory. It's responsible for the layout within a chart.
// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need
// It is this service's responsibility of carrying out that layout.
Chart.layoutService = {
defaults: {},
// Register a box to a chartInstance. A box is simply a reference to an object that requires layout. eg. Scales, Legend, Plugins.
addBox: function(chartInstance, box) {
if (!chartInstance.boxes) {
chartInstance.boxes = [];
}
chartInstance.boxes.push(box);
},
removeBox: function(chartInstance, box) {
if (!chartInstance.boxes) {
return;
}
chartInstance.boxes.splice(chartInstance.boxes.indexOf(box), 1);
},
// The most important function
update: function(chartInstance, width, height) {
if (!chartInstance) {
return;
}
var xPadding = 0;
var yPadding = 0;
var leftBoxes = helpers.where(chartInstance.boxes, function(box) {
return box.options.position === 'left';
});
var rightBoxes = helpers.where(chartInstance.boxes, function(box) {
return box.options.position === 'right';
});
var topBoxes = helpers.where(chartInstance.boxes, function(box) {
return box.options.position === 'top';
});
var bottomBoxes = helpers.where(chartInstance.boxes, function(box) {
return box.options.position === 'bottom';
});
// Boxes that overlay the chartarea such as the radialLinear scale
var chartAreaBoxes = helpers.where(chartInstance.boxes, function(box) {
return box.options.position === 'chartArea';
});
// Ensure that full width boxes are at the very top / bottom
topBoxes.sort(function(a, b) {
return (b.options.fullWidth ? 1 : 0) - (a.options.fullWidth ? 1 : 0);
});
bottomBoxes.sort(function(a, b) {
return (a.options.fullWidth ? 1 : 0) - (b.options.fullWidth ? 1 : 0);
});
// Essentially we now have any number of boxes on each of the 4 sides.
// Our canvas looks like the following.
// The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
// B1 is the bottom axis
// There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
// These locations are single-box locations only, when trying to register a chartArea location that is already taken,
// an error will be thrown.
//
// |----------------------------------------------------|
// | T1 (Full Width) |
// |----------------------------------------------------|
// | | | T2 | |
// | |----|-------------------------------------|----|
// | | | C1 | | C2 | |
// | | |----| |----| |
// | | | | |
// | L1 | L2 | ChartArea (C0) | R1 |
// | | | | |
// | | |----| |----| |
// | | | C3 | | C4 | |
// | |----|-------------------------------------|----|
// | | | B1 | |
// |----------------------------------------------------|
// | B2 (Full Width) |
// |----------------------------------------------------|
//
// What we do to find the best sizing, we do the following
// 1. Determine the minimum size of the chart area.
// 2. Split the remaining width equally between each vertical axis
// 3. Split the remaining height equally between each horizontal axis
// 4. Give each layout the maximum size it can be. The layout will return it's minimum size
// 5. Adjust the sizes of each axis based on it's minimum reported size.
// 6. Refit each axis
// 7. Position each axis in the final location
// 8. Tell the chart the final location of the chart area
// 9. Tell any axes that overlay the chart area the positions of the chart area
// Step 1
var chartWidth = width - (2 * xPadding);
var chartHeight = height - (2 * yPadding);
var chartAreaWidth = chartWidth / 2; // min 50%
var chartAreaHeight = chartHeight / 2; // min 50%
// Step 2
var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length);
// Step 3
var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length);
// Step 4
var maxChartAreaWidth = chartWidth;
var maxChartAreaHeight = chartHeight;
var minBoxSizes = [];
function getMinimumBoxSize(box) {
var minSize;
var isHorizontal = box.isHorizontal();
if (isHorizontal) {
minSize = box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight);
maxChartAreaHeight -= minSize.height;
} else {
minSize = box.update(verticalBoxWidth, chartAreaHeight);
maxChartAreaWidth -= minSize.width;
}
minBoxSizes.push({
horizontal: isHorizontal,
minSize: minSize,
box: box
});
}
helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize);
// At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could
// be if the axes are drawn at their minimum sizes.
// Steps 5 & 6
var totalLeftBoxesWidth = xPadding;
var totalRightBoxesWidth = xPadding;
var totalTopBoxesHeight = yPadding;
var totalBottomBoxesHeight = yPadding;
// Function to fit a box
function fitBox(box) {
var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBox) {
return minBox.box === box;
});
if (minBoxSize) {
if (box.isHorizontal()) {
var scaleMargin = {
left: totalLeftBoxesWidth,
right: totalRightBoxesWidth,
top: 0,
bottom: 0
};
// Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends
// on the margin. Sometimes they need to increase in size slightly
box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin);
} else {
box.update(minBoxSize.minSize.width, maxChartAreaHeight);
}
}
}
// Update, and calculate the left and right margins for the horizontal boxes
helpers.each(leftBoxes.concat(rightBoxes), fitBox);
helpers.each(leftBoxes, function(box) {
totalLeftBoxesWidth += box.width;
});
helpers.each(rightBoxes, function(box) {
totalRightBoxesWidth += box.width;
});
// Set the Left and Right margins for the horizontal boxes
helpers.each(topBoxes.concat(bottomBoxes), fitBox);
// Figure out how much margin is on the top and bottom of the vertical boxes
helpers.each(topBoxes, function(box) {
totalTopBoxesHeight += box.height;
});
helpers.each(bottomBoxes, function(box) {
totalBottomBoxesHeight += box.height;
});
function finalFitVerticalBox(box) {
var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minSize) {
return minSize.box === box;
});
var scaleMargin = {
left: 0,
right: 0,
top: totalTopBoxesHeight,
bottom: totalBottomBoxesHeight
};
if (minBoxSize) {
box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin);
}
}
// Let the left layout know the final margin
helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox);
// Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)
totalLeftBoxesWidth = xPadding;
totalRightBoxesWidth = xPadding;
totalTopBoxesHeight = yPadding;
totalBottomBoxesHeight = yPadding;
helpers.each(leftBoxes, function(box) {
totalLeftBoxesWidth += box.width;
});
helpers.each(rightBoxes, function(box) {
totalRightBoxesWidth += box.width;
});
helpers.each(topBoxes, function(box) {
totalTopBoxesHeight += box.height;
});
helpers.each(bottomBoxes, function(box) {
totalBottomBoxesHeight += box.height;
});
// Figure out if our chart area changed. This would occur if the dataset layout label rotation
// changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
// without calling `fit` again
var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight;
var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth;
if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) {
helpers.each(leftBoxes, function(box) {
box.height = newMaxChartAreaHeight;
});
helpers.each(rightBoxes, function(box) {
box.height = newMaxChartAreaHeight;
});
helpers.each(topBoxes, function(box) {
if (!box.options.fullWidth) {
box.width = newMaxChartAreaWidth;
}
});
helpers.each(bottomBoxes, function(box) {
if (!box.options.fullWidth) {
box.width = newMaxChartAreaWidth;
}
});
maxChartAreaHeight = newMaxChartAreaHeight;
maxChartAreaWidth = newMaxChartAreaWidth;
}
// Step 7 - Position the boxes
var left = xPadding;
var top = yPadding;
function placeBox(box) {
if (box.isHorizontal()) {
box.left = box.options.fullWidth ? xPadding : totalLeftBoxesWidth;
box.right = box.options.fullWidth ? width - xPadding : totalLeftBoxesWidth + maxChartAreaWidth;
box.top = top;
box.bottom = top + box.height;
// Move to next point
top = box.bottom;
} else {
box.left = left;
box.right = left + box.width;
box.top = totalTopBoxesHeight;
box.bottom = totalTopBoxesHeight + maxChartAreaHeight;
// Move to next point
left = box.right;
}
}
helpers.each(leftBoxes.concat(topBoxes), placeBox);
// Account for chart width and height
left += maxChartAreaWidth;
top += maxChartAreaHeight;
helpers.each(rightBoxes, placeBox);
helpers.each(bottomBoxes, placeBox);
// Step 8
chartInstance.chartArea = {
left: totalLeftBoxesWidth,
top: totalTopBoxesHeight,
right: totalLeftBoxesWidth + maxChartAreaWidth,
bottom: totalTopBoxesHeight + maxChartAreaHeight
};
// Step 9
helpers.each(chartAreaBoxes, function(box) {
box.left = chartInstance.chartArea.left;
box.top = chartInstance.chartArea.top;
box.right = chartInstance.chartArea.right;
box.bottom = chartInstance.chartArea.bottom;
box.update(maxChartAreaWidth, maxChartAreaHeight);
});
}
};
};
},{}],29:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers;
var noop = helpers.noop;
Chart.defaults.global.legend = {
display: true,
position: 'top',
fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes)
reverse: false,
// a callback that will handle
onClick: function(e, legendItem) {
var index = legendItem.datasetIndex;
var ci = this.chart;
var meta = ci.getDatasetMeta(index);
// See controller.isDatasetVisible comment
meta.hidden = meta.hidden === null? !ci.data.datasets[index].hidden : null;
// We hid a dataset ... rerender the chart
ci.update();
},
onHover: null,
labels: {
boxWidth: 40,
padding: 10,
// Generates labels shown in the legend
// Valid properties to return:
// text : text to display
// fillStyle : fill of coloured box
// strokeStyle: stroke of coloured box
// hidden : if this legend item refers to a hidden item
// lineCap : cap style for line
// lineDash
// lineDashOffset :
// lineJoin :
// lineWidth :
generateLabels: function(chart) {
var data = chart.data;
return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) {
return {
text: dataset.label,
fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]),
hidden: !chart.isDatasetVisible(i),
lineCap: dataset.borderCapStyle,
lineDash: dataset.borderDash,
lineDashOffset: dataset.borderDashOffset,
lineJoin: dataset.borderJoinStyle,
lineWidth: dataset.borderWidth,
strokeStyle: dataset.borderColor,
pointStyle: dataset.pointStyle,
// Below is extra data used for toggling the datasets
datasetIndex: i
};
}, this) : [];
}
}
};
Chart.Legend = Chart.Element.extend({
initialize: function(config) {
helpers.extend(this, config);
// Contains hit boxes for each dataset (in dataset order)
this.legendHitBoxes = [];
// Are we in doughnut mode which has a different data type
this.doughnutMode = false;
},
// These methods are ordered by lifecyle. Utilities then follow.
// Any function defined here is inherited by all legend types.
// Any function can be extended by the legend type
beforeUpdate: noop,
update: function(maxWidth, maxHeight, margins) {
var me = this;
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
me.beforeUpdate();
// Absorb the master measurements
me.maxWidth = maxWidth;
me.maxHeight = maxHeight;
me.margins = margins;
// Dimensions
me.beforeSetDimensions();
me.setDimensions();
me.afterSetDimensions();
// Labels
me.beforeBuildLabels();
me.buildLabels();
me.afterBuildLabels();
// Fit
me.beforeFit();
me.fit();
me.afterFit();
//
me.afterUpdate();
return me.minSize;
},
afterUpdate: noop,
//
beforeSetDimensions: noop,
setDimensions: function() {
var me = this;
// Set the unconstrained dimension before label rotation
if (me.isHorizontal()) {
// Reset position before calculating rotation
me.width = me.maxWidth;
me.left = 0;
me.right = me.width;
} else {
me.height = me.maxHeight;
// Reset position before calculating rotation
me.top = 0;
me.bottom = me.height;
}
// Reset padding
me.paddingLeft = 0;
me.paddingTop = 0;
me.paddingRight = 0;
me.paddingBottom = 0;
// Reset minSize
me.minSize = {
width: 0,
height: 0
};
},
afterSetDimensions: noop,
//
beforeBuildLabels: noop,
buildLabels: function() {
var me = this;
me.legendItems = me.options.labels.generateLabels.call(me, me.chart);
if (me.options.reverse) {
me.legendItems.reverse();
}
},
afterBuildLabels: noop,
//
beforeFit: noop,
fit: function() {
var me = this;
var opts = me.options;
var labelOpts = opts.labels;
var display = opts.display;
var ctx = me.ctx;
var globalDefault = Chart.defaults.global,
itemOrDefault = helpers.getValueOrDefault,
fontSize = itemOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize),
fontStyle = itemOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle),
fontFamily = itemOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily),
labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
// Reset hit boxes
var hitboxes = me.legendHitBoxes = [];
var minSize = me.minSize;
var isHorizontal = me.isHorizontal();
if (isHorizontal) {
minSize.width = me.maxWidth; // fill all the width
minSize.height = display ? 10 : 0;
} else {
minSize.width = display ? 10 : 0;
minSize.height = me.maxHeight; // fill all the height
}
// Increase sizes here
if (display) {
ctx.font = labelFont;
if (isHorizontal) {
// Labels
// Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
var lineWidths = me.lineWidths = [0];
var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0;
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
helpers.each(me.legendItems, function(legendItem, i) {
var boxWidth = labelOpts.usePointStyle ?
fontSize * Math.sqrt(2) :
labelOpts.boxWidth;
var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) {
totalHeight += fontSize + (labelOpts.padding);
lineWidths[lineWidths.length] = me.left;
}
// Store the hitbox width and height here. Final position will be updated in `draw`
hitboxes[i] = {
left: 0,
top: 0,
width: width,
height: fontSize
};
lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
});
minSize.height += totalHeight;
} else {
var vPadding = labelOpts.padding;
var columnWidths = me.columnWidths = [];
var totalWidth = labelOpts.padding;
var currentColWidth = 0;
var currentColHeight = 0;
var itemHeight = fontSize + vPadding;
helpers.each(me.legendItems, function(legendItem, i) {
// If usePointStyle is set, multiple boxWidth by 2 since it represents
// the radius and not truly the width
var boxWidth = labelOpts.usePointStyle ? 2 * labelOpts.boxWidth : labelOpts.boxWidth;
var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
// If too tall, go to new column
if (currentColHeight + itemHeight > minSize.height) {
totalWidth += currentColWidth + labelOpts.padding;
columnWidths.push(currentColWidth); // previous column width
currentColWidth = 0;
currentColHeight = 0;
}
// Get max width
currentColWidth = Math.max(currentColWidth, itemWidth);
currentColHeight += itemHeight;
// Store the hitbox width and height here. Final position will be updated in `draw`
hitboxes[i] = {
left: 0,
top: 0,
width: itemWidth,
height: fontSize
};
});
totalWidth += currentColWidth;
columnWidths.push(currentColWidth);
minSize.width += totalWidth;
}
}
me.width = minSize.width;
me.height = minSize.height;
},
afterFit: noop,
// Shared Methods
isHorizontal: function() {
return this.options.position === 'top' || this.options.position === 'bottom';
},
// Actualy draw the legend on the canvas
draw: function() {
var me = this;
var opts = me.options;
var labelOpts = opts.labels;
var globalDefault = Chart.defaults.global,
lineDefault = globalDefault.elements.line,
legendWidth = me.width,
lineWidths = me.lineWidths;
if (opts.display) {
var ctx = me.ctx,
cursor,
itemOrDefault = helpers.getValueOrDefault,
fontColor = itemOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor),
fontSize = itemOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize),
fontStyle = itemOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle),
fontFamily = itemOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily),
labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
// Canvas setup
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.lineWidth = 0.5;
ctx.strokeStyle = fontColor; // for strikethrough effect
ctx.fillStyle = fontColor; // render in correct colour
ctx.font = labelFont;
var boxWidth = labelOpts.boxWidth,
hitboxes = me.legendHitBoxes;
// current position
var drawLegendBox = function(x, y, legendItem) {
if (isNaN(boxWidth) || boxWidth <= 0) {
return;
}
// Set the ctx for the box
ctx.save();
ctx.fillStyle = itemOrDefault(legendItem.fillStyle, globalDefault.defaultColor);
ctx.lineCap = itemOrDefault(legendItem.lineCap, lineDefault.borderCapStyle);
ctx.lineDashOffset = itemOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset);
ctx.lineJoin = itemOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle);
ctx.lineWidth = itemOrDefault(legendItem.lineWidth, lineDefault.borderWidth);
ctx.strokeStyle = itemOrDefault(legendItem.strokeStyle, globalDefault.defaultColor);
var isLineWidthZero = (itemOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0);
if (ctx.setLineDash) {
// IE 9 and 10 do not support line dash
ctx.setLineDash(itemOrDefault(legendItem.lineDash, lineDefault.borderDash));
}
if (opts.labels && opts.labels.usePointStyle) {
// Recalulate x and y for drawPoint() because its expecting
// x and y to be center of figure (instead of top left)
var radius = fontSize * Math.SQRT2 / 2;
var offSet = radius / Math.SQRT2;
var centerX = x + offSet;
var centerY = y + offSet;
// Draw pointStyle as legend symbol
Chart.canvasHelpers.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
} else {
// Draw box as legend symbol
if (!isLineWidthZero) {
ctx.strokeRect(x, y, boxWidth, fontSize);
}
ctx.fillRect(x, y, boxWidth, fontSize);
}
ctx.restore();
};
var fillText = function(x, y, legendItem, textWidth) {
ctx.fillText(legendItem.text, boxWidth + (fontSize / 2) + x, y);
if (legendItem.hidden) {
// Strikethrough the text if hidden
ctx.beginPath();
ctx.lineWidth = 2;
ctx.moveTo(boxWidth + (fontSize / 2) + x, y + (fontSize / 2));
ctx.lineTo(boxWidth + (fontSize / 2) + x + textWidth, y + (fontSize / 2));
ctx.stroke();
}
};
// Horizontal
var isHorizontal = me.isHorizontal();
if (isHorizontal) {
cursor = {
x: me.left + ((legendWidth - lineWidths[0]) / 2),
y: me.top + labelOpts.padding,
line: 0
};
} else {
cursor = {
x: me.left + labelOpts.padding,
y: me.top + labelOpts.padding,
line: 0
};
}
var itemHeight = fontSize + labelOpts.padding;
helpers.each(me.legendItems, function(legendItem, i) {
var textWidth = ctx.measureText(legendItem.text).width,
width = labelOpts.usePointStyle ?
fontSize + (fontSize / 2) + textWidth :
boxWidth + (fontSize / 2) + textWidth,
x = cursor.x,
y = cursor.y;
if (isHorizontal) {
if (x + width >= legendWidth) {
y = cursor.y += itemHeight;
cursor.line++;
x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2);
}
} else if (y + itemHeight > me.bottom) {
x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
y = cursor.y = me.top;
cursor.line++;
}
drawLegendBox(x, y, legendItem);
hitboxes[i].left = x;
hitboxes[i].top = y;
// Fill the actual label
fillText(x, y, legendItem, textWidth);
if (isHorizontal) {
cursor.x += width + (labelOpts.padding);
} else {
cursor.y += itemHeight;
}
});
}
},
// Handle an event
handleEvent: function(e) {
var me = this;
var opts = me.options;
var type = e.type === 'mouseup' ? 'click' : e.type;
if (type === 'mousemove') {
if (!opts.onHover) {
return;
}
} else if (type === 'click') {
if (!opts.onClick) {
return;
}
} else {
return;
}
var position = helpers.getRelativePosition(e, me.chart.chart),
x = position.x,
y = position.y;
if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
// See if we are touching one of the dataset boxes
var lh = me.legendHitBoxes;
for (var i = 0; i < lh.length; ++i) {
var hitBox = lh[i];
if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
// Touching an element
if (type === 'click') {
opts.onClick.call(me, e, me.legendItems[i]);
break;
} else if (type === 'mousemove') {
opts.onHover.call(me, e, me.legendItems[i]);
break;
}
}
}
}
}
});
// Register the legend plugin
Chart.plugins.register({
beforeInit: function(chartInstance) {
var opts = chartInstance.options;
var legendOpts = opts.legend;
if (legendOpts) {
chartInstance.legend = new Chart.Legend({
ctx: chartInstance.chart.ctx,
options: legendOpts,
chart: chartInstance
});
Chart.layoutService.addBox(chartInstance, chartInstance.legend);
}
}
});
};
},{}],30:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var noop = Chart.helpers.noop;
/**
* The plugin service singleton
* @namespace Chart.plugins
* @since 2.1.0
*/
Chart.plugins = {
_plugins: [],
/**
* Registers the given plugin(s) if not already registered.
* @param {Array|Object} plugins plugin instance(s).
*/
register: function(plugins) {
var p = this._plugins;
([]).concat(plugins).forEach(function(plugin) {
if (p.indexOf(plugin) === -1) {
p.push(plugin);
}
});
},
/**
* Unregisters the given plugin(s) only if registered.
* @param {Array|Object} plugins plugin instance(s).
*/
unregister: function(plugins) {
var p = this._plugins;
([]).concat(plugins).forEach(function(plugin) {
var idx = p.indexOf(plugin);
if (idx !== -1) {
p.splice(idx, 1);
}
});
},
/**
* Remove all registered p^lugins.
* @since 2.1.5
*/
clear: function() {
this._plugins = [];
},
/**
* Returns the number of registered plugins?
* @returns {Number}
* @since 2.1.5
*/
count: function() {
return this._plugins.length;
},
/**
* Returns all registered plugin intances.
* @returns {Array} array of plugin objects.
* @since 2.1.5
*/
getAll: function() {
return this._plugins;
},
/**
* Calls registered plugins on the specified extension, with the given args. This
* method immediately returns as soon as a plugin explicitly returns false. The
* returned value can be used, for instance, to interrupt the current action.
* @param {String} extension the name of the plugin method to call (e.g. 'beforeUpdate').
* @param {Array} [args] extra arguments to apply to the extension call.
* @returns {Boolean} false if any of the plugins return false, else returns true.
*/
notify: function(extension, args) {
var plugins = this._plugins;
var ilen = plugins.length;
var i, plugin;
for (i=0; i<ilen; ++i) {
plugin = plugins[i];
if (typeof plugin[extension] === 'function') {
if (plugin[extension].apply(plugin, args || []) === false) {
return false;
}
}
}
return true;
}
};
/**
* Plugin extension methods.
* @interface Chart.PluginBase
* @since 2.1.0
*/
Chart.PluginBase = Chart.Element.extend({
// Called at start of chart init
beforeInit: noop,
// Called at end of chart init
afterInit: noop,
// Called at start of update
beforeUpdate: noop,
// Called at end of update
afterUpdate: noop,
// Called at start of draw
beforeDraw: noop,
// Called at end of draw
afterDraw: noop,
// Called during destroy
destroy: noop
});
/**
* Provided for backward compatibility, use Chart.plugins instead
* @namespace Chart.pluginService
* @deprecated since version 2.1.5
* @todo remove me at version 3
*/
Chart.pluginService = Chart.plugins;
};
},{}],31:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers;
Chart.defaults.scale = {
display: true,
position: 'left',
// grid line settings
gridLines: {
display: true,
color: 'rgba(0, 0, 0, 0.1)',
lineWidth: 1,
drawBorder: true,
drawOnChartArea: true,
drawTicks: true,
tickMarkLength: 10,
zeroLineWidth: 1,
zeroLineColor: 'rgba(0,0,0,0.25)',
offsetGridLines: false,
borderDash: [],
borderDashOffset: 0.0
},
// scale label
scaleLabel: {
// actual label
labelString: '',
// display property
display: false
},
// label settings
ticks: {
beginAtZero: false,
minRotation: 0,
maxRotation: 50,
mirror: false,
padding: 10,
reverse: false,
display: true,
autoSkip: true,
autoSkipPadding: 0,
labelOffset: 0,
// We pass through arrays to be rendered as multiline labels, we convert Others to strings here.
callback: function(value) {
return helpers.isArray(value) ? value : '' + value;
}
}
};
Chart.Scale = Chart.Element.extend({
// These methods are ordered by lifecyle. Utilities then follow.
// Any function defined here is inherited by all scale types.
// Any function can be extended by the scale type
beforeUpdate: function() {
helpers.callCallback(this.options.beforeUpdate, [this]);
},
update: function(maxWidth, maxHeight, margins) {
var me = this;
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
me.beforeUpdate();
// Absorb the master measurements
me.maxWidth = maxWidth;
me.maxHeight = maxHeight;
me.margins = helpers.extend({
left: 0,
right: 0,
top: 0,
bottom: 0
}, margins);
// Dimensions
me.beforeSetDimensions();
me.setDimensions();
me.afterSetDimensions();
// Data min/max
me.beforeDataLimits();
me.determineDataLimits();
me.afterDataLimits();
// Ticks
me.beforeBuildTicks();
me.buildTicks();
me.afterBuildTicks();
me.beforeTickToLabelConversion();
me.convertTicksToLabels();
me.afterTickToLabelConversion();
// Tick Rotation
me.beforeCalculateTickRotation();
me.calculateTickRotation();
me.afterCalculateTickRotation();
// Fit
me.beforeFit();
me.fit();
me.afterFit();
//
me.afterUpdate();
return me.minSize;
},
afterUpdate: function() {
helpers.callCallback(this.options.afterUpdate, [this]);
},
//
beforeSetDimensions: function() {
helpers.callCallback(this.options.beforeSetDimensions, [this]);
},
setDimensions: function() {
var me = this;
// Set the unconstrained dimension before label rotation
if (me.isHorizontal()) {
// Reset position before calculating rotation
me.width = me.maxWidth;
me.left = 0;
me.right = me.width;
} else {
me.height = me.maxHeight;
// Reset position before calculating rotation
me.top = 0;
me.bottom = me.height;
}
// Reset padding
me.paddingLeft = 0;
me.paddingTop = 0;
me.paddingRight = 0;
me.paddingBottom = 0;
},
afterSetDimensions: function() {
helpers.callCallback(this.options.afterSetDimensions, [this]);
},
// Data limits
beforeDataLimits: function() {
helpers.callCallback(this.options.beforeDataLimits, [this]);
},
determineDataLimits: helpers.noop,
afterDataLimits: function() {
helpers.callCallback(this.options.afterDataLimits, [this]);
},
//
beforeBuildTicks: function() {
helpers.callCallback(this.options.beforeBuildTicks, [this]);
},
buildTicks: helpers.noop,
afterBuildTicks: function() {
helpers.callCallback(this.options.afterBuildTicks, [this]);
},
beforeTickToLabelConversion: function() {
helpers.callCallback(this.options.beforeTickToLabelConversion, [this]);
},
convertTicksToLabels: function() {
var me = this;
// Convert ticks to strings
me.ticks = me.ticks.map(function(numericalTick, index, ticks) {
if (me.options.ticks.userCallback) {
return me.options.ticks.userCallback(numericalTick, index, ticks);
}
return me.options.ticks.callback(numericalTick, index, ticks);
},
me);
},
afterTickToLabelConversion: function() {
helpers.callCallback(this.options.afterTickToLabelConversion, [this]);
},
//
beforeCalculateTickRotation: function() {
helpers.callCallback(this.options.beforeCalculateTickRotation, [this]);
},
calculateTickRotation: function() {
var me = this;
var context = me.ctx;
var globalDefaults = Chart.defaults.global;
var optionTicks = me.options.ticks;
// Get the width of each grid by calculating the difference
// between x offsets between 0 and 1.
var tickFontSize = helpers.getValueOrDefault(optionTicks.fontSize, globalDefaults.defaultFontSize);
var tickFontStyle = helpers.getValueOrDefault(optionTicks.fontStyle, globalDefaults.defaultFontStyle);
var tickFontFamily = helpers.getValueOrDefault(optionTicks.fontFamily, globalDefaults.defaultFontFamily);
var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
context.font = tickLabelFont;
var firstWidth = context.measureText(me.ticks[0]).width;
var lastWidth = context.measureText(me.ticks[me.ticks.length - 1]).width;
var firstRotated;
me.labelRotation = optionTicks.minRotation || 0;
me.paddingRight = 0;
me.paddingLeft = 0;
if (me.options.display) {
if (me.isHorizontal()) {
me.paddingRight = lastWidth / 2 + 3;
me.paddingLeft = firstWidth / 2 + 3;
if (!me.longestTextCache) {
me.longestTextCache = {};
}
var originalLabelWidth = helpers.longestText(context, tickLabelFont, me.ticks, me.longestTextCache);
var labelWidth = originalLabelWidth;
var cosRotation;
var sinRotation;
// Allow 3 pixels x2 padding either side for label readability
// only the index matters for a dataset scale, but we want a consistent interface between scales
var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6;
// Max label rotation can be set or default to 90 - also act as a loop counter
while (labelWidth > tickWidth && me.labelRotation < optionTicks.maxRotation) {
cosRotation = Math.cos(helpers.toRadians(me.labelRotation));
sinRotation = Math.sin(helpers.toRadians(me.labelRotation));
firstRotated = cosRotation * firstWidth;
// We're right aligning the text now.
if (firstRotated + tickFontSize / 2 > me.yLabelWidth) {
me.paddingLeft = firstRotated + tickFontSize / 2;
}
me.paddingRight = tickFontSize / 2;
if (sinRotation * originalLabelWidth > me.maxHeight) {
// go back one step
me.labelRotation--;
break;
}
me.labelRotation++;
labelWidth = cosRotation * originalLabelWidth;
}
}
}
if (me.margins) {
me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0);
me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0);
}
},
afterCalculateTickRotation: function() {
helpers.callCallback(this.options.afterCalculateTickRotation, [this]);
},
//
beforeFit: function() {
helpers.callCallback(this.options.beforeFit, [this]);
},
fit: function() {
var me = this;
// Reset
var minSize = me.minSize = {
width: 0,
height: 0
};
var opts = me.options;
var globalDefaults = Chart.defaults.global;
var tickOpts = opts.ticks;
var scaleLabelOpts = opts.scaleLabel;
var gridLineOpts = opts.gridLines;
var display = opts.display;
var isHorizontal = me.isHorizontal();
var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
var tickFontStyle = helpers.getValueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle);
var tickFontFamily = helpers.getValueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily);
var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
var scaleLabelFontSize = helpers.getValueOrDefault(scaleLabelOpts.fontSize, globalDefaults.defaultFontSize);
var tickMarkLength = opts.gridLines.tickMarkLength;
// Width
if (isHorizontal) {
// subtract the margins to line up with the chartArea if we are a full width scale
minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth;
} else {
minSize.width = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
}
// height
if (isHorizontal) {
minSize.height = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
} else {
minSize.height = me.maxHeight; // fill all the height
}
// Are we showing a title for the scale?
if (scaleLabelOpts.display && display) {
if (isHorizontal) {
minSize.height += (scaleLabelFontSize * 1.5);
} else {
minSize.width += (scaleLabelFontSize * 1.5);
}
}
if (tickOpts.display && display) {
// Don't bother fitting the ticks if we are not showing them
if (!me.longestTextCache) {
me.longestTextCache = {};
}
var largestTextWidth = helpers.longestText(me.ctx, tickLabelFont, me.ticks, me.longestTextCache);
var tallestLabelHeightInLines = helpers.numberOfLabelLines(me.ticks);
var lineSpace = tickFontSize * 0.5;
if (isHorizontal) {
// A horizontal axis is more constrained by the height.
me.longestLabelWidth = largestTextWidth;
// TODO - improve this calculation
var labelHeight = (Math.sin(helpers.toRadians(me.labelRotation)) * me.longestLabelWidth) + (tickFontSize * tallestLabelHeightInLines) + (lineSpace * tallestLabelHeightInLines);
minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight);
me.ctx.font = tickLabelFont;
var firstLabelWidth = me.ctx.measureText(me.ticks[0]).width;
var lastLabelWidth = me.ctx.measureText(me.ticks[me.ticks.length - 1]).width;
// Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned which means that the right padding is dominated
// by the font height
var cosRotation = Math.cos(helpers.toRadians(me.labelRotation));
var sinRotation = Math.sin(helpers.toRadians(me.labelRotation));
me.paddingLeft = me.labelRotation !== 0 ? (cosRotation * firstLabelWidth) + 3 : firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges
me.paddingRight = me.labelRotation !== 0 ? (sinRotation * (tickFontSize / 2)) + 3 : lastLabelWidth / 2 + 3; // when rotated
} else {
// A vertical axis is more constrained by the width. Labels are the dominant factor here, so get that length first
var maxLabelWidth = me.maxWidth - minSize.width;
// Account for padding
var mirror = tickOpts.mirror;
if (!mirror) {
largestTextWidth += me.options.ticks.padding;
} else {
// If mirrored text is on the inside so don't expand
largestTextWidth = 0;
}
if (largestTextWidth < maxLabelWidth) {
// We don't need all the room
minSize.width += largestTextWidth;
} else {
// Expand to max size
minSize.width = me.maxWidth;
}
me.paddingTop = tickFontSize / 2;
me.paddingBottom = tickFontSize / 2;
}
}
if (me.margins) {
me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0);
me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0);
me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0);
me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0);
}
me.width = minSize.width;
me.height = minSize.height;
},
afterFit: function() {
helpers.callCallback(this.options.afterFit, [this]);
},
// Shared Methods
isHorizontal: function() {
return this.options.position === 'top' || this.options.position === 'bottom';
},
isFullWidth: function() {
return (this.options.fullWidth);
},
// Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not
getRightValue: function(rawValue) {
// Null and undefined values first
if (rawValue === null || typeof(rawValue) === 'undefined') {
return NaN;
}
// isNaN(object) returns true, so make sure NaN is checking for a number
if (typeof(rawValue) === 'number' && isNaN(rawValue)) {
return NaN;
}
// If it is in fact an object, dive in one more level
if (typeof(rawValue) === 'object') {
if ((rawValue instanceof Date) || (rawValue.isValid)) {
return rawValue;
}
return this.getRightValue(this.isHorizontal() ? rawValue.x : rawValue.y);
}
// Value is good, return it
return rawValue;
},
// Used to get the value to display in the tooltip for the data at the given index
// function getLabelForIndex(index, datasetIndex)
getLabelForIndex: helpers.noop,
// Used to get data value locations. Value can either be an index or a numerical value
getPixelForValue: helpers.noop,
// Used to get the data value from a given pixel. This is the inverse of getPixelForValue
getValueForPixel: helpers.noop,
// Used for tick location, should
getPixelForTick: function(index, includeOffset) {
var me = this;
if (me.isHorizontal()) {
var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
var tickWidth = innerWidth / Math.max((me.ticks.length - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
var pixel = (tickWidth * index) + me.paddingLeft;
if (includeOffset) {
pixel += tickWidth / 2;
}
var finalVal = me.left + Math.round(pixel);
finalVal += me.isFullWidth() ? me.margins.left : 0;
return finalVal;
}
var innerHeight = me.height - (me.paddingTop + me.paddingBottom);
return me.top + (index * (innerHeight / (me.ticks.length - 1)));
},
// Utility for getting the pixel location of a percentage of scale
getPixelForDecimal: function(decimal /* , includeOffset*/) {
var me = this;
if (me.isHorizontal()) {
var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
var valueOffset = (innerWidth * decimal) + me.paddingLeft;
var finalVal = me.left + Math.round(valueOffset);
finalVal += me.isFullWidth() ? me.margins.left : 0;
return finalVal;
}
return me.top + (decimal * me.height);
},
getBasePixel: function() {
var me = this;
var min = me.min;
var max = me.max;
return me.getPixelForValue(
me.beginAtZero? 0:
min < 0 && max < 0? max :
min > 0 && max > 0? min :
0);
},
// Actualy draw the scale on the canvas
// @param {rectangle} chartArea : the area of the chart to draw full grid lines on
draw: function(chartArea) {
var me = this;
var options = me.options;
if (!options.display) {
return;
}
var context = me.ctx;
var globalDefaults = Chart.defaults.global;
var optionTicks = options.ticks;
var gridLines = options.gridLines;
var scaleLabel = options.scaleLabel;
var isRotated = me.labelRotation !== 0;
var skipRatio;
var useAutoskipper = optionTicks.autoSkip;
var isHorizontal = me.isHorizontal();
// figure out the maximum number of gridlines to show
var maxTicks;
if (optionTicks.maxTicksLimit) {
maxTicks = optionTicks.maxTicksLimit;
}
var tickFontColor = helpers.getValueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor);
var tickFontSize = helpers.getValueOrDefault(optionTicks.fontSize, globalDefaults.defaultFontSize);
var tickFontStyle = helpers.getValueOrDefault(optionTicks.fontStyle, globalDefaults.defaultFontStyle);
var tickFontFamily = helpers.getValueOrDefault(optionTicks.fontFamily, globalDefaults.defaultFontFamily);
var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
var tl = gridLines.tickMarkLength;
var borderDash = helpers.getValueOrDefault(gridLines.borderDash, globalDefaults.borderDash);
var borderDashOffset = helpers.getValueOrDefault(gridLines.borderDashOffset, globalDefaults.borderDashOffset);
var scaleLabelFontColor = helpers.getValueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor);
var scaleLabelFontSize = helpers.getValueOrDefault(scaleLabel.fontSize, globalDefaults.defaultFontSize);
var scaleLabelFontStyle = helpers.getValueOrDefault(scaleLabel.fontStyle, globalDefaults.defaultFontStyle);
var scaleLabelFontFamily = helpers.getValueOrDefault(scaleLabel.fontFamily, globalDefaults.defaultFontFamily);
var scaleLabelFont = helpers.fontString(scaleLabelFontSize, scaleLabelFontStyle, scaleLabelFontFamily);
var labelRotationRadians = helpers.toRadians(me.labelRotation);
var cosRotation = Math.cos(labelRotationRadians);
var longestRotatedLabel = me.longestLabelWidth * cosRotation;
// Make sure we draw text in the correct color and font
context.fillStyle = tickFontColor;
var itemsToDraw = [];
if (isHorizontal) {
skipRatio = false;
// Only calculate the skip ratio with the half width of longestRotateLabel if we got an actual rotation
// See #2584
if (isRotated) {
longestRotatedLabel /= 2;
}
if ((longestRotatedLabel + optionTicks.autoSkipPadding) * me.ticks.length > (me.width - (me.paddingLeft + me.paddingRight))) {
skipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * me.ticks.length) / (me.width - (me.paddingLeft + me.paddingRight)));
}
// if they defined a max number of optionTicks,
// increase skipRatio until that number is met
if (maxTicks && me.ticks.length > maxTicks) {
while (!skipRatio || me.ticks.length / (skipRatio || 1) > maxTicks) {
if (!skipRatio) {
skipRatio = 1;
}
skipRatio += 1;
}
}
if (!useAutoskipper) {
skipRatio = false;
}
}
var xTickStart = options.position === 'right' ? me.left : me.right - tl;
var xTickEnd = options.position === 'right' ? me.left + tl : me.right;
var yTickStart = options.position === 'bottom' ? me.top : me.bottom - tl;
var yTickEnd = options.position === 'bottom' ? me.top + tl : me.bottom;
helpers.each(me.ticks, function(label, index) {
// If the callback returned a null or undefined value, do not draw this line
if (label === undefined || label === null) {
return;
}
var isLastTick = me.ticks.length === index + 1;
// Since we always show the last tick,we need may need to hide the last shown one before
var shouldSkip = (skipRatio > 1 && index % skipRatio > 0) || (index % skipRatio === 0 && index + skipRatio >= me.ticks.length);
if (shouldSkip && !isLastTick || (label === undefined || label === null)) {
return;
}
var lineWidth, lineColor;
if (index === (typeof me.zeroLineIndex !== 'undefined' ? me.zeroLineIndex : 0)) {
// Draw the first index specially
lineWidth = gridLines.zeroLineWidth;
lineColor = gridLines.zeroLineColor;
} else {
lineWidth = helpers.getValueAtIndexOrDefault(gridLines.lineWidth, index);
lineColor = helpers.getValueAtIndexOrDefault(gridLines.color, index);
}
// Common properties
var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY;
var textAlign = 'middle';
var textBaseline = 'middle';
if (isHorizontal) {
if (!isRotated) {
textBaseline = options.position === 'top' ? 'bottom' : 'top';
}
textAlign = isRotated ? 'right' : 'center';
var xLineValue = me.getPixelForTick(index) + helpers.aliasPixel(lineWidth); // xvalues for grid lines
labelX = me.getPixelForTick(index, gridLines.offsetGridLines) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option)
labelY = (isRotated) ? me.top + 12 : options.position === 'top' ? me.bottom - tl : me.top + tl;
tx1 = tx2 = x1 = x2 = xLineValue;
ty1 = yTickStart;
ty2 = yTickEnd;
y1 = chartArea.top;
y2 = chartArea.bottom;
} else {
if (options.position === 'left') {
if (optionTicks.mirror) {
labelX = me.right + optionTicks.padding;
textAlign = 'left';
} else {
labelX = me.right - optionTicks.padding;
textAlign = 'right';
}
// right side
} else if (optionTicks.mirror) {
labelX = me.left - optionTicks.padding;
textAlign = 'right';
} else {
labelX = me.left + optionTicks.padding;
textAlign = 'left';
}
var yLineValue = me.getPixelForTick(index); // xvalues for grid lines
yLineValue += helpers.aliasPixel(lineWidth);
labelY = me.getPixelForTick(index, gridLines.offsetGridLines);
tx1 = xTickStart;
tx2 = xTickEnd;
x1 = chartArea.left;
x2 = chartArea.right;
ty1 = ty2 = y1 = y2 = yLineValue;
}
itemsToDraw.push({
tx1: tx1,
ty1: ty1,
tx2: tx2,
ty2: ty2,
x1: x1,
y1: y1,
x2: x2,
y2: y2,
labelX: labelX,
labelY: labelY,
glWidth: lineWidth,
glColor: lineColor,
glBorderDash: borderDash,
glBorderDashOffset: borderDashOffset,
rotation: -1 * labelRotationRadians,
label: label,
textBaseline: textBaseline,
textAlign: textAlign
});
});
// Draw all of the tick labels, tick marks, and grid lines at the correct places
helpers.each(itemsToDraw, function(itemToDraw) {
if (gridLines.display) {
context.save();
context.lineWidth = itemToDraw.glWidth;
context.strokeStyle = itemToDraw.glColor;
if (context.setLineDash) {
context.setLineDash(itemToDraw.glBorderDash);
context.lineDashOffset = itemToDraw.glBorderDashOffset;
}
context.beginPath();
if (gridLines.drawTicks) {
context.moveTo(itemToDraw.tx1, itemToDraw.ty1);
context.lineTo(itemToDraw.tx2, itemToDraw.ty2);
}
if (gridLines.drawOnChartArea) {
context.moveTo(itemToDraw.x1, itemToDraw.y1);
context.lineTo(itemToDraw.x2, itemToDraw.y2);
}
context.stroke();
context.restore();
}
if (optionTicks.display) {
context.save();
context.translate(itemToDraw.labelX, itemToDraw.labelY);
context.rotate(itemToDraw.rotation);
context.font = tickLabelFont;
context.textBaseline = itemToDraw.textBaseline;
context.textAlign = itemToDraw.textAlign;
var label = itemToDraw.label;
if (helpers.isArray(label)) {
for (var i = 0, y = -(label.length - 1)*tickFontSize*0.75; i < label.length; ++i) {
// We just make sure the multiline element is a string here..
context.fillText('' + label[i], 0, y);
// apply same lineSpacing as calculated @ L#320
y += (tickFontSize * 1.5);
}
} else {
context.fillText(label, 0, 0);
}
context.restore();
}
});
if (scaleLabel.display) {
// Draw the scale label
var scaleLabelX;
var scaleLabelY;
var rotation = 0;
if (isHorizontal) {
scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width
scaleLabelY = options.position === 'bottom' ? me.bottom - (scaleLabelFontSize / 2) : me.top + (scaleLabelFontSize / 2);
} else {
var isLeft = options.position === 'left';
scaleLabelX = isLeft ? me.left + (scaleLabelFontSize / 2) : me.right - (scaleLabelFontSize / 2);
scaleLabelY = me.top + ((me.bottom - me.top) / 2);
rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI;
}
context.save();
context.translate(scaleLabelX, scaleLabelY);
context.rotate(rotation);
context.textAlign = 'center';
context.textBaseline = 'middle';
context.fillStyle = scaleLabelFontColor; // render in correct colour
context.font = scaleLabelFont;
context.fillText(scaleLabel.labelString, 0, 0);
context.restore();
}
if (gridLines.drawBorder) {
// Draw the line at the edge of the axis
context.lineWidth = helpers.getValueAtIndexOrDefault(gridLines.lineWidth, 0);
context.strokeStyle = helpers.getValueAtIndexOrDefault(gridLines.color, 0);
var x1 = me.left,
x2 = me.right,
y1 = me.top,
y2 = me.bottom;
var aliasPixel = helpers.aliasPixel(context.lineWidth);
if (isHorizontal) {
y1 = y2 = options.position === 'top' ? me.bottom : me.top;
y1 += aliasPixel;
y2 += aliasPixel;
} else {
x1 = x2 = options.position === 'left' ? me.right : me.left;
x1 += aliasPixel;
x2 += aliasPixel;
}
context.beginPath();
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.stroke();
}
}
});
};
},{}],32:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers;
Chart.scaleService = {
// Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
// use the new chart options to grab the correct scale
constructors: {},
// Use a registration function so that we can move to an ES6 map when we no longer need to support
// old browsers
// Scale config defaults
defaults: {},
registerScaleType: function(type, scaleConstructor, defaults) {
this.constructors[type] = scaleConstructor;
this.defaults[type] = helpers.clone(defaults);
},
getScaleConstructor: function(type) {
return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined;
},
getScaleDefaults: function(type) {
// Return the scale defaults merged with the global settings so that we always use the latest ones
return this.defaults.hasOwnProperty(type) ? helpers.scaleMerge(Chart.defaults.scale, this.defaults[type]) : {};
},
updateScaleDefaults: function(type, additions) {
var defaults = this.defaults;
if (defaults.hasOwnProperty(type)) {
defaults[type] = helpers.extend(defaults[type], additions);
}
},
addScalesToLayout: function(chartInstance) {
// Adds each scale to the chart.boxes array to be sized accordingly
helpers.each(chartInstance.scales, function(scale) {
Chart.layoutService.addBox(chartInstance, scale);
});
}
};
};
},{}],33:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers;
Chart.defaults.global.title = {
display: false,
position: 'top',
fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes)
fontStyle: 'bold',
padding: 10,
// actual title
text: ''
};
var noop = helpers.noop;
Chart.Title = Chart.Element.extend({
initialize: function(config) {
var me = this;
helpers.extend(me, config);
me.options = helpers.configMerge(Chart.defaults.global.title, config.options);
// Contains hit boxes for each dataset (in dataset order)
me.legendHitBoxes = [];
},
// These methods are ordered by lifecyle. Utilities then follow.
beforeUpdate: function() {
var chartOpts = this.chart.options;
if (chartOpts && chartOpts.title) {
this.options = helpers.configMerge(Chart.defaults.global.title, chartOpts.title);
}
},
update: function(maxWidth, maxHeight, margins) {
var me = this;
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
me.beforeUpdate();
// Absorb the master measurements
me.maxWidth = maxWidth;
me.maxHeight = maxHeight;
me.margins = margins;
// Dimensions
me.beforeSetDimensions();
me.setDimensions();
me.afterSetDimensions();
// Labels
me.beforeBuildLabels();
me.buildLabels();
me.afterBuildLabels();
// Fit
me.beforeFit();
me.fit();
me.afterFit();
//
me.afterUpdate();
return me.minSize;
},
afterUpdate: noop,
//
beforeSetDimensions: noop,
setDimensions: function() {
var me = this;
// Set the unconstrained dimension before label rotation
if (me.isHorizontal()) {
// Reset position before calculating rotation
me.width = me.maxWidth;
me.left = 0;
me.right = me.width;
} else {
me.height = me.maxHeight;
// Reset position before calculating rotation
me.top = 0;
me.bottom = me.height;
}
// Reset padding
me.paddingLeft = 0;
me.paddingTop = 0;
me.paddingRight = 0;
me.paddingBottom = 0;
// Reset minSize
me.minSize = {
width: 0,
height: 0
};
},
afterSetDimensions: noop,
//
beforeBuildLabels: noop,
buildLabels: noop,
afterBuildLabels: noop,
//
beforeFit: noop,
fit: function() {
var me = this,
valueOrDefault = helpers.getValueOrDefault,
opts = me.options,
globalDefaults = Chart.defaults.global,
display = opts.display,
fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize),
minSize = me.minSize;
if (me.isHorizontal()) {
minSize.width = me.maxWidth; // fill all the width
minSize.height = display ? fontSize + (opts.padding * 2) : 0;
} else {
minSize.width = display ? fontSize + (opts.padding * 2) : 0;
minSize.height = me.maxHeight; // fill all the height
}
me.width = minSize.width;
me.height = minSize.height;
},
afterFit: noop,
// Shared Methods
isHorizontal: function() {
var pos = this.options.position;
return pos === 'top' || pos === 'bottom';
},
// Actualy draw the title block on the canvas
draw: function() {
var me = this,
ctx = me.ctx,
valueOrDefault = helpers.getValueOrDefault,
opts = me.options,
globalDefaults = Chart.defaults.global;
if (opts.display) {
var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize),
fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle),
fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily),
titleFont = helpers.fontString(fontSize, fontStyle, fontFamily),
rotation = 0,
titleX,
titleY,
top = me.top,
left = me.left,
bottom = me.bottom,
right = me.right;
ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour
ctx.font = titleFont;
// Horizontal
if (me.isHorizontal()) {
titleX = left + ((right - left) / 2); // midpoint of the width
titleY = top + ((bottom - top) / 2); // midpoint of the height
} else {
titleX = opts.position === 'left' ? left + (fontSize / 2) : right - (fontSize / 2);
titleY = top + ((bottom - top) / 2);
rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);
}
ctx.save();
ctx.translate(titleX, titleY);
ctx.rotate(rotation);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(opts.text, 0, 0);
ctx.restore();
}
}
});
// Register the title plugin
Chart.plugins.register({
beforeInit: function(chartInstance) {
var opts = chartInstance.options;
var titleOpts = opts.title;
if (titleOpts) {
chartInstance.titleBlock = new Chart.Title({
ctx: chartInstance.chart.ctx,
options: titleOpts,
chart: chartInstance
});
Chart.layoutService.addBox(chartInstance, chartInstance.titleBlock);
}
}
});
};
},{}],34:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers;
Chart.defaults.global.tooltips = {
enabled: true,
custom: null,
mode: 'single',
backgroundColor: 'rgba(0,0,0,0.8)',
titleFontStyle: 'bold',
titleSpacing: 2,
titleMarginBottom: 6,
titleFontColor: '#fff',
titleAlign: 'left',
bodySpacing: 2,
bodyFontColor: '#fff',
bodyAlign: 'left',
footerFontStyle: 'bold',
footerSpacing: 2,
footerMarginTop: 6,
footerFontColor: '#fff',
footerAlign: 'left',
yPadding: 6,
xPadding: 6,
yAlign: 'center',
xAlign: 'center',
caretSize: 5,
cornerRadius: 6,
multiKeyBackground: '#fff',
callbacks: {
// Args are: (tooltipItems, data)
beforeTitle: helpers.noop,
title: function(tooltipItems, data) {
// Pick first xLabel for now
var title = '';
var labels = data.labels;
var labelCount = labels ? labels.length : 0;
if (tooltipItems.length > 0) {
var item = tooltipItems[0];
if (item.xLabel) {
title = item.xLabel;
} else if (labelCount > 0 && item.index < labelCount) {
title = labels[item.index];
}
}
return title;
},
afterTitle: helpers.noop,
// Args are: (tooltipItems, data)
beforeBody: helpers.noop,
// Args are: (tooltipItem, data)
beforeLabel: helpers.noop,
label: function(tooltipItem, data) {
var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || '';
return datasetLabel + ': ' + tooltipItem.yLabel;
},
labelColor: function(tooltipItem, chartInstance) {
var meta = chartInstance.getDatasetMeta(tooltipItem.datasetIndex);
var activeElement = meta.data[tooltipItem.index];
var view = activeElement._view;
return {
borderColor: view.borderColor,
backgroundColor: view.backgroundColor
};
},
afterLabel: helpers.noop,
// Args are: (tooltipItems, data)
afterBody: helpers.noop,
// Args are: (tooltipItems, data)
beforeFooter: helpers.noop,
footer: helpers.noop,
afterFooter: helpers.noop
}
};
// Helper to push or concat based on if the 2nd parameter is an array or not
function pushOrConcat(base, toPush) {
if (toPush) {
if (helpers.isArray(toPush)) {
// base = base.concat(toPush);
Array.prototype.push.apply(base, toPush);
} else {
base.push(toPush);
}
}
return base;
}
function getAveragePosition(elements) {
if (!elements.length) {
return false;
}
var i, len;
var xPositions = [];
var yPositions = [];
for (i = 0, len = elements.length; i < len; ++i) {
var el = elements[i];
if (el && el.hasValue()) {
var pos = el.tooltipPosition();
xPositions.push(pos.x);
yPositions.push(pos.y);
}
}
var x = 0,
y = 0;
for (i = 0; i < xPositions.length; ++i) {
if (xPositions[i]) {
x += xPositions[i];
y += yPositions[i];
}
}
return {
x: Math.round(x / xPositions.length),
y: Math.round(y / xPositions.length)
};
}
// Private helper to create a tooltip iteam model
// @param element : the chart element (point, arc, bar) to create the tooltip item for
// @return : new tooltip item
function createTooltipItem(element) {
var xScale = element._xScale;
var yScale = element._yScale || element._scale; // handle radar || polarArea charts
var index = element._index,
datasetIndex = element._datasetIndex;
return {
xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '',
yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '',
index: index,
datasetIndex: datasetIndex
};
}
Chart.Tooltip = Chart.Element.extend({
initialize: function() {
var me = this;
var globalDefaults = Chart.defaults.global;
var tooltipOpts = me._options;
var getValueOrDefault = helpers.getValueOrDefault;
helpers.extend(me, {
_model: {
// Positioning
xPadding: tooltipOpts.xPadding,
yPadding: tooltipOpts.yPadding,
xAlign: tooltipOpts.xAlign,
yAlign: tooltipOpts.yAlign,
// Body
bodyFontColor: tooltipOpts.bodyFontColor,
_bodyFontFamily: getValueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily),
_bodyFontStyle: getValueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle),
_bodyAlign: tooltipOpts.bodyAlign,
bodyFontSize: getValueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize),
bodySpacing: tooltipOpts.bodySpacing,
// Title
titleFontColor: tooltipOpts.titleFontColor,
_titleFontFamily: getValueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily),
_titleFontStyle: getValueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle),
titleFontSize: getValueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize),
_titleAlign: tooltipOpts.titleAlign,
titleSpacing: tooltipOpts.titleSpacing,
titleMarginBottom: tooltipOpts.titleMarginBottom,
// Footer
footerFontColor: tooltipOpts.footerFontColor,
_footerFontFamily: getValueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily),
_footerFontStyle: getValueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle),
footerFontSize: getValueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize),
_footerAlign: tooltipOpts.footerAlign,
footerSpacing: tooltipOpts.footerSpacing,
footerMarginTop: tooltipOpts.footerMarginTop,
// Appearance
caretSize: tooltipOpts.caretSize,
cornerRadius: tooltipOpts.cornerRadius,
backgroundColor: tooltipOpts.backgroundColor,
opacity: 0,
legendColorBackground: tooltipOpts.multiKeyBackground
}
});
},
// Get the title
// Args are: (tooltipItem, data)
getTitle: function() {
var me = this;
var opts = me._options;
var callbacks = opts.callbacks;
var beforeTitle = callbacks.beforeTitle.apply(me, arguments),
title = callbacks.title.apply(me, arguments),
afterTitle = callbacks.afterTitle.apply(me, arguments);
var lines = [];
lines = pushOrConcat(lines, beforeTitle);
lines = pushOrConcat(lines, title);
lines = pushOrConcat(lines, afterTitle);
return lines;
},
// Args are: (tooltipItem, data)
getBeforeBody: function() {
var lines = this._options.callbacks.beforeBody.apply(this, arguments);
return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
},
// Args are: (tooltipItem, data)
getBody: function(tooltipItems, data) {
var me = this;
var callbacks = me._options.callbacks;
var bodyItems = [];
helpers.each(tooltipItems, function(tooltipItem) {
var bodyItem = {
before: [],
lines: [],
after: []
};
pushOrConcat(bodyItem.before, callbacks.beforeLabel.call(me, tooltipItem, data));
pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data));
pushOrConcat(bodyItem.after, callbacks.afterLabel.call(me, tooltipItem, data));
bodyItems.push(bodyItem);
});
return bodyItems;
},
// Args are: (tooltipItem, data)
getAfterBody: function() {
var lines = this._options.callbacks.afterBody.apply(this, arguments);
return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
},
// Get the footer and beforeFooter and afterFooter lines
// Args are: (tooltipItem, data)
getFooter: function() {
var me = this;
var callbacks = me._options.callbacks;
var beforeFooter = callbacks.beforeFooter.apply(me, arguments);
var footer = callbacks.footer.apply(me, arguments);
var afterFooter = callbacks.afterFooter.apply(me, arguments);
var lines = [];
lines = pushOrConcat(lines, beforeFooter);
lines = pushOrConcat(lines, footer);
lines = pushOrConcat(lines, afterFooter);
return lines;
},
update: function(changed) {
var me = this;
var opts = me._options;
var model = me._model;
var active = me._active;
var data = me._data;
var chartInstance = me._chartInstance;
var i, len;
if (active.length) {
model.opacity = 1;
var labelColors = [],
tooltipPosition = getAveragePosition(active);
var tooltipItems = [];
for (i = 0, len = active.length; i < len; ++i) {
tooltipItems.push(createTooltipItem(active[i]));
}
// If the user provided a sorting function, use it to modify the tooltip items
if (opts.itemSort) {
tooltipItems = tooltipItems.sort(function(a, b) {
return opts.itemSort(a, b, data);
});
}
// If there is more than one item, show color items
if (active.length > 1) {
helpers.each(tooltipItems, function(tooltipItem) {
labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, chartInstance));
});
}
// Build the Text Lines
helpers.extend(model, {
title: me.getTitle(tooltipItems, data),
beforeBody: me.getBeforeBody(tooltipItems, data),
body: me.getBody(tooltipItems, data),
afterBody: me.getAfterBody(tooltipItems, data),
footer: me.getFooter(tooltipItems, data),
x: Math.round(tooltipPosition.x),
y: Math.round(tooltipPosition.y),
caretPadding: helpers.getValueOrDefault(tooltipPosition.padding, 2),
labelColors: labelColors
});
// We need to determine alignment of
var tooltipSize = me.getTooltipSize(model);
me.determineAlignment(tooltipSize); // Smart Tooltip placement to stay on the canvas
helpers.extend(model, me.getBackgroundPoint(model, tooltipSize));
} else {
me._model.opacity = 0;
}
if (changed && opts.custom) {
opts.custom.call(me, model);
}
return me;
},
getTooltipSize: function(vm) {
var ctx = this._chart.ctx;
var size = {
height: vm.yPadding * 2, // Tooltip Padding
width: 0
};
// Count of all lines in the body
var body = vm.body;
var combinedBodyLength = body.reduce(function(count, bodyItem) {
return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length;
}, 0);
combinedBodyLength += vm.beforeBody.length + vm.afterBody.length;
var titleLineCount = vm.title.length;
var footerLineCount = vm.footer.length;
var titleFontSize = vm.titleFontSize,
bodyFontSize = vm.bodyFontSize,
footerFontSize = vm.footerFontSize;
size.height += titleLineCount * titleFontSize; // Title Lines
size.height += (titleLineCount - 1) * vm.titleSpacing; // Title Line Spacing
size.height += titleLineCount ? vm.titleMarginBottom : 0; // Title's bottom Margin
size.height += combinedBodyLength * bodyFontSize; // Body Lines
size.height += combinedBodyLength ? (combinedBodyLength - 1) * vm.bodySpacing : 0; // Body Line Spacing
size.height += footerLineCount ? vm.footerMarginTop : 0; // Footer Margin
size.height += footerLineCount * (footerFontSize); // Footer Lines
size.height += footerLineCount ? (footerLineCount - 1) * vm.footerSpacing : 0; // Footer Line Spacing
// Title width
var widthPadding = 0;
var maxLineWidth = function(line) {
size.width = Math.max(size.width, ctx.measureText(line).width + widthPadding);
};
ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
helpers.each(vm.title, maxLineWidth);
// Body width
ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
helpers.each(vm.beforeBody.concat(vm.afterBody), maxLineWidth);
// Body lines may include some extra width due to the color box
widthPadding = body.length > 1 ? (bodyFontSize + 2) : 0;
helpers.each(body, function(bodyItem) {
helpers.each(bodyItem.before, maxLineWidth);
helpers.each(bodyItem.lines, maxLineWidth);
helpers.each(bodyItem.after, maxLineWidth);
});
// Reset back to 0
widthPadding = 0;
// Footer width
ctx.font = helpers.fontString(footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
helpers.each(vm.footer, maxLineWidth);
// Add padding
size.width += 2 * vm.xPadding;
return size;
},
determineAlignment: function(size) {
var me = this;
var model = me._model;
var chart = me._chart;
var chartArea = me._chartInstance.chartArea;
if (model.y < size.height) {
model.yAlign = 'top';
} else if (model.y > (chart.height - size.height)) {
model.yAlign = 'bottom';
}
var lf, rf; // functions to determine left, right alignment
var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart
var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges
var midX = (chartArea.left + chartArea.right) / 2;
var midY = (chartArea.top + chartArea.bottom) / 2;
if (model.yAlign === 'center') {
lf = function(x) {
return x <= midX;
};
rf = function(x) {
return x > midX;
};
} else {
lf = function(x) {
return x <= (size.width / 2);
};
rf = function(x) {
return x >= (chart.width - (size.width / 2));
};
}
olf = function(x) {
return x + size.width > chart.width;
};
orf = function(x) {
return x - size.width < 0;
};
yf = function(y) {
return y <= midY ? 'top' : 'bottom';
};
if (lf(model.x)) {
model.xAlign = 'left';
// Is tooltip too wide and goes over the right side of the chart.?
if (olf(model.x)) {
model.xAlign = 'center';
model.yAlign = yf(model.y);
}
} else if (rf(model.x)) {
model.xAlign = 'right';
// Is tooltip too wide and goes outside left edge of canvas?
if (orf(model.x)) {
model.xAlign = 'center';
model.yAlign = yf(model.y);
}
}
},
getBackgroundPoint: function(vm, size) {
// Background Position
var pt = {
x: vm.x,
y: vm.y
};
var caretSize = vm.caretSize,
caretPadding = vm.caretPadding,
cornerRadius = vm.cornerRadius,
xAlign = vm.xAlign,
yAlign = vm.yAlign,
paddingAndSize = caretSize + caretPadding,
radiusAndPadding = cornerRadius + caretPadding;
if (xAlign === 'right') {
pt.x -= size.width;
} else if (xAlign === 'center') {
pt.x -= (size.width / 2);
}
if (yAlign === 'top') {
pt.y += paddingAndSize;
} else if (yAlign === 'bottom') {
pt.y -= size.height + paddingAndSize;
} else {
pt.y -= (size.height / 2);
}
if (yAlign === 'center') {
if (xAlign === 'left') {
pt.x += paddingAndSize;
} else if (xAlign === 'right') {
pt.x -= paddingAndSize;
}
} else if (xAlign === 'left') {
pt.x -= radiusAndPadding;
} else if (xAlign === 'right') {
pt.x += radiusAndPadding;
}
return pt;
},
drawCaret: function(tooltipPoint, size, opacity) {
var vm = this._view;
var ctx = this._chart.ctx;
var x1, x2, x3;
var y1, y2, y3;
var caretSize = vm.caretSize;
var cornerRadius = vm.cornerRadius;
var xAlign = vm.xAlign,
yAlign = vm.yAlign;
var ptX = tooltipPoint.x,
ptY = tooltipPoint.y;
var width = size.width,
height = size.height;
if (yAlign === 'center') {
// Left or right side
if (xAlign === 'left') {
x1 = ptX;
x2 = x1 - caretSize;
x3 = x1;
} else {
x1 = ptX + width;
x2 = x1 + caretSize;
x3 = x1;
}
y2 = ptY + (height / 2);
y1 = y2 - caretSize;
y3 = y2 + caretSize;
} else {
if (xAlign === 'left') {
x1 = ptX + cornerRadius;
x2 = x1 + caretSize;
x3 = x2 + caretSize;
} else if (xAlign === 'right') {
x1 = ptX + width - cornerRadius;
x2 = x1 - caretSize;
x3 = x2 - caretSize;
} else {
x2 = ptX + (width / 2);
x1 = x2 - caretSize;
x3 = x2 + caretSize;
}
if (yAlign === 'top') {
y1 = ptY;
y2 = y1 - caretSize;
y3 = y1;
} else {
y1 = ptY + height;
y2 = y1 + caretSize;
y3 = y1;
}
}
var bgColor = helpers.color(vm.backgroundColor);
ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString();
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.lineTo(x3, y3);
ctx.closePath();
ctx.fill();
},
drawTitle: function(pt, vm, ctx, opacity) {
var title = vm.title;
if (title.length) {
ctx.textAlign = vm._titleAlign;
ctx.textBaseline = 'top';
var titleFontSize = vm.titleFontSize,
titleSpacing = vm.titleSpacing;
var titleFontColor = helpers.color(vm.titleFontColor);
ctx.fillStyle = titleFontColor.alpha(opacity * titleFontColor.alpha()).rgbString();
ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
var i, len;
for (i = 0, len = title.length; i < len; ++i) {
ctx.fillText(title[i], pt.x, pt.y);
pt.y += titleFontSize + titleSpacing; // Line Height and spacing
if (i + 1 === title.length) {
pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing
}
}
}
},
drawBody: function(pt, vm, ctx, opacity) {
var bodyFontSize = vm.bodyFontSize;
var bodySpacing = vm.bodySpacing;
var body = vm.body;
ctx.textAlign = vm._bodyAlign;
ctx.textBaseline = 'top';
var bodyFontColor = helpers.color(vm.bodyFontColor);
var textColor = bodyFontColor.alpha(opacity * bodyFontColor.alpha()).rgbString();
ctx.fillStyle = textColor;
ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
// Before Body
var xLinePadding = 0;
var fillLineOfText = function(line) {
ctx.fillText(line, pt.x + xLinePadding, pt.y);
pt.y += bodyFontSize + bodySpacing;
};
// Before body lines
helpers.each(vm.beforeBody, fillLineOfText);
var drawColorBoxes = body.length > 1;
xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0;
// Draw body lines now
helpers.each(body, function(bodyItem, i) {
helpers.each(bodyItem.before, fillLineOfText);
helpers.each(bodyItem.lines, function(line) {
// Draw Legend-like boxes if needed
if (drawColorBoxes) {
// Fill a white rect so that colours merge nicely if the opacity is < 1
ctx.fillStyle = helpers.color(vm.legendColorBackground).alpha(opacity).rgbaString();
ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
// Border
ctx.strokeStyle = helpers.color(vm.labelColors[i].borderColor).alpha(opacity).rgbaString();
ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
// Inner square
ctx.fillStyle = helpers.color(vm.labelColors[i].backgroundColor).alpha(opacity).rgbaString();
ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
ctx.fillStyle = textColor;
}
fillLineOfText(line);
});
helpers.each(bodyItem.after, fillLineOfText);
});
// Reset back to 0 for after body
xLinePadding = 0;
// After body lines
helpers.each(vm.afterBody, fillLineOfText);
pt.y -= bodySpacing; // Remove last body spacing
},
drawFooter: function(pt, vm, ctx, opacity) {
var footer = vm.footer;
if (footer.length) {
pt.y += vm.footerMarginTop;
ctx.textAlign = vm._footerAlign;
ctx.textBaseline = 'top';
var footerFontColor = helpers.color(vm.footerFontColor);
ctx.fillStyle = footerFontColor.alpha(opacity * footerFontColor.alpha()).rgbString();
ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
helpers.each(footer, function(line) {
ctx.fillText(line, pt.x, pt.y);
pt.y += vm.footerFontSize + vm.footerSpacing;
});
}
},
draw: function() {
var ctx = this._chart.ctx;
var vm = this._view;
if (vm.opacity === 0) {
return;
}
var tooltipSize = this.getTooltipSize(vm);
var pt = {
x: vm.x,
y: vm.y
};
// IE11/Edge does not like very small opacities, so snap to 0
var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
if (this._options.enabled) {
// Draw Background
var bgColor = helpers.color(vm.backgroundColor);
ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString();
helpers.drawRoundedRectangle(ctx, pt.x, pt.y, tooltipSize.width, tooltipSize.height, vm.cornerRadius);
ctx.fill();
// Draw Caret
this.drawCaret(pt, tooltipSize, opacity);
// Draw Title, Body, and Footer
pt.x += vm.xPadding;
pt.y += vm.yPadding;
// Titles
this.drawTitle(pt, vm, ctx, opacity);
// Body
this.drawBody(pt, vm, ctx, opacity);
// Footer
this.drawFooter(pt, vm, ctx, opacity);
}
}
});
};
},{}],35:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers,
globalOpts = Chart.defaults.global;
globalOpts.elements.arc = {
backgroundColor: globalOpts.defaultColor,
borderColor: '#fff',
borderWidth: 2
};
Chart.elements.Arc = Chart.Element.extend({
inLabelRange: function(mouseX) {
var vm = this._view;
if (vm) {
return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
}
return false;
},
inRange: function(chartX, chartY) {
var vm = this._view;
if (vm) {
var pointRelativePosition = helpers.getAngleFromPoint(vm, {
x: chartX,
y: chartY
}),
angle = pointRelativePosition.angle,
distance = pointRelativePosition.distance;
// Sanitise angle range
var startAngle = vm.startAngle;
var endAngle = vm.endAngle;
while (endAngle < startAngle) {
endAngle += 2.0 * Math.PI;
}
while (angle > endAngle) {
angle -= 2.0 * Math.PI;
}
while (angle < startAngle) {
angle += 2.0 * Math.PI;
}
// Check if within the range of the open/close angle
var betweenAngles = (angle >= startAngle && angle <= endAngle),
withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius);
return (betweenAngles && withinRadius);
}
return false;
},
tooltipPosition: function() {
var vm = this._view;
var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2),
rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
return {
x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
};
},
draw: function() {
var ctx = this._chart.ctx,
vm = this._view,
sA = vm.startAngle,
eA = vm.endAngle;
ctx.beginPath();
ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA);
ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true);
ctx.closePath();
ctx.strokeStyle = vm.borderColor;
ctx.lineWidth = vm.borderWidth;
ctx.fillStyle = vm.backgroundColor;
ctx.fill();
ctx.lineJoin = 'bevel';
if (vm.borderWidth) {
ctx.stroke();
}
}
});
};
},{}],36:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers;
var globalDefaults = Chart.defaults.global;
Chart.defaults.global.elements.line = {
tension: 0.4,
backgroundColor: globalDefaults.defaultColor,
borderWidth: 3,
borderColor: globalDefaults.defaultColor,
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
capBezierPoints: true,
fill: true // do we fill in the area between the line and its base axis
};
Chart.elements.Line = Chart.Element.extend({
draw: function() {
var me = this;
var vm = me._view;
var spanGaps = vm.spanGaps;
var scaleZero = vm.scaleZero;
var loop = me._loop;
var ctx = me._chart.ctx;
ctx.save();
// Helper function to draw a line to a point
function lineToPoint(previousPoint, point) {
var pointVM = point._view;
if (point._view.steppedLine === true) {
ctx.lineTo(pointVM.x, previousPoint._view.y);
ctx.lineTo(pointVM.x, pointVM.y);
} else if (point._view.tension === 0) {
ctx.lineTo(pointVM.x, pointVM.y);
} else {
ctx.bezierCurveTo(
previousPoint._view.controlPointNextX,
previousPoint._view.controlPointNextY,
pointVM.controlPointPreviousX,
pointVM.controlPointPreviousY,
pointVM.x,
pointVM.y
);
}
}
var points = me._children.slice(); // clone array
var lastDrawnIndex = -1;
// If we are looping, adding the first point again
if (loop && points.length) {
points.push(points[0]);
}
var index, current, previous, currentVM;
// Fill Line
if (points.length && vm.fill) {
ctx.beginPath();
for (index = 0; index < points.length; ++index) {
current = points[index];
previous = helpers.previousItem(points, index);
currentVM = current._view;
// First point moves to it's starting position no matter what
if (index === 0) {
if (loop) {
ctx.moveTo(scaleZero.x, scaleZero.y);
} else {
ctx.moveTo(currentVM.x, scaleZero);
}
if (!currentVM.skip) {
lastDrawnIndex = index;
ctx.lineTo(currentVM.x, currentVM.y);
}
} else {
previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex];
if (currentVM.skip) {
// Only do this if this is the first point that is skipped
if (!spanGaps && lastDrawnIndex === (index - 1)) {
if (loop) {
ctx.lineTo(scaleZero.x, scaleZero.y);
} else {
ctx.lineTo(previous._view.x, scaleZero);
}
}
} else {
if (lastDrawnIndex !== (index - 1)) {
// There was a gap and this is the first point after the gap. If we've never drawn a point, this is a special case.
// If the first data point is NaN, then there is no real gap to skip
if (spanGaps && lastDrawnIndex !== -1) {
// We are spanning the gap, so simple draw a line to this point
lineToPoint(previous, current);
} else if (loop) {
ctx.lineTo(currentVM.x, currentVM.y);
} else {
ctx.lineTo(currentVM.x, scaleZero);
ctx.lineTo(currentVM.x, currentVM.y);
}
} else {
// Line to next point
lineToPoint(previous, current);
}
lastDrawnIndex = index;
}
}
}
if (!loop && lastDrawnIndex !== -1) {
ctx.lineTo(points[lastDrawnIndex]._view.x, scaleZero);
}
ctx.fillStyle = vm.backgroundColor || globalDefaults.defaultColor;
ctx.closePath();
ctx.fill();
}
// Stroke Line Options
var globalOptionLineElements = globalDefaults.elements.line;
ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle;
// IE 9 and 10 do not support line dash
if (ctx.setLineDash) {
ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash);
}
ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset;
ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle;
ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth;
ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor;
// Stroke Line
ctx.beginPath();
lastDrawnIndex = -1;
for (index = 0; index < points.length; ++index) {
current = points[index];
previous = helpers.previousItem(points, index);
currentVM = current._view;
// First point moves to it's starting position no matter what
if (index === 0) {
if (!currentVM.skip) {
ctx.moveTo(currentVM.x, currentVM.y);
lastDrawnIndex = index;
}
} else {
previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex];
if (!currentVM.skip) {
if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) {
// There was a gap and this is the first point after the gap
ctx.moveTo(currentVM.x, currentVM.y);
} else {
// Line to next point
lineToPoint(previous, current);
}
lastDrawnIndex = index;
}
}
}
ctx.stroke();
ctx.restore();
}
});
};
},{}],37:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers,
globalOpts = Chart.defaults.global,
defaultColor = globalOpts.defaultColor;
globalOpts.elements.point = {
radius: 3,
pointStyle: 'circle',
backgroundColor: defaultColor,
borderWidth: 1,
borderColor: defaultColor,
// Hover
hitRadius: 1,
hoverRadius: 4,
hoverBorderWidth: 1
};
Chart.elements.Point = Chart.Element.extend({
inRange: function(mouseX, mouseY) {
var vm = this._view;
return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false;
},
inLabelRange: function(mouseX) {
var vm = this._view;
return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;
},
tooltipPosition: function() {
var vm = this._view;
return {
x: vm.x,
y: vm.y,
padding: vm.radius + vm.borderWidth
};
},
draw: function() {
var vm = this._view;
var ctx = this._chart.ctx;
var pointStyle = vm.pointStyle;
var radius = vm.radius;
var x = vm.x;
var y = vm.y;
if (vm.skip) {
return;
}
ctx.strokeStyle = vm.borderColor || defaultColor;
ctx.lineWidth = helpers.getValueOrDefault(vm.borderWidth, globalOpts.elements.point.borderWidth);
ctx.fillStyle = vm.backgroundColor || defaultColor;
Chart.canvasHelpers.drawPoint(ctx, pointStyle, radius, x, y);
}
});
};
},{}],38:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var globalOpts = Chart.defaults.global;
globalOpts.elements.rectangle = {
backgroundColor: globalOpts.defaultColor,
borderWidth: 0,
borderColor: globalOpts.defaultColor,
borderSkipped: 'bottom'
};
Chart.elements.Rectangle = Chart.Element.extend({
draw: function() {
var ctx = this._chart.ctx;
var vm = this._view;
var halfWidth = vm.width / 2,
leftX = vm.x - halfWidth,
rightX = vm.x + halfWidth,
top = vm.base - (vm.base - vm.y),
halfStroke = vm.borderWidth / 2;
// Canvas doesn't allow us to stroke inside the width so we can
// adjust the sizes to fit if we're setting a stroke on the line
if (vm.borderWidth) {
leftX += halfStroke;
rightX -= halfStroke;
top += halfStroke;
}
ctx.beginPath();
ctx.fillStyle = vm.backgroundColor;
ctx.strokeStyle = vm.borderColor;
ctx.lineWidth = vm.borderWidth;
// Corner points, from bottom-left to bottom-right clockwise
// | 1 2 |
// | 0 3 |
var corners = [
[leftX, vm.base],
[leftX, top],
[rightX, top],
[rightX, vm.base]
];
// Find first (starting) corner with fallback to 'bottom'
var borders = ['bottom', 'left', 'top', 'right'];
var startCorner = borders.indexOf(vm.borderSkipped, 0);
if (startCorner === -1) {
startCorner = 0;
}
function cornerAt(index) {
return corners[(startCorner + index) % 4];
}
// Draw rectangle from 'startCorner'
ctx.moveTo.apply(ctx, cornerAt(0));
for (var i = 1; i < 4; i++) {
ctx.lineTo.apply(ctx, cornerAt(i));
}
ctx.fill();
if (vm.borderWidth) {
ctx.stroke();
}
},
height: function() {
var vm = this._view;
return vm.base - vm.y;
},
inRange: function(mouseX, mouseY) {
var vm = this._view;
return vm ?
(vm.y < vm.base ?
(mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.y && mouseY <= vm.base) :
(mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.base && mouseY <= vm.y)) :
false;
},
inLabelRange: function(mouseX) {
var vm = this._view;
return vm ? (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) : false;
},
tooltipPosition: function() {
var vm = this._view;
return {
x: vm.x,
y: vm.y
};
}
});
};
},{}],39:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers;
// Default config for a category scale
var defaultConfig = {
position: 'bottom'
};
var DatasetScale = Chart.Scale.extend({
/**
* Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use tose
* else fall back to data.labels
* @private
*/
getLabels: function() {
var data = this.chart.data;
return (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels;
},
// Implement this so that
determineDataLimits: function() {
var me = this;
var labels = me.getLabels();
me.minIndex = 0;
me.maxIndex = labels.length - 1;
var findIndex;
if (me.options.ticks.min !== undefined) {
// user specified min value
findIndex = helpers.indexOf(labels, me.options.ticks.min);
me.minIndex = findIndex !== -1 ? findIndex : me.minIndex;
}
if (me.options.ticks.max !== undefined) {
// user specified max value
findIndex = helpers.indexOf(labels, me.options.ticks.max);
me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex;
}
me.min = labels[me.minIndex];
me.max = labels[me.maxIndex];
},
buildTicks: function() {
var me = this;
var labels = me.getLabels();
// If we are viewing some subset of labels, slice the original array
me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1);
},
getLabelForIndex: function(index, datasetIndex) {
var me = this;
var data = me.chart.data;
var isHorizontal = me.isHorizontal();
if ((data.xLabels && isHorizontal) || (data.yLabels && !isHorizontal)) {
return me.getRightValue(data.datasets[datasetIndex].data[index]);
}
return me.ticks[index];
},
// Used to get data value locations. Value can either be an index or a numerical value
getPixelForValue: function(value, index, datasetIndex, includeOffset) {
var me = this;
// 1 is added because we need the length but we have the indexes
var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
if (value !== undefined && isNaN(index)) {
var labels = me.getLabels();
var idx = labels.indexOf(value);
index = idx !== -1 ? idx : index;
}
if (me.isHorizontal()) {
var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
var valueWidth = innerWidth / offsetAmt;
var widthOffset = (valueWidth * (index - me.minIndex)) + me.paddingLeft;
if (me.options.gridLines.offsetGridLines && includeOffset || me.maxIndex === me.minIndex && includeOffset) {
widthOffset += (valueWidth / 2);
}
return me.left + Math.round(widthOffset);
}
var innerHeight = me.height - (me.paddingTop + me.paddingBottom);
var valueHeight = innerHeight / offsetAmt;
var heightOffset = (valueHeight * (index - me.minIndex)) + me.paddingTop;
if (me.options.gridLines.offsetGridLines && includeOffset) {
heightOffset += (valueHeight / 2);
}
return me.top + Math.round(heightOffset);
},
getPixelForTick: function(index, includeOffset) {
return this.getPixelForValue(this.ticks[index], index + this.minIndex, null, includeOffset);
},
getValueForPixel: function(pixel) {
var me = this;
var value;
var offsetAmt = Math.max((me.ticks.length - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
var horz = me.isHorizontal();
var innerDimension = horz ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom);
var valueDimension = innerDimension / offsetAmt;
pixel -= horz ? me.left : me.top;
if (me.options.gridLines.offsetGridLines) {
pixel -= (valueDimension / 2);
}
pixel -= horz ? me.paddingLeft : me.paddingTop;
if (pixel <= 0) {
value = 0;
} else {
value = Math.round(pixel / valueDimension);
}
return value;
},
getBasePixel: function() {
return this.bottom;
}
});
Chart.scaleService.registerScaleType('category', DatasetScale, defaultConfig);
};
},{}],40:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers;
var defaultConfig = {
position: 'left',
ticks: {
callback: function(tickValue, index, ticks) {
// If we have lots of ticks, don't use the ones
var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0];
// If we have a number like 2.5 as the delta, figure out how many decimal places we need
if (Math.abs(delta) > 1) {
if (tickValue !== Math.floor(tickValue)) {
// not an integer
delta = tickValue - Math.floor(tickValue);
}
}
var logDelta = helpers.log10(Math.abs(delta));
var tickString = '';
if (tickValue !== 0) {
var numDecimal = -1 * Math.floor(logDelta);
numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places
tickString = tickValue.toFixed(numDecimal);
} else {
tickString = '0'; // never show decimal places for 0
}
return tickString;
}
}
};
var LinearScale = Chart.LinearScaleBase.extend({
determineDataLimits: function() {
var me = this;
var opts = me.options;
var chart = me.chart;
var data = chart.data;
var datasets = data.datasets;
var isHorizontal = me.isHorizontal();
function IDMatches(meta) {
return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
}
// First Calculate the range
me.min = null;
me.max = null;
if (opts.stacked) {
var valuesPerType = {};
helpers.each(datasets, function(dataset, datasetIndex) {
var meta = chart.getDatasetMeta(datasetIndex);
if (valuesPerType[meta.type] === undefined) {
valuesPerType[meta.type] = {
positiveValues: [],
negativeValues: []
};
}
// Store these per type
var positiveValues = valuesPerType[meta.type].positiveValues;
var negativeValues = valuesPerType[meta.type].negativeValues;
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
helpers.each(dataset.data, function(rawValue, index) {
var value = +me.getRightValue(rawValue);
if (isNaN(value) || meta.data[index].hidden) {
return;
}
positiveValues[index] = positiveValues[index] || 0;
negativeValues[index] = negativeValues[index] || 0;
if (opts.relativePoints) {
positiveValues[index] = 100;
} else if (value < 0) {
negativeValues[index] += value;
} else {
positiveValues[index] += value;
}
});
}
});
helpers.each(valuesPerType, function(valuesForType) {
var values = valuesForType.positiveValues.concat(valuesForType.negativeValues);
var minVal = helpers.min(values);
var maxVal = helpers.max(values);
me.min = me.min === null ? minVal : Math.min(me.min, minVal);
me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
});
} else {
helpers.each(datasets, function(dataset, datasetIndex) {
var meta = chart.getDatasetMeta(datasetIndex);
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
helpers.each(dataset.data, function(rawValue, index) {
var value = +me.getRightValue(rawValue);
if (isNaN(value) || meta.data[index].hidden) {
return;
}
if (me.min === null) {
me.min = value;
} else if (value < me.min) {
me.min = value;
}
if (me.max === null) {
me.max = value;
} else if (value > me.max) {
me.max = value;
}
});
}
});
}
// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
this.handleTickRangeOptions();
},
getTickLimit: function() {
var maxTicks;
var me = this;
var tickOpts = me.options.ticks;
if (me.isHorizontal()) {
maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50));
} else {
// The factor of 2 used to scale the font size has been experimentally determined.
var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, Chart.defaults.global.defaultFontSize);
maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize)));
}
return maxTicks;
},
// Called after the ticks are built. We need
handleDirectionalChanges: function() {
if (!this.isHorizontal()) {
// We are in a vertical orientation. The top value is the highest. So reverse the array
this.ticks.reverse();
}
},
getLabelForIndex: function(index, datasetIndex) {
return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
},
// Utils
getPixelForValue: function(value) {
// This must be called after fit has been run so that
// this.left, this.top, this.right, and this.bottom have been defined
var me = this;
var paddingLeft = me.paddingLeft;
var paddingBottom = me.paddingBottom;
var start = me.start;
var rightValue = +me.getRightValue(value);
var pixel;
var innerDimension;
var range = me.end - start;
if (me.isHorizontal()) {
innerDimension = me.width - (paddingLeft + me.paddingRight);
pixel = me.left + (innerDimension / range * (rightValue - start));
return Math.round(pixel + paddingLeft);
}
innerDimension = me.height - (me.paddingTop + paddingBottom);
pixel = (me.bottom - paddingBottom) - (innerDimension / range * (rightValue - start));
return Math.round(pixel);
},
getValueForPixel: function(pixel) {
var me = this;
var isHorizontal = me.isHorizontal();
var paddingLeft = me.paddingLeft;
var paddingBottom = me.paddingBottom;
var innerDimension = isHorizontal ? me.width - (paddingLeft + me.paddingRight) : me.height - (me.paddingTop + paddingBottom);
var offset = (isHorizontal ? pixel - me.left - paddingLeft : me.bottom - paddingBottom - pixel) / innerDimension;
return me.start + ((me.end - me.start) * offset);
},
getPixelForTick: function(index) {
return this.getPixelForValue(this.ticksAsNumbers[index]);
}
});
Chart.scaleService.registerScaleType('linear', LinearScale, defaultConfig);
};
},{}],41:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers,
noop = helpers.noop;
Chart.LinearScaleBase = Chart.Scale.extend({
handleTickRangeOptions: function() {
var me = this;
var opts = me.options;
var tickOpts = opts.ticks;
// If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
// do nothing since that would make the chart weird. If the user really wants a weird chart
// axis, they can manually override it
if (tickOpts.beginAtZero) {
var minSign = helpers.sign(me.min);
var maxSign = helpers.sign(me.max);
if (minSign < 0 && maxSign < 0) {
// move the top up to 0
me.max = 0;
} else if (minSign > 0 && maxSign > 0) {
// move the botttom down to 0
me.min = 0;
}
}
if (tickOpts.min !== undefined) {
me.min = tickOpts.min;
} else if (tickOpts.suggestedMin !== undefined) {
me.min = Math.min(me.min, tickOpts.suggestedMin);
}
if (tickOpts.max !== undefined) {
me.max = tickOpts.max;
} else if (tickOpts.suggestedMax !== undefined) {
me.max = Math.max(me.max, tickOpts.suggestedMax);
}
if (me.min === me.max) {
me.max++;
if (!tickOpts.beginAtZero) {
me.min--;
}
}
},
getTickLimit: noop,
handleDirectionalChanges: noop,
buildTicks: function() {
var me = this;
var opts = me.options;
var ticks = me.ticks = [];
var tickOpts = opts.ticks;
var getValueOrDefault = helpers.getValueOrDefault;
// Figure out what the max number of ticks we can support it is based on the size of
// the axis area. For now, we say that the minimum tick spacing in pixels must be 50
// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
// the graph
var maxTicks = me.getTickLimit();
// Make sure we always have at least 2 ticks
maxTicks = Math.max(2, maxTicks);
// To get a "nice" value for the tick spacing, we will use the appropriately named
// "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
// for details.
var spacing;
var fixedStepSizeSet = (tickOpts.fixedStepSize && tickOpts.fixedStepSize > 0) || (tickOpts.stepSize && tickOpts.stepSize > 0);
if (fixedStepSizeSet) {
spacing = getValueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize);
} else {
var niceRange = helpers.niceNum(me.max - me.min, false);
spacing = helpers.niceNum(niceRange / (maxTicks - 1), true);
}
var niceMin = Math.floor(me.min / spacing) * spacing;
var niceMax = Math.ceil(me.max / spacing) * spacing;
var numSpaces = (niceMax - niceMin) / spacing;
// If very close to our rounded value, use it.
if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
numSpaces = Math.round(numSpaces);
} else {
numSpaces = Math.ceil(numSpaces);
}
// Put the values into the ticks array
ticks.push(tickOpts.min !== undefined ? tickOpts.min : niceMin);
for (var j = 1; j < numSpaces; ++j) {
ticks.push(niceMin + (j * spacing));
}
ticks.push(tickOpts.max !== undefined ? tickOpts.max : niceMax);
me.handleDirectionalChanges();
// At this point, we need to update our max and min given the tick values since we have expanded the
// range of the scale
me.max = helpers.max(ticks);
me.min = helpers.min(ticks);
if (tickOpts.reverse) {
ticks.reverse();
me.start = me.max;
me.end = me.min;
} else {
me.start = me.min;
me.end = me.max;
}
},
convertTicksToLabels: function() {
var me = this;
me.ticksAsNumbers = me.ticks.slice();
me.zeroLineIndex = me.ticks.indexOf(0);
Chart.Scale.prototype.convertTicksToLabels.call(me);
}
});
};
},{}],42:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers;
var defaultConfig = {
position: 'left',
// label settings
ticks: {
callback: function(value, index, arr) {
var remain = value / (Math.pow(10, Math.floor(helpers.log10(value))));
if (value === 0) {
return '0';
} else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === arr.length - 1) {
return value.toExponential();
}
return '';
}
}
};
var LogarithmicScale = Chart.Scale.extend({
determineDataLimits: function() {
var me = this;
var opts = me.options;
var tickOpts = opts.ticks;
var chart = me.chart;
var data = chart.data;
var datasets = data.datasets;
var getValueOrDefault = helpers.getValueOrDefault;
var isHorizontal = me.isHorizontal();
function IDMatches(meta) {
return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
}
// Calculate Range
me.min = null;
me.max = null;
me.minNotZero = null;
if (opts.stacked) {
var valuesPerType = {};
helpers.each(datasets, function(dataset, datasetIndex) {
var meta = chart.getDatasetMeta(datasetIndex);
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
if (valuesPerType[meta.type] === undefined) {
valuesPerType[meta.type] = [];
}
helpers.each(dataset.data, function(rawValue, index) {
var values = valuesPerType[meta.type];
var value = +me.getRightValue(rawValue);
if (isNaN(value) || meta.data[index].hidden) {
return;
}
values[index] = values[index] || 0;
if (opts.relativePoints) {
values[index] = 100;
} else {
// Don't need to split positive and negative since the log scale can't handle a 0 crossing
values[index] += value;
}
});
}
});
helpers.each(valuesPerType, function(valuesForType) {
var minVal = helpers.min(valuesForType);
var maxVal = helpers.max(valuesForType);
me.min = me.min === null ? minVal : Math.min(me.min, minVal);
me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
});
} else {
helpers.each(datasets, function(dataset, datasetIndex) {
var meta = chart.getDatasetMeta(datasetIndex);
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
helpers.each(dataset.data, function(rawValue, index) {
var value = +me.getRightValue(rawValue);
if (isNaN(value) || meta.data[index].hidden) {
return;
}
if (me.min === null) {
me.min = value;
} else if (value < me.min) {
me.min = value;
}
if (me.max === null) {
me.max = value;
} else if (value > me.max) {
me.max = value;
}
if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) {
me.minNotZero = value;
}
});
}
});
}
me.min = getValueOrDefault(tickOpts.min, me.min);
me.max = getValueOrDefault(tickOpts.max, me.max);
if (me.min === me.max) {
if (me.min !== 0 && me.min !== null) {
me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1);
me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1);
} else {
me.min = 1;
me.max = 10;
}
}
},
buildTicks: function() {
var me = this;
var opts = me.options;
var tickOpts = opts.ticks;
var getValueOrDefault = helpers.getValueOrDefault;
// Reset the ticks array. Later on, we will draw a grid line at these positions
// The array simply contains the numerical value of the spots where ticks will be
var ticks = me.ticks = [];
// Figure out what the max number of ticks we can support it is based on the size of
// the axis area. For now, we say that the minimum tick spacing in pixels must be 50
// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
// the graph
var tickVal = getValueOrDefault(tickOpts.min, Math.pow(10, Math.floor(helpers.log10(me.min))));
while (tickVal < me.max) {
ticks.push(tickVal);
var exp;
var significand;
if (tickVal === 0) {
exp = Math.floor(helpers.log10(me.minNotZero));
significand = Math.round(me.minNotZero / Math.pow(10, exp));
} else {
exp = Math.floor(helpers.log10(tickVal));
significand = Math.floor(tickVal / Math.pow(10, exp)) + 1;
}
if (significand === 10) {
significand = 1;
++exp;
}
tickVal = significand * Math.pow(10, exp);
}
var lastTick = getValueOrDefault(tickOpts.max, tickVal);
ticks.push(lastTick);
if (!me.isHorizontal()) {
// We are in a vertical orientation. The top value is the highest. So reverse the array
ticks.reverse();
}
// At this point, we need to update our max and min given the tick values since we have expanded the
// range of the scale
me.max = helpers.max(ticks);
me.min = helpers.min(ticks);
if (tickOpts.reverse) {
ticks.reverse();
me.start = me.max;
me.end = me.min;
} else {
me.start = me.min;
me.end = me.max;
}
},
convertTicksToLabels: function() {
this.tickValues = this.ticks.slice();
Chart.Scale.prototype.convertTicksToLabels.call(this);
},
// Get the correct tooltip label
getLabelForIndex: function(index, datasetIndex) {
return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
},
getPixelForTick: function(index) {
return this.getPixelForValue(this.tickValues[index]);
},
getPixelForValue: function(value) {
var me = this;
var innerDimension;
var pixel;
var start = me.start;
var newVal = +me.getRightValue(value);
var range;
var paddingTop = me.paddingTop;
var paddingBottom = me.paddingBottom;
var paddingLeft = me.paddingLeft;
var opts = me.options;
var tickOpts = opts.ticks;
if (me.isHorizontal()) {
range = helpers.log10(me.end) - helpers.log10(start); // todo: if start === 0
if (newVal === 0) {
pixel = me.left + paddingLeft;
} else {
innerDimension = me.width - (paddingLeft + me.paddingRight);
pixel = me.left + (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
pixel += paddingLeft;
}
} else {
// Bottom - top since pixels increase downard on a screen
innerDimension = me.height - (paddingTop + paddingBottom);
if (start === 0 && !tickOpts.reverse) {
range = helpers.log10(me.end) - helpers.log10(me.minNotZero);
if (newVal === start) {
pixel = me.bottom - paddingBottom;
} else if (newVal === me.minNotZero) {
pixel = me.bottom - paddingBottom - innerDimension * 0.02;
} else {
pixel = me.bottom - paddingBottom - innerDimension * 0.02 - (innerDimension * 0.98/ range * (helpers.log10(newVal)-helpers.log10(me.minNotZero)));
}
} else if (me.end === 0 && tickOpts.reverse) {
range = helpers.log10(me.start) - helpers.log10(me.minNotZero);
if (newVal === me.end) {
pixel = me.top + paddingTop;
} else if (newVal === me.minNotZero) {
pixel = me.top + paddingTop + innerDimension * 0.02;
} else {
pixel = me.top + paddingTop + innerDimension * 0.02 + (innerDimension * 0.98/ range * (helpers.log10(newVal)-helpers.log10(me.minNotZero)));
}
} else {
range = helpers.log10(me.end) - helpers.log10(start);
innerDimension = me.height - (paddingTop + paddingBottom);
pixel = (me.bottom - paddingBottom) - (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
}
}
return pixel;
},
getValueForPixel: function(pixel) {
var me = this;
var range = helpers.log10(me.end) - helpers.log10(me.start);
var value, innerDimension;
if (me.isHorizontal()) {
innerDimension = me.width - (me.paddingLeft + me.paddingRight);
value = me.start * Math.pow(10, (pixel - me.left - me.paddingLeft) * range / innerDimension);
} else { // todo: if start === 0
innerDimension = me.height - (me.paddingTop + me.paddingBottom);
value = Math.pow(10, (me.bottom - me.paddingBottom - pixel) * range / innerDimension) / me.start;
}
return value;
}
});
Chart.scaleService.registerScaleType('logarithmic', LogarithmicScale, defaultConfig);
};
},{}],43:[function(require,module,exports){
'use strict';
module.exports = function(Chart) {
var helpers = Chart.helpers;
var globalDefaults = Chart.defaults.global;
var defaultConfig = {
display: true,
// Boolean - Whether to animate scaling the chart from the centre
animate: true,
lineArc: false,
position: 'chartArea',
angleLines: {
display: true,
color: 'rgba(0, 0, 0, 0.1)',
lineWidth: 1
},
// label settings
ticks: {
// Boolean - Show a backdrop to the scale label
showLabelBackdrop: true,
// String - The colour of the label backdrop
backdropColor: 'rgba(255,255,255,0.75)',
// Number - The backdrop padding above & below the label in pixels
backdropPaddingY: 2,
// Number - The backdrop padding to the side of the label in pixels
backdropPaddingX: 2
},
pointLabels: {
// Number - Point label font size in pixels
fontSize: 10,
// Function - Used to convert point labels
callback: function(label) {
return label;
}
}
};
var LinearRadialScale = Chart.LinearScaleBase.extend({
getValueCount: function() {
return this.chart.data.labels.length;
},
setDimensions: function() {
var me = this;
var opts = me.options;
var tickOpts = opts.ticks;
// Set the unconstrained dimension before label rotation
me.width = me.maxWidth;
me.height = me.maxHeight;
me.xCenter = Math.round(me.width / 2);
me.yCenter = Math.round(me.height / 2);
var minSize = helpers.min([me.height, me.width]);
var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2);
},
determineDataLimits: function() {
var me = this;
var chart = me.chart;
me.min = null;
me.max = null;
helpers.each(chart.data.datasets, function(dataset, datasetIndex) {
if (chart.isDatasetVisible(datasetIndex)) {
var meta = chart.getDatasetMeta(datasetIndex);
helpers.each(dataset.data, function(rawValue, index) {
var value = +me.getRightValue(rawValue);
if (isNaN(value) || meta.data[index].hidden) {
return;
}
if (me.min === null) {
me.min = value;
} else if (value < me.min) {
me.min = value;
}
if (me.max === null) {
me.max = value;
} else if (value > me.max) {
me.max = value;
}
});
}
});
// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
me.handleTickRangeOptions();
},
getTickLimit: function() {
var tickOpts = this.options.ticks;
var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize)));
},
convertTicksToLabels: function() {
var me = this;
Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me);
// Point labels
me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me);
},
getLabelForIndex: function(index, datasetIndex) {
return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
},
fit: function() {
/*
* Right, this is really confusing and there is a lot of maths going on here
* The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
*
* Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
*
* Solution:
*
* We assume the radius of the polygon is half the size of the canvas at first
* at each index we check if the text overlaps.
*
* Where it does, we store that angle and that index.
*
* After finding the largest index and angle we calculate how much we need to remove
* from the shape radius to move the point inwards by that x.
*
* We average the left and right distances to get the maximum shape radius that can fit in the box
* along with labels.
*
* Once we have that, we can find the centre point for the chart, by taking the x text protrusion
* on each side, removing that from the size, halving it and adding the left x protrusion width.
*
* This will mean we have a shape fitted to the canvas, as large as it can be with the labels
* and position it in the most space efficient manner
*
* https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
*/
var pointLabels = this.options.pointLabels;
var pointLabelFontSize = helpers.getValueOrDefault(pointLabels.fontSize, globalDefaults.defaultFontSize);
var pointLabeFontStyle = helpers.getValueOrDefault(pointLabels.fontStyle, globalDefaults.defaultFontStyle);
var pointLabeFontFamily = helpers.getValueOrDefault(pointLabels.fontFamily, globalDefaults.defaultFontFamily);
var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily);
// Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
// Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
var largestPossibleRadius = helpers.min([(this.height / 2 - pointLabelFontSize - 5), this.width / 2]),
pointPosition,
i,
textWidth,
halfTextWidth,
furthestRight = this.width,
furthestRightIndex,
furthestRightAngle,
furthestLeft = 0,
furthestLeftIndex,
furthestLeftAngle,
xProtrusionLeft,
xProtrusionRight,
radiusReductionRight,
radiusReductionLeft;
this.ctx.font = pointLabeFont;
for (i = 0; i < this.getValueCount(); i++) {
// 5px to space the text slightly out - similar to what we do in the draw function.
pointPosition = this.getPointPosition(i, largestPossibleRadius);
textWidth = this.ctx.measureText(this.pointLabels[i] ? this.pointLabels[i] : '').width + 5;
// Add quarter circle to make degree 0 mean top of circle
var angleRadians = this.getIndexAngle(i) + (Math.PI / 2);
var angle = (angleRadians * 360 / (2 * Math.PI)) % 360;
if (angle === 0 || angle === 180) {
// At angle 0 and 180, we're at exactly the top/bottom
// of the radar chart, so text will be aligned centrally, so we'll half it and compare
// w/left and right text sizes
halfTextWidth = textWidth / 2;
if (pointPosition.x + halfTextWidth > furthestRight) {
furthestRight = pointPosition.x + halfTextWidth;
furthestRightIndex = i;
}
if (pointPosition.x - halfTextWidth < furthestLeft) {
furthestLeft = pointPosition.x - halfTextWidth;
furthestLeftIndex = i;
}
} else if (angle < 180) {
// Less than half the values means we'll left align the text
if (pointPosition.x + textWidth > furthestRight) {
furthestRight = pointPosition.x + textWidth;
furthestRightIndex = i;
}
// More than half the values means we'll right align the text
} else if (pointPosition.x - textWidth < furthestLeft) {
furthestLeft = pointPosition.x - textWidth;
furthestLeftIndex = i;
}
}
xProtrusionLeft = furthestLeft;
xProtrusionRight = Math.ceil(furthestRight - this.width);
furthestRightAngle = this.getIndexAngle(furthestRightIndex);
furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2);
radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2);
// Ensure we actually need to reduce the size of the chart
radiusReductionRight = (helpers.isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
radiusReductionLeft = (helpers.isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
this.drawingArea = Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2);
this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
},
setCenterPoint: function(leftMovement, rightMovement) {
var me = this;
var maxRight = me.width - rightMovement - me.drawingArea,
maxLeft = leftMovement + me.drawingArea;
me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left);
// Always vertically in the centre as the text height doesn't change
me.yCenter = Math.round((me.height / 2) + me.top);
},
getIndexAngle: function(index) {
var angleMultiplier = (Math.PI * 2) / this.getValueCount();
var startAngle = this.chart.options && this.chart.options.startAngle ?
this.chart.options.startAngle :
0;
var startAngleRadians = startAngle * Math.PI * 2 / 360;
// Start from the top instead of right, so remove a quarter of the circle
return index * angleMultiplier - (Math.PI / 2) + startAngleRadians;
},
getDistanceFromCenterForValue: function(value) {
var me = this;
if (value === null) {
return 0; // null always in center
}
// Take into account half font size + the yPadding of the top value
var scalingFactor = me.drawingArea / (me.max - me.min);
if (me.options.reverse) {
return (me.max - value) * scalingFactor;
}
return (value - me.min) * scalingFactor;
},
getPointPosition: function(index, distanceFromCenter) {
var me = this;
var thisAngle = me.getIndexAngle(index);
return {
x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter,
y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter
};
},
getPointPositionForValue: function(index, value) {
return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
},
getBasePosition: function() {
var me = this;
var min = me.min;
var max = me.max;
return me.getPointPositionForValue(0,
me.beginAtZero? 0:
min < 0 && max < 0? max :
min > 0 && max > 0? min :
0);
},
draw: function() {
var me = this;
var opts = me.options;
var gridLineOpts = opts.gridLines;
var tickOpts = opts.ticks;
var angleLineOpts = opts.angleLines;
var pointLabelOpts = opts.pointLabels;
var getValueOrDefault = helpers.getValueOrDefault;
if (opts.display) {
var ctx = me.ctx;
// Tick Font
var tickFontSize = getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
var tickFontStyle = getValueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle);
var tickFontFamily = getValueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily);
var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
helpers.each(me.ticks, function(label, index) {
// Don't draw a centre value (if it is minimum)
if (index > 0 || opts.reverse) {
var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
var yHeight = me.yCenter - yCenterOffset;
// Draw circular lines around the scale
if (gridLineOpts.display && index !== 0) {
ctx.strokeStyle = helpers.getValueAtIndexOrDefault(gridLineOpts.color, index - 1);
ctx.lineWidth = helpers.getValueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1);
if (opts.lineArc) {
// Draw circular arcs between the points
ctx.beginPath();
ctx.arc(me.xCenter, me.yCenter, yCenterOffset, 0, Math.PI * 2);
ctx.closePath();
ctx.stroke();
} else {
// Draw straight lines connecting each index
ctx.beginPath();
for (var i = 0; i < me.getValueCount(); i++) {
var pointPosition = me.getPointPosition(i, yCenterOffset);
if (i === 0) {
ctx.moveTo(pointPosition.x, pointPosition.y);
} else {
ctx.lineTo(pointPosition.x, pointPosition.y);
}
}
ctx.closePath();
ctx.stroke();
}
}
if (tickOpts.display) {
var tickFontColor = getValueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor);
ctx.font = tickLabelFont;
if (tickOpts.showLabelBackdrop) {
var labelWidth = ctx.measureText(label).width;
ctx.fillStyle = tickOpts.backdropColor;
ctx.fillRect(
me.xCenter - labelWidth / 2 - tickOpts.backdropPaddingX,
yHeight - tickFontSize / 2 - tickOpts.backdropPaddingY,
labelWidth + tickOpts.backdropPaddingX * 2,
tickFontSize + tickOpts.backdropPaddingY * 2
);
}
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = tickFontColor;
ctx.fillText(label, me.xCenter, yHeight);
}
}
});
if (!opts.lineArc) {
ctx.lineWidth = angleLineOpts.lineWidth;
ctx.strokeStyle = angleLineOpts.color;
var outerDistance = me.getDistanceFromCenterForValue(opts.reverse ? me.min : me.max);
// Point Label Font
var pointLabelFontSize = getValueOrDefault(pointLabelOpts.fontSize, globalDefaults.defaultFontSize);
var pointLabeFontStyle = getValueOrDefault(pointLabelOpts.fontStyle, globalDefaults.defaultFontStyle);
var pointLabeFontFamily = getValueOrDefault(pointLabelOpts.fontFamily, globalDefaults.defaultFontFamily);
var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily);
for (var i = me.getValueCount() - 1; i >= 0; i--) {
if (angleLineOpts.display) {
var outerPosition = me.getPointPosition(i, outerDistance);
ctx.beginPath();
ctx.moveTo(me.xCenter, me.yCenter);
ctx.lineTo(outerPosition.x, outerPosition.y);
ctx.stroke();
ctx.closePath();
}
// Extra 3px out for some label spacing
var pointLabelPosition = me.getPointPosition(i, outerDistance + 5);
// Keep this in loop since we may support array properties here
var pointLabelFontColor = getValueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor);
ctx.font = pointLabeFont;
ctx.fillStyle = pointLabelFontColor;
var pointLabels = me.pointLabels;
// Add quarter circle to make degree 0 mean top of circle
var angleRadians = this.getIndexAngle(i) + (Math.PI / 2);
var angle = (angleRadians * 360 / (2 * Math.PI)) % 360;
if (angle === 0 || angle === 180) {
ctx.textAlign = 'center';
} else if (angle < 180) {
ctx.textAlign = 'left';
} else {
ctx.textAlign = 'right';
}
// Set the correct text baseline based on outer positioning
if (angle === 90 || angle === 270) {
ctx.textBaseline = 'middle';
} else if (angle > 270 || angle < 90) {
ctx.textBaseline = 'bottom';
} else {
ctx.textBaseline = 'top';
}
ctx.fillText(pointLabels[i] ? pointLabels[i] : '', pointLabelPosition.x, pointLabelPosition.y);
}
}
}
}
});
Chart.scaleService.registerScaleType('radialLinear', LinearRadialScale, defaultConfig);
};
},{}],44:[function(require,module,exports){
/* global window: false */
'use strict';
var moment = require(1);
moment = typeof(moment) === 'function' ? moment : window.moment;
module.exports = function(Chart) {
var helpers = Chart.helpers;
var time = {
units: [{
name: 'millisecond',
steps: [1, 2, 5, 10, 20, 50, 100, 250, 500]
}, {
name: 'second',
steps: [1, 2, 5, 10, 30]
}, {
name: 'minute',
steps: [1, 2, 5, 10, 30]
}, {
name: 'hour',
steps: [1, 2, 3, 6, 12]
}, {
name: 'day',
steps: [1, 2, 5]
}, {
name: 'week',
maxStep: 4
}, {
name: 'month',
maxStep: 3
}, {
name: 'quarter',
maxStep: 4
}, {
name: 'year',
maxStep: false
}]
};
var defaultConfig = {
position: 'bottom',
time: {
parser: false, // false == a pattern string from http://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment
format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/
unit: false, // false == automatic or override with week, month, year, etc.
round: false, // none, or override with week, month, year, etc.
displayFormat: false, // DEPRECATED
isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/
minUnit: 'millisecond',
// defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/
displayFormats: {
millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM,
second: 'h:mm:ss a', // 11:20:01 AM
minute: 'h:mm:ss a', // 11:20:01 AM
hour: 'MMM D, hA', // Sept 4, 5PM
day: 'll', // Sep 4 2015
week: 'll', // Week 46, or maybe "[W]WW - YYYY" ?
month: 'MMM YYYY', // Sept 2015
quarter: '[Q]Q - YYYY', // Q3
year: 'YYYY' // 2015
}
},
ticks: {
autoSkip: false
}
};
var TimeScale = Chart.Scale.extend({
initialize: function() {
if (!moment) {
throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com');
}
Chart.Scale.prototype.initialize.call(this);
},
getLabelMoment: function(datasetIndex, index) {
if (datasetIndex === null || index === null) {
return null;
}
if (typeof this.labelMoments[datasetIndex] !== 'undefined') {
return this.labelMoments[datasetIndex][index];
}
return null;
},
getLabelDiff: function(datasetIndex, index) {
var me = this;
if (datasetIndex === null || index === null) {
return null;
}
if (me.labelDiffs === undefined) {
me.buildLabelDiffs();
}
if (typeof me.labelDiffs[datasetIndex] !== 'undefined') {
return me.labelDiffs[datasetIndex][index];
}
return null;
},
getMomentStartOf: function(tick) {
var me = this;
if (me.options.time.unit === 'week' && me.options.time.isoWeekday !== false) {
return tick.clone().startOf('isoWeek').isoWeekday(me.options.time.isoWeekday);
}
return tick.clone().startOf(me.tickUnit);
},
determineDataLimits: function() {
var me = this;
me.labelMoments = [];
// Only parse these once. If the dataset does not have data as x,y pairs, we will use
// these
var scaleLabelMoments = [];
if (me.chart.data.labels && me.chart.data.labels.length > 0) {
helpers.each(me.chart.data.labels, function(label) {
var labelMoment = me.parseTime(label);
if (labelMoment.isValid()) {
if (me.options.time.round) {
labelMoment.startOf(me.options.time.round);
}
scaleLabelMoments.push(labelMoment);
}
}, me);
me.firstTick = moment.min.call(me, scaleLabelMoments);
me.lastTick = moment.max.call(me, scaleLabelMoments);
} else {
me.firstTick = null;
me.lastTick = null;
}
helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) {
var momentsForDataset = [];
var datasetVisible = me.chart.isDatasetVisible(datasetIndex);
if (typeof dataset.data[0] === 'object' && dataset.data[0] !== null) {
helpers.each(dataset.data, function(value) {
var labelMoment = me.parseTime(me.getRightValue(value));
if (labelMoment.isValid()) {
if (me.options.time.round) {
labelMoment.startOf(me.options.time.round);
}
momentsForDataset.push(labelMoment);
if (datasetVisible) {
// May have gone outside the scale ranges, make sure we keep the first and last ticks updated
me.firstTick = me.firstTick !== null ? moment.min(me.firstTick, labelMoment) : labelMoment;
me.lastTick = me.lastTick !== null ? moment.max(me.lastTick, labelMoment) : labelMoment;
}
}
}, me);
} else {
// We have no labels. Use the ones from the scale
momentsForDataset = scaleLabelMoments;
}
me.labelMoments.push(momentsForDataset);
}, me);
// Set these after we've done all the data
if (me.options.time.min) {
me.firstTick = me.parseTime(me.options.time.min);
}
if (me.options.time.max) {
me.lastTick = me.parseTime(me.options.time.max);
}
// We will modify these, so clone for later
me.firstTick = (me.firstTick || moment()).clone();
me.lastTick = (me.lastTick || moment()).clone();
},
buildLabelDiffs: function() {
var me = this;
me.labelDiffs = [];
var scaleLabelDiffs = [];
// Parse common labels once
if (me.chart.data.labels && me.chart.data.labels.length > 0) {
helpers.each(me.chart.data.labels, function(label) {
var labelMoment = me.parseTime(label);
if (labelMoment.isValid()) {
if (me.options.time.round) {
labelMoment.startOf(me.options.time.round);
}
scaleLabelDiffs.push(labelMoment.diff(me.firstTick, me.tickUnit, true));
}
}, me);
}
helpers.each(me.chart.data.datasets, function(dataset) {
var diffsForDataset = [];
if (typeof dataset.data[0] === 'object' && dataset.data[0] !== null) {
helpers.each(dataset.data, function(value) {
var labelMoment = me.parseTime(me.getRightValue(value));
if (labelMoment.isValid()) {
if (me.options.time.round) {
labelMoment.startOf(me.options.time.round);
}
diffsForDataset.push(labelMoment.diff(me.firstTick, me.tickUnit, true));
}
}, me);
} else {
// We have no labels. Use common ones
diffsForDataset = scaleLabelDiffs;
}
me.labelDiffs.push(diffsForDataset);
}, me);
},
buildTicks: function() {
var me = this;
me.ctx.save();
var tickFontSize = helpers.getValueOrDefault(me.options.ticks.fontSize, Chart.defaults.global.defaultFontSize);
var tickFontStyle = helpers.getValueOrDefault(me.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle);
var tickFontFamily = helpers.getValueOrDefault(me.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily);
var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
me.ctx.font = tickLabelFont;
me.ticks = [];
me.unitScale = 1; // How much we scale the unit by, ie 2 means 2x unit per step
me.scaleSizeInUnits = 0; // How large the scale is in the base unit (seconds, minutes, etc)
// Set unit override if applicable
if (me.options.time.unit) {
me.tickUnit = me.options.time.unit || 'day';
me.displayFormat = me.options.time.displayFormats[me.tickUnit];
me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true);
me.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, 1);
} else {
// Determine the smallest needed unit of the time
var innerWidth = me.isHorizontal() ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom);
// Crude approximation of what the label length might be
var tempFirstLabel = me.tickFormatFunction(me.firstTick, 0, []);
var tickLabelWidth = me.ctx.measureText(tempFirstLabel).width;
var cosRotation = Math.cos(helpers.toRadians(me.options.ticks.maxRotation));
var sinRotation = Math.sin(helpers.toRadians(me.options.ticks.maxRotation));
tickLabelWidth = (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation);
var labelCapacity = innerWidth / (tickLabelWidth);
// Start as small as possible
me.tickUnit = me.options.time.minUnit;
me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true);
me.displayFormat = me.options.time.displayFormats[me.tickUnit];
var unitDefinitionIndex = 0;
var unitDefinition = time.units[unitDefinitionIndex];
// While we aren't ideal and we don't have units left
while (unitDefinitionIndex < time.units.length) {
// Can we scale this unit. If `false` we can scale infinitely
me.unitScale = 1;
if (helpers.isArray(unitDefinition.steps) && Math.ceil(me.scaleSizeInUnits / labelCapacity) < helpers.max(unitDefinition.steps)) {
// Use one of the prefedined steps
for (var idx = 0; idx < unitDefinition.steps.length; ++idx) {
if (unitDefinition.steps[idx] >= Math.ceil(me.scaleSizeInUnits / labelCapacity)) {
me.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, unitDefinition.steps[idx]);
break;
}
}
break;
} else if ((unitDefinition.maxStep === false) || (Math.ceil(me.scaleSizeInUnits / labelCapacity) < unitDefinition.maxStep)) {
// We have a max step. Scale this unit
me.unitScale = helpers.getValueOrDefault(me.options.time.unitStepSize, Math.ceil(me.scaleSizeInUnits / labelCapacity));
break;
} else {
// Move to the next unit up
++unitDefinitionIndex;
unitDefinition = time.units[unitDefinitionIndex];
me.tickUnit = unitDefinition.name;
var leadingUnitBuffer = me.firstTick.diff(me.getMomentStartOf(me.firstTick), me.tickUnit, true);
var trailingUnitBuffer = me.getMomentStartOf(me.lastTick.clone().add(1, me.tickUnit)).diff(me.lastTick, me.tickUnit, true);
me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true) + leadingUnitBuffer + trailingUnitBuffer;
me.displayFormat = me.options.time.displayFormats[unitDefinition.name];
}
}
}
var roundedStart;
// Only round the first tick if we have no hard minimum
if (!me.options.time.min) {
me.firstTick = me.getMomentStartOf(me.firstTick);
roundedStart = me.firstTick;
} else {
roundedStart = me.getMomentStartOf(me.firstTick);
}
// Only round the last tick if we have no hard maximum
if (!me.options.time.max) {
var roundedEnd = me.getMomentStartOf(me.lastTick);
var delta = roundedEnd.diff(me.lastTick, me.tickUnit, true);
if (delta < 0) {
// Do not use end of because we need me to be in the next time unit
me.lastTick = me.getMomentStartOf(me.lastTick.add(1, me.tickUnit));
} else if (delta >= 0) {
me.lastTick = roundedEnd;
}
me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true);
}
// Tick displayFormat override
if (me.options.time.displayFormat) {
me.displayFormat = me.options.time.displayFormat;
}
// first tick. will have been rounded correctly if options.time.min is not specified
me.ticks.push(me.firstTick.clone());
// For every unit in between the first and last moment, create a moment and add it to the ticks tick
for (var i = 1; i <= me.scaleSizeInUnits; ++i) {
var newTick = roundedStart.clone().add(i, me.tickUnit);
// Are we greater than the max time
if (me.options.time.max && newTick.diff(me.lastTick, me.tickUnit, true) >= 0) {
break;
}
if (i % me.unitScale === 0) {
me.ticks.push(newTick);
}
}
// Always show the right tick
var diff = me.ticks[me.ticks.length - 1].diff(me.lastTick, me.tickUnit);
if (diff !== 0 || me.scaleSizeInUnits === 0) {
// this is a weird case. If the <max> option is the same as the end option, we can't just diff the times because the tick was created from the roundedStart
// but the last tick was not rounded.
if (me.options.time.max) {
me.ticks.push(me.lastTick.clone());
me.scaleSizeInUnits = me.lastTick.diff(me.ticks[0], me.tickUnit, true);
} else {
me.ticks.push(me.lastTick.clone());
me.scaleSizeInUnits = me.lastTick.diff(me.firstTick, me.tickUnit, true);
}
}
me.ctx.restore();
// Invalidate label diffs cache
me.labelDiffs = undefined;
},
// Get tooltip label
getLabelForIndex: function(index, datasetIndex) {
var me = this;
var label = me.chart.data.labels && index < me.chart.data.labels.length ? me.chart.data.labels[index] : '';
if (typeof me.chart.data.datasets[datasetIndex].data[0] === 'object') {
label = me.getRightValue(me.chart.data.datasets[datasetIndex].data[index]);
}
// Format nicely
if (me.options.time.tooltipFormat) {
label = me.parseTime(label).format(me.options.time.tooltipFormat);
}
return label;
},
// Function to format an individual tick mark
tickFormatFunction: function(tick, index, ticks) {
var formattedTick = tick.format(this.displayFormat);
var tickOpts = this.options.ticks;
var callback = helpers.getValueOrDefault(tickOpts.callback, tickOpts.userCallback);
if (callback) {
return callback(formattedTick, index, ticks);
}
return formattedTick;
},
convertTicksToLabels: function() {
var me = this;
me.tickMoments = me.ticks;
me.ticks = me.ticks.map(me.tickFormatFunction, me);
},
getPixelForValue: function(value, index, datasetIndex) {
var me = this;
var offset = null;
if (index !== undefined && datasetIndex !== undefined) {
offset = me.getLabelDiff(datasetIndex, index);
}
if (offset === null) {
if (!value || !value.isValid) {
// not already a moment object
value = me.parseTime(me.getRightValue(value));
}
if (value && value.isValid && value.isValid()) {
offset = value.diff(me.firstTick, me.tickUnit, true);
}
}
if (offset !== null) {
var decimal = offset !== 0 ? offset / me.scaleSizeInUnits : offset;
if (me.isHorizontal()) {
var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
var valueOffset = (innerWidth * decimal) + me.paddingLeft;
return me.left + Math.round(valueOffset);
}
var innerHeight = me.height - (me.paddingTop + me.paddingBottom);
var heightOffset = (innerHeight * decimal) + me.paddingTop;
return me.top + Math.round(heightOffset);
}
},
getPixelForTick: function(index) {
return this.getPixelForValue(this.tickMoments[index], null, null);
},
getValueForPixel: function(pixel) {
var me = this;
var innerDimension = me.isHorizontal() ? me.width - (me.paddingLeft + me.paddingRight) : me.height - (me.paddingTop + me.paddingBottom);
var offset = (pixel - (me.isHorizontal() ? me.left + me.paddingLeft : me.top + me.paddingTop)) / innerDimension;
offset *= me.scaleSizeInUnits;
return me.firstTick.clone().add(moment.duration(offset, me.tickUnit).asSeconds(), 'seconds');
},
parseTime: function(label) {
var me = this;
if (typeof me.options.time.parser === 'string') {
return moment(label, me.options.time.parser);
}
if (typeof me.options.time.parser === 'function') {
return me.options.time.parser(label);
}
// Date objects
if (typeof label.getMonth === 'function' || typeof label === 'number') {
return moment(label);
}
// Moment support
if (label.isValid && label.isValid()) {
return label;
}
// Custom parsing (return an instance of moment)
if (typeof me.options.time.format !== 'string' && me.options.time.format.call) {
console.warn('options.time.format is deprecated and replaced by options.time.parser. See http://nnnick.github.io/Chart.js/docs-v2/#scales-time-scale');
return me.options.time.format(label);
}
// Moment format parsing
return moment(label, me.options.time.format);
}
});
Chart.scaleService.registerScaleType('time', TimeScale, defaultConfig);
};
},{"1":1}]},{},[7])(7)
});
<!DOCTYPE html>
<html>
<head>
<title>Leaflet Layers Control Example</title>
<script src="http://www.chartjs.org/assets/Chart.js"></script>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.0.1/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.0.1/dist/leaflet.js"></script>
<script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
<script type="text/javascript" src="http://www.chartjs.org/assets/Chart.js"> </script>
<script src="Chart.js"></script>
<style>
#map {
width: 100%;
height: 760px;
align: center;
}
.info {
padding: 16px 10px;
font: 14px/16px Arial, Helvetica, sans-serif;
background: white;
background: rgba(255,255,255,0.8);
box-shadow: 0 0 15px rgba(0,0,0,0.2);
border-radius: 5px;
}
.info h4 {
margin: 0 0 5px;
color: 'white';
}
.legend {
text-align: left;
line-height: 18px;
color: #555;
}
.legend i {
width: 18px;
height: 18px;
float: left;
margin-right: 8px;
opacity: 0.7;
}
button {
position:absolute;
top:410px;
}
</style>
</head>
<body>
<div id="map"></div>
<script src="https://unpkg.com/leaflet@1.0.1/dist/leaflet.js"></script>
<script type="text/javascript">
var newChart = function(labels, data) {
var dataLength = labels ? labels.length : 0;
console.log
console.log('we\'re in newChart', labels, data);
var backgroundColors = ['rgba(235,127,134, 0.9)',
'rgba(206,102,147, 0.9)',
'rgba(129,55,83, 0.9)',
'rgba(211,156,131, 0.9)',
'rgba(153, 102, 255, 0.9)',
'rgba(255, 159, 64, 0.9)'];
var colors = [];
for (var i = 0; i < dataLength; i++) {
colors.push(backgroundColors[i]);
};
console.log('newChart colors', colors);
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: '# of Votes',
data: data,
backgroundColor: colors,
borderColor: colors,
borderWidth: 1
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero:true
}
}]
}
}
});
};
var map = L.map('map').setView([39.0119, -98.4842],4.5, zoomSnap = 0.1, zoomDelta = 0.1);
L.tileLayer('http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="https://carto.com/attributions">CARTO</a>'
}).addTo(map);
var info = L.control();
info.onAdd = function(map) {
this._div = L.DomUtil.create('div', 'info');
this.update();
return this._div;
};
info.update = function(props) {
if (props) {
if (props.party == 'Democratic') {
var labels = ['Hillary Clinton', 'Bernie Sanders'];
var data = [props.clinton_1746_president, props.sanders_1445_president];
console.log('labels', labels, 'data', data);
var dems = '<h4>US Primary Election Data 2016</h4>' + '<br />' + (props ? props.name + ' County' + '<br />' + '<br />' + '<b>Democratic Party Winner: ' + props.d_winner + '</b><br />'+ 'Margin of Victory (%): ' + props.d_margin_pc.toFixed(2):'mehak');
dems += '<canvas id="myChart" width="10" height="10"></canvas>';
this._div.innerHTML = dems;
newChart(labels, data);
} else {
var labels = ['Trump', 'Cruz','Kasich','Rubio'];
var data = [props.trump_8639_president, props.cruz_61815_president, props.kasich_36679_president, props.rubio_53044_president];
var reps = '<h4>US Primary Election Data 2016</h4>' + '<br />' + (props ? props.name + ' County' + '<br />' + '<br />' + '<b>Republican Party Winner: ' + props.r_winner + '</b><br />'+ 'Margin of Victory (%): ' + props.r_margin_pc.toFixed(2):'andy');
reps += '<canvas id="myChart" width="10" height="10"></canvas>';
this._div.innerHTML = reps;
newChart(labels, data);
}
}
console.log('props:', props);
};
info.addTo(map);
function getColor(b) {
return b <= 0.0 & b> -0.25 ? '#85c4c9' :
b <= -0.25 & b > -0.5 ? '#4f90a6':
b <= -0.5 & b> -0.75 ? '#3b738f':
b <= -0.75 & b>= -1.0 ? '#2a5674':
b > 0.0 & b <= 0.25 ? '#facba6' :
b > 0.25 & b <= 0.5 ? '#f8b58b':
b > 0.5 & b <= 0.75 ? '#f2855d':
b > 0.75 & b <= 1.0 ? '#eb4a40':
'grey' ;
}
function style_1(feature){
return{
weight: 1,
opacity: 1,
color: 'white',
};
}
function style(feature) {
return {
weight: 0.6,
opacity: 0.4,
color: 'white',
fillOpacity: 0.8,
fillColor: getColor(feature.properties.frac_tot)
};
}
function highlightFeature(e) {
console.log('highlightFeature was entered');
var layer = e.target;
layer.setStyle({
weight: 1.5,
color: 'black',
dashArray: '',
fillOpacity: 0.7
});
if (!L.Browser.ie && !L.Browser.opera) {
layer.bringToFront();
}
info.update(layer.feature.properties);
}
var geojson;
function resetHighlight(e) {
geojson.resetStyle(e.target);
info.update();
}
function onEachFeature(feature, layer) {
console.log('onEachFeature was entered');
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight
});
}
$.getJSON ("https://mehak-carto.cartodb.com/api/v2/sql?q=SELECT%20*%20FROM%20finalest%20&format=geojson&filename=finalest", function(data) {
console.log('geojson retrieved');
geojson = L.geoJson(data, {
style: style,
onEachFeature: onEachFeature
}).addTo(map);
});
map.attributionControl.addAttribution('Primary Election Results 2016 &copy; <a href="https://www.kaggle.com/">Kaggle</a>');
var legend = L.control({position: 'bottomright'});
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
grades = [100, 75, 50, 25, 0, 25, 50, 75, 100],
labels = ['<strong>Party Bent</strong>'],
from, to;
var x=1;
var y=1;
for (var i = 0; i < grades.length - 1; i++) {
from = grades[i];
to = grades[i + 1];
y++;
labels.push(
'<i style="background:' + getColor(x,x-0.25) + '"></i> ' + from + (' to ' + to)
);
x-=0.25;
}
div.innerHTML = labels.join('<br>');
return div;
};
legend.addTo(map);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment