'use strict'; angular.module('highcharts-ng', []) .directive('highchart', function () { function prependMethod(obj, method, func) { var original = obj[method]; obj[method] = function () { var args = Array.prototype.slice.call(arguments); func.apply(this, args); if(original) { return original.apply(this, args); } else { return; } }; } function deepExtend(destination, source) { for (var property in source) { if (source[property] && source[property].constructor && source[property].constructor === Object) { destination[property] = destination[property] || {}; deepExtend(destination[property], source[property]); } else { destination[property] = source[property]; } } return destination; } var seriesId = 0; var ensureIds = function (series) { series.forEach(function (s) { if (!angular.isDefined(s.id)) { s.id = "series-" + seriesId++; } }); } var defaultOptions = { chart: { events: {} }, title: {}, series: [], navigator: {enabled: false} } var getMergedOptions = function (scope, element, config) { var mergedOptions = {} if (config.options) { mergedOptions = deepExtend(defaultOptions, config.options); } else { mergedOptions = defaultOptions; } mergedOptions.chart.renderTo = element[0]; if(scope.config.xAxis) { prependMethod(mergedOptions.chart.events, 'selection', function(e){ var thisChart = this; if(e.xAxis) { scope.$apply(function () { scope.config.xAxis.currentMin = e.xAxis[0].min; scope.config.xAxis.currentMax = e.xAxis[0].max; }); } else { //handle reset button - zoom out to all scope.$apply(function () { scope.config.xAxis.currentMin = thisChart.xAxis[0].dataMin; scope.config.xAxis.currentMax = thisChart.xAxis[0].dataMax; }); } }); prependMethod(mergedOptions.chart.events, 'addSeries', function(e){ scope.config.xAxis.currentMin = this.xAxis[0].min || scope.config.xAxis.currentMin; scope.config.xAxis.currentMax = this.xAxis[0].max || scope.config.xAxis.currentMax; }); } if(config.xAxis) { mergedOptions.xAxis = angular.copy(config.xAxis) } if(config.title) { mergedOptions.title = config.title } return mergedOptions } var updateZoom = function (axis, modelAxis) { var extremes = axis.getExtremes(); if(modelAxis.currentMin !== extremes.dataMin || modelAxis.currentMax !== extremes.dataMax) { axis.setExtremes(modelAxis.currentMin, modelAxis.currentMax, false); } } var processExtremes = function(chart, axis) { if(axis.currentMin || axis.currentMax) { chart.xAxis[0].setExtremes(axis.currentMin, axis.currentMax, true); } } var processSeries = function(chart, series) { var ids = [] if(series) { ensureIds(series); //Find series to add or update series.forEach(function (s) { ids.push(s.id) var chartSeries = chart.get(s.id); if (chartSeries) { chartSeries.update(angular.copy(s), false); } else { chart.addSeries(angular.copy(s), false) } }); } //Now remove any missing series chart.series.forEach(function (s) { if (ids.indexOf(s.options.id) < 0) { s.remove(false); } }); } var initialiseChart = function(scope, element, config) { var mergedOptions = getMergedOptions(scope, element, config); var chart = new Highcharts.Chart(mergedOptions); if(config.xAxis) { processExtremes(chart, config.xAxis); } processSeries(chart, config.series); chart.redraw(); return chart; } return { restrict: 'EC', replace: true, template: '
', scope: { config: '=' }, link: function (scope, element, attrs) { var chart = initialiseChart(scope, element, scope.config); scope.$watch("config.series", function (newSeries, oldSeries) { //do nothing when called on registration if (newSeries === oldSeries) return; processSeries(chart, newSeries); chart.redraw(); }, true); scope.$watch("config.title", function (newTitle) { chart.setTitle(newTitle, true); }, true); scope.$watch("config.loading", function (loading) { if(loading === false) { chart.hideLoading() } else { chart.showLoading() } }, true); scope.$watch("config.xAxis", function (newAxes, oldAxes) { if (newAxes === oldAxes) return; if(newAxes) { chart.xAxis[0].update(newAxes); updateZoom(chart.xAxis[0], angular.copy(newAxes)); chart.redraw(); } }, true); scope.$watch("config.options", function (newOptions, oldOptions, scope) { //do nothing when called on registration if (newOptions === oldOptions) return; chart.destroy(); chart = initialiseChart(scope, element, scope.config); }, true); } } });