123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- (function (factory) {
- 'use strict';
- if (typeof exports === 'object') {
- // Node/CommonJS
- module.exports = factory(
- typeof angular !== 'undefined' ? angular : require('angular'),
- typeof Chart !== 'undefined' ? Chart : require('chart.js'));
- } else if (typeof define === 'function' && define.amd) {
- // AMD. Register as an anonymous module.
- define(['chart'], factory);
- } else {
- // Browser globals
- if (typeof angular === 'undefined') {
- throw new Error('AngularJS framework needs to be included, see https://angularjs.org/');
- } else if (typeof Chart === 'undefined') {
- throw new Error('Chart.js library needs to be included, see http://jtblin.github.io/angular-chart.js/');
- }
- factory(angular, Chart);
- }
- }(function (Chart) {
- 'use strict';
- Chart.defaults.global.multiTooltipTemplate = '<%if (datasetLabel){%><%=datasetLabel%>: <%}%><%= value %>';
- Chart.defaults.global.tooltips.mode = 'label';
- Chart.defaults.global.elements.line.borderWidth = 2;
- Chart.defaults.global.elements.rectangle.borderWidth = 2;
- Chart.defaults.global.legend.display = false;
- Chart.defaults.global.colors = [
- '#97BBCD', // blue
- '#DCDCDC', // light grey
- '#F7464A', // red
- '#46BFBD', // green
- '#FDB45C', // yellow
- '#949FB1', // grey
- '#4D5360' // dark grey
- ];
- var useExcanvas = typeof window.G_vmlCanvasManager === 'object' &&
- window.G_vmlCanvasManager !== null &&
- typeof window.G_vmlCanvasManager.initElement === 'function';
- if (useExcanvas) Chart.defaults.global.animation = false;
- return angular.module('chart.js', [])
- .provider('ChartJs', ChartJsProvider)
- .factory('ChartJsFactory', ['ChartJs', '$timeout', ChartJsFactory])
- .directive('chartBase', ['ChartJsFactory', function (ChartJsFactory) { return new ChartJsFactory(); }])
- .directive('chartLine', ['ChartJsFactory', function (ChartJsFactory) { return new ChartJsFactory('line'); }])
- .directive('chartBar', ['ChartJsFactory', function (ChartJsFactory) { return new ChartJsFactory('bar'); }])
- .directive('chartHorizontalBar', ['ChartJsFactory', function (ChartJsFactory) { return new ChartJsFactory('horizontalBar'); }])
- .directive('chartRadar', ['ChartJsFactory', function (ChartJsFactory) { return new ChartJsFactory('radar'); }])
- .directive('chartDoughnut', ['ChartJsFactory', function (ChartJsFactory) { return new ChartJsFactory('doughnut'); }])
- .directive('chartPie', ['ChartJsFactory', function (ChartJsFactory) { return new ChartJsFactory('pie'); }])
- .directive('chartPolarArea', ['ChartJsFactory', function (ChartJsFactory) { return new ChartJsFactory('polarArea'); }])
- .directive('chartBubble', ['ChartJsFactory', function (ChartJsFactory) { return new ChartJsFactory('bubble'); }])
- .name;
- /**
- * Wrapper for chart.js
- * Allows configuring chart js using the provider
- *
- * angular.module('myModule', ['chart.js']).config(function(ChartJsProvider) {
- * ChartJsProvider.setOptions({ responsive: false });
- * ChartJsProvider.setOptions('Line', { responsive: true });
- * })))
- */
- function ChartJsProvider () {
- var options = { responsive: true };
- var ChartJs = {
- Chart: Chart,
- getOptions: function (type) {
- var typeOptions = type && options[type] || {};
- return angular.extend({}, options, typeOptions);
- }
- };
- /**
- * Allow to set global options during configuration
- */
- this.setOptions = function (type, customOptions) {
- // If no type was specified set option for the global object
- if (! customOptions) {
- customOptions = type;
- options = angular.merge(options, customOptions);
- } else {
- // Set options for the specific chart
- options[type] = angular.merge(options[type] || {}, customOptions);
- }
- angular.merge(ChartJs.Chart.defaults, options);
- };
- this.$get = function () {
- return ChartJs;
- };
- }
- function ChartJsFactory (ChartJs, $timeout) {
- return function chart (type) {
- return {
- restrict: 'CA',
- scope: {
- chartGetColor: '=?',
- chartType: '=',
- chartData: '=?',
- chartLabels: '=?',
- chartOptions: '=?',
- chartSeries: '=?',
- chartColors: '=?',
- chartClick: '=?',
- chartHover: '=?',
- chartDatasetOverride: '=?'
- },
- link: function (scope, elem/*, attrs */) {
- if (useExcanvas) window.G_vmlCanvasManager.initElement(elem[0]);
- // Order of setting "watch" matter
- scope.$watch('chartData', watchData, true);
- scope.$watch('chartSeries', watchOther, true);
- scope.$watch('chartLabels', watchOther, true);
- scope.$watch('chartOptions', watchOther, true);
- scope.$watch('chartColors', watchOther, true);
- scope.$watch('chartDatasetOverride', watchOther, true);
- scope.$watch('chartType', watchType, false);
- scope.$on('$destroy', function () {
- destroyChart(scope);
- });
- scope.$on('$resize', function () {
- if (scope.chart) scope.chart.resize();
- });
- function watchData (newVal, oldVal) {
- if (! newVal || ! newVal.length || (Array.isArray(newVal[0]) && ! newVal[0].length)) {
- destroyChart(scope);
- return;
- }
- var chartType = type || scope.chartType;
- if (! chartType) return;
- if (scope.chart && canUpdateChart(newVal, oldVal))
- return updateChart(newVal, scope);
- createChart(chartType, scope, elem);
- }
- function watchOther (newVal, oldVal) {
- if (isEmpty(newVal)) return;
- if (angular.equals(newVal, oldVal)) return;
- var chartType = type || scope.chartType;
- if (! chartType) return;
- // chart.update() doesn't work for series and labels
- // so we have to re-create the chart entirely
- createChart(chartType, scope, elem);
- }
- function watchType (newVal, oldVal) {
- if (isEmpty(newVal)) return;
- if (angular.equals(newVal, oldVal)) return;
- createChart(newVal, scope, elem);
- }
- }
- };
- };
- function createChart (type, scope, elem) {
- var options = getChartOptions(type, scope);
- if (! hasData(scope) || ! canDisplay(type, scope, elem, options)) return;
- var cvs = elem[0];
- var ctx = cvs.getContext('2d');
- scope.chartGetColor = getChartColorFn(scope);
- var data = getChartData(type, scope);
- // Destroy old chart if it exists to avoid ghost charts issue
- // https://github.com/jtblin/angular-chart.js/issues/187
- destroyChart(scope);
- scope.chart = new ChartJs.Chart(ctx, {
- type: type,
- data: data,
- options: options
- });
- scope.$emit('chart-create', scope.chart);
- bindEvents(cvs, scope);
- }
- function canUpdateChart (newVal, oldVal) {
- if (newVal && oldVal && newVal.length && oldVal.length) {
- return Array.isArray(newVal[0]) ?
- newVal.length === oldVal.length && newVal.every(function (element, index) {
- return element.length === oldVal[index].length; }) :
- oldVal.reduce(sum, 0) > 0 ? newVal.length === oldVal.length : false;
- }
- return false;
- }
- function sum (carry, val) {
- return carry + val;
- }
- function getEventHandler (scope, action, triggerOnlyOnChange) {
- var lastState = {
- point: void 0,
- points: void 0
- };
- return function (evt) {
- var atEvent = scope.chart.getElementAtEvent || scope.chart.getPointAtEvent;
- var atEvents = scope.chart.getElementsAtEvent || scope.chart.getPointsAtEvent;
- if (atEvents) {
- var points = atEvents.call(scope.chart, evt);
- var point = atEvent ? atEvent.call(scope.chart, evt)[0] : void 0;
- if (triggerOnlyOnChange === false ||
- (! angular.equals(lastState.points, points) && ! angular.equals(lastState.point, point))
- ) {
- lastState.point = point;
- lastState.points = points;
- scope[action](points, evt, point);
- }
- }
- };
- }
- function getColors (type, scope) {
- var colors = angular.copy(scope.chartColors ||
- ChartJs.getOptions(type).chartColors ||
- Chart.defaults.global.colors
- );
- var notEnoughColors = colors.length < scope.chartData.length;
- while (colors.length < scope.chartData.length) {
- colors.push(scope.chartGetColor());
- }
- // mutate colors in this case as we don't want
- // the colors to change on each refresh
- if (notEnoughColors) scope.chartColors = colors;
- return colors.map(convertColor);
- }
- function convertColor (color) {
- // Allows RGB and RGBA colors to be input as a string: e.g.: "rgb(159,204,0)", "rgba(159,204,0, 0.5)"
- if (typeof color === 'string' && color[0] === 'r') return getColor(rgbStringToRgb(color));
- // Allows hex colors to be input as a string.
- if (typeof color === 'string' && color[0] === '#') return getColor(hexToRgb(color.substr(1)));
- // Allows colors to be input as an object, bypassing getColor() entirely
- if (typeof color === 'object' && color !== null) return color;
- return getRandomColor();
- }
- function getRandomColor () {
- var color = [getRandomInt(0, 255), getRandomInt(0, 255), getRandomInt(0, 255)];
- return getColor(color);
- }
- function getColor (color) {
- var alpha = color[3] || 1;
- color = color.slice(0, 3);
- return {
- backgroundColor: rgba(color, 0.2),
- pointBackgroundColor: rgba(color, alpha),
- pointHoverBackgroundColor: rgba(color, 0.8),
- borderColor: rgba(color, alpha),
- pointBorderColor: '#fff',
- pointHoverBorderColor: rgba(color, alpha)
- };
- }
- function getRandomInt (min, max) {
- return Math.floor(Math.random() * (max - min + 1)) + min;
- }
- function rgba (color, alpha) {
- // rgba not supported by IE8
- return useExcanvas ? 'rgb(' + color.join(',') + ')' : 'rgba(' + color.concat(alpha).join(',') + ')';
- }
- // Credit: http://stackoverflow.com/a/11508164/1190235
- function hexToRgb (hex) {
- var bigint = parseInt(hex, 16),
- r = (bigint >> 16) & 255,
- g = (bigint >> 8) & 255,
- b = bigint & 255;
- return [r, g, b];
- }
- function rgbStringToRgb (color) {
- var match = color.match(/^rgba?\(([\d,.]+)\)$/);
- if (! match) throw new Error('Cannot parse rgb value');
- color = match[1].split(',');
- return color.map(Number);
- }
- function hasData (scope) {
- return scope.chartData && scope.chartData.length;
- }
- function getChartColorFn (scope) {
- return typeof scope.chartGetColor === 'function' ? scope.chartGetColor : getRandomColor;
- }
- function getChartData (type, scope) {
- var colors = getColors(type, scope);
- return Array.isArray(scope.chartData[0]) ?
- getDataSets(scope.chartLabels, scope.chartData, scope.chartSeries || [], colors, scope.chartDatasetOverride) :
- getData(scope.chartLabels, scope.chartData, colors, scope.chartDatasetOverride);
- }
- function getDataSets (labels, data, series, colors, datasetOverride) {
- return {
- labels: labels,
- datasets: data.map(function (item, i) {
- var dataset = angular.extend({}, colors[i], {
- label: series[i],
- data: item
- });
- if (datasetOverride && datasetOverride.length >= i) {
- angular.merge(dataset, datasetOverride[i]);
- }
- return dataset;
- })
- };
- }
- function getData (labels, data, colors, datasetOverride) {
- var dataset = {
- labels: labels,
- datasets: [{
- data: data,
- backgroundColor: colors.map(function (color) {
- return color.pointBackgroundColor;
- }),
- hoverBackgroundColor: colors.map(function (color) {
- return color.backgroundColor;
- })
- }]
- };
- if (datasetOverride) {
- angular.merge(dataset.datasets[0], datasetOverride);
- }
- return dataset;
- }
- function getChartOptions (type, scope) {
- return angular.extend({}, ChartJs.getOptions(type), scope.chartOptions);
- }
- function bindEvents (cvs, scope) {
- cvs.onclick = scope.chartClick ? getEventHandler(scope, 'chartClick', false) : angular.noop;
- cvs.onmousemove = scope.chartHover ? getEventHandler(scope, 'chartHover', true) : angular.noop;
- }
- function updateChart (values, scope) {
- if (Array.isArray(scope.chartData[0])) {
- scope.chart.data.datasets.forEach(function (dataset, i) {
- dataset.data = values[i];
- });
- } else {
- scope.chart.data.datasets[0].data = values;
- }
- scope.chart.update();
- scope.$emit('chart-update', scope.chart);
- }
- function isEmpty (value) {
- return ! value ||
- (Array.isArray(value) && ! value.length) ||
- (typeof value === 'object' && ! Object.keys(value).length);
- }
- function canDisplay (type, scope, elem, options) {
- // TODO: check parent?
- if (options.responsive && elem[0].clientHeight === 0) {
- $timeout(function () {
- createChart(type, scope, elem);
- }, 50, false);
- return false;
- }
- return true;
- }
- function destroyChart(scope) {
- if(! scope.chart) return;
- scope.chart.destroy();
- scope.$emit('chart-destroy', scope.chart);
- }
- }
- }));
|