4 * Copyright 2016 RIFT.IO Inc
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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
21 line-chart - v1.1.9 - 21 June 2015
22 https://github.com/n3-charts/line-chart
23 Copyright (c) 2015 n3-charts
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; };
28 old_m
= angular
.module('n3-charts.linechart', ['n3charts.utils']);
30 m
= angular
.module('n3-line-chart', ['n3charts.utils']);
32 directive = function(name
, conf
) {
33 old_m
.directive(name
, conf
);
34 return m
.directive(name
, conf
);
37 directive('linechart', [
38 'n3utils', '$window', '$timeout', function(n3utils
, $window
, $timeout
) {
40 link = function(scope
, element
, attrs
, ctrl
) {
41 var dispatch
, id
, initialHandlers
, isUpdatingOptions
, promise
, updateEvents
, window_resize
, _u
;
43 dispatch
= _u
.getEventDispatcher();
45 element
[0].style
['font-size'] = 0;
46 scope
.redraw = function() {
49 isUpdatingOptions
= false;
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();
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';
66 svg
= _u
.bootstrap(element
[0], id
, dimensions
);
68 return (options
.series
.filter(function(s
) {
69 return s
.axis
=== key
&& s
.visible
!== false;
72 axes
= _u
.createAxes(svg
, dimensions
, options
.axes
).andAddThemIf({
78 if (dataPerSeries
.length
) {
79 _u
.setScalesDomain(axes
, scope
.data
, options
.series
, svg
, options
);
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
);
89 if (options
.drawLegend
) {
90 _u
.drawLegend(svg
, options
.series
, dimensions
, handlers
, dispatch
);
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
);
98 updateEvents = function() {
100 dispatch
.on('click', scope
.oldclick
);
101 } else if (scope
.click
) {
102 dispatch
.on('click', scope
.click
);
104 dispatch
.on('click', null);
106 if (scope
.oldhover
) {
107 dispatch
.on('hover', scope
.oldhover
);
108 } else if (scope
.hover
) {
109 dispatch
.on('hover', scope
.hover
);
111 dispatch
.on('hover', null);
113 if (scope
.oldfocus
) {
114 dispatch
.on('focus', scope
.oldfocus
);
115 } else if (scope
.focus
) {
116 dispatch
.on('focus', scope
.focus
);
118 dispatch
.on('focus', null);
121 return dispatch
.on('toggle', scope
.toggle
);
123 return dispatch
.on('toggle', null);
127 window_resize = function() {
128 if (promise
!= null) {
129 $timeout
.cancel(promise
);
131 return promise
= $timeout(scope
.redraw
, 1);
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
)
155 template
: '<div></div>',
161 mod
= angular
.module('n3charts.utils', []);
163 mod
.factory('n3utils', [
164 '$window', '$log', '$rootScope', function($window
, $log
, $rootScope
) {
166 addPatterns: function(svg
, series
) {
168 pattern
= svg
.select('defs').selectAll('pattern').data(series
.filter(function(s
) {
170 })).enter().append('pattern').attr({
172 return s
.type
+ 'Pattern_' + s
.index
;
174 patternUnits
: "userSpaceOnUse",
179 }).append('g').style({
180 'fill': function(s
) {
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");
191 drawArea: function(svg
, scales
, data
, options
) {
192 var areaSeries
, drawers
;
193 areaSeries
= data
.filter(function(series
) {
194 return series
.type
=== 'area';
196 this.addPatterns(svg
, areaSeries
);
198 y
: this.createLeftAreaDrawer(scales
, options
.lineMode
, options
.tension
),
199 y2
: this.createRightAreaDrawer(scales
, options
.lineMode
, options
.tension
)
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) {
207 return "url(#areaPattern_" + s
.index
+ ")";
208 }).style('opacity', function(s
) {
214 }).attr('d', function(d
) {
215 return drawers
[d
.axis
](d
.values
);
219 createLeftAreaDrawer: function(scales
, mode
, tension
) {
220 return d3
.svg
.area().x(function(d
) {
221 return scales
.xScale(d
.x
);
223 return scales
.yScale(d
.y0
);
225 return scales
.yScale(d
.y0
+ d
.y
);
226 }).interpolate(mode
).tension(tension
);
228 createRightAreaDrawer: function(scales
, mode
, tension
) {
229 return d3
.svg
.area().x(function(d
) {
230 return scales
.xScale(d
.x
);
232 return scales
.y2Scale(d
.y0
);
234 return scales
.y2Scale(d
.y0
+ d
.y
);
235 }).interpolate(mode
).tension(tension
);
237 getPseudoColumns: function(data
, options
) {
238 var keys
, pseudoColumns
;
239 data
= data
.filter(function(s
) {
240 return s
.type
=== 'column';
244 data
.forEach(function(series
) {
245 var i
, inAStack
, index
;
247 options
.stacks
.forEach(function(stack
, index
) {
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) {
254 return inAStack
= true;
257 if (inAStack
=== false) {
258 i
= pseudoColumns
[series
.id
] = index
= keys
.length
;
263 pseudoColumns
: pseudoColumns
,
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
) {
273 return e
>= range
[0] && e
<= range
[1];
277 }).reduce(function(prev
, cur
, i
, arr
) {
279 diff
= i
> 0 ? cur
- arr
[i
- 1] : Number
.MAX_VALUE
;
285 }, Number
.MAX_VALUE
);
288 getBestColumnWidth: function(axes
, dimensions
, seriesData
, options
) {
289 var colData
, delta
, innerWidth
, keys
, nSeries
, pseudoColumns
, _ref
;
290 if (!(seriesData
&& seriesData
.length
!== 0)) {
293 if ((seriesData
.filter(function(s
) {
294 return s
.type
=== 'column';
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
);
303 delta
= this.getMinDelta(colData
, 'x', axes
.xScale
, [0, innerWidth
]);
304 if (delta
> innerWidth
) {
305 delta
= 0.25 * innerWidth
;
307 nSeries
= keys
.length
;
308 return parseInt((delta
- options
.columnsHGap
) / nSeries
);
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);
316 if (pseudoColumns
[s
.id
] == null) {
319 index
= pseudoColumns
[s
.id
];
320 return x1(index
) - keys
.length
* columnWidth
/ 2;
323 drawColumns: function(svg
, axes
, data
, columnWidth
, options
, handlers
, dispatch
) {
325 data
= data
.filter(function(s
) {
326 return s
.type
=== 'column';
328 x1
= this.getColumnAxis(data
, columnWidth
, options
);
329 data
.forEach(function(s
) {
330 return s
.xOffset
= x1(s
) + columnWidth
* .5;
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)";
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
) {
348 'stroke-width': '1px',
349 'fill-opacity': function(d
) {
359 return axes
.xScale(d
.x
);
361 height: function(d
) {
363 return axes
[d
.axis
+ 'Scale'].range()[0];
365 return Math
.abs(axes
[d
.axis
+ 'Scale'](d
.y0
+ d
.y
) - axes
[d
.axis
+ 'Scale'](d
.y0
));
371 return axes
[d
.axis
+ 'Scale'](Math
.max(0, d
.y0
+ d
.y
));
375 'click': function(d
, i
) {
376 return dispatch
.click(d
, i
);
378 }).on('mouseover', function(d
, i
) {
379 dispatch
.hover(d
, i
);
380 return typeof handlers
.onMouseOver
=== "function" ? handlers
.onMouseOver(svg
, {
383 y
: axes
[d
.axis
+ 'Scale'](d
.y0
+ d
.y
),
385 }, options
.axes
) : void 0;
386 }).on('mouseout', function(d
) {
387 return typeof handlers
.onMouseOut
=== "function" ? handlers
.onMouseOut(svg
) : void 0;
392 drawDots: function(svg
, axes
, data
, options
, handlers
, dispatch
) {
394 dotGroup
= svg
.select('.content').selectAll('.dotGroup').data(data
.filter(function(s
) {
396 return ((_ref
= s
.type
) === 'line' || _ref
=== 'area') && s
.drawDots
;
397 })).enter().append('g');
399 "class": function(s
) {
400 return "dotGroup series_" + s
.index
;
405 }).selectAll('.dot').data(function(d
) {
407 }).enter().append('circle').attr({
413 return axes
.xScale(d
.x
);
416 return axes
[d
.axis
+ 'Scale'](d
.y
+ d
.y0
);
420 'stroke-width': '2px'
422 'click': function(d
, i
) {
423 return dispatch
.click(d
, i
);
426 'mouseover': function(d
, i
) {
427 return dispatch
.hover(d
, i
);
430 if (options
.tooltip
.mode
!== 'none') {
431 dotGroup
.on('mouseover', function(series
) {
433 target
= d3
.select(d3
.event
.target
);
435 target
.attr('r', function(s
) {
436 return s
.dotSize
+ 2;
438 return typeof handlers
.onMouseOver
=== "function" ? handlers
.onMouseOver(svg
, {
440 x
: target
.attr('cx'),
441 y
: target
.attr('cy'),
443 }, options
.axes
) : void 0;
444 }).on('mouseout', function(d
) {
445 d3
.select(d3
.event
.target
).attr('r', function(s
) {
448 return typeof handlers
.onMouseOut
=== "function" ? handlers
.onMouseOut(svg
) : void 0;
453 getEventDispatcher: function() {
455 events
= ['focus', 'hover', 'click', 'toggle'];
456 return d3
.dispatch
.apply(this, events
);
458 computeLegendLayout: function(svg
, series
, dimensions
) {
459 var cumul
, i
, j
, leftLayout
, leftWidths
, padding
, rightLayout
, rightWidths
, that
, w
;
462 leftWidths
= this.getLegendItemsWidths(svg
, 'y');
465 while (i
< leftWidths
.length
) {
466 leftLayout
.push(leftWidths
[i
- 1] + leftLayout
[i
- 1] + padding
);
469 rightWidths
= this.getLegendItemsWidths(svg
, 'y2');
470 if (!(rightWidths
.length
> 0)) {
473 w
= dimensions
.width
- dimensions
.right
- dimensions
.left
;
476 j
= rightWidths
.length
- 1;
478 rightLayout
.push(w
- cumul
- rightWidths
[j
]);
479 cumul
+= rightWidths
[j
] + padding
;
482 rightLayout
.reverse();
483 return [leftLayout
, rightLayout
];
485 getLegendItemsWidths: function(svg
, axis
) {
486 var bbox
, i
, items
, that
, widths
;
489 return that
.getTextBBox(t
).width
;
491 items
= svg
.selectAll(".legendItem." + axis
);
492 if (!(items
.length
> 0)) {
497 while (i
< items
[0].length
) {
498 widths
.push(bbox(items
[0][i
]));
503 drawLegend: function(svg
, series
, dimensions
, handlers
, dispatch
) {
504 var d
, groups
, legend
, that
, translateLegends
;
506 legend
= svg
.append('g').attr('class', 'legend');
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
) {
512 visibility
= !(s
.visible
!== false);
513 dispatch
.toggle(s
, i
, visibility
);
514 return typeof handlers
.onSeriesVisibilityChange
=== "function" ? handlers
.onSeriesVisibilityChange({
517 newVisibility
: visibility
521 'class': function(s
, i
) {
522 return "legendItem series_" + i
+ " " + s
.axis
;
524 'opacity': function(s
, i
) {
525 if (s
.visible
=== false) {
526 that
.toggleSeries(svg
, i
);
531 }).each(function(s
) {
533 item
= d3
.select(this);
534 item
.append('circle').attr({
537 'stroke-width': '2px',
540 item
.append('path').attr({
541 'clip-path': 'url(#legend-clip)',
542 'fill-opacity': (_ref
= s
.type
) === 'area' || _ref
=== 'column' ? '1' : '0',
545 'stroke-width': '2px',
546 'd': that
.getLegendItemPath(s
, d
, d
)
548 item
.append('circle').attr({
551 'stroke-width': '2px',
554 return item
.append('text').attr({
555 'class': function(d
, i
) {
556 return "legendText series_" + i
;
558 'font-family': 'Courier',
560 'transform': 'translate(13, 4)',
561 'text-rendering': 'geometric-precision'
562 }).text(s
.label
|| s
.y
);
564 translateLegends = function() {
565 var left
, right
, _ref
;
566 _ref
= that
.computeLegendLayout(svg
, series
, dimensions
), left
= _ref
[0], right
= _ref
[1];
568 'transform': function(s
, i
) {
569 if (s
.axis
=== 'y') {
570 return "translate(" + (left
.shift()) + "," + (dimensions
.height
- 40) + ")";
572 return "translate(" + (right
.shift()) + "," + (dimensions
.height
- 40) + ")";
578 setTimeout(translateLegends
, 0);
581 getLegendItemPath: function(series
, w
, h
) {
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
+ ' ';
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';
595 toggleSeries: function(svg
, index
) {
598 svg
.select('.content').selectAll('.series_' + index
).style('display', function(s
) {
599 if (d3
.select(this).style('display') === 'none') {
609 drawLines: function(svg
, scales
, data
, options
, handlers
) {
610 var drawers
, interpolateData
, lineGroup
;
612 y
: this.createLeftLineDrawer(scales
, options
.lineMode
, options
.tension
),
613 y2
: this.createRightLineDrawer(scales
, options
.lineMode
, options
.tension
)
615 lineGroup
= svg
.select('.content').selectAll('.lineGroup').data(data
.filter(function(s
) {
617 return (_ref
= s
.type
) === 'line' || _ref
=== 'area';
618 })).enter().append('g');
619 lineGroup
.style('stroke', function(s
) {
621 }).attr('class', function(s
) {
622 return "lineGroup series_" + s
.index
;
623 }).append('path').attr({
626 return drawers
[d
.axis
](d
.values
);
630 'stroke-width': function(s
) {
633 'stroke-dasharray': function(s
) {
634 if (s
.lineMode
=== 'dashed') {
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
);
645 mousePos
= d3
.mouse(this);
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
) {
659 if ((typeof maxXPos
=== "undefined" || maxXPos
=== null) || x
> maxXPos
) {
663 if ((typeof minYPos
=== "undefined" || minYPos
=== null) || y
< minYPos
) {
666 if ((typeof maxYPos
=== "undefined" || maxYPos
=== null) || y
> maxYPos
) {
669 if ((typeof minYValue
=== "undefined" || minYValue
=== null) || datum
.y
< minYValue
) {
672 if ((typeof maxYValue
=== "undefined" || maxYValue
=== null) || datum
.y
> maxYValue
) {
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
);
684 return typeof handlers
.onMouseOver
=== "function" ? handlers
.onMouseOver(svg
, {
689 }, options
.axes
) : void 0;
691 lineGroup
.on('mousemove', interpolateData
).on('mouseout', function(d
) {
692 return typeof handlers
.onMouseOut
=== "function" ? handlers
.onMouseOut(svg
) : void 0;
697 createLeftLineDrawer: function(scales
, mode
, tension
) {
698 return d3
.svg
.line().x(function(d
) {
699 return scales
.xScale(d
.x
);
701 return scales
.yScale(d
.y
+ d
.y0
);
702 }).interpolate(mode
).tension(tension
);
704 createRightLineDrawer: function(scales
, mode
, tension
) {
705 return d3
.svg
.line().x(function(d
) {
706 return scales
.xScale(d
.x
);
708 return scales
.y2Scale(d
.y
+ d
.y0
);
709 }).interpolate(mode
).tension(tension
);
711 getPixelCssProp: function(element
, propertyName
) {
713 string
= $window
.getComputedStyle(element
, null).getPropertyValue(propertyName
);
714 return +string
.replace(/px$/, '');
716 getDefaultMargins: function() {
724 getDefaultThumbnailMargins: function() {
732 getElementDimensions: function(element
, width
, height
) {
733 var bottom
, dim
, left
, parent
, right
, top
;
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
;
744 getDimensions: function(options
, element
, attrs
) {
746 dim
= this.getElementDimensions(element
[0].parentElement
, attrs
.width
, attrs
.height
);
747 dim
= angular
.extend(options
.margin
, dim
);
750 clean: function(element
) {
751 return d3
.select(element
).on('keydown', null).on('keyup', null).select('svg').remove();
754 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c
) {
756 r
= Math
.random() * 16 | 0;
757 v
= c
=== 'x' ? r
: r
& 0x3 | 0x8;
758 return v
.toString(16);
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({
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({
774 'width': width
- dimensions
.left
- dimensions
.right
,
775 'height': height
- dimensions
.top
- dimensions
.bottom
779 createContent: function(svg
, id
, options
) {
781 content
= svg
.append('g').attr('class', 'content');
782 if (options
.hideOverflow
) {
783 return content
.attr('clip-path', "url(#content-clip-" + id
+ ")");
786 createGlass: function(svg
, dimensions
, handlers
, axes
, data
, options
, dispatch
, columnWidth
) {
787 var glass
, scrubberGroup
, that
;
789 glass
= svg
.append('g').attr({
790 'class': 'glass-container',
793 scrubberGroup
= glass
.selectAll('.scrubberItem').data(data
).enter().append('g').attr('class', function(s
, i
) {
794 return "scrubberItem series_" + i
;
796 scrubberGroup
.each(function(s
, i
) {
798 item
= d3
.select(this);
799 g
= item
.append('g').attr({
802 g
.append('path').attr({
803 'class': "scrubberPath series_" + i
,
807 that
.styleTooltip(g
.append('text').style('text-anchor', 'start').attr({
808 'class': function(d
, i
) {
809 return "scrubberText series_" + i
;
812 'transform': 'translate(7, 3)',
813 'text-rendering': 'geometric-precision'
814 })).text(s
.label
|| s
.y
);
815 g2
= item
.append('g').attr({
818 g2
.append('path').attr({
819 'class': "scrubberPath series_" + i
,
823 that
.styleTooltip(g2
.append('text').style('text-anchor', 'end').attr({
824 'class': "scrubberText series_" + i
,
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
,
833 'stroke-width': '2px',
837 return glass
.append('rect').attr({
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
);
845 getDataPerSeries: function(data
, options
) {
846 var axes
, layout
, series
, straightened
;
847 series
= options
.series
;
849 if (!(series
&& series
.length
&& data
&& data
.length
)) {
852 straightened
= series
.map(function(s
, i
) {
862 thickness
: s
.thickness
,
863 drawDots
: s
.drawDots
!== false
865 if (s
.dotSize
!= null) {
866 seriesData
.dotSize
= s
.dotSize
;
868 if (s
.striped
=== true) {
869 seriesData
.striped
= true;
871 if (s
.lineMode
!= null) {
872 seriesData
.lineMode
= s
.lineMode
;
875 seriesData
.id
= s
.id
;
877 data
.filter(function(row
) {
878 return row
[s
.y
] != null;
879 }).forEach(function(row
) {
882 x
: row
[options
.axes
.x
.key
],
887 if (s
.dotSize
!= null) {
888 d
.dotSize
= s
.dotSize
;
890 return seriesData
.values
.push(d
);
894 if ((options
.stacks
== null) || options
.stacks
.length
=== 0) {
897 layout
= d3
.layout
.stack().values(function(s
) {
900 options
.stacks
.forEach(function(stack
) {
902 if (!(stack
.series
.length
> 0)) {
905 layers
= straightened
.filter(function(s
, i
) {
907 return (s
.id
!= null) && (_ref
= s
.id
, __indexOf
.call(stack
.series
, _ref
) >= 0);
909 return layout(layers
);
913 estimateSideTooltipWidth: function(svg
, text
) {
915 t
= svg
.append('text');
917 this.styleTooltip(t
);
918 bbox
= this.getTextBBox(t
[0][0]);
922 getTextBBox: function(svgTextElement
) {
924 if (svgTextElement
!== null) {
926 return svgTextElement
.getBBox();
939 getWidestTickWidth: function(svg
, axisKey
) {
940 var bbox
, max
, ticks
, _ref
;
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
);
951 getWidestOrdinate: function(data
, series
, options
) {
954 data
.forEach(function(row
) {
955 return series
.forEach(function(series
) {
958 if ((series
.axis
!= null) && ((_ref
= options
.axes
[series
.axis
]) != null ? _ref
.ticksFormatter
: void 0)) {
959 v
= options
.axes
[series
.axis
].ticksFormatter(v
);
964 if (('' + v
).length
> ('' + widest
).length
) {
971 getDefaultOptions: function() {
978 margin
: this.getDefaultMargins(),
996 sanitizeOptions: function(options
, mode
) {
998 if (options
== null) {
1001 if (mode
=== 'thumbnail') {
1002 options
.drawLegend
= false;
1003 options
.drawDots
= false;
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;
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
);
1029 sanitizeMargins: function(options
) {
1030 var attrs
, margin
, opt
, value
;
1031 attrs
= ['top', 'right', 'bottom', 'left'];
1033 for (opt
in options
) {
1034 value
= options
[opt
];
1035 if (__indexOf
.call(attrs
, opt
) >= 0) {
1036 margin
[opt
] = parseFloat(value
);
1041 sanitizeSeriesStacks: function(stacks
, series
) {
1043 if (stacks
== null) {
1047 series
.forEach(function(s
) {
1048 return seriesKeys
[s
.id
] = s
;
1050 stacks
.forEach(function(stack
) {
1051 return stack
.series
.forEach(function(id
) {
1055 if (s
.axis
!== stack
.axis
) {
1056 return $log
.warn("Series " + id
+ " is not on the same axis as its stack");
1060 return $log
.warn("Unknown series found in stack : " + id
);
1067 sanitizeTooltip: function(options
) {
1074 if ((_ref
= options
.mode
) !== 'none' && _ref
!== 'axes' && _ref
!== 'scrubber') {
1075 options
.mode
= 'scrubber';
1077 if (options
.mode
=== 'scrubber') {
1078 delete options
.interpolate
;
1080 options
.interpolate
= !!options
.interpolate
;
1082 if (options
.mode
=== 'scrubber' && options
.interpolate
) {
1083 throw new Error('Interpolation is not supported for scrubber tooltip mode.');
1087 sanitizeSeriesOptions: function(options
) {
1088 var colors
, knownIds
;
1089 if (options
== null) {
1092 colors
= d3
.scale
.category10();
1094 options
.forEach(function(s
, i
) {
1095 if (knownIds
[s
.id
] != null) {
1096 throw new Error("Twice the same ID (" + s
.id
+ ") ? Really ?");
1099 return knownIds
[s
.id
] = s
;
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') {
1112 } else if (!/^\d+px$/.test(s
.thickness
)) {
1113 s
.thickness
= '1px';
1115 if ((_ref2
= s
.type
) === 'line' || _ref2
=== 'area') {
1116 if ((_ref3
= s
.lineMode
) !== 'dashed') {
1119 if (s
.drawDots
!== false && (s
.dotSize
== null)) {
1125 while (knownIds
["series_" + cnt
] != null) {
1128 s
.id
= "series_" + cnt
;
1131 if (s
.drawDots
=== false) {
1132 return delete s
.dotSize
;
1137 sanitizeAxes: function(axesOptions
, secondAxis
) {
1139 if (axesOptions
== null) {
1142 axesOptions
.x
= this.sanitizeAxisOptions(axesOptions
.x
);
1143 (_base
= axesOptions
.x
).key
|| (_base
.key
= "x");
1144 axesOptions
.y
= this.sanitizeAxisOptions(axesOptions
.y
);
1146 axesOptions
.y2
= this.sanitizeAxisOptions(axesOptions
.y2
);
1150 sanitizeExtrema: function(options
) {
1152 min
= this.getSanitizedNumber(options
.min
);
1158 max
= this.getSanitizedNumber(options
.max
);
1160 return options
.max
= max
;
1162 return delete options
.max
;
1165 getSanitizedNumber: function(value
) {
1167 if (value
== null) {
1170 number
= parseFloat(value
);
1171 if (isNaN(number
)) {
1172 $log
.warn("Invalid extremum value : " + value
+ ", deleting it.");
1177 sanitizeAxisOptions: function(options
) {
1178 if (options
== null) {
1183 options
.type
|| (options
.type
= 'linear');
1184 if (options
.ticksRotate
!= null) {
1185 options
.ticksRotate
= this.getSanitizedNumber(options
.ticksRotate
);
1187 if (options
.labelFunction
!= null) {
1188 options
.ticksFormatter
= options
.labelFunction
;
1190 if (options
.ticksFormat
!= null) {
1191 if (options
.type
=== 'date') {
1192 options
.ticksFormatter
= d3
.time
.format(options
.ticksFormat
);
1194 options
.ticksFormatter
= d3
.format(options
.ticksFormat
);
1196 if (options
.tooltipFormatter
== null) {
1197 options
.tooltipFormatter
= options
.ticksFormatter
;
1200 if (options
.tooltipFormat
!= null) {
1201 if (options
.type
=== 'date') {
1202 options
.tooltipFormatter
= d3
.time
.format(options
.tooltipFormat
);
1204 options
.tooltipFormatter
= d3
.format(options
.tooltipFormat
);
1207 if (options
.ticksInterval
!= null) {
1208 options
.ticksInterval
= this.getSanitizedNumber(options
.ticksInterval
);
1210 this.sanitizeExtrema(options
);
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
;
1221 if (axesOptions
.x
.type
=== 'date') {
1222 x
= d3
.time
.scale().rangeRound([0, width
]);
1224 x
= d3
.scale
.linear().rangeRound([0, width
]);
1226 xAxis
= this.createAxis(x
, 'x', axesOptions
);
1228 if (axesOptions
.y
.type
=== 'log') {
1229 y
= d3
.scale
.log().clamp(true).rangeRound([height
, 0]);
1231 y
= d3
.scale
.linear().rangeRound([height
, 0]);
1234 yAxis
= this.createAxis(y
, 'y', axesOptions
);
1236 if (createY2Axis
&& axesOptions
.y2
.type
=== 'log') {
1237 y2
= d3
.scale
.log().clamp(true).rangeRound([height
, 0]);
1239 y2
= d3
.scale
.linear().rangeRound([height
, 0]);
1242 y2Axis
= this.createAxis(y2
, 'y2', axesOptions
);
1243 style = function(group
) {
1245 'font': '10px Courier',
1246 'shape-rendering': 'crispEdges'
1248 return group
.selectAll('path').style({
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
);
1265 if (!!conditions
.y
) {
1266 svg
.append('g').attr('class', 'y axis').call(yAxis
).call(style
);
1268 if (createY2Axis
&& !!conditions
.y2
) {
1269 svg
.append('g').attr('class', 'y2 axis').attr('transform', 'translate(' + width
+ ', 0)').call(y2Axis
).call(style
);
1283 createAxis: function(scale
, key
, options
) {
1291 axis
= d3
.svg
.axis().scale(scale
).orient(sides
[key
]).tickFormat(o
!= null ? o
.ticksFormatter
: void 0);
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
);
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');
1311 if ((series
.filter(function(s
) {
1312 return s
.axis
=== 'y' && s
.visible
!== false;
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');
1321 if ((series
.filter(function(s
) {
1322 return s
.axis
=== 'y2' && s
.visible
!== false;
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');
1332 getVerticalDomain: function(options
, data
, series
, key
) {
1333 var domain
, mySeries
, o
;
1334 if (!(o
= options
.axes
[key
])) {
1337 if ((o
.ticks
!= null) && angular
.isArray(o
.ticks
)) {
1338 return [o
.ticks
[0], o
.ticks
[o
.ticks
.length
- 1]];
1340 mySeries
= series
.filter(function(s
) {
1341 return s
.axis
=== key
&& s
.visible
!== false;
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
;
1348 if (o
.type
=== 'log') {
1349 domain
[0] = domain
[0] === 0 ? 0.001 : domain
[0];
1351 if (o
.min
!= null) {
1354 if (o
.max
!= null) {
1359 yExtent: function(series
, data
, stacks
) {
1360 var groups
, maxY
, minY
;
1361 minY
= Number
.POSITIVE_INFINITY
;
1362 maxY
= Number
.NEGATIVE_INFINITY
;
1364 stacks
.forEach(function(stack
) {
1365 return groups
.push(stack
.series
.map(function(id
) {
1366 return (series
.filter(function(s
) {
1371 series
.forEach(function(series
, i
) {
1374 stacks
.forEach(function(stack
) {
1376 if (_ref
= series
.id
, __indexOf
.call(stack
.series
, _ref
) >= 0) {
1377 return isInStack
= true;
1381 return groups
.push([series
]);
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
);
1391 return maxY
= Math
.max(maxY
, d3
.max(data
, function(d
) {
1392 return group
.reduce((function(a
, s
) {
1397 if (minY
=== maxY
) {
1399 return [0, minY
* 2];
1401 return [minY
* 2, 0];
1404 return [minY
, maxY
];
1406 setXScale: function(xScale
, data
, series
, axesOptions
) {
1408 domain
= this.xExtent(data
, axesOptions
.x
.key
);
1409 if (series
.filter(function(s
) {
1410 return s
.type
=== 'column';
1412 this.adjustXDomainForColumns(domain
, data
, axesOptions
.x
.key
);
1415 if (o
.min
!= null) {
1418 if (o
.max
!= null) {
1421 return xScale
.domain(domain
);
1423 xExtent: function(data
, key
) {
1425 _ref
= d3
.extent(data
, function(d
) {
1427 }), from = _ref
[0], to
= _ref
[1];
1430 return [0, from * 2];
1432 return [from * 2, 0];
1437 adjustXDomainForColumns: function(domain
, data
, field
) {
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
);
1444 domain
[0] = domain
[0] - step
;
1445 return domain
[1] = domain
[1] + step
;
1448 getAverageStep: function(data
, field
) {
1450 if (!(data
.length
> 1)) {
1454 n
= data
.length
- 1;
1457 sum
+= data
[i
+ 1][field
] - data
[i
][field
];
1462 haveSecondYAxis: function(series
) {
1463 return !series
.every(function(s
) {
1464 return s
.axis
!== 'y2';
1467 showScrubber: function(svg
, glass
, axes
, data
, options
, dispatch
, columnWidth
) {
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
);
1474 return glass
.on('mouseout', function() {
1475 glass
.on('mousemove', null);
1476 return svg
.selectAll('.glass-container').attr('opacity', 0);
1479 getClosestPoint: function(values
, xValue
) {
1480 var d
, d0
, d1
, i
, xBisector
;
1481 xBisector
= d3
.bisector(function(d
) {
1484 i
= xBisector(values
, xValue
);
1488 if (i
> values
.length
- 1) {
1489 return values
[values
.length
- 1];
1493 d
= xValue
- d0
.x
> d1
.x
- xValue
? d1
: d0
;
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);
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);
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
]);
1520 right
= item
.select('.rightTT');
1521 rText
= right
.select('text');
1523 left
= item
.select('.leftTT');
1524 lText
= left
.select('text');
1527 right
: that
.getTextBBox(rText
[0][0]).width
+ 5,
1528 left
: that
.getTextBBox(lText
[0][0]).width
+ 5
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) {
1536 } else if (side
=== 'right') {
1537 if (xPos
+ sizes
.right
> that
.getTextBBox(svg
.select('.glass')[0][0]).width
) {
1541 if (side
=== 'left') {
1542 ease(right
).attr('opacity', 0);
1543 ease(left
).attr('opacity', 1);
1545 ease(right
).attr('opacity', 1);
1546 ease(left
).attr('opacity', 0);
1548 positions
[index
] = {
1551 y
: axes
[v
.axis
+ 'Scale'](v
.y
+ v
.y0
),
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
);
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) {
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) + ")";
1574 return "translate(" + (4 + tickLength
+ xOffset
) + ", " + (p
.labelOffset
+ 3) + ")";
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
+ ")"
1583 getScrubberPath: function(w
, yOffset
, side
, padding
) {
1584 var h
, p
, xdir
, ydir
;
1588 xdir
= side
=== 'left' ? 1 : -1;
1590 if (yOffset
!== 0) {
1591 ydir
= Math
.abs(yOffset
) / yOffset
;
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('');
1596 preventOverlapping: function(positions
) {
1597 var abscissas
, getNeighbours
, h
, offset
;
1600 positions
.forEach(function(p
) {
1602 abscissas
[_name
= p
.x
] || (abscissas
[_name
] = {
1606 return abscissas
[p
.x
][p
.side
].push(p
);
1608 getNeighbours = function(side
) {
1609 var foundNeighbour
, neighbourhood
, neighbours
, neighboursForX
, p
, sides
, x
, y
, _ref
;
1611 for (x
in abscissas
) {
1612 sides
= abscissas
[x
];
1613 if (sides
[side
].length
=== 0) {
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;
1627 if (!foundNeighbour
) {
1628 neighboursForX
[p
.y
] = [p
];
1631 neighbours
.push(neighboursForX
);
1635 offset = function(neighboursForAbscissas
) {
1636 var abs
, n
, neighbours
, start
, step
, xNeighbours
, y
;
1638 for (abs
in neighboursForAbscissas
) {
1639 xNeighbours
= neighboursForAbscissas
[abs
];
1640 for (y
in xNeighbours
) {
1641 neighbours
= xNeighbours
[y
];
1642 n
= neighbours
.length
;
1644 neighbours
[0].labelOffset
= 0;
1647 neighbours
= neighbours
.sort(function(a
, b
) {
1651 start
= -(step
/ 2) * (n
/ 2);
1653 start
= -(n
- 1) / 2 * step
;
1655 neighbours
.forEach(function(neighbour
, i
) {
1656 return neighbour
.labelOffset
= start
+ step
* i
;
1661 offset(getNeighbours('left'));
1662 offset(getNeighbours('right'));
1665 getTooltipHandlers: function(options
) {
1666 if (options
.tooltip
.mode
=== 'scrubber') {
1668 onChartHover
: angular
.bind(this, this.showScrubber
)
1672 onMouseOver
: angular
.bind(this, this.onMouseOver
),
1673 onMouseOut
: angular
.bind(this, this.onMouseOut
)
1677 styleTooltip: function(d3TextElement
) {
1678 return d3TextElement
.attr({
1679 'font-family': 'monospace',
1682 'text-rendering': 'geometric-precision'
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
;
1694 xTooltip
= svg
.append('g').attr({
1696 'class': 'xTooltip',
1699 xTooltip
.append('path').attr('transform', "translate(0," + (height
+ 1) + ")");
1700 this.styleTooltip(xTooltip
.append('text').style('text-anchor', 'middle').attr({
1703 'transform': 'translate(0,' + (height
+ 19) + ')'
1705 yTooltip
= svg
.append('g').attr({
1707 "class": 'yTooltip',
1710 yTooltip
.append('path');
1711 this.styleTooltip(yTooltip
.append('text').attr({
1715 if (axesOptions
.y2
!= null) {
1716 y2Tooltip
= svg
.append('g').attr({
1718 'class': 'y2Tooltip',
1720 'transform': 'translate(' + width
+ ',0)'
1722 y2Tooltip
.append('path');
1723 return this.styleTooltip(y2Tooltip
.append('text').attr({
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
);
1734 return this.updateYTooltip(svg
, event
, axesOptions
.y
);
1737 onMouseOut: function(svg
) {
1738 return this.hideTooltips(svg
);
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({
1746 'transform': "translate(" + x
+ ",0)"
1748 _f
= xAxisOptions
.tooltipFormatter
;
1749 textX
= _f
? _f(datum
.x
) : datum
.x
;
1750 label
= xTooltip
.select('text');
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]));
1755 getXTooltipPath: function(textElement
) {
1757 w
= Math
.max(this.getTextBBox(textElement
).width
, 15);
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';
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({
1768 'transform': "translate(0, " + y
+ ")"
1770 _f
= yAxisOptions
.tooltipFormatter
;
1771 textY
= _f
? _f(datum
.y
) : datum
.y
;
1772 label
= yTooltip
.select('text');
1774 w
= this.getTextBBox(label
[0][0]).width
+ 5;
1776 'transform': 'translate(' + (-w
- 2) + ',3)',
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
));
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');
1791 w
= this.getTextBBox(label
[0][0]).width
+ 5;
1793 'transform': 'translate(7, ' + (parseFloat(y
) + 3) + ')',
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
+ ')'
1802 getYTooltipPath: function(w
) {
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';
1808 getY2TooltipPath: function(w
) {
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';
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);