Rift.IO OSM R1 Initial Submission
[osm/UI.git] / skyquake / framework / js / n3-line-chart.js
1
2 /*
3 *
4 * Copyright 2016 RIFT.IO Inc
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 */
19
20 /*
21 line-chart - v1.1.9 - 21 June 2015
22 https://github.com/n3-charts/line-chart
23 Copyright (c) 2015 n3-charts
24 */
25 var directive, m, mod, old_m,
26 __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
27
28 old_m = angular.module('n3-charts.linechart', ['n3charts.utils']);
29
30 m = angular.module('n3-line-chart', ['n3charts.utils']);
31
32 directive = function(name, conf) {
33 old_m.directive(name, conf);
34 return m.directive(name, conf);
35 };
36
37 directive('linechart', [
38 'n3utils', '$window', '$timeout', function(n3utils, $window, $timeout) {
39 var link;
40 link = function(scope, element, attrs, ctrl) {
41 var dispatch, id, initialHandlers, isUpdatingOptions, promise, updateEvents, window_resize, _u;
42 _u = n3utils;
43 dispatch = _u.getEventDispatcher();
44 id = _u.uuid();
45 element[0].style['font-size'] = 0;
46 scope.redraw = function() {
47 scope.update();
48 };
49 isUpdatingOptions = false;
50 initialHandlers = {
51 onSeriesVisibilityChange: function(_arg) {
52 var index, newVisibility, series;
53 series = _arg.series, index = _arg.index, newVisibility = _arg.newVisibility;
54 scope.options.series[index].visible = newVisibility;
55 return scope.$apply();
56 }
57 };
58 scope.update = function() {
59 var axes, columnWidth, dataPerSeries, dimensions, fn, handlers, isThumbnail, options, svg;
60 options = _u.sanitizeOptions(scope.options, attrs.mode);
61 handlers = angular.extend(initialHandlers, _u.getTooltipHandlers(options));
62 dataPerSeries = _u.getDataPerSeries(scope.data, options);
63 dimensions = _u.getDimensions(options, element, attrs);
64 isThumbnail = attrs.mode === 'thumbnail';
65 _u.clean(element[0]);
66 svg = _u.bootstrap(element[0], id, dimensions);
67 fn = function(key) {
68 return (options.series.filter(function(s) {
69 return s.axis === key && s.visible !== false;
70 })).length > 0;
71 };
72 axes = _u.createAxes(svg, dimensions, options.axes).andAddThemIf({
73 all: !isThumbnail,
74 x: true,
75 y: fn('y'),
76 y2: fn('y2')
77 });
78 if (dataPerSeries.length) {
79 _u.setScalesDomain(axes, scope.data, options.series, svg, options);
80 }
81 _u.createContent(svg, id, options, handlers);
82 if (dataPerSeries.length) {
83 columnWidth = _u.getBestColumnWidth(axes, dimensions, dataPerSeries, options);
84 _u.drawArea(svg, axes, dataPerSeries, options, handlers).drawColumns(svg, axes, dataPerSeries, columnWidth, options, handlers, dispatch).drawLines(svg, axes, dataPerSeries, options, handlers);
85 if (options.drawDots) {
86 _u.drawDots(svg, axes, dataPerSeries, options, handlers, dispatch);
87 }
88 }
89 if (options.drawLegend) {
90 _u.drawLegend(svg, options.series, dimensions, handlers, dispatch);
91 }
92 if (options.tooltip.mode === 'scrubber') {
93 return _u.createGlass(svg, dimensions, handlers, axes, dataPerSeries, options, dispatch, columnWidth);
94 } else if (options.tooltip.mode !== 'none') {
95 return _u.addTooltips(svg, dimensions, options.axes);
96 }
97 };
98 updateEvents = function() {
99 if (scope.oldclick) {
100 dispatch.on('click', scope.oldclick);
101 } else if (scope.click) {
102 dispatch.on('click', scope.click);
103 } else {
104 dispatch.on('click', null);
105 }
106 if (scope.oldhover) {
107 dispatch.on('hover', scope.oldhover);
108 } else if (scope.hover) {
109 dispatch.on('hover', scope.hover);
110 } else {
111 dispatch.on('hover', null);
112 }
113 if (scope.oldfocus) {
114 dispatch.on('focus', scope.oldfocus);
115 } else if (scope.focus) {
116 dispatch.on('focus', scope.focus);
117 } else {
118 dispatch.on('focus', null);
119 }
120 if (scope.toggle) {
121 return dispatch.on('toggle', scope.toggle);
122 } else {
123 return dispatch.on('toggle', null);
124 }
125 };
126 promise = void 0;
127 window_resize = function() {
128 if (promise != null) {
129 $timeout.cancel(promise);
130 }
131 return promise = $timeout(scope.redraw, 1);
132 };
133 $window.addEventListener('resize', window_resize);
134 scope.$watch('data', scope.redraw, true);
135 scope.$watch('options', scope.redraw, true);
136 scope.$watchCollection('[click, hover, focus, toggle]', updateEvents);
137 scope.$watchCollection('[oldclick, oldhover, oldfocus]', updateEvents);
138 console.log('data', scope.data)
139 window_resize();
140 };
141 return {
142 replace: true,
143 restrict: 'E',
144 scope: {
145 data: '=',
146 options: '=',
147 oldclick: '=click',
148 oldhover: '=hover',
149 oldfocus: '=focus',
150 click: '=onClick',
151 hover: '=onHover',
152 focus: '=onFocus',
153 toggle: '=onToggle'
154 },
155 template: '<div></div>',
156 link: link
157 };
158 }
159 ]);
160
161 mod = angular.module('n3charts.utils', []);
162
163 mod.factory('n3utils', [
164 '$window', '$log', '$rootScope', function($window, $log, $rootScope) {
165 return {
166 addPatterns: function(svg, series) {
167 var pattern;
168 pattern = svg.select('defs').selectAll('pattern').data(series.filter(function(s) {
169 return s.striped;
170 })).enter().append('pattern').attr({
171 id: function(s) {
172 return s.type + 'Pattern_' + s.index;
173 },
174 patternUnits: "userSpaceOnUse",
175 x: 0,
176 y: 0,
177 width: 60,
178 height: 60
179 }).append('g').style({
180 'fill': function(s) {
181 return s.color;
182 },
183 'fill-opacity': 0.3
184 });
185 pattern.append('rect').style('fill-opacity', 0.3).attr('width', 60).attr('height', 60);
186 pattern.append('path').attr('d', "M 10 0 l10 0 l -20 20 l 0 -10 z");
187 pattern.append('path').attr('d', "M40 0 l10 0 l-50 50 l0 -10 z");
188 pattern.append('path').attr('d', "M60 10 l0 10 l-40 40 l-10 0 z");
189 return pattern.append('path').attr('d', "M60 40 l0 10 l-10 10 l -10 0 z");
190 },
191 drawArea: function(svg, scales, data, options) {
192 var areaSeries, drawers;
193 areaSeries = data.filter(function(series) {
194 return series.type === 'area';
195 });
196 this.addPatterns(svg, areaSeries);
197 drawers = {
198 y: this.createLeftAreaDrawer(scales, options.lineMode, options.tension),
199 y2: this.createRightAreaDrawer(scales, options.lineMode, options.tension)
200 };
201 svg.select('.content').selectAll('.areaGroup').data(areaSeries).enter().append('g').attr('class', function(s) {
202 return 'areaGroup ' + 'series_' + s.index;
203 }).append('path').attr('class', 'area').style('fill', function(s) {
204 if (s.striped !== true) {
205 return s.color;
206 }
207 return "url(#areaPattern_" + s.index + ")";
208 }).style('opacity', function(s) {
209 if (s.striped) {
210 return '1';
211 } else {
212 return '0.3';
213 }
214 }).attr('d', function(d) {
215 return drawers[d.axis](d.values);
216 });
217 return this;
218 },
219 createLeftAreaDrawer: function(scales, mode, tension) {
220 return d3.svg.area().x(function(d) {
221 return scales.xScale(d.x);
222 }).y0(function(d) {
223 return scales.yScale(d.y0);
224 }).y1(function(d) {
225 return scales.yScale(d.y0 + d.y);
226 }).interpolate(mode).tension(tension);
227 },
228 createRightAreaDrawer: function(scales, mode, tension) {
229 return d3.svg.area().x(function(d) {
230 return scales.xScale(d.x);
231 }).y0(function(d) {
232 return scales.y2Scale(d.y0);
233 }).y1(function(d) {
234 return scales.y2Scale(d.y0 + d.y);
235 }).interpolate(mode).tension(tension);
236 },
237 getPseudoColumns: function(data, options) {
238 var keys, pseudoColumns;
239 data = data.filter(function(s) {
240 return s.type === 'column';
241 });
242 pseudoColumns = {};
243 keys = [];
244 data.forEach(function(series) {
245 var i, inAStack, index;
246 inAStack = false;
247 options.stacks.forEach(function(stack, index) {
248 var _ref;
249 if ((series.id != null) && (_ref = series.id, __indexOf.call(stack.series, _ref) >= 0)) {
250 pseudoColumns[series.id] = index;
251 if (__indexOf.call(keys, index) < 0) {
252 keys.push(index);
253 }
254 return inAStack = true;
255 }
256 });
257 if (inAStack === false) {
258 i = pseudoColumns[series.id] = index = keys.length;
259 return keys.push(i);
260 }
261 });
262 return {
263 pseudoColumns: pseudoColumns,
264 keys: keys
265 };
266 },
267 getMinDelta: function(seriesData, key, scale, range) {
268 return d3.min(seriesData.map(function(series) {
269 return series.values.map(function(d) {
270 return scale(d[key]);
271 }).filter(function(e) {
272 if (range) {
273 return e >= range[0] && e <= range[1];
274 } else {
275 return true;
276 }
277 }).reduce(function(prev, cur, i, arr) {
278 var diff;
279 diff = i > 0 ? cur - arr[i - 1] : Number.MAX_VALUE;
280 if (diff < prev) {
281 return diff;
282 } else {
283 return prev;
284 }
285 }, Number.MAX_VALUE);
286 }));
287 },
288 getBestColumnWidth: function(axes, dimensions, seriesData, options) {
289 var colData, delta, innerWidth, keys, nSeries, pseudoColumns, _ref;
290 if (!(seriesData && seriesData.length !== 0)) {
291 return 10;
292 }
293 if ((seriesData.filter(function(s) {
294 return s.type === 'column';
295 })).length === 0) {
296 return 10;
297 }
298 _ref = this.getPseudoColumns(seriesData, options), pseudoColumns = _ref.pseudoColumns, keys = _ref.keys;
299 innerWidth = dimensions.width - dimensions.left - dimensions.right;
300 colData = seriesData.filter(function(d) {
301 return pseudoColumns.hasOwnProperty(d.id);
302 });
303 delta = this.getMinDelta(colData, 'x', axes.xScale, [0, innerWidth]);
304 if (delta > innerWidth) {
305 delta = 0.25 * innerWidth;
306 }
307 nSeries = keys.length;
308 return parseInt((delta - options.columnsHGap) / nSeries);
309 },
310 getColumnAxis: function(data, columnWidth, options) {
311 var keys, pseudoColumns, x1, _ref;
312 _ref = this.getPseudoColumns(data, options), pseudoColumns = _ref.pseudoColumns, keys = _ref.keys;
313 x1 = d3.scale.ordinal().domain(keys).rangeBands([0, keys.length * columnWidth], 0);
314 return function(s) {
315 var index;
316 if (pseudoColumns[s.id] == null) {
317 return 0;
318 }
319 index = pseudoColumns[s.id];
320 return x1(index) - keys.length * columnWidth / 2;
321 };
322 },
323 drawColumns: function(svg, axes, data, columnWidth, options, handlers, dispatch) {
324 var colGroup, x1;
325 data = data.filter(function(s) {
326 return s.type === 'column';
327 });
328 x1 = this.getColumnAxis(data, columnWidth, options);
329 data.forEach(function(s) {
330 return s.xOffset = x1(s) + columnWidth * .5;
331 });
332 colGroup = svg.select('.content').selectAll('.columnGroup').data(data).enter().append("g").attr('class', function(s) {
333 return 'columnGroup series_' + s.index;
334 }).attr('transform', function(s) {
335 return "translate(" + x1(s) + ",0)";
336 });
337 colGroup.each(function(series) {
338 return d3.select(this).selectAll("rect").data(series.values).enter().append("rect").style({
339 'stroke': series.color,
340 'fill': series.color,
341 'stroke-opacity': function(d) {
342 if (d.y === 0) {
343 return '0';
344 } else {
345 return '1';
346 }
347 },
348 'stroke-width': '1px',
349 'fill-opacity': function(d) {
350 if (d.y === 0) {
351 return 0;
352 } else {
353 return 0.7;
354 }
355 }
356 }).attr({
357 width: columnWidth,
358 x: function(d) {
359 return axes.xScale(d.x);
360 },
361 height: function(d) {
362 if (d.y === 0) {
363 return axes[d.axis + 'Scale'].range()[0];
364 }
365 return Math.abs(axes[d.axis + 'Scale'](d.y0 + d.y) - axes[d.axis + 'Scale'](d.y0));
366 },
367 y: function(d) {
368 if (d.y === 0) {
369 return 0;
370 } else {
371 return axes[d.axis + 'Scale'](Math.max(0, d.y0 + d.y));
372 }
373 }
374 }).on({
375 'click': function(d, i) {
376 return dispatch.click(d, i);
377 }
378 }).on('mouseover', function(d, i) {
379 dispatch.hover(d, i);
380 return typeof handlers.onMouseOver === "function" ? handlers.onMouseOver(svg, {
381 series: series,
382 x: axes.xScale(d.x),
383 y: axes[d.axis + 'Scale'](d.y0 + d.y),
384 datum: d
385 }, options.axes) : void 0;
386 }).on('mouseout', function(d) {
387 return typeof handlers.onMouseOut === "function" ? handlers.onMouseOut(svg) : void 0;
388 });
389 });
390 return this;
391 },
392 drawDots: function(svg, axes, data, options, handlers, dispatch) {
393 var dotGroup;
394 dotGroup = svg.select('.content').selectAll('.dotGroup').data(data.filter(function(s) {
395 var _ref;
396 return ((_ref = s.type) === 'line' || _ref === 'area') && s.drawDots;
397 })).enter().append('g');
398 dotGroup.attr({
399 "class": function(s) {
400 return "dotGroup series_" + s.index;
401 },
402 fill: function(s) {
403 return s.color;
404 }
405 }).selectAll('.dot').data(function(d) {
406 return d.values;
407 }).enter().append('circle').attr({
408 'class': 'dot',
409 'r': function(d) {
410 return d.dotSize;
411 },
412 'cx': function(d) {
413 return axes.xScale(d.x);
414 },
415 'cy': function(d) {
416 return axes[d.axis + 'Scale'](d.y + d.y0);
417 }
418 }).style({
419 'stroke': 'white',
420 'stroke-width': '2px'
421 }).on({
422 'click': function(d, i) {
423 return dispatch.click(d, i);
424 }
425 }).on({
426 'mouseover': function(d, i) {
427 return dispatch.hover(d, i);
428 }
429 });
430 if (options.tooltip.mode !== 'none') {
431 dotGroup.on('mouseover', function(series) {
432 var d, target;
433 target = d3.select(d3.event.target);
434 d = target.datum();
435 target.attr('r', function(s) {
436 return s.dotSize + 2;
437 });
438 return typeof handlers.onMouseOver === "function" ? handlers.onMouseOver(svg, {
439 series: series,
440 x: target.attr('cx'),
441 y: target.attr('cy'),
442 datum: d
443 }, options.axes) : void 0;
444 }).on('mouseout', function(d) {
445 d3.select(d3.event.target).attr('r', function(s) {
446 return s.dotSize;
447 });
448 return typeof handlers.onMouseOut === "function" ? handlers.onMouseOut(svg) : void 0;
449 });
450 }
451 return this;
452 },
453 getEventDispatcher: function() {
454 var events;
455 events = ['focus', 'hover', 'click', 'toggle'];
456 return d3.dispatch.apply(this, events);
457 },
458 computeLegendLayout: function(svg, series, dimensions) {
459 var cumul, i, j, leftLayout, leftWidths, padding, rightLayout, rightWidths, that, w;
460 padding = 10;
461 that = this;
462 leftWidths = this.getLegendItemsWidths(svg, 'y');
463 leftLayout = [0];
464 i = 1;
465 while (i < leftWidths.length) {
466 leftLayout.push(leftWidths[i - 1] + leftLayout[i - 1] + padding);
467 i++;
468 }
469 rightWidths = this.getLegendItemsWidths(svg, 'y2');
470 if (!(rightWidths.length > 0)) {
471 return [leftLayout];
472 }
473 w = dimensions.width - dimensions.right - dimensions.left;
474 cumul = 0;
475 rightLayout = [];
476 j = rightWidths.length - 1;
477 while (j >= 0) {
478 rightLayout.push(w - cumul - rightWidths[j]);
479 cumul += rightWidths[j] + padding;
480 j--;
481 }
482 rightLayout.reverse();
483 return [leftLayout, rightLayout];
484 },
485 getLegendItemsWidths: function(svg, axis) {
486 var bbox, i, items, that, widths;
487 that = this;
488 bbox = function(t) {
489 return that.getTextBBox(t).width;
490 };
491 items = svg.selectAll(".legendItem." + axis);
492 if (!(items.length > 0)) {
493 return [];
494 }
495 widths = [];
496 i = 0;
497 while (i < items[0].length) {
498 widths.push(bbox(items[0][i]));
499 i++;
500 }
501 return widths;
502 },
503 drawLegend: function(svg, series, dimensions, handlers, dispatch) {
504 var d, groups, legend, that, translateLegends;
505 that = this;
506 legend = svg.append('g').attr('class', 'legend');
507 d = 16;
508 svg.select('defs').append('svg:clipPath').attr('id', 'legend-clip').append('circle').attr('r', d / 2);
509 groups = legend.selectAll('.legendItem').data(series);
510 groups.enter().append('g').on('click', function(s, i) {
511 var visibility;
512 visibility = !(s.visible !== false);
513 dispatch.toggle(s, i, visibility);
514 return typeof handlers.onSeriesVisibilityChange === "function" ? handlers.onSeriesVisibilityChange({
515 series: s,
516 index: i,
517 newVisibility: visibility
518 }) : void 0;
519 });
520 groups.attr({
521 'class': function(s, i) {
522 return "legendItem series_" + i + " " + s.axis;
523 },
524 'opacity': function(s, i) {
525 if (s.visible === false) {
526 that.toggleSeries(svg, i);
527 return '0.2';
528 }
529 return '1';
530 }
531 }).each(function(s) {
532 var item, _ref;
533 item = d3.select(this);
534 item.append('circle').attr({
535 'fill': s.color,
536 'stroke': s.color,
537 'stroke-width': '2px',
538 'r': d / 2
539 });
540 item.append('path').attr({
541 'clip-path': 'url(#legend-clip)',
542 'fill-opacity': (_ref = s.type) === 'area' || _ref === 'column' ? '1' : '0',
543 'fill': 'white',
544 'stroke': 'white',
545 'stroke-width': '2px',
546 'd': that.getLegendItemPath(s, d, d)
547 });
548 item.append('circle').attr({
549 'fill-opacity': 0,
550 'stroke': s.color,
551 'stroke-width': '2px',
552 'r': d / 2
553 });
554 return item.append('text').attr({
555 'class': function(d, i) {
556 return "legendText series_" + i;
557 },
558 'font-family': 'Courier',
559 'font-size': 10,
560 'transform': 'translate(13, 4)',
561 'text-rendering': 'geometric-precision'
562 }).text(s.label || s.y);
563 });
564 translateLegends = function() {
565 var left, right, _ref;
566 _ref = that.computeLegendLayout(svg, series, dimensions), left = _ref[0], right = _ref[1];
567 return groups.attr({
568 'transform': function(s, i) {
569 if (s.axis === 'y') {
570 return "translate(" + (left.shift()) + "," + (dimensions.height - 40) + ")";
571 } else {
572 return "translate(" + (right.shift()) + "," + (dimensions.height - 40) + ")";
573 }
574 }
575 });
576 };
577 translateLegends();
578 setTimeout(translateLegends, 0);
579 return this;
580 },
581 getLegendItemPath: function(series, w, h) {
582 var base_path, path;
583 if (series.type === 'column') {
584 path = 'M' + (-w / 3) + ' ' + (-h / 8) + ' l0 ' + h + ' ';
585 path += 'M0' + ' ' + (-h / 3) + ' l0 ' + h + ' ';
586 path += 'M' + w / 3 + ' ' + (-h / 10) + ' l0 ' + h + ' ';
587 return path;
588 }
589 base_path = 'M-' + w / 2 + ' 0' + h / 3 + ' l' + w / 3 + ' -' + h / 3 + ' l' + w / 3 + ' ' + h / 3 + ' l' + w / 3 + ' -' + 2 * h / 3;
590 if (series.type === 'area') {
591 base_path + ' l0 ' + h + ' l-' + w + ' 0z';
592 }
593 return base_path;
594 },
595 toggleSeries: function(svg, index) {
596 var isVisible;
597 isVisible = false;
598 svg.select('.content').selectAll('.series_' + index).style('display', function(s) {
599 if (d3.select(this).style('display') === 'none') {
600 isVisible = true;
601 return 'initial';
602 } else {
603 isVisible = false;
604 return 'none';
605 }
606 });
607 return isVisible;
608 },
609 drawLines: function(svg, scales, data, options, handlers) {
610 var drawers, interpolateData, lineGroup;
611 drawers = {
612 y: this.createLeftLineDrawer(scales, options.lineMode, options.tension),
613 y2: this.createRightLineDrawer(scales, options.lineMode, options.tension)
614 };
615 lineGroup = svg.select('.content').selectAll('.lineGroup').data(data.filter(function(s) {
616 var _ref;
617 return (_ref = s.type) === 'line' || _ref === 'area';
618 })).enter().append('g');
619 lineGroup.style('stroke', function(s) {
620 return s.color;
621 }).attr('class', function(s) {
622 return "lineGroup series_" + s.index;
623 }).append('path').attr({
624 "class": 'line',
625 d: function(d) {
626 return drawers[d.axis](d.values);
627 }
628 }).style({
629 'fill': 'none',
630 'stroke-width': function(s) {
631 return s.thickness;
632 },
633 'stroke-dasharray': function(s) {
634 if (s.lineMode === 'dashed') {
635 return '10,3';
636 }
637 return void 0;
638 }
639 });
640 if (options.tooltip.interpolate) {
641 interpolateData = function(series) {
642 var datum, error, i, interpDatum, maxXPos, maxXValue, maxYPos, maxYValue, minXPos, minXValue, minYPos, minYValue, mousePos, target, valuesData, x, xPercentage, xVal, y, yPercentage, yVal, _i, _len;
643 target = d3.select(d3.event.target);
644 try {
645 mousePos = d3.mouse(this);
646 } catch (_error) {
647 error = _error;
648 mousePos = [0, 0];
649 }
650 valuesData = target.datum().values;
651 for (i = _i = 0, _len = valuesData.length; _i < _len; i = ++_i) {
652 datum = valuesData[i];
653 x = scales.xScale(datum.x);
654 y = scales.yScale(datum.y);
655 if ((typeof minXPos === "undefined" || minXPos === null) || x < minXPos) {
656 minXPos = x;
657 minXValue = datum.x;
658 }
659 if ((typeof maxXPos === "undefined" || maxXPos === null) || x > maxXPos) {
660 maxXPos = x;
661 maxXValue = datum.x;
662 }
663 if ((typeof minYPos === "undefined" || minYPos === null) || y < minYPos) {
664 minYPos = y;
665 }
666 if ((typeof maxYPos === "undefined" || maxYPos === null) || y > maxYPos) {
667 maxYPos = y;
668 }
669 if ((typeof minYValue === "undefined" || minYValue === null) || datum.y < minYValue) {
670 minYValue = datum.y;
671 }
672 if ((typeof maxYValue === "undefined" || maxYValue === null) || datum.y > maxYValue) {
673 maxYValue = datum.y;
674 }
675 }
676 xPercentage = (mousePos[0] - minXPos) / (maxXPos - minXPos);
677 yPercentage = (mousePos[1] - minYPos) / (maxYPos - minYPos);
678 xVal = Math.round(xPercentage * (maxXValue - minXValue) + minXValue);
679 yVal = Math.round((1 - yPercentage) * (maxYValue - minYValue) + minYValue);
680 interpDatum = {
681 x: xVal,
682 y: yVal
683 };
684 return typeof handlers.onMouseOver === "function" ? handlers.onMouseOver(svg, {
685 series: series,
686 x: mousePos[0],
687 y: mousePos[1],
688 datum: interpDatum
689 }, options.axes) : void 0;
690 };
691 lineGroup.on('mousemove', interpolateData).on('mouseout', function(d) {
692 return typeof handlers.onMouseOut === "function" ? handlers.onMouseOut(svg) : void 0;
693 });
694 }
695 return this;
696 },
697 createLeftLineDrawer: function(scales, mode, tension) {
698 return d3.svg.line().x(function(d) {
699 return scales.xScale(d.x);
700 }).y(function(d) {
701 return scales.yScale(d.y + d.y0);
702 }).interpolate(mode).tension(tension);
703 },
704 createRightLineDrawer: function(scales, mode, tension) {
705 return d3.svg.line().x(function(d) {
706 return scales.xScale(d.x);
707 }).y(function(d) {
708 return scales.y2Scale(d.y + d.y0);
709 }).interpolate(mode).tension(tension);
710 },
711 getPixelCssProp: function(element, propertyName) {
712 var string;
713 string = $window.getComputedStyle(element, null).getPropertyValue(propertyName);
714 return +string.replace(/px$/, '');
715 },
716 getDefaultMargins: function() {
717 return {
718 top: 20,
719 right: 50,
720 bottom: 60,
721 left: 50
722 };
723 },
724 getDefaultThumbnailMargins: function() {
725 return {
726 top: 1,
727 right: 1,
728 bottom: 2,
729 left: 0
730 };
731 },
732 getElementDimensions: function(element, width, height) {
733 var bottom, dim, left, parent, right, top;
734 dim = {};
735 parent = element;
736 top = this.getPixelCssProp(parent, 'padding-top');
737 bottom = this.getPixelCssProp(parent, 'padding-bottom');
738 left = this.getPixelCssProp(parent, 'padding-left');
739 right = this.getPixelCssProp(parent, 'padding-right');
740 dim.width = +(width || parent.offsetWidth || 900) - left - right;
741 dim.height = +(height || parent.offsetHeight || 500) - top - bottom;
742 return dim;
743 },
744 getDimensions: function(options, element, attrs) {
745 var dim;
746 dim = this.getElementDimensions(element[0].parentElement, attrs.width, attrs.height);
747 dim = angular.extend(options.margin, dim);
748 return dim;
749 },
750 clean: function(element) {
751 return d3.select(element).on('keydown', null).on('keyup', null).select('svg').remove();
752 },
753 uuid: function() {
754 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
755 var r, v;
756 r = Math.random() * 16 | 0;
757 v = c === 'x' ? r : r & 0x3 | 0x8;
758 return v.toString(16);
759 });
760 },
761 bootstrap: function(element, id, dimensions) {
762 var defs, height, svg, width;
763 d3.select(element).classed('chart', true);
764 width = dimensions.width;
765 height = dimensions.height;
766 svg = d3.select(element).append('svg').attr({
767 width: width,
768 height: height
769 }).append('g').attr('transform', 'translate(' + dimensions.left + ',' + dimensions.top + ')');
770 defs = svg.append('defs').attr('class', 'patterns');
771 defs.append('clipPath').attr('class', 'content-clip').attr('id', "content-clip-" + id).append('rect').attr({
772 'x': 0,
773 'y': 0,
774 'width': width - dimensions.left - dimensions.right,
775 'height': height - dimensions.top - dimensions.bottom
776 });
777 return svg;
778 },
779 createContent: function(svg, id, options) {
780 var content;
781 content = svg.append('g').attr('class', 'content');
782 if (options.hideOverflow) {
783 return content.attr('clip-path', "url(#content-clip-" + id + ")");
784 }
785 },
786 createGlass: function(svg, dimensions, handlers, axes, data, options, dispatch, columnWidth) {
787 var glass, scrubberGroup, that;
788 that = this;
789 glass = svg.append('g').attr({
790 'class': 'glass-container',
791 'opacity': 0
792 });
793 scrubberGroup = glass.selectAll('.scrubberItem').data(data).enter().append('g').attr('class', function(s, i) {
794 return "scrubberItem series_" + i;
795 });
796 scrubberGroup.each(function(s, i) {
797 var g, g2, item;
798 item = d3.select(this);
799 g = item.append('g').attr({
800 'class': "rightTT"
801 });
802 g.append('path').attr({
803 'class': "scrubberPath series_" + i,
804 'y': '-7px',
805 'fill': s.color
806 });
807 that.styleTooltip(g.append('text').style('text-anchor', 'start').attr({
808 'class': function(d, i) {
809 return "scrubberText series_" + i;
810 },
811 'height': '14px',
812 'transform': 'translate(7, 3)',
813 'text-rendering': 'geometric-precision'
814 })).text(s.label || s.y);
815 g2 = item.append('g').attr({
816 'class': "leftTT"
817 });
818 g2.append('path').attr({
819 'class': "scrubberPath series_" + i,
820 'y': '-7px',
821 'fill': s.color
822 });
823 that.styleTooltip(g2.append('text').style('text-anchor', 'end').attr({
824 'class': "scrubberText series_" + i,
825 'height': '14px',
826 'transform': 'translate(-13, 3)',
827 'text-rendering': 'geometric-precision'
828 })).text(s.label || s.y);
829 return item.append('circle').attr({
830 'class': "scrubberDot series_" + i,
831 'fill': 'white',
832 'stroke': s.color,
833 'stroke-width': '2px',
834 'r': 4
835 });
836 });
837 return glass.append('rect').attr({
838 "class": 'glass',
839 width: dimensions.width - dimensions.left - dimensions.right,
840 height: dimensions.height - dimensions.top - dimensions.bottom
841 }).style('fill', 'white').style('fill-opacity', 0.000001).on('mouseover', function() {
842 return handlers.onChartHover(svg, d3.select(this), axes, data, options, dispatch, columnWidth);
843 });
844 },
845 getDataPerSeries: function(data, options) {
846 var axes, layout, series, straightened;
847 series = options.series;
848 axes = options.axes;
849 if (!(series && series.length && data && data.length)) {
850 return [];
851 }
852 straightened = series.map(function(s, i) {
853 var seriesData;
854 seriesData = {
855 index: i,
856 name: s.y,
857 values: [],
858 color: s.color,
859 axis: s.axis || 'y',
860 xOffset: 0,
861 type: s.type,
862 thickness: s.thickness,
863 drawDots: s.drawDots !== false
864 };
865 if (s.dotSize != null) {
866 seriesData.dotSize = s.dotSize;
867 }
868 if (s.striped === true) {
869 seriesData.striped = true;
870 }
871 if (s.lineMode != null) {
872 seriesData.lineMode = s.lineMode;
873 }
874 if (s.id) {
875 seriesData.id = s.id;
876 }
877 data.filter(function(row) {
878 return row[s.y] != null;
879 }).forEach(function(row) {
880 var d;
881 d = {
882 x: row[options.axes.x.key],
883 y: row[s.y],
884 y0: 0,
885 axis: s.axis || 'y'
886 };
887 if (s.dotSize != null) {
888 d.dotSize = s.dotSize;
889 }
890 return seriesData.values.push(d);
891 });
892 return seriesData;
893 });
894 if ((options.stacks == null) || options.stacks.length === 0) {
895 return straightened;
896 }
897 layout = d3.layout.stack().values(function(s) {
898 return s.values;
899 });
900 options.stacks.forEach(function(stack) {
901 var layers;
902 if (!(stack.series.length > 0)) {
903 return;
904 }
905 layers = straightened.filter(function(s, i) {
906 var _ref;
907 return (s.id != null) && (_ref = s.id, __indexOf.call(stack.series, _ref) >= 0);
908 });
909 return layout(layers);
910 });
911 return straightened;
912 },
913 estimateSideTooltipWidth: function(svg, text) {
914 var bbox, t;
915 t = svg.append('text');
916 t.text('' + text);
917 this.styleTooltip(t);
918 bbox = this.getTextBBox(t[0][0]);
919 t.remove();
920 return bbox;
921 },
922 getTextBBox: function(svgTextElement) {
923 var error;
924 if (svgTextElement !== null) {
925 try {
926 return svgTextElement.getBBox();
927 } catch (_error) {
928 error = _error;
929 return {
930 height: 0,
931 width: 0,
932 y: 0,
933 x: 0
934 };
935 }
936 }
937 return {};
938 },
939 getWidestTickWidth: function(svg, axisKey) {
940 var bbox, max, ticks, _ref;
941 max = 0;
942 bbox = this.getTextBBox;
943 ticks = svg.select("." + axisKey + ".axis").selectAll('.tick');
944 if ((_ref = ticks[0]) != null) {
945 _ref.forEach(function(t) {
946 return max = Math.max(max, bbox(t).width);
947 });
948 }
949 return max;
950 },
951 getWidestOrdinate: function(data, series, options) {
952 var widest;
953 widest = '';
954 data.forEach(function(row) {
955 return series.forEach(function(series) {
956 var v, _ref;
957 v = row[series.y];
958 if ((series.axis != null) && ((_ref = options.axes[series.axis]) != null ? _ref.ticksFormatter : void 0)) {
959 v = options.axes[series.axis].ticksFormatter(v);
960 }
961 if (v == null) {
962 return;
963 }
964 if (('' + v).length > ('' + widest).length) {
965 return widest = v;
966 }
967 });
968 });
969 return widest;
970 },
971 getDefaultOptions: function() {
972 return {
973 tooltip: {
974 mode: 'scrubber'
975 },
976 lineMode: 'linear',
977 tension: 0.7,
978 margin: this.getDefaultMargins(),
979 axes: {
980 x: {
981 type: 'linear',
982 key: 'x'
983 },
984 y: {
985 type: 'linear'
986 }
987 },
988 series: [],
989 drawLegend: true,
990 drawDots: true,
991 stacks: [],
992 columnsHGap: 5,
993 hideOverflow: false
994 };
995 },
996 sanitizeOptions: function(options, mode) {
997 var defaultMargin;
998 if (options == null) {
999 options = {};
1000 }
1001 if (mode === 'thumbnail') {
1002 options.drawLegend = false;
1003 options.drawDots = false;
1004 options.tooltip = {
1005 mode: 'none',
1006 interpolate: false
1007 };
1008 }
1009 options.series = this.sanitizeSeriesOptions(options.series);
1010 options.stacks = this.sanitizeSeriesStacks(options.stacks, options.series);
1011 options.axes = this.sanitizeAxes(options.axes, this.haveSecondYAxis(options.series));
1012 options.tooltip = this.sanitizeTooltip(options.tooltip);
1013 options.margin = this.sanitizeMargins(options.margin);
1014 options.lineMode || (options.lineMode = this.getDefaultOptions().lineMode);
1015 options.tension = /^\d+(\.\d+)?$/.test(options.tension) ? options.tension : this.getDefaultOptions().tension;
1016 options.drawLegend = options.drawLegend !== false;
1017 options.drawDots = options.drawDots !== false;
1018 if (!angular.isNumber(options.columnsHGap)) {
1019 options.columnsHGap = 5;
1020 }
1021 options.hideOverflow = options.hideOverflow || false;
1022 defaultMargin = mode === 'thumbnail' ? this.getDefaultThumbnailMargins() : this.getDefaultMargins();
1023 options.series = angular.extend(this.getDefaultOptions().series, options.series);
1024 options.axes = angular.extend(this.getDefaultOptions().axes, options.axes);
1025 options.tooltip = angular.extend(this.getDefaultOptions().tooltip, options.tooltip);
1026 options.margin = angular.extend(defaultMargin, options.margin);
1027 return options;
1028 },
1029 sanitizeMargins: function(options) {
1030 var attrs, margin, opt, value;
1031 attrs = ['top', 'right', 'bottom', 'left'];
1032 margin = {};
1033 for (opt in options) {
1034 value = options[opt];
1035 if (__indexOf.call(attrs, opt) >= 0) {
1036 margin[opt] = parseFloat(value);
1037 }
1038 }
1039 return margin;
1040 },
1041 sanitizeSeriesStacks: function(stacks, series) {
1042 var seriesKeys;
1043 if (stacks == null) {
1044 return [];
1045 }
1046 seriesKeys = {};
1047 series.forEach(function(s) {
1048 return seriesKeys[s.id] = s;
1049 });
1050 stacks.forEach(function(stack) {
1051 return stack.series.forEach(function(id) {
1052 var s;
1053 s = seriesKeys[id];
1054 if (s != null) {
1055 if (s.axis !== stack.axis) {
1056 return $log.warn("Series " + id + " is not on the same axis as its stack");
1057 }
1058 } else {
1059 if (!s) {
1060 return $log.warn("Unknown series found in stack : " + id);
1061 }
1062 }
1063 });
1064 });
1065 return stacks;
1066 },
1067 sanitizeTooltip: function(options) {
1068 var _ref;
1069 if (!options) {
1070 return {
1071 mode: 'scrubber'
1072 };
1073 }
1074 if ((_ref = options.mode) !== 'none' && _ref !== 'axes' && _ref !== 'scrubber') {
1075 options.mode = 'scrubber';
1076 }
1077 if (options.mode === 'scrubber') {
1078 delete options.interpolate;
1079 } else {
1080 options.interpolate = !!options.interpolate;
1081 }
1082 if (options.mode === 'scrubber' && options.interpolate) {
1083 throw new Error('Interpolation is not supported for scrubber tooltip mode.');
1084 }
1085 return options;
1086 },
1087 sanitizeSeriesOptions: function(options) {
1088 var colors, knownIds;
1089 if (options == null) {
1090 return [];
1091 }
1092 colors = d3.scale.category10();
1093 knownIds = {};
1094 options.forEach(function(s, i) {
1095 if (knownIds[s.id] != null) {
1096 throw new Error("Twice the same ID (" + s.id + ") ? Really ?");
1097 }
1098 if (s.id != null) {
1099 return knownIds[s.id] = s;
1100 }
1101 });
1102 options.forEach(function(s, i) {
1103 var cnt, _ref, _ref1, _ref2, _ref3;
1104 s.axis = ((_ref = s.axis) != null ? _ref.toLowerCase() : void 0) !== 'y2' ? 'y' : 'y2';
1105 s.color || (s.color = colors(i));
1106 s.type = (_ref1 = s.type) === 'line' || _ref1 === 'area' || _ref1 === 'column' ? s.type : "line";
1107 if (s.type === 'column') {
1108 delete s.thickness;
1109 delete s.lineMode;
1110 delete s.drawDots;
1111 delete s.dotSize;
1112 } else if (!/^\d+px$/.test(s.thickness)) {
1113 s.thickness = '1px';
1114 }
1115 if ((_ref2 = s.type) === 'line' || _ref2 === 'area') {
1116 if ((_ref3 = s.lineMode) !== 'dashed') {
1117 delete s.lineMode;
1118 }
1119 if (s.drawDots !== false && (s.dotSize == null)) {
1120 s.dotSize = 2;
1121 }
1122 }
1123 if (s.id == null) {
1124 cnt = 0;
1125 while (knownIds["series_" + cnt] != null) {
1126 cnt++;
1127 }
1128 s.id = "series_" + cnt;
1129 knownIds[s.id] = s;
1130 }
1131 if (s.drawDots === false) {
1132 return delete s.dotSize;
1133 }
1134 });
1135 return options;
1136 },
1137 sanitizeAxes: function(axesOptions, secondAxis) {
1138 var _base;
1139 if (axesOptions == null) {
1140 axesOptions = {};
1141 }
1142 axesOptions.x = this.sanitizeAxisOptions(axesOptions.x);
1143 (_base = axesOptions.x).key || (_base.key = "x");
1144 axesOptions.y = this.sanitizeAxisOptions(axesOptions.y);
1145 if (secondAxis) {
1146 axesOptions.y2 = this.sanitizeAxisOptions(axesOptions.y2);
1147 }
1148 return axesOptions;
1149 },
1150 sanitizeExtrema: function(options) {
1151 var max, min;
1152 min = this.getSanitizedNumber(options.min);
1153 if (min != null) {
1154 options.min = min;
1155 } else {
1156 delete options.min;
1157 }
1158 max = this.getSanitizedNumber(options.max);
1159 if (max != null) {
1160 return options.max = max;
1161 } else {
1162 return delete options.max;
1163 }
1164 },
1165 getSanitizedNumber: function(value) {
1166 var number;
1167 if (value == null) {
1168 return void 0;
1169 }
1170 number = parseFloat(value);
1171 if (isNaN(number)) {
1172 $log.warn("Invalid extremum value : " + value + ", deleting it.");
1173 return void 0;
1174 }
1175 return number;
1176 },
1177 sanitizeAxisOptions: function(options) {
1178 if (options == null) {
1179 return {
1180 type: 'linear'
1181 };
1182 }
1183 options.type || (options.type = 'linear');
1184 if (options.ticksRotate != null) {
1185 options.ticksRotate = this.getSanitizedNumber(options.ticksRotate);
1186 }
1187 if (options.labelFunction != null) {
1188 options.ticksFormatter = options.labelFunction;
1189 }
1190 if (options.ticksFormat != null) {
1191 if (options.type === 'date') {
1192 options.ticksFormatter = d3.time.format(options.ticksFormat);
1193 } else {
1194 options.ticksFormatter = d3.format(options.ticksFormat);
1195 }
1196 if (options.tooltipFormatter == null) {
1197 options.tooltipFormatter = options.ticksFormatter;
1198 }
1199 }
1200 if (options.tooltipFormat != null) {
1201 if (options.type === 'date') {
1202 options.tooltipFormatter = d3.time.format(options.tooltipFormat);
1203 } else {
1204 options.tooltipFormatter = d3.format(options.tooltipFormat);
1205 }
1206 }
1207 if (options.ticksInterval != null) {
1208 options.ticksInterval = this.getSanitizedNumber(options.ticksInterval);
1209 }
1210 this.sanitizeExtrema(options);
1211 return options;
1212 },
1213 createAxes: function(svg, dimensions, axesOptions) {
1214 var createY2Axis, height, style, width, x, xAxis, y, y2, y2Axis, yAxis;
1215 createY2Axis = axesOptions.y2 != null;
1216 width = dimensions.width;
1217 height = dimensions.height;
1218 width = width - dimensions.left - dimensions.right;
1219 height = height - dimensions.top - dimensions.bottom;
1220 x = void 0;
1221 if (axesOptions.x.type === 'date') {
1222 x = d3.time.scale().rangeRound([0, width]);
1223 } else {
1224 x = d3.scale.linear().rangeRound([0, width]);
1225 }
1226 xAxis = this.createAxis(x, 'x', axesOptions);
1227 y = void 0;
1228 if (axesOptions.y.type === 'log') {
1229 y = d3.scale.log().clamp(true).rangeRound([height, 0]);
1230 } else {
1231 y = d3.scale.linear().rangeRound([height, 0]);
1232 }
1233 y.clamp(true);
1234 yAxis = this.createAxis(y, 'y', axesOptions);
1235 y2 = void 0;
1236 if (createY2Axis && axesOptions.y2.type === 'log') {
1237 y2 = d3.scale.log().clamp(true).rangeRound([height, 0]);
1238 } else {
1239 y2 = d3.scale.linear().rangeRound([height, 0]);
1240 }
1241 y2.clamp(true);
1242 y2Axis = this.createAxis(y2, 'y2', axesOptions);
1243 style = function(group) {
1244 group.style({
1245 'font': '10px Courier',
1246 'shape-rendering': 'crispEdges'
1247 });
1248 return group.selectAll('path').style({
1249 'fill': 'none',
1250 'stroke': '#000'
1251 });
1252 };
1253 return {
1254 xScale: x,
1255 yScale: y,
1256 y2Scale: y2,
1257 xAxis: xAxis,
1258 yAxis: yAxis,
1259 y2Axis: y2Axis,
1260 andAddThemIf: function(conditions) {
1261 if (!!conditions.all) {
1262 if (!!conditions.x) {
1263 svg.append('g').attr('class', 'x axis').attr('transform', 'translate(0,' + height + ')').call(xAxis).call(style);
1264 }
1265 if (!!conditions.y) {
1266 svg.append('g').attr('class', 'y axis').call(yAxis).call(style);
1267 }
1268 if (createY2Axis && !!conditions.y2) {
1269 svg.append('g').attr('class', 'y2 axis').attr('transform', 'translate(' + width + ', 0)').call(y2Axis).call(style);
1270 }
1271 }
1272 return {
1273 xScale: x,
1274 yScale: y,
1275 y2Scale: y2,
1276 xAxis: xAxis,
1277 yAxis: yAxis,
1278 y2Axis: y2Axis
1279 };
1280 }
1281 };
1282 },
1283 createAxis: function(scale, key, options) {
1284 var axis, o, sides;
1285 sides = {
1286 x: 'bottom',
1287 y: 'left',
1288 y2: 'right'
1289 };
1290 o = options[key];
1291 axis = d3.svg.axis().scale(scale).orient(sides[key]).tickFormat(o != null ? o.ticksFormatter : void 0);
1292 if (o == null) {
1293 return axis;
1294 }
1295 if (angular.isArray(o.ticks)) {
1296 axis.tickValues(o.ticks);
1297 } else if (angular.isNumber(o.ticks)) {
1298 axis.ticks(o.ticks);
1299 } else if (angular.isFunction(o.ticks)) {
1300 axis.ticks(o.ticks, o.ticksInterval);
1301 }
1302 return axis;
1303 },
1304 setScalesDomain: function(scales, data, series, svg, options) {
1305 var axis, y2Domain, yDomain;
1306 this.setXScale(scales.xScale, data, series, options.axes);
1307 axis = svg.selectAll('.x.axis').call(scales.xAxis);
1308 if (options.axes.x.ticksRotate != null) {
1309 axis.selectAll('.tick>text').attr('dy', null).attr('transform', 'translate(0,5) rotate(' + options.axes.x.ticksRotate + ' 0,6)').style('text-anchor', options.axes.x.ticksRotate >= 0 ? 'start' : 'end');
1310 }
1311 if ((series.filter(function(s) {
1312 return s.axis === 'y' && s.visible !== false;
1313 })).length > 0) {
1314 yDomain = this.getVerticalDomain(options, data, series, 'y');
1315 scales.yScale.domain(yDomain).nice();
1316 axis = svg.selectAll('.y.axis').call(scales.yAxis);
1317 if (options.axes.y.ticksRotate != null) {
1318 axis.selectAll('.tick>text').attr('transform', 'rotate(' + options.axes.y.ticksRotate + ' -6,0)').style('text-anchor', 'end');
1319 }
1320 }
1321 if ((series.filter(function(s) {
1322 return s.axis === 'y2' && s.visible !== false;
1323 })).length > 0) {
1324 y2Domain = this.getVerticalDomain(options, data, series, 'y2');
1325 scales.y2Scale.domain(y2Domain).nice();
1326 axis = svg.selectAll('.y2.axis').call(scales.y2Axis);
1327 if (options.axes.y2.ticksRotate != null) {
1328 return axis.selectAll('.tick>text').attr('transform', 'rotate(' + options.axes.y2.ticksRotate + ' 6,0)').style('text-anchor', 'start');
1329 }
1330 }
1331 },
1332 getVerticalDomain: function(options, data, series, key) {
1333 var domain, mySeries, o;
1334 if (!(o = options.axes[key])) {
1335 return [];
1336 }
1337 if ((o.ticks != null) && angular.isArray(o.ticks)) {
1338 return [o.ticks[0], o.ticks[o.ticks.length - 1]];
1339 }
1340 mySeries = series.filter(function(s) {
1341 return s.axis === key && s.visible !== false;
1342 });
1343 domain = this.yExtent(series.filter(function(s) {
1344 return s.axis === key && s.visible !== false;
1345 }), data, options.stacks.filter(function(stack) {
1346 return stack.axis === key;
1347 }));
1348 if (o.type === 'log') {
1349 domain[0] = domain[0] === 0 ? 0.001 : domain[0];
1350 }
1351 if (o.min != null) {
1352 domain[0] = o.min;
1353 }
1354 if (o.max != null) {
1355 domain[1] = o.max;
1356 }
1357 return domain;
1358 },
1359 yExtent: function(series, data, stacks) {
1360 var groups, maxY, minY;
1361 minY = Number.POSITIVE_INFINITY;
1362 maxY = Number.NEGATIVE_INFINITY;
1363 groups = [];
1364 stacks.forEach(function(stack) {
1365 return groups.push(stack.series.map(function(id) {
1366 return (series.filter(function(s) {
1367 return s.id === id;
1368 }))[0];
1369 }));
1370 });
1371 series.forEach(function(series, i) {
1372 var isInStack;
1373 isInStack = false;
1374 stacks.forEach(function(stack) {
1375 var _ref;
1376 if (_ref = series.id, __indexOf.call(stack.series, _ref) >= 0) {
1377 return isInStack = true;
1378 }
1379 });
1380 if (!isInStack) {
1381 return groups.push([series]);
1382 }
1383 });
1384 groups.forEach(function(group) {
1385 group = group.filter(Boolean);
1386 minY = Math.min(minY, d3.min(data, function(d) {
1387 return group.reduce((function(a, s) {
1388 return Math.min(a, d[s.y]);
1389 }), Number.POSITIVE_INFINITY);
1390 }));
1391 return maxY = Math.max(maxY, d3.max(data, function(d) {
1392 return group.reduce((function(a, s) {
1393 return a + d[s.y];
1394 }), 0);
1395 }));
1396 });
1397 if (minY === maxY) {
1398 if (minY > 0) {
1399 return [0, minY * 2];
1400 } else {
1401 return [minY * 2, 0];
1402 }
1403 }
1404 return [minY, maxY];
1405 },
1406 setXScale: function(xScale, data, series, axesOptions) {
1407 var domain, o;
1408 domain = this.xExtent(data, axesOptions.x.key);
1409 if (series.filter(function(s) {
1410 return s.type === 'column';
1411 }).length) {
1412 this.adjustXDomainForColumns(domain, data, axesOptions.x.key);
1413 }
1414 o = axesOptions.x;
1415 if (o.min != null) {
1416 domain[0] = o.min;
1417 }
1418 if (o.max != null) {
1419 domain[1] = o.max;
1420 }
1421 return xScale.domain(domain);
1422 },
1423 xExtent: function(data, key) {
1424 var from, to, _ref;
1425 _ref = d3.extent(data, function(d) {
1426 return d[key];
1427 }), from = _ref[0], to = _ref[1];
1428 if (from === to) {
1429 if (from > 0) {
1430 return [0, from * 2];
1431 } else {
1432 return [from * 2, 0];
1433 }
1434 }
1435 return [from, to];
1436 },
1437 adjustXDomainForColumns: function(domain, data, field) {
1438 var step;
1439 step = this.getAverageStep(data, field);
1440 if (angular.isDate(domain[0])) {
1441 domain[0] = new Date(domain[0].getTime() - step);
1442 return domain[1] = new Date(domain[1].getTime() + step);
1443 } else {
1444 domain[0] = domain[0] - step;
1445 return domain[1] = domain[1] + step;
1446 }
1447 },
1448 getAverageStep: function(data, field) {
1449 var i, n, sum;
1450 if (!(data.length > 1)) {
1451 return 0;
1452 }
1453 sum = 0;
1454 n = data.length - 1;
1455 i = 0;
1456 while (i < n) {
1457 sum += data[i + 1][field] - data[i][field];
1458 i++;
1459 }
1460 return sum / n;
1461 },
1462 haveSecondYAxis: function(series) {
1463 return !series.every(function(s) {
1464 return s.axis !== 'y2';
1465 });
1466 },
1467 showScrubber: function(svg, glass, axes, data, options, dispatch, columnWidth) {
1468 var that;
1469 that = this;
1470 glass.on('mousemove', function() {
1471 svg.selectAll('.glass-container').attr('opacity', 1);
1472 return that.updateScrubber(svg, d3.mouse(this), axes, data, options, dispatch, columnWidth);
1473 });
1474 return glass.on('mouseout', function() {
1475 glass.on('mousemove', null);
1476 return svg.selectAll('.glass-container').attr('opacity', 0);
1477 });
1478 },
1479 getClosestPoint: function(values, xValue) {
1480 var d, d0, d1, i, xBisector;
1481 xBisector = d3.bisector(function(d) {
1482 return d.x;
1483 }).left;
1484 i = xBisector(values, xValue);
1485 if (i === 0) {
1486 return values[0];
1487 }
1488 if (i > values.length - 1) {
1489 return values[values.length - 1];
1490 }
1491 d0 = values[i - 1];
1492 d1 = values[i];
1493 d = xValue - d0.x > d1.x - xValue ? d1 : d0;
1494 return d;
1495 },
1496 updateScrubber: function(svg, _arg, axes, data, options, dispatch, columnWidth) {
1497 var ease, positions, that, tickLength, x, y;
1498 x = _arg[0], y = _arg[1];
1499 ease = function(element) {
1500 return element.transition().duration(50);
1501 };
1502 that = this;
1503 positions = [];
1504 data.forEach(function(series, index) {
1505 var color, item, lText, left, rText, right, side, sizes, text, v, xInvert, xPos, yInvert;
1506 item = svg.select(".scrubberItem.series_" + index);
1507 if (options.series[index].visible === false) {
1508 item.attr('opacity', 0);
1509 return;
1510 }
1511 item.attr('opacity', 1);
1512 xInvert = axes.xScale.invert(x);
1513 yInvert = axes.yScale.invert(y);
1514 v = that.getClosestPoint(series.values, xInvert);
1515 dispatch.focus(v, series.values.indexOf(v), [xInvert, yInvert]);
1516 text = v.x + ' : ' + v.y;
1517 if (options.tooltip.formatter) {
1518 text = options.tooltip.formatter(v.x, v.y, options.series[index]);
1519 }
1520 right = item.select('.rightTT');
1521 rText = right.select('text');
1522 rText.text(text);
1523 left = item.select('.leftTT');
1524 lText = left.select('text');
1525 lText.text(text);
1526 sizes = {
1527 right: that.getTextBBox(rText[0][0]).width + 5,
1528 left: that.getTextBBox(lText[0][0]).width + 5
1529 };
1530 side = series.axis === 'y2' ? 'right' : 'left';
1531 xPos = axes.xScale(v.x);
1532 if (side === 'left') {
1533 if (xPos + that.getTextBBox(lText[0][0]).x - 10 < 0) {
1534 side = 'right';
1535 }
1536 } else if (side === 'right') {
1537 if (xPos + sizes.right > that.getTextBBox(svg.select('.glass')[0][0]).width) {
1538 side = 'left';
1539 }
1540 }
1541 if (side === 'left') {
1542 ease(right).attr('opacity', 0);
1543 ease(left).attr('opacity', 1);
1544 } else {
1545 ease(right).attr('opacity', 1);
1546 ease(left).attr('opacity', 0);
1547 }
1548 positions[index] = {
1549 index: index,
1550 x: xPos,
1551 y: axes[v.axis + 'Scale'](v.y + v.y0),
1552 side: side,
1553 sizes: sizes
1554 };
1555 color = angular.isFunction(series.color) ? series.color(v, series.values.indexOf(v)) : series.color;
1556 item.selectAll('circle').attr('stroke', color);
1557 return item.selectAll('path').attr('fill', color);
1558 });
1559 positions = this.preventOverlapping(positions);
1560 tickLength = Math.max(15, 100 / columnWidth);
1561 return data.forEach(function(series, index) {
1562 var item, p, tt, xOffset;
1563 if (options.series[index].visible === false) {
1564 return;
1565 }
1566 p = positions[index];
1567 item = svg.select(".scrubberItem.series_" + index);
1568 tt = item.select("." + p.side + "TT");
1569 xOffset = (p.side === 'left' ? series.xOffset : -series.xOffset);
1570 tt.select('text').attr('transform', function() {
1571 if (p.side === 'left') {
1572 return "translate(" + (-3 - tickLength - xOffset) + ", " + (p.labelOffset + 3) + ")";
1573 } else {
1574 return "translate(" + (4 + tickLength + xOffset) + ", " + (p.labelOffset + 3) + ")";
1575 }
1576 });
1577 tt.select('path').attr('d', that.getScrubberPath(p.sizes[p.side] + 1, p.labelOffset, p.side, tickLength + xOffset));
1578 return ease(item).attr({
1579 'transform': "translate(" + (positions[index].x + series.xOffset) + ", " + positions[index].y + ")"
1580 });
1581 });
1582 },
1583 getScrubberPath: function(w, yOffset, side, padding) {
1584 var h, p, xdir, ydir;
1585 h = 18;
1586 p = padding;
1587 w = w;
1588 xdir = side === 'left' ? 1 : -1;
1589 ydir = 1;
1590 if (yOffset !== 0) {
1591 ydir = Math.abs(yOffset) / yOffset;
1592 }
1593 yOffset || (yOffset = 0);
1594 return ["m0 0", "l" + xdir + " 0", "l0 " + (yOffset + ydir), "l" + (-xdir * (p + 1)) + " 0", "l0 " + (-h / 2 - ydir), "l" + (-xdir * w) + " 0", "l0 " + h, "l" + (xdir * w) + " 0", "l0 " + (-h / 2 - ydir), "l" + (xdir * (p - 1)) + " 0", "l0 " + (-yOffset + ydir), "l1 0", "z"].join('');
1595 },
1596 preventOverlapping: function(positions) {
1597 var abscissas, getNeighbours, h, offset;
1598 h = 18;
1599 abscissas = {};
1600 positions.forEach(function(p) {
1601 var _name;
1602 abscissas[_name = p.x] || (abscissas[_name] = {
1603 left: [],
1604 right: []
1605 });
1606 return abscissas[p.x][p.side].push(p);
1607 });
1608 getNeighbours = function(side) {
1609 var foundNeighbour, neighbourhood, neighbours, neighboursForX, p, sides, x, y, _ref;
1610 neighbours = [];
1611 for (x in abscissas) {
1612 sides = abscissas[x];
1613 if (sides[side].length === 0) {
1614 continue;
1615 }
1616 neighboursForX = {};
1617 while (sides[side].length > 0) {
1618 p = sides[side].pop();
1619 foundNeighbour = false;
1620 for (y in neighboursForX) {
1621 neighbourhood = neighboursForX[y];
1622 if ((+y - h <= (_ref = p.y) && _ref <= +y + h)) {
1623 neighbourhood.push(p);
1624 foundNeighbour = true;
1625 }
1626 }
1627 if (!foundNeighbour) {
1628 neighboursForX[p.y] = [p];
1629 }
1630 }
1631 neighbours.push(neighboursForX);
1632 }
1633 return neighbours;
1634 };
1635 offset = function(neighboursForAbscissas) {
1636 var abs, n, neighbours, start, step, xNeighbours, y;
1637 step = 20;
1638 for (abs in neighboursForAbscissas) {
1639 xNeighbours = neighboursForAbscissas[abs];
1640 for (y in xNeighbours) {
1641 neighbours = xNeighbours[y];
1642 n = neighbours.length;
1643 if (n === 1) {
1644 neighbours[0].labelOffset = 0;
1645 continue;
1646 }
1647 neighbours = neighbours.sort(function(a, b) {
1648 return a.y - b.y;
1649 });
1650 if (n % 2 === 0) {
1651 start = -(step / 2) * (n / 2);
1652 } else {
1653 start = -(n - 1) / 2 * step;
1654 }
1655 neighbours.forEach(function(neighbour, i) {
1656 return neighbour.labelOffset = start + step * i;
1657 });
1658 }
1659 }
1660 };
1661 offset(getNeighbours('left'));
1662 offset(getNeighbours('right'));
1663 return positions;
1664 },
1665 getTooltipHandlers: function(options) {
1666 if (options.tooltip.mode === 'scrubber') {
1667 return {
1668 onChartHover: angular.bind(this, this.showScrubber)
1669 };
1670 } else {
1671 return {
1672 onMouseOver: angular.bind(this, this.onMouseOver),
1673 onMouseOut: angular.bind(this, this.onMouseOut)
1674 };
1675 }
1676 },
1677 styleTooltip: function(d3TextElement) {
1678 return d3TextElement.attr({
1679 'font-family': 'monospace',
1680 'font-size': 10,
1681 'fill': 'white',
1682 'text-rendering': 'geometric-precision'
1683 });
1684 },
1685 addTooltips: function(svg, dimensions, axesOptions) {
1686 var h, height, p, w, width, xTooltip, y2Tooltip, yTooltip;
1687 width = dimensions.width;
1688 height = dimensions.height;
1689 width = width - dimensions.left - dimensions.right;
1690 height = height - dimensions.top - dimensions.bottom;
1691 w = 24;
1692 h = 18;
1693 p = 5;
1694 xTooltip = svg.append('g').attr({
1695 'id': 'xTooltip',
1696 'class': 'xTooltip',
1697 'opacity': 0
1698 });
1699 xTooltip.append('path').attr('transform', "translate(0," + (height + 1) + ")");
1700 this.styleTooltip(xTooltip.append('text').style('text-anchor', 'middle').attr({
1701 'width': w,
1702 'height': h,
1703 'transform': 'translate(0,' + (height + 19) + ')'
1704 }));
1705 yTooltip = svg.append('g').attr({
1706 id: 'yTooltip',
1707 "class": 'yTooltip',
1708 opacity: 0
1709 });
1710 yTooltip.append('path');
1711 this.styleTooltip(yTooltip.append('text').attr({
1712 'width': h,
1713 'height': w
1714 }));
1715 if (axesOptions.y2 != null) {
1716 y2Tooltip = svg.append('g').attr({
1717 'id': 'y2Tooltip',
1718 'class': 'y2Tooltip',
1719 'opacity': 0,
1720 'transform': 'translate(' + width + ',0)'
1721 });
1722 y2Tooltip.append('path');
1723 return this.styleTooltip(y2Tooltip.append('text').attr({
1724 'width': h,
1725 'height': w
1726 }));
1727 }
1728 },
1729 onMouseOver: function(svg, event, axesOptions) {
1730 this.updateXTooltip(svg, event, axesOptions.x);
1731 if (event.series.axis === 'y2') {
1732 return this.updateY2Tooltip(svg, event, axesOptions.y2);
1733 } else {
1734 return this.updateYTooltip(svg, event, axesOptions.y);
1735 }
1736 },
1737 onMouseOut: function(svg) {
1738 return this.hideTooltips(svg);
1739 },
1740 updateXTooltip: function(svg, _arg, xAxisOptions) {
1741 var color, datum, label, series, textX, x, xTooltip, _f;
1742 x = _arg.x, datum = _arg.datum, series = _arg.series;
1743 xTooltip = svg.select("#xTooltip");
1744 xTooltip.transition().attr({
1745 'opacity': 1.0,
1746 'transform': "translate(" + x + ",0)"
1747 });
1748 _f = xAxisOptions.tooltipFormatter;
1749 textX = _f ? _f(datum.x) : datum.x;
1750 label = xTooltip.select('text');
1751 label.text(textX);
1752 color = angular.isFunction(series.color) ? series.color(datum, series.values.indexOf(datum)) : series.color;
1753 return xTooltip.select('path').style('fill', color).attr('d', this.getXTooltipPath(label[0][0]));
1754 },
1755 getXTooltipPath: function(textElement) {
1756 var h, p, w;
1757 w = Math.max(this.getTextBBox(textElement).width, 15);
1758 h = 18;
1759 p = 5;
1760 return 'm-' + w / 2 + ' ' + p + ' ' + 'l0 ' + h + ' ' + 'l' + w + ' 0 ' + 'l0 ' + '' + (-h) + 'l' + (-w / 2 + p) + ' 0 ' + 'l' + (-p) + ' -' + h / 4 + ' ' + 'l' + (-p) + ' ' + h / 4 + ' ' + 'l' + (-w / 2 + p) + ' 0z';
1761 },
1762 updateYTooltip: function(svg, _arg, yAxisOptions) {
1763 var color, datum, label, series, textY, w, y, yTooltip, _f;
1764 y = _arg.y, datum = _arg.datum, series = _arg.series;
1765 yTooltip = svg.select("#yTooltip");
1766 yTooltip.transition().attr({
1767 'opacity': 1.0,
1768 'transform': "translate(0, " + y + ")"
1769 });
1770 _f = yAxisOptions.tooltipFormatter;
1771 textY = _f ? _f(datum.y) : datum.y;
1772 label = yTooltip.select('text');
1773 label.text(textY);
1774 w = this.getTextBBox(label[0][0]).width + 5;
1775 label.attr({
1776 'transform': 'translate(' + (-w - 2) + ',3)',
1777 'width': w
1778 });
1779 color = angular.isFunction(series.color) ? series.color(datum, series.values.indexOf(datum)) : series.color;
1780 return yTooltip.select('path').style('fill', color).attr('d', this.getYTooltipPath(w));
1781 },
1782 updateY2Tooltip: function(svg, _arg, yAxisOptions) {
1783 var color, datum, label, series, textY, w, y, y2Tooltip, _f;
1784 y = _arg.y, datum = _arg.datum, series = _arg.series;
1785 y2Tooltip = svg.select("#y2Tooltip");
1786 y2Tooltip.transition().attr('opacity', 1.0);
1787 _f = yAxisOptions.tooltipFormatter;
1788 textY = _f ? _f(datum.y) : datum.y;
1789 label = y2Tooltip.select('text');
1790 label.text(textY);
1791 w = this.getTextBBox(label[0][0]).width + 5;
1792 label.attr({
1793 'transform': 'translate(7, ' + (parseFloat(y) + 3) + ')',
1794 'w': w
1795 });
1796 color = angular.isFunction(series.color) ? series.color(datum, series.values.indexOf(datum)) : series.color;
1797 return y2Tooltip.select('path').style('fill', color).attr({
1798 'd': this.getY2TooltipPath(w),
1799 'transform': 'translate(0, ' + y + ')'
1800 });
1801 },
1802 getYTooltipPath: function(w) {
1803 var h, p;
1804 h = 18;
1805 p = 5;
1806 return 'm0 0' + 'l' + (-p) + ' ' + (-p) + ' ' + 'l0 ' + (-h / 2 + p) + ' ' + 'l' + (-w) + ' 0 ' + 'l0 ' + h + ' ' + 'l' + w + ' 0 ' + 'l0 ' + (-h / 2 + p) + 'l' + (-p) + ' ' + p + 'z';
1807 },
1808 getY2TooltipPath: function(w) {
1809 var h, p;
1810 h = 18;
1811 p = 5;
1812 return 'm0 0' + 'l' + p + ' ' + p + ' ' + 'l0 ' + (h / 2 - p) + ' ' + 'l' + w + ' 0 ' + 'l0 ' + (-h) + ' ' + 'l' + (-w) + ' 0 ' + 'l0 ' + (h / 2 - p) + ' ' + 'l' + (-p) + ' ' + p + 'z';
1813 },
1814 hideTooltips: function(svg) {
1815 svg.select("#xTooltip").transition().attr('opacity', 0);
1816 svg.select("#yTooltip").transition().attr('opacity', 0);
1817 return svg.select("#y2Tooltip").transition().attr('opacity', 0);
1818 }
1819 };
1820 }
1821 ]);