diff --git a/src/components/chart/chart.directive.coffee b/src/components/chart/chart.directive.coffee index 10b84b25..cbad37f5 100644 --- a/src/components/chart/chart.directive.coffee +++ b/src/components/chart/chart.directive.coffee @@ -46,7 +46,7 @@ angular $log.error(error) (chartData) -> userAgent = $window.navigator.userAgent - + # Previously, this hack was only for Safari, # then we activated it for chrome after release of version 51, # now it becomes the default behaviour as IE11 has shown some unstability in the display of charts diff --git a/src/components/widgets-common/chart-threshold/chart-threshold.component.coffee b/src/components/widgets-common/chart-threshold/chart-threshold.component.coffee index d5d4cad2..6eed97c6 100644 --- a/src/components/widgets-common/chart-threshold/chart-threshold.component.coffee +++ b/src/components/widgets-common/chart-threshold/chart-threshold.component.coffee @@ -67,6 +67,10 @@ module.component('chartThreshold', { , 100) return + handleInvalidAlertAmount = -> + toastr.error("Please choose a number one or greater.", 'Error') + ctrl.loading = false + ctrl.saveKpi = -> return if ctrl.loading ctrl.loading = true @@ -74,7 +78,10 @@ module.component('chartThreshold', { params.targets[ctrl.kpi.watchables[0]] = [{ "#{ctrl.kpiTargetMode}": parseFloat(ctrl.draftTarget.value) }] - return unless ImpacKpisSvc.validateKpiTargets(params.targets) + + unless ImpacKpisSvc.validateKpiTargets(params.targets) + return handleInvalidAlertAmount() + promise = if ctrl.isEditingKpi ImpacKpisSvc.update(getKpi(), params, false).then( (kpi)-> @@ -121,7 +128,7 @@ module.component('chartThreshold', { onChartNotify = (chart)-> ctrl.chart = chart return unless validateHistParameters() - ctrl.chart.options.chartOnClickCallbacks.push(onChartClick) + ctrl.chart.addOnClickCallback(onChartClick) _.each buildThresholdsFromKpis(), (threshold)-> thresholdSerie = ctrl.chart.findThreshold(threshold.kpiId) thresholdSerie = ctrl.chart.addThreshold(threshold) unless thresholdSerie? diff --git a/src/components/widgets/accounts-cash-balance/accounts-cash-balance.directive.coffee b/src/components/widgets/accounts-cash-balance/accounts-cash-balance.directive.coffee index 6bc66080..b214fb59 100644 --- a/src/components/widgets/accounts-cash-balance/accounts-cash-balance.directive.coffee +++ b/src/components/widgets/accounts-cash-balance/accounts-cash-balance.directive.coffee @@ -4,11 +4,16 @@ module.controller('WidgetAccountsCashBalanceCtrl', ($scope, $q, $timeout, $filte w = $scope.widget # Define settings - # -------------------------------------- + # ------------------------------------- $scope.orgDeferred = $q.defer() settingsPromises = [$scope.orgDeferred.promise] + # Setup Highcharts Options + # ------------------------------------- + getPeriod = -> + w.metadata? && w.metadata.hist_parameters? && w.metadata.hist_parameters.period || 'MONTHLY' + # Widget specific methods # -------------------------------------- w.initContext = -> @@ -46,9 +51,6 @@ module.controller('WidgetAccountsCashBalanceCtrl', ($scope, $q, $timeout, $filte return '#000' unless serie serie.color - getPeriod = -> - w.metadata? && w.metadata.hist_parameters? && w.metadata.hist_parameters.period || 'MONTHLY' - getSerieByAccount = (series, account)-> _.find(series, (serie)-> (serie.id || serie.options && serie.options.id) == account.id) @@ -79,19 +81,23 @@ module.controller('WidgetAccountsCashBalanceCtrl', ($scope, $q, $timeout, $filte # Called after initContext - draws the chart using HighCharts w.format = -> - options = - chartType: 'line' - currency: w.metadata.currency - period: getPeriod() - showToday: true - showLegend: false - withZooming: + $timeout -> + _highChartOptions = + chartType: 'line' + currency: w.metadata.currency + period: getPeriod() + showToday: true + showLegend: false + + # Add Custom Highchart Options. + $scope.chart = new HighchartsFactory($scope.chartId(), w.content.chart.series, _highChartOptions) + $scope.chart.addXAxisOptions({ defaults: w.metadata.xAxis callback: onZoom + }) + $scope.chart.removeLegend() - $timeout -> - $scope.chart ||= new HighchartsFactory($scope.chartId(), w.content.chart, options) - $scope.chart.render(w.content.chart, options) + $scope.chart.render() # Widget is ready: can trigger the "wait for settings to be ready" # -------------------------------------- diff --git a/src/components/widgets/accounts-cash-projection/accounts-cash-projection.directive.coffee b/src/components/widgets/accounts-cash-projection/accounts-cash-projection.directive.coffee index 40172fa7..107f9dc4 100644 --- a/src/components/widgets/accounts-cash-projection/accounts-cash-projection.directive.coffee +++ b/src/components/widgets/accounts-cash-projection/accounts-cash-projection.directive.coffee @@ -193,28 +193,26 @@ module.controller('WidgetAccountsCashProjectionCtrl', ($scope, $q, $filter, $tim # Executed after the widget and its settings are initialised and ready w.format = -> - # Instantiate and render chart - options = + _highChartOptions = chartType: 'line' - chartOnClickCallbacks: [] currency: w.metadata.currency showToday: true - showLegend: true - withZooming: - defaults: w.metadata.xAxis - callback: onZoom - $scope.chart ||= new HighchartsFactory($scope.chartId(), w.content.chart, options) - $scope.chart.render(w.content.chart, options) - - # Add events callbacks to chart object + # Add custom options to the chart before render. + $scope.chart = new HighchartsFactory($scope.chartId(), w.content.chart.series, _highChartOptions) $scope.chart.addCustomLegend(legendFormatter) $scope.chart.addSeriesEvent('click', onClickBar) $scope.chart.addSeriesEvent('legendItemClick', onClickLegend) + $scope.chart.addXAxisOptions(({ + defaults: w.metadata.xAxis + callback: onZoom + })) + $scope.chart.render() # Notifies parent element that the chart is ready to be displayed $scope.chartDeferred.notify($scope.chart) + $scope.widgetDeferred.resolve(settingsPromises) ) diff --git a/src/services/highcharts-factory/highcharts-factory.svc.coffee b/src/services/highcharts-factory/highcharts-factory.svc.coffee index 30387801..69cf6494 100644 --- a/src/services/highcharts-factory/highcharts-factory.svc.coffee +++ b/src/services/highcharts-factory/highcharts-factory.svc.coffee @@ -4,69 +4,51 @@ angular templates = line: Object.freeze - get: (series = [], options = {})-> - zoomingOptions = _.get(options, 'withZooming') - xAxisOptions = if zoomingOptions? - { - events: - setExtremes: zoomingOptions.callback - max: _.get(zoomingOptions.defaults, 'max') - min: _.get(zoomingOptions.defaults, 'min') - } - - chart: - type: 'line' - zoomType: 'x' - spacingTop: 20 - events: - click: (event)-> _.each(_.get(options, 'chartOnClickCallbacks', []), (cb)-> cb(event)) + chart: + type: 'line' + zoomType: 'x' + spacingTop: 20 + legend: + enabled: true + layout: 'vertical' + align: 'left' + verticalAlign: 'middle' + title: null + credits: + enabled: false + yAxis: title: null - credits: - enabled: false - legend: - enabled: _.get(options, 'showLegend', true) - layout: 'vertical' - align: 'left' - verticalAlign: 'middle' - xAxis: xAxisOptions - yAxis: - title: null - startOnTick: true - minPadding: 0 - series: series - rangeSelector: - buttons: [ - { type: 'month', count: 4, text: 'def.' }, - { type: 'month', count: 1, text: '1m' }, - { type: 'month', count: 3, text: '3m' }, - { type: 'month', count: 6, text: '6m' }, - { type: 'year', count: 1, text: '1y' }, - { type: 'all', text: 'All' } - ] - selected: (if _.get(xAxisOptions, 'min') then null else 0) + startOnTick: true + minPadding: 0 + buttons: [ + { type: 'month', count: 4, text: 'def.' }, + { type: 'month', count: 1, text: '1m' }, + { type: 'month', count: 3, text: '3m' }, + { type: 'month', count: 6, text: '6m' }, + { type: 'year', count: 1, text: '1y' }, + { type: 'all', text: 'All' } + ] todayUTC = moment().startOf('day').add(moment().utcOffset(), 'minutes') class Chart - constructor: (@id, @data = {}, @options = {})-> - @_template = templates[@options.chartType] + constructor: (@id, @series = {}, @settings = {})-> + # Setup the basic options for highcharts. + template = templates[@settings.chartType] + formatters = @formatters(@settings.currency) + todayMarker = @todayMarker(@settings.showToday, @settings.markerColor) + onClickCallbacks = @onClickCallbacks(@settings.chartOnClickCallbacks) + series = { series: @series } + @highChartOptions = angular.merge({}, series, template, formatters, todayMarker, onClickCallbacks) return - render: (data, options)-> - @data = data if _.isObject(data) - angular.extend(@options, options) - chartConfig = angular.merge({}, @template(), @formatters(), @todayMarker()) - if _.isEmpty(@hc) - @hc = Highcharts.stockChart(@id, chartConfig) - else - @hc.update(chartConfig) + render: () -> + # Options are already populated in the constructor, and through the options setter methods. + # It is faster to create a new stockChart than to update an existing one when data changes. + @hc = Highcharts.stockChart(@id, @highChartOptions) return @ - template: -> - @_template.get(@data.series, @options) - - formatters: -> - currency = @options.currency + formatters: (currency) -> xAxis: labels: formatter: -> @@ -86,11 +68,11 @@ angular name = _.startCase _.trim name.toLowerCase().replace(/\s*projected\s*/, ' ') "#{date}
#{name}: #{amount}" - todayMarker: -> - return {} unless @options.showToday + todayMarker: (showToday, markerColor = 'rgba(0, 85, 255, 0.2)') -> + return {} unless showToday xAxis: plotLines: [{ - color: _.get(@options, 'todayMarkerColor', 'rgba(0, 85, 255, 0.2)') + color: markerColor value: todayUTC.unix() * 1000 width: 1 label: @@ -104,7 +86,7 @@ angular addThreshold: (thresholdOptions)-> return if _.isEmpty(@hc) # Initialize data matrix - data = angular.copy @data.series[0].data + data = angular.copy @series[0].data for vector in data # When in the past, set y-axis value at null if !thresholdOptions.fullLengthThresholds && moment(vector[0]) < todayUTC @@ -130,22 +112,51 @@ angular return unless thresholdSerie? && eventName? && _.isFunction(callback) Highcharts.addEvent(thresholdSerie, eventName, (_event)-> callback(thresholdSerie)) - # Extend default chart formatters to add custom legend img icon - addCustomLegend: (formatterCallback, useHTML = true) -> - @hc.legend.update({ - useHTML: useHTML - labelFormatter: formatterCallback - }) + addCustomLegend: (labelFormatter, useHTML = true, showLegend = true) -> + legend = + legend: + labelFormatter: labelFormatter + useHTML: useHTML + enabled: showLegend + angular.merge(@highChartOptions, legend) + + removeLegend: () -> + legend = + legend: + enabled: false + angular.merge(@highChartOptions, legend) - # Adds events to series objects addSeriesEvent: (eventName, callback) -> - return if _.isEmpty(@hc) eventHash = {} eventHash[eventName] = callback - @hc.update({ + plotOptions = plotOptions: series: events: eventHash - }) - @hc + angular.merge(@highChartOptions, plotOptions) + + onClickCallbacks: (chartOnClickCallbacks = []) -> + # We need onClickCallbacks pointing to settings.onClickCallbacks, so that we can add callbacks to the settings later on. + @settings.chartOnClickCallbacks = chartOnClickCallbacks + click = (event) -> _.each(@settings.chartOnClickCallbacks, (cb) -> cb(event)) + chart: + events: + click: click.bind(@) + + addOnClickCallback: (event) -> + @settings.chartOnClickCallbacks.push(event) + + addXAxisOptions: (zoomingOptions) -> + xAxisOptions = if zoomingOptions? + events: + setExtremes: zoomingOptions.callback + max: _.get(zoomingOptions.defaults, 'max') + min: _.get(zoomingOptions.defaults, 'min') + + xAxis = + xAxis: xAxisOptions + rangeSelector: + selected: (if _.get(xAxisOptions, 'min') then null else 0) + + angular.merge(@highChartOptions, xAxis) )