Built motion from commit b598105.|2.0.7
[motion2.git] / public / bower_components / angular-nvd3 / dist / angular-nvd3.js
1 /**************************************************************************
2 * AngularJS-nvD3, v1.0.7; MIT
3 * http://krispo.github.io/angular-nvd3
4 **************************************************************************/
5 (function(){
6
7     'use strict';
8
9     angular.module('nvd3', [])
10
11         .directive('nvd3', ['nvd3Utils', function(nvd3Utils){
12             return {
13                 restrict: 'AE',
14                 scope: {
15                     data: '=',      //chart data, [required]
16                     options: '=',   //chart options, according to nvd3 core api, [required]
17                     api: '=?',      //directive global api, [optional]
18                     events: '=?',   //global events that directive would subscribe to, [optional]
19                     config: '=?',    //global directive configuration, [optional]
20                     onReady: '&?' //callback function that is called with internal scope when directive is created [optional]
21                 },
22                 link: function(scope, element, attrs){
23                     var defaultConfig = {
24                         extended: false,
25                         visible: true,
26                         disabled: false,
27                         refreshDataOnly: true,
28                         deepWatchOptions: true,
29                         deepWatchData: true,
30                         deepWatchDataDepth: 2, // 0 - by reference (cheap), 1 - by collection item (the middle), 2 - by value (expensive)
31                         debounce: 10, // default 10ms, time silence to prevent refresh while multiple options changes at a time
32                         debounceImmediate: true // immediate flag for debounce function
33                     };
34
35                     //flag indicates if directive and chart is ready
36                     scope.isReady = false;
37
38                     //basic directive configuration
39                     scope._config = angular.extend(defaultConfig, scope.config);
40
41                     //directive global api
42                     scope.api = {
43                         // Fully refresh directive
44                         refresh: function(){
45                             scope.api.updateWithOptions(scope.options);
46                             scope.isReady = true;
47                         },
48
49                         // Fully refresh directive with specified timeout
50                         refreshWithTimeout: function(t){
51                             setTimeout(function(){
52                                 scope.api.refresh();
53                             }, t);
54                         },
55
56                         // Update chart layout (for example if container is resized)
57                         update: function() {
58                             if (scope.chart && scope.svg) {
59                                 scope.svg.datum(scope.data).call(scope.chart);
60                                 // scope.chart.update();
61                             } else {
62                                 scope.api.refresh();
63                             }
64                         },
65
66                         // Update chart layout with specified timeout
67                         updateWithTimeout: function(t){
68                             setTimeout(function(){
69                                 scope.api.update();
70                             }, t);
71                         },
72
73                         // Update chart with new options
74                         updateWithOptions: function(options){
75                             // Clearing
76                             scope.api.clearElement();
77
78                             // Exit if options are not yet bound
79                             if (angular.isDefined(options) === false) return;
80
81                             // Exit if chart is hidden
82                             if (!scope._config.visible) return;
83
84                             // Initialize chart with specific type
85                             scope.chart = nv.models[options.chart.type]();
86
87                             // Generate random chart ID
88                             scope.chart.id = Math.random().toString(36).substr(2, 15);
89
90                             angular.forEach(scope.chart, function(value, key){
91                                 if (key[0] === '_');
92                                 else if ([
93                                         'clearHighlights',
94                                         'highlightPoint',
95                                         'id',
96                                         'options',
97                                         'resizeHandler',
98                                         'state',
99                                         'open',
100                                         'close',
101                                         'tooltipContent'
102                                     ].indexOf(key) >= 0);
103
104                                 else if (key === 'dispatch') {
105                                     if (options.chart[key] === undefined || options.chart[key] === null) {
106                                         if (scope._config.extended) options.chart[key] = {};
107                                     }
108                                     configureEvents(scope.chart[key], options.chart[key]);
109                                 }
110
111                                 else if ([
112                                         'bars',
113                                         'bars1',
114                                         'bars2',
115                                         'boxplot',
116                                         'bullet',
117                                         'controls',
118                                         'discretebar',
119                                         'distX',
120                                         'distY',
121                                         'interactiveLayer',
122                                         'legend',
123                                         'lines',
124                                         'lines1',
125                                         'lines2',
126                                         'multibar',
127                                         'pie',
128                                         'scatter',
129                                         'scatters1',
130                                         'scatters2',
131                                         'sparkline',
132                                         'stack1',
133                                         'stack2',
134                                         'sunburst',
135                                         'tooltip',
136                                         'x2Axis',
137                                         'xAxis',
138                                         'y1Axis',
139                                         'y2Axis',
140                                         'y3Axis',
141                                         'y4Axis',
142                                         'yAxis',
143                                         'yAxis1',
144                                         'yAxis2'
145                                     ].indexOf(key) >= 0 ||
146                                         // stacked is a component for stackedAreaChart, but a boolean for multiBarChart and multiBarHorizontalChart
147                                     (key === 'stacked' && options.chart.type === 'stackedAreaChart')) {
148                                     if (options.chart[key] === undefined || options.chart[key] === null) {
149                                         if (scope._config.extended) options.chart[key] = {};
150                                     }
151                                     configure(scope.chart[key], options.chart[key], options.chart.type);
152                                 }
153
154                                 //TODO: need to fix bug in nvd3
155                                 else if ((key === 'focusHeight') && options.chart.type === 'lineChart');
156                                 else if ((key === 'focusHeight') && options.chart.type === 'lineWithFocusChart');
157                                 else if ((key === 'xTickFormat' || key === 'yTickFormat') && options.chart.type === 'lineWithFocusChart');
158                                 else if ((key === 'tooltips') && options.chart.type === 'boxPlotChart');
159                                 else if ((key === 'tooltipXContent' || key === 'tooltipYContent') && options.chart.type === 'scatterChart');
160                                 else if ((key === 'x' || key === 'y') && options.chart.type === 'forceDirectedGraph');
161
162                                 else if (options.chart[key] === undefined || options.chart[key] === null){
163                                     if (scope._config.extended) {
164                                         if (key==='barColor')
165                                             options.chart[key] = value()();
166                                         else
167                                             options.chart[key] = value();
168                                     }
169                                 }
170
171                                 else scope.chart[key](options.chart[key]);
172                             });
173
174                             // Update with data
175                             if (options.chart.type === 'sunburstChart') {
176                                 scope.api.updateWithData(angular.copy(scope.data));
177                             } else {
178                                 scope.api.updateWithData(scope.data);
179                             }
180
181                             // Configure wrappers
182                             if (options['title'] || scope._config.extended) configureWrapper('title');
183                             if (options['subtitle'] || scope._config.extended) configureWrapper('subtitle');
184                             if (options['caption'] || scope._config.extended) configureWrapper('caption');
185
186
187                             // Configure styles
188                             if (options['styles'] || scope._config.extended) configureStyles();
189
190                             nv.addGraph(function() {
191                                 if (!scope.chart) return;
192
193                                 // Remove resize handler. Due to async execution should be placed here, not in the clearElement
194                                 if (scope.chart.resizeHandler) scope.chart.resizeHandler.clear();
195
196                                 // Update the chart when window resizes
197                                 scope.chart.resizeHandler = nv.utils.windowResize(function() {
198                                     scope.chart && scope.chart.update && scope.chart.update();
199                                 });
200
201                                 /// Zoom feature
202                                 if (options.chart.zoom !== undefined && [
203                                         'scatterChart',
204                                         'lineChart',
205                                         'candlestickBarChart',
206                                         'cumulativeLineChart',
207                                         'historicalBarChart',
208                                         'ohlcBarChart',
209                                         'stackedAreaChart'
210                                     ].indexOf(options.chart.type) > -1) {
211                                     nvd3Utils.zoom(scope, options);
212                                 }
213
214                                 return scope.chart;
215                             }, options.chart['callback']);
216                         },
217
218                         // Update chart with new data
219                         updateWithData: function (data){
220                             if (data) {
221                                 // remove whole svg element with old data
222                                 d3.select(element[0]).select('svg').remove();
223
224                                 var h, w;
225
226                                 // Select the current element to add <svg> element and to render the chart in
227                                 scope.svg = d3.select(element[0]).append('svg');
228                                 if (h = scope.options.chart.height) {
229                                     if (!isNaN(+h)) h += 'px'; //check if height is number
230                                     scope.svg.attr('height', h).style({height: h});
231                                 }
232                                 if (w = scope.options.chart.width) {
233                                     if (!isNaN(+w)) w += 'px'; //check if width is number
234                                     scope.svg.attr('width', w).style({width: w});
235                                 } else {
236                                     scope.svg.attr('width', '100%').style({width: '100%'});
237                                 }
238
239                                 scope.svg.datum(data).call(scope.chart);
240                             }
241                         },
242
243                         // Fully clear directive element
244                         clearElement: function (){
245                             element.find('.title').remove();
246                             element.find('.subtitle').remove();
247                             element.find('.caption').remove();
248                             element.empty();
249
250                             // remove tooltip if exists
251                             if (scope.chart && scope.chart.tooltip && scope.chart.tooltip.id) {
252                                 d3.select('#' + scope.chart.tooltip.id()).remove();
253                             }
254
255                             // To be compatible with old nvd3 (v1.7.1)
256                             if (nv.graphs && scope.chart) {
257                                 for (var i = nv.graphs.length - 1; i >= 0; i--) {
258                                     if (nv.graphs[i] && (nv.graphs[i].id === scope.chart.id)) {
259                                         nv.graphs.splice(i, 1);
260                                     }
261                                 }
262                             }
263                             if (nv.tooltip && nv.tooltip.cleanup) {
264                                 nv.tooltip.cleanup();
265                             }
266                             if (scope.chart && scope.chart.resizeHandler) scope.chart.resizeHandler.clear();
267                             scope.chart = null;
268                         },
269
270                         // Get full directive scope
271                         getScope: function(){ return scope; },
272
273                         // Get directive element
274                         getElement: function(){ return element; }
275                     };
276
277                     // Configure the chart model with the passed options
278                     function configure(chart, options, chartType){
279                         if (chart && options){
280                             angular.forEach(chart, function(value, key){
281                                 if (key[0] === '_');
282                                 else if (key === 'dispatch') {
283                                     if (options[key] === undefined || options[key] === null) {
284                                         if (scope._config.extended) options[key] = {};
285                                     }
286                                     configureEvents(value, options[key]);
287                                 }
288                                 else if (key === 'tooltip') {
289                                     if (options[key] === undefined || options[key] === null) {
290                                         if (scope._config.extended) options[key] = {};
291                                     }
292                                     configure(chart[key], options[key], chartType);
293                                 }
294                                 else if (key === 'contentGenerator') {
295                                     if (options[key]) chart[key](options[key]);
296                                 }
297                                 else if ([
298                                         'axis',
299                                         'clearHighlights',
300                                         'defined',
301                                         'highlightPoint',
302                                         'nvPointerEventsClass',
303                                         'options',
304                                         'rangeBand',
305                                         'rangeBands',
306                                         'scatter',
307                                         'open',
308                                         'close',
309                                         'node'
310                                     ].indexOf(key) === -1) {
311                                     if (options[key] === undefined || options[key] === null){
312                                         if (scope._config.extended) options[key] = value();
313                                     }
314                                     else chart[key](options[key]);
315                                 }
316                             });
317                         }
318                     }
319
320                     // Subscribe to the chart events (contained in 'dispatch')
321                     // and pass eventHandler functions in the 'options' parameter
322                     function configureEvents(dispatch, options){
323                         if (dispatch && options){
324                             angular.forEach(dispatch, function(value, key){
325                                 if (options[key] === undefined || options[key] === null){
326                                     if (scope._config.extended) options[key] = value.on;
327                                 }
328                                 else dispatch.on(key + '._', options[key]);
329                             });
330                         }
331                     }
332
333                     // Configure 'title', 'subtitle', 'caption'.
334                     // nvd3 has no sufficient models for it yet.
335                     function configureWrapper(name){
336                         var _ = nvd3Utils.deepExtend(defaultWrapper(name), scope.options[name] || {});
337
338                         if (scope._config.extended) scope.options[name] = _;
339
340                         var wrapElement = angular.element('<div></div>').html(_['html'] || '')
341                             .addClass(name).addClass(_.className)
342                             .removeAttr('style')
343                             .css(_.css);
344
345                         if (!_['html']) wrapElement.text(_.text);
346
347                         if (_.enable) {
348                             if (name === 'title') element.prepend(wrapElement);
349                             else if (name === 'subtitle') angular.element(element[0].querySelector('.title')).after(wrapElement);
350                             else if (name === 'caption') element.append(wrapElement);
351                         }
352                     }
353
354                     // Add some styles to the whole directive element
355                     function configureStyles(){
356                         var _ = nvd3Utils.deepExtend(defaultStyles(), scope.options['styles'] || {});
357
358                         if (scope._config.extended) scope.options['styles'] = _;
359
360                         angular.forEach(_.classes, function(value, key){
361                             value ? element.addClass(key) : element.removeClass(key);
362                         });
363
364                         element.removeAttr('style').css(_.css);
365                     }
366
367                     // Default values for 'title', 'subtitle', 'caption'
368                     function defaultWrapper(_){
369                         switch (_){
370                             case 'title': return {
371                                 enable: false,
372                                 text: 'Write Your Title',
373                                 className: 'h4',
374                                 css: {
375                                     width: scope.options.chart.width + 'px',
376                                     textAlign: 'center'
377                                 }
378                             };
379                             case 'subtitle': return {
380                                 enable: false,
381                                 text: 'Write Your Subtitle',
382                                 css: {
383                                     width: scope.options.chart.width + 'px',
384                                     textAlign: 'center'
385                                 }
386                             };
387                             case 'caption': return {
388                                 enable: false,
389                                 text: 'Figure 1. Write Your Caption text.',
390                                 css: {
391                                     width: scope.options.chart.width + 'px',
392                                     textAlign: 'center'
393                                 }
394                             };
395                         }
396                     }
397
398                     // Default values for styles
399                     function defaultStyles(){
400                         return {
401                             classes: {
402                                 'with-3d-shadow': true,
403                                 'with-transitions': true,
404                                 'gallery': false
405                             },
406                             css: {}
407                         };
408                     }
409
410                     /* Event Handling */
411                     // Watching on options changing
412                     if (scope._config.deepWatchOptions) {
413                         scope.$watch('options', nvd3Utils.debounce(function(newOptions){
414                             if (!scope._config.disabled) scope.api.refresh();
415                         }, scope._config.debounce, scope._config.debounceImmediate), true);
416                     }
417
418                     // Watching on data changing
419                     function dataWatchFn(newData, oldData) {
420                         if (newData !== oldData){
421                             if (!scope._config.disabled) {
422                                 scope._config.refreshDataOnly ? scope.api.update() : scope.api.refresh(); // if wanted to refresh data only, use update method, otherwise use full refresh.
423                             }
424                         }
425                     }
426                     if (scope._config.deepWatchData) {
427                         if (scope._config.deepWatchDataDepth === 1) {
428                             scope.$watchCollection('data', dataWatchFn);
429                         } else {
430                             scope.$watch('data', dataWatchFn, scope._config.deepWatchDataDepth === 2);
431                         }
432                     }
433
434                     // Watching on config changing
435                     scope.$watch('config', function(newConfig, oldConfig){
436                         if (newConfig !== oldConfig){
437                             scope._config = angular.extend(defaultConfig, newConfig);
438                             scope.api.refresh();
439                         }
440                     }, true);
441
442                     // Refresh chart first time if deepWatchOptions and deepWatchData are false
443                     if (!scope._config.deepWatchOptions && !scope._config.deepWatchData) {
444                         scope.api.refresh();
445                     }
446
447                     //subscribe on global events
448                     angular.forEach(scope.events, function(eventHandler, event){
449                         scope.$on(event, function(e, args){
450                             return eventHandler(e, scope, args);
451                         });
452                     });
453
454                     // remove completely when directive is destroyed
455                     element.on('$destroy', function () {
456                         scope.api.clearElement();
457                     });
458
459                     // trigger onReady callback if directive is ready
460                     scope.$watch('isReady', function(isReady){
461                         if (isReady) {
462                             if (scope.onReady && typeof scope.onReady() === 'function') scope.onReady()(scope, element);
463                         }
464                     });
465                 }
466             };
467         }])
468
469         .factory('nvd3Utils', function(){
470             return {
471                 debounce: function(func, wait, immediate) {
472                     var timeout;
473                     return function() {
474                         var context = this, args = arguments;
475                         var later = function() {
476                             timeout = null;
477                             if (!immediate) func.apply(context, args);
478                         };
479                         var callNow = immediate && !timeout;
480                         clearTimeout(timeout);
481                         timeout = setTimeout(later, wait);
482                         if (callNow) func.apply(context, args);
483                     };
484                 },
485                 deepExtend: function(dst){
486                     var me = this;
487                     angular.forEach(arguments, function(obj) {
488                         if (obj !== dst) {
489                             angular.forEach(obj, function(value, key) {
490                                 if (dst[key] && dst[key].constructor && dst[key].constructor === Object) {
491                                     me.deepExtend(dst[key], value);
492                                 } else {
493                                     dst[key] = value;
494                                 }
495                             });
496                         }
497                     });
498                     return dst;
499                 },
500                 zoom: function(scope, options) {
501                     var zoom = options.chart.zoom;
502
503                     // check if zoom enabled
504                     var enabled = (typeof zoom.enabled === 'undefined' || zoom.enabled === null) ? true : zoom.enabled;
505                     if (!enabled) return;
506
507                     var xScale = scope.chart.xAxis.scale()
508                         , yScale = scope.chart.yAxis.scale()
509                         , xDomain = scope.chart.xDomain || xScale.domain
510                         , yDomain = scope.chart.yDomain || yScale.domain
511                         , x_boundary = xScale.domain().slice()
512                         , y_boundary = yScale.domain().slice()
513
514                     // initialize zoom options
515                         , scale = zoom.scale || 1
516                         , translate = zoom.translate || [0, 0]
517                         , scaleExtent = zoom.scaleExtent || [1, 10]
518                         , useFixedDomain = zoom.useFixedDomain || false
519                         , useNiceScale = zoom.useNiceScale || false
520                         , horizontalOff = zoom.horizontalOff || false
521                         , verticalOff = zoom.verticalOff || false
522                         , unzoomEventType = zoom.unzoomEventType || 'dblclick.zoom'
523
524                     // auxiliary functions
525                         , fixDomain
526                         , d3zoom
527                         , zoomed
528                         , unzoomed
529                         , zoomend
530                         ;
531
532                     // ensure nice axis
533                     if (useNiceScale) {
534                         xScale.nice();
535                         yScale.nice();
536                     }
537
538                     // fix domain
539                     fixDomain = function (domain, boundary) {
540                         domain[0] = Math.min(Math.max(domain[0], boundary[0]), boundary[1] - boundary[1] / scaleExtent[1]);
541                         domain[1] = Math.max(boundary[0] + boundary[1] / scaleExtent[1], Math.min(domain[1], boundary[1]));
542                         return domain;
543                     };
544
545                     // zoom event handler
546                     zoomed = function () {
547                         if (zoom.zoomed !== undefined) {
548                             var domains = zoom.zoomed(xScale.domain(), yScale.domain());
549                             if (!horizontalOff) xDomain([domains.x1, domains.x2]);
550                             if (!verticalOff) yDomain([domains.y1, domains.y2]);
551                         } else {
552                             if (!horizontalOff) xDomain(useFixedDomain ? fixDomain(xScale.domain(), x_boundary) : xScale.domain());
553                             if (!verticalOff) yDomain(useFixedDomain ? fixDomain(yScale.domain(), y_boundary) : yScale.domain());
554                         }
555                         scope.chart.update();
556                     };
557
558                     // unzoomed event handler
559                     unzoomed = function () {
560                         if (zoom.unzoomed !== undefined) {
561                             var domains = zoom.unzoomed(xScale.domain(), yScale.domain());
562                             if (!horizontalOff) xDomain([domains.x1, domains.x2]);
563                             if (!verticalOff) yDomain([domains.y1, domains.y2]);
564                         } else {
565                             if (!horizontalOff) xDomain(x_boundary);
566                             if (!verticalOff) yDomain(y_boundary);
567                         }
568                         d3zoom.scale(scale).translate(translate);
569                         scope.chart.update();
570                     };
571
572                     // zoomend event handler
573                     zoomend = function () {
574                         if (zoom.zoomend !== undefined) {
575                             zoom.zoomend();
576                         }
577                     };
578
579                     // create d3 zoom handler
580                     d3zoom = d3.behavior.zoom()
581                         .x(xScale)
582                         .y(yScale)
583                         .scaleExtent(scaleExtent)
584                         .on('zoom', zoomed)
585                         .on('zoomend', zoomend);
586
587                     scope.svg.call(d3zoom);
588
589                     d3zoom.scale(scale).translate(translate).event(scope.svg);
590
591                     if (unzoomEventType !== 'none') scope.svg.on(unzoomEventType, unzoomed);
592                 }
593             };
594         });
595 })();