| |
| /* |
| * |
| * Copyright 2016 RIFT.IO Inc |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| /* |
| line-chart - v1.1.9 - 21 June 2015 |
| https://github.com/n3-charts/line-chart |
| Copyright (c) 2015 n3-charts |
| */ |
| var directive, m, mod, old_m, |
| __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; }; |
| |
| old_m = angular.module('n3-charts.linechart', ['n3charts.utils']); |
| |
| m = angular.module('n3-line-chart', ['n3charts.utils']); |
| |
| directive = function(name, conf) { |
| old_m.directive(name, conf); |
| return m.directive(name, conf); |
| }; |
| |
| directive('linechart', [ |
| 'n3utils', '$window', '$timeout', function(n3utils, $window, $timeout) { |
| var link; |
| link = function(scope, element, attrs, ctrl) { |
| var dispatch, id, initialHandlers, isUpdatingOptions, promise, updateEvents, window_resize, _u; |
| _u = n3utils; |
| dispatch = _u.getEventDispatcher(); |
| id = _u.uuid(); |
| element[0].style['font-size'] = 0; |
| scope.redraw = function() { |
| scope.update(); |
| }; |
| isUpdatingOptions = false; |
| initialHandlers = { |
| onSeriesVisibilityChange: function(_arg) { |
| var index, newVisibility, series; |
| series = _arg.series, index = _arg.index, newVisibility = _arg.newVisibility; |
| scope.options.series[index].visible = newVisibility; |
| return scope.$apply(); |
| } |
| }; |
| scope.update = function() { |
| var axes, columnWidth, dataPerSeries, dimensions, fn, handlers, isThumbnail, options, svg; |
| options = _u.sanitizeOptions(scope.options, attrs.mode); |
| handlers = angular.extend(initialHandlers, _u.getTooltipHandlers(options)); |
| dataPerSeries = _u.getDataPerSeries(scope.data, options); |
| dimensions = _u.getDimensions(options, element, attrs); |
| isThumbnail = attrs.mode === 'thumbnail'; |
| _u.clean(element[0]); |
| svg = _u.bootstrap(element[0], id, dimensions); |
| fn = function(key) { |
| return (options.series.filter(function(s) { |
| return s.axis === key && s.visible !== false; |
| })).length > 0; |
| }; |
| axes = _u.createAxes(svg, dimensions, options.axes).andAddThemIf({ |
| all: !isThumbnail, |
| x: true, |
| y: fn('y'), |
| y2: fn('y2') |
| }); |
| if (dataPerSeries.length) { |
| _u.setScalesDomain(axes, scope.data, options.series, svg, options); |
| } |
| _u.createContent(svg, id, options, handlers); |
| if (dataPerSeries.length) { |
| columnWidth = _u.getBestColumnWidth(axes, dimensions, dataPerSeries, options); |
| _u.drawArea(svg, axes, dataPerSeries, options, handlers).drawColumns(svg, axes, dataPerSeries, columnWidth, options, handlers, dispatch).drawLines(svg, axes, dataPerSeries, options, handlers); |
| if (options.drawDots) { |
| _u.drawDots(svg, axes, dataPerSeries, options, handlers, dispatch); |
| } |
| } |
| if (options.drawLegend) { |
| _u.drawLegend(svg, options.series, dimensions, handlers, dispatch); |
| } |
| if (options.tooltip.mode === 'scrubber') { |
| return _u.createGlass(svg, dimensions, handlers, axes, dataPerSeries, options, dispatch, columnWidth); |
| } else if (options.tooltip.mode !== 'none') { |
| return _u.addTooltips(svg, dimensions, options.axes); |
| } |
| }; |
| updateEvents = function() { |
| if (scope.oldclick) { |
| dispatch.on('click', scope.oldclick); |
| } else if (scope.click) { |
| dispatch.on('click', scope.click); |
| } else { |
| dispatch.on('click', null); |
| } |
| if (scope.oldhover) { |
| dispatch.on('hover', scope.oldhover); |
| } else if (scope.hover) { |
| dispatch.on('hover', scope.hover); |
| } else { |
| dispatch.on('hover', null); |
| } |
| if (scope.oldfocus) { |
| dispatch.on('focus', scope.oldfocus); |
| } else if (scope.focus) { |
| dispatch.on('focus', scope.focus); |
| } else { |
| dispatch.on('focus', null); |
| } |
| if (scope.toggle) { |
| return dispatch.on('toggle', scope.toggle); |
| } else { |
| return dispatch.on('toggle', null); |
| } |
| }; |
| promise = void 0; |
| window_resize = function() { |
| if (promise != null) { |
| $timeout.cancel(promise); |
| } |
| return promise = $timeout(scope.redraw, 1); |
| }; |
| $window.addEventListener('resize', window_resize); |
| scope.$watch('data', scope.redraw, true); |
| scope.$watch('options', scope.redraw, true); |
| scope.$watchCollection('[click, hover, focus, toggle]', updateEvents); |
| scope.$watchCollection('[oldclick, oldhover, oldfocus]', updateEvents); |
| console.log('data', scope.data) |
| window_resize(); |
| }; |
| return { |
| replace: true, |
| restrict: 'E', |
| scope: { |
| data: '=', |
| options: '=', |
| oldclick: '=click', |
| oldhover: '=hover', |
| oldfocus: '=focus', |
| click: '=onClick', |
| hover: '=onHover', |
| focus: '=onFocus', |
| toggle: '=onToggle' |
| }, |
| template: '<div></div>', |
| link: link |
| }; |
| } |
| ]); |
| |
| mod = angular.module('n3charts.utils', []); |
| |
| mod.factory('n3utils', [ |
| '$window', '$log', '$rootScope', function($window, $log, $rootScope) { |
| return { |
| addPatterns: function(svg, series) { |
| var pattern; |
| pattern = svg.select('defs').selectAll('pattern').data(series.filter(function(s) { |
| return s.striped; |
| })).enter().append('pattern').attr({ |
| id: function(s) { |
| return s.type + 'Pattern_' + s.index; |
| }, |
| patternUnits: "userSpaceOnUse", |
| x: 0, |
| y: 0, |
| width: 60, |
| height: 60 |
| }).append('g').style({ |
| 'fill': function(s) { |
| return s.color; |
| }, |
| 'fill-opacity': 0.3 |
| }); |
| pattern.append('rect').style('fill-opacity', 0.3).attr('width', 60).attr('height', 60); |
| pattern.append('path').attr('d', "M 10 0 l10 0 l -20 20 l 0 -10 z"); |
| pattern.append('path').attr('d', "M40 0 l10 0 l-50 50 l0 -10 z"); |
| pattern.append('path').attr('d', "M60 10 l0 10 l-40 40 l-10 0 z"); |
| return pattern.append('path').attr('d', "M60 40 l0 10 l-10 10 l -10 0 z"); |
| }, |
| drawArea: function(svg, scales, data, options) { |
| var areaSeries, drawers; |
| areaSeries = data.filter(function(series) { |
| return series.type === 'area'; |
| }); |
| this.addPatterns(svg, areaSeries); |
| drawers = { |
| y: this.createLeftAreaDrawer(scales, options.lineMode, options.tension), |
| y2: this.createRightAreaDrawer(scales, options.lineMode, options.tension) |
| }; |
| svg.select('.content').selectAll('.areaGroup').data(areaSeries).enter().append('g').attr('class', function(s) { |
| return 'areaGroup ' + 'series_' + s.index; |
| }).append('path').attr('class', 'area').style('fill', function(s) { |
| if (s.striped !== true) { |
| return s.color; |
| } |
| return "url(#areaPattern_" + s.index + ")"; |
| }).style('opacity', function(s) { |
| if (s.striped) { |
| return '1'; |
| } else { |
| return '0.3'; |
| } |
| }).attr('d', function(d) { |
| return drawers[d.axis](d.values); |
| }); |
| return this; |
| }, |
| createLeftAreaDrawer: function(scales, mode, tension) { |
| return d3.svg.area().x(function(d) { |
| return scales.xScale(d.x); |
| }).y0(function(d) { |
| return scales.yScale(d.y0); |
| }).y1(function(d) { |
| return scales.yScale(d.y0 + d.y); |
| }).interpolate(mode).tension(tension); |
| }, |
| createRightAreaDrawer: function(scales, mode, tension) { |
| return d3.svg.area().x(function(d) { |
| return scales.xScale(d.x); |
| }).y0(function(d) { |
| return scales.y2Scale(d.y0); |
| }).y1(function(d) { |
| return scales.y2Scale(d.y0 + d.y); |
| }).interpolate(mode).tension(tension); |
| }, |
| getPseudoColumns: function(data, options) { |
| var keys, pseudoColumns; |
| data = data.filter(function(s) { |
| return s.type === 'column'; |
| }); |
| pseudoColumns = {}; |
| keys = []; |
| data.forEach(function(series) { |
| var i, inAStack, index; |
| inAStack = false; |
| options.stacks.forEach(function(stack, index) { |
| var _ref; |
| if ((series.id != null) && (_ref = series.id, __indexOf.call(stack.series, _ref) >= 0)) { |
| pseudoColumns[series.id] = index; |
| if (__indexOf.call(keys, index) < 0) { |
| keys.push(index); |
| } |
| return inAStack = true; |
| } |
| }); |
| if (inAStack === false) { |
| i = pseudoColumns[series.id] = index = keys.length; |
| return keys.push(i); |
| } |
| }); |
| return { |
| pseudoColumns: pseudoColumns, |
| keys: keys |
| }; |
| }, |
| getMinDelta: function(seriesData, key, scale, range) { |
| return d3.min(seriesData.map(function(series) { |
| return series.values.map(function(d) { |
| return scale(d[key]); |
| }).filter(function(e) { |
| if (range) { |
| return e >= range[0] && e <= range[1]; |
| } else { |
| return true; |
| } |
| }).reduce(function(prev, cur, i, arr) { |
| var diff; |
| diff = i > 0 ? cur - arr[i - 1] : Number.MAX_VALUE; |
| if (diff < prev) { |
| return diff; |
| } else { |
| return prev; |
| } |
| }, Number.MAX_VALUE); |
| })); |
| }, |
| getBestColumnWidth: function(axes, dimensions, seriesData, options) { |
| var colData, delta, innerWidth, keys, nSeries, pseudoColumns, _ref; |
| if (!(seriesData && seriesData.length !== 0)) { |
| return 10; |
| } |
| if ((seriesData.filter(function(s) { |
| return s.type === 'column'; |
| })).length === 0) { |
| return 10; |
| } |
| _ref = this.getPseudoColumns(seriesData, options), pseudoColumns = _ref.pseudoColumns, keys = _ref.keys; |
| innerWidth = dimensions.width - dimensions.left - dimensions.right; |
| colData = seriesData.filter(function(d) { |
| return pseudoColumns.hasOwnProperty(d.id); |
| }); |
| delta = this.getMinDelta(colData, 'x', axes.xScale, [0, innerWidth]); |
| if (delta > innerWidth) { |
| delta = 0.25 * innerWidth; |
| } |
| nSeries = keys.length; |
| return parseInt((delta - options.columnsHGap) / nSeries); |
| }, |
| getColumnAxis: function(data, columnWidth, options) { |
| var keys, pseudoColumns, x1, _ref; |
| _ref = this.getPseudoColumns(data, options), pseudoColumns = _ref.pseudoColumns, keys = _ref.keys; |
| x1 = d3.scale.ordinal().domain(keys).rangeBands([0, keys.length * columnWidth], 0); |
| return function(s) { |
| var index; |
| if (pseudoColumns[s.id] == null) { |
| return 0; |
| } |
| index = pseudoColumns[s.id]; |
| return x1(index) - keys.length * columnWidth / 2; |
| }; |
| }, |
| drawColumns: function(svg, axes, data, columnWidth, options, handlers, dispatch) { |
| var colGroup, x1; |
| data = data.filter(function(s) { |
| return s.type === 'column'; |
| }); |
| x1 = this.getColumnAxis(data, columnWidth, options); |
| data.forEach(function(s) { |
| return s.xOffset = x1(s) + columnWidth * .5; |
| }); |
| colGroup = svg.select('.content').selectAll('.columnGroup').data(data).enter().append("g").attr('class', function(s) { |
| return 'columnGroup series_' + s.index; |
| }).attr('transform', function(s) { |
| return "translate(" + x1(s) + ",0)"; |
| }); |
| colGroup.each(function(series) { |
| return d3.select(this).selectAll("rect").data(series.values).enter().append("rect").style({ |
| 'stroke': series.color, |
| 'fill': series.color, |
| 'stroke-opacity': function(d) { |
| if (d.y === 0) { |
| return '0'; |
| } else { |
| return '1'; |
| } |
| }, |
| 'stroke-width': '1px', |
| 'fill-opacity': function(d) { |
| if (d.y === 0) { |
| return 0; |
| } else { |
| return 0.7; |
| } |
| } |
| }).attr({ |
| width: columnWidth, |
| x: function(d) { |
| return axes.xScale(d.x); |
| }, |
| height: function(d) { |
| if (d.y === 0) { |
| return axes[d.axis + 'Scale'].range()[0]; |
| } |
| return Math.abs(axes[d.axis + 'Scale'](d.y0 + d.y) - axes[d.axis + 'Scale'](d.y0)); |
| }, |
| y: function(d) { |
| if (d.y === 0) { |
| return 0; |
| } else { |
| return axes[d.axis + 'Scale'](Math.max(0, d.y0 + d.y)); |
| } |
| } |
| }).on({ |
| 'click': function(d, i) { |
| return dispatch.click(d, i); |
| } |
| }).on('mouseover', function(d, i) { |
| dispatch.hover(d, i); |
| return typeof handlers.onMouseOver === "function" ? handlers.onMouseOver(svg, { |
| series: series, |
| x: axes.xScale(d.x), |
| y: axes[d.axis + 'Scale'](d.y0 + d.y), |
| datum: d |
| }, options.axes) : void 0; |
| }).on('mouseout', function(d) { |
| return typeof handlers.onMouseOut === "function" ? handlers.onMouseOut(svg) : void 0; |
| }); |
| }); |
| return this; |
| }, |
| drawDots: function(svg, axes, data, options, handlers, dispatch) { |
| var dotGroup; |
| dotGroup = svg.select('.content').selectAll('.dotGroup').data(data.filter(function(s) { |
| var _ref; |
| return ((_ref = s.type) === 'line' || _ref === 'area') && s.drawDots; |
| })).enter().append('g'); |
| dotGroup.attr({ |
| "class": function(s) { |
| return "dotGroup series_" + s.index; |
| }, |
| fill: function(s) { |
| return s.color; |
| } |
| }).selectAll('.dot').data(function(d) { |
| return d.values; |
| }).enter().append('circle').attr({ |
| 'class': 'dot', |
| 'r': function(d) { |
| return d.dotSize; |
| }, |
| 'cx': function(d) { |
| return axes.xScale(d.x); |
| }, |
| 'cy': function(d) { |
| return axes[d.axis + 'Scale'](d.y + d.y0); |
| } |
| }).style({ |
| 'stroke': 'white', |
| 'stroke-width': '2px' |
| }).on({ |
| 'click': function(d, i) { |
| return dispatch.click(d, i); |
| } |
| }).on({ |
| 'mouseover': function(d, i) { |
| return dispatch.hover(d, i); |
| } |
| }); |
| if (options.tooltip.mode !== 'none') { |
| dotGroup.on('mouseover', function(series) { |
| var d, target; |
| target = d3.select(d3.event.target); |
| d = target.datum(); |
| target.attr('r', function(s) { |
| return s.dotSize + 2; |
| }); |
| return typeof handlers.onMouseOver === "function" ? handlers.onMouseOver(svg, { |
| series: series, |
| x: target.attr('cx'), |
| y: target.attr('cy'), |
| datum: d |
| }, options.axes) : void 0; |
| }).on('mouseout', function(d) { |
| d3.select(d3.event.target).attr('r', function(s) { |
| return s.dotSize; |
| }); |
| return typeof handlers.onMouseOut === "function" ? handlers.onMouseOut(svg) : void 0; |
| }); |
| } |
| return this; |
| }, |
| getEventDispatcher: function() { |
| var events; |
| events = ['focus', 'hover', 'click', 'toggle']; |
| return d3.dispatch.apply(this, events); |
| }, |
| computeLegendLayout: function(svg, series, dimensions) { |
| var cumul, i, j, leftLayout, leftWidths, padding, rightLayout, rightWidths, that, w; |
| padding = 10; |
| that = this; |
| leftWidths = this.getLegendItemsWidths(svg, 'y'); |
| leftLayout = [0]; |
| i = 1; |
| while (i < leftWidths.length) { |
| leftLayout.push(leftWidths[i - 1] + leftLayout[i - 1] + padding); |
| i++; |
| } |
| rightWidths = this.getLegendItemsWidths(svg, 'y2'); |
| if (!(rightWidths.length > 0)) { |
| return [leftLayout]; |
| } |
| w = dimensions.width - dimensions.right - dimensions.left; |
| cumul = 0; |
| rightLayout = []; |
| j = rightWidths.length - 1; |
| while (j >= 0) { |
| rightLayout.push(w - cumul - rightWidths[j]); |
| cumul += rightWidths[j] + padding; |
| j--; |
| } |
| rightLayout.reverse(); |
| return [leftLayout, rightLayout]; |
| }, |
| getLegendItemsWidths: function(svg, axis) { |
| var bbox, i, items, that, widths; |
| that = this; |
| bbox = function(t) { |
| return that.getTextBBox(t).width; |
| }; |
| items = svg.selectAll(".legendItem." + axis); |
| if (!(items.length > 0)) { |
| return []; |
| } |
| widths = []; |
| i = 0; |
| while (i < items[0].length) { |
| widths.push(bbox(items[0][i])); |
| i++; |
| } |
| return widths; |
| }, |
| drawLegend: function(svg, series, dimensions, handlers, dispatch) { |
| var d, groups, legend, that, translateLegends; |
| that = this; |
| legend = svg.append('g').attr('class', 'legend'); |
| d = 16; |
| svg.select('defs').append('svg:clipPath').attr('id', 'legend-clip').append('circle').attr('r', d / 2); |
| groups = legend.selectAll('.legendItem').data(series); |
| groups.enter().append('g').on('click', function(s, i) { |
| var visibility; |
| visibility = !(s.visible !== false); |
| dispatch.toggle(s, i, visibility); |
| return typeof handlers.onSeriesVisibilityChange === "function" ? handlers.onSeriesVisibilityChange({ |
| series: s, |
| index: i, |
| newVisibility: visibility |
| }) : void 0; |
| }); |
| groups.attr({ |
| 'class': function(s, i) { |
| return "legendItem series_" + i + " " + s.axis; |
| }, |
| 'opacity': function(s, i) { |
| if (s.visible === false) { |
| that.toggleSeries(svg, i); |
| return '0.2'; |
| } |
| return '1'; |
| } |
| }).each(function(s) { |
| var item, _ref; |
| item = d3.select(this); |
| item.append('circle').attr({ |
| 'fill': s.color, |
| 'stroke': s.color, |
| 'stroke-width': '2px', |
| 'r': d / 2 |
| }); |
| item.append('path').attr({ |
| 'clip-path': 'url(#legend-clip)', |
| 'fill-opacity': (_ref = s.type) === 'area' || _ref === 'column' ? '1' : '0', |
| 'fill': 'white', |
| 'stroke': 'white', |
| 'stroke-width': '2px', |
| 'd': that.getLegendItemPath(s, d, d) |
| }); |
| item.append('circle').attr({ |
| 'fill-opacity': 0, |
| 'stroke': s.color, |
| 'stroke-width': '2px', |
| 'r': d / 2 |
| }); |
| return item.append('text').attr({ |
| 'class': function(d, i) { |
| return "legendText series_" + i; |
| }, |
| 'font-family': 'Courier', |
| 'font-size': 10, |
| 'transform': 'translate(13, 4)', |
| 'text-rendering': 'geometric-precision' |
| }).text(s.label || s.y); |
| }); |
| translateLegends = function() { |
| var left, right, _ref; |
| _ref = that.computeLegendLayout(svg, series, dimensions), left = _ref[0], right = _ref[1]; |
| return groups.attr({ |
| 'transform': function(s, i) { |
| if (s.axis === 'y') { |
| return "translate(" + (left.shift()) + "," + (dimensions.height - 40) + ")"; |
| } else { |
| return "translate(" + (right.shift()) + "," + (dimensions.height - 40) + ")"; |
| } |
| } |
| }); |
| }; |
| translateLegends(); |
| setTimeout(translateLegends, 0); |
| return this; |
| }, |
| getLegendItemPath: function(series, w, h) { |
| var base_path, path; |
| if (series.type === 'column') { |
| path = 'M' + (-w / 3) + ' ' + (-h / 8) + ' l0 ' + h + ' '; |
| path += 'M0' + ' ' + (-h / 3) + ' l0 ' + h + ' '; |
| path += 'M' + w / 3 + ' ' + (-h / 10) + ' l0 ' + h + ' '; |
| return path; |
| } |
| base_path = 'M-' + w / 2 + ' 0' + h / 3 + ' l' + w / 3 + ' -' + h / 3 + ' l' + w / 3 + ' ' + h / 3 + ' l' + w / 3 + ' -' + 2 * h / 3; |
| if (series.type === 'area') { |
| base_path + ' l0 ' + h + ' l-' + w + ' 0z'; |
| } |
| return base_path; |
| }, |
| toggleSeries: function(svg, index) { |
| var isVisible; |
| isVisible = false; |
| svg.select('.content').selectAll('.series_' + index).style('display', function(s) { |
| if (d3.select(this).style('display') === 'none') { |
| isVisible = true; |
| return 'initial'; |
| } else { |
| isVisible = false; |
| return 'none'; |
| } |
| }); |
| return isVisible; |
| }, |
| drawLines: function(svg, scales, data, options, handlers) { |
| var drawers, interpolateData, lineGroup; |
| drawers = { |
| y: this.createLeftLineDrawer(scales, options.lineMode, options.tension), |
| y2: this.createRightLineDrawer(scales, options.lineMode, options.tension) |
| }; |
| lineGroup = svg.select('.content').selectAll('.lineGroup').data(data.filter(function(s) { |
| var _ref; |
| return (_ref = s.type) === 'line' || _ref === 'area'; |
| })).enter().append('g'); |
| lineGroup.style('stroke', function(s) { |
| return s.color; |
| }).attr('class', function(s) { |
| return "lineGroup series_" + s.index; |
| }).append('path').attr({ |
| "class": 'line', |
| d: function(d) { |
| return drawers[d.axis](d.values); |
| } |
| }).style({ |
| 'fill': 'none', |
| 'stroke-width': function(s) { |
| return s.thickness; |
| }, |
| 'stroke-dasharray': function(s) { |
| if (s.lineMode === 'dashed') { |
| return '10,3'; |
| } |
| return void 0; |
| } |
| }); |
| if (options.tooltip.interpolate) { |
| interpolateData = function(series) { |
| var datum, error, i, interpDatum, maxXPos, maxXValue, maxYPos, maxYValue, minXPos, minXValue, minYPos, minYValue, mousePos, target, valuesData, x, xPercentage, xVal, y, yPercentage, yVal, _i, _len; |
| target = d3.select(d3.event.target); |
| try { |
| mousePos = d3.mouse(this); |
| } catch (_error) { |
| error = _error; |
| mousePos = [0, 0]; |
| } |
| valuesData = target.datum().values; |
| for (i = _i = 0, _len = valuesData.length; _i < _len; i = ++_i) { |
| datum = valuesData[i]; |
| x = scales.xScale(datum.x); |
| y = scales.yScale(datum.y); |
| if ((typeof minXPos === "undefined" || minXPos === null) || x < minXPos) { |
| minXPos = x; |
| minXValue = datum.x; |
| } |
| if ((typeof maxXPos === "undefined" || maxXPos === null) || x > maxXPos) { |
| maxXPos = x; |
| maxXValue = datum.x; |
| } |
| if ((typeof minYPos === "undefined" || minYPos === null) || y < minYPos) { |
| minYPos = y; |
| } |
| if ((typeof maxYPos === "undefined" || maxYPos === null) || y > maxYPos) { |
| maxYPos = y; |
| } |
| if ((typeof minYValue === "undefined" || minYValue === null) || datum.y < minYValue) { |
| minYValue = datum.y; |
| } |
| if ((typeof maxYValue === "undefined" || maxYValue === null) || datum.y > maxYValue) { |
| maxYValue = datum.y; |
| } |
| } |
| xPercentage = (mousePos[0] - minXPos) / (maxXPos - minXPos); |
| yPercentage = (mousePos[1] - minYPos) / (maxYPos - minYPos); |
| xVal = Math.round(xPercentage * (maxXValue - minXValue) + minXValue); |
| yVal = Math.round((1 - yPercentage) * (maxYValue - minYValue) + minYValue); |
| interpDatum = { |
| x: xVal, |
| y: yVal |
| }; |
| return typeof handlers.onMouseOver === "function" ? handlers.onMouseOver(svg, { |
| series: series, |
| x: mousePos[0], |
| y: mousePos[1], |
| datum: interpDatum |
| }, options.axes) : void 0; |
| }; |
| lineGroup.on('mousemove', interpolateData).on('mouseout', function(d) { |
| return typeof handlers.onMouseOut === "function" ? handlers.onMouseOut(svg) : void 0; |
| }); |
| } |
| return this; |
| }, |
| createLeftLineDrawer: function(scales, mode, tension) { |
| return d3.svg.line().x(function(d) { |
| return scales.xScale(d.x); |
| }).y(function(d) { |
| return scales.yScale(d.y + d.y0); |
| }).interpolate(mode).tension(tension); |
| }, |
| createRightLineDrawer: function(scales, mode, tension) { |
| return d3.svg.line().x(function(d) { |
| return scales.xScale(d.x); |
| }).y(function(d) { |
| return scales.y2Scale(d.y + d.y0); |
| }).interpolate(mode).tension(tension); |
| }, |
| getPixelCssProp: function(element, propertyName) { |
| var string; |
| string = $window.getComputedStyle(element, null).getPropertyValue(propertyName); |
| return +string.replace(/px$/, ''); |
| }, |
| getDefaultMargins: function() { |
| return { |
| top: 20, |
| right: 50, |
| bottom: 60, |
| left: 50 |
| }; |
| }, |
| getDefaultThumbnailMargins: function() { |
| return { |
| top: 1, |
| right: 1, |
| bottom: 2, |
| left: 0 |
| }; |
| }, |
| getElementDimensions: function(element, width, height) { |
| var bottom, dim, left, parent, right, top; |
| dim = {}; |
| parent = element; |
| top = this.getPixelCssProp(parent, 'padding-top'); |
| bottom = this.getPixelCssProp(parent, 'padding-bottom'); |
| left = this.getPixelCssProp(parent, 'padding-left'); |
| right = this.getPixelCssProp(parent, 'padding-right'); |
| dim.width = +(width || parent.offsetWidth || 900) - left - right; |
| dim.height = +(height || parent.offsetHeight || 500) - top - bottom; |
| return dim; |
| }, |
| getDimensions: function(options, element, attrs) { |
| var dim; |
| dim = this.getElementDimensions(element[0].parentElement, attrs.width, attrs.height); |
| dim = angular.extend(options.margin, dim); |
| return dim; |
| }, |
| clean: function(element) { |
| return d3.select(element).on('keydown', null).on('keyup', null).select('svg').remove(); |
| }, |
| uuid: function() { |
| return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { |
| var r, v; |
| r = Math.random() * 16 | 0; |
| v = c === 'x' ? r : r & 0x3 | 0x8; |
| return v.toString(16); |
| }); |
| }, |
| bootstrap: function(element, id, dimensions) { |
| var defs, height, svg, width; |
| d3.select(element).classed('chart', true); |
| width = dimensions.width; |
| height = dimensions.height; |
| svg = d3.select(element).append('svg').attr({ |
| width: width, |
| height: height |
| }).append('g').attr('transform', 'translate(' + dimensions.left + ',' + dimensions.top + ')'); |
| defs = svg.append('defs').attr('class', 'patterns'); |
| defs.append('clipPath').attr('class', 'content-clip').attr('id', "content-clip-" + id).append('rect').attr({ |
| 'x': 0, |
| 'y': 0, |
| 'width': width - dimensions.left - dimensions.right, |
| 'height': height - dimensions.top - dimensions.bottom |
| }); |
| return svg; |
| }, |
| createContent: function(svg, id, options) { |
| var content; |
| content = svg.append('g').attr('class', 'content'); |
| if (options.hideOverflow) { |
| return content.attr('clip-path', "url(#content-clip-" + id + ")"); |
| } |
| }, |
| createGlass: function(svg, dimensions, handlers, axes, data, options, dispatch, columnWidth) { |
| var glass, scrubberGroup, that; |
| that = this; |
| glass = svg.append('g').attr({ |
| 'class': 'glass-container', |
| 'opacity': 0 |
| }); |
| scrubberGroup = glass.selectAll('.scrubberItem').data(data).enter().append('g').attr('class', function(s, i) { |
| return "scrubberItem series_" + i; |
| }); |
| scrubberGroup.each(function(s, i) { |
| var g, g2, item; |
| item = d3.select(this); |
| g = item.append('g').attr({ |
| 'class': "rightTT" |
| }); |
| g.append('path').attr({ |
| 'class': "scrubberPath series_" + i, |
| 'y': '-7px', |
| 'fill': s.color |
| }); |
| that.styleTooltip(g.append('text').style('text-anchor', 'start').attr({ |
| 'class': function(d, i) { |
| return "scrubberText series_" + i; |
| }, |
| 'height': '14px', |
| 'transform': 'translate(7, 3)', |
| 'text-rendering': 'geometric-precision' |
| })).text(s.label || s.y); |
| g2 = item.append('g').attr({ |
| 'class': "leftTT" |
| }); |
| g2.append('path').attr({ |
| 'class': "scrubberPath series_" + i, |
| 'y': '-7px', |
| 'fill': s.color |
| }); |
| that.styleTooltip(g2.append('text').style('text-anchor', 'end').attr({ |
| 'class': "scrubberText series_" + i, |
| 'height': '14px', |
| 'transform': 'translate(-13, 3)', |
| 'text-rendering': 'geometric-precision' |
| })).text(s.label || s.y); |
| return item.append('circle').attr({ |
| 'class': "scrubberDot series_" + i, |
| 'fill': 'white', |
| 'stroke': s.color, |
| 'stroke-width': '2px', |
| 'r': 4 |
| }); |
| }); |
| return glass.append('rect').attr({ |
| "class": 'glass', |
| width: dimensions.width - dimensions.left - dimensions.right, |
| height: dimensions.height - dimensions.top - dimensions.bottom |
| }).style('fill', 'white').style('fill-opacity', 0.000001).on('mouseover', function() { |
| return handlers.onChartHover(svg, d3.select(this), axes, data, options, dispatch, columnWidth); |
| }); |
| }, |
| getDataPerSeries: function(data, options) { |
| var axes, layout, series, straightened; |
| series = options.series; |
| axes = options.axes; |
| if (!(series && series.length && data && data.length)) { |
| return []; |
| } |
| straightened = series.map(function(s, i) { |
| var seriesData; |
| seriesData = { |
| index: i, |
| name: s.y, |
| values: [], |
| color: s.color, |
| axis: s.axis || 'y', |
| xOffset: 0, |
| type: s.type, |
| thickness: s.thickness, |
| drawDots: s.drawDots !== false |
| }; |
| if (s.dotSize != null) { |
| seriesData.dotSize = s.dotSize; |
| } |
| if (s.striped === true) { |
| seriesData.striped = true; |
| } |
| if (s.lineMode != null) { |
| seriesData.lineMode = s.lineMode; |
| } |
| if (s.id) { |
| seriesData.id = s.id; |
| } |
| data.filter(function(row) { |
| return row[s.y] != null; |
| }).forEach(function(row) { |
| var d; |
| d = { |
| x: row[options.axes.x.key], |
| y: row[s.y], |
| y0: 0, |
| axis: s.axis || 'y' |
| }; |
| if (s.dotSize != null) { |
| d.dotSize = s.dotSize; |
| } |
| return seriesData.values.push(d); |
| }); |
| return seriesData; |
| }); |
| if ((options.stacks == null) || options.stacks.length === 0) { |
| return straightened; |
| } |
| layout = d3.layout.stack().values(function(s) { |
| return s.values; |
| }); |
| options.stacks.forEach(function(stack) { |
| var layers; |
| if (!(stack.series.length > 0)) { |
| return; |
| } |
| layers = straightened.filter(function(s, i) { |
| var _ref; |
| return (s.id != null) && (_ref = s.id, __indexOf.call(stack.series, _ref) >= 0); |
| }); |
| return layout(layers); |
| }); |
| return straightened; |
| }, |
| estimateSideTooltipWidth: function(svg, text) { |
| var bbox, t; |
| t = svg.append('text'); |
| t.text('' + text); |
| this.styleTooltip(t); |
| bbox = this.getTextBBox(t[0][0]); |
| t.remove(); |
| return bbox; |
| }, |
| getTextBBox: function(svgTextElement) { |
| var error; |
| if (svgTextElement !== null) { |
| try { |
| return svgTextElement.getBBox(); |
| } catch (_error) { |
| error = _error; |
| return { |
| height: 0, |
| width: 0, |
| y: 0, |
| x: 0 |
| }; |
| } |
| } |
| return {}; |
| }, |
| getWidestTickWidth: function(svg, axisKey) { |
| var bbox, max, ticks, _ref; |
| max = 0; |
| bbox = this.getTextBBox; |
| ticks = svg.select("." + axisKey + ".axis").selectAll('.tick'); |
| if ((_ref = ticks[0]) != null) { |
| _ref.forEach(function(t) { |
| return max = Math.max(max, bbox(t).width); |
| }); |
| } |
| return max; |
| }, |
| getWidestOrdinate: function(data, series, options) { |
| var widest; |
| widest = ''; |
| data.forEach(function(row) { |
| return series.forEach(function(series) { |
| var v, _ref; |
| v = row[series.y]; |
| if ((series.axis != null) && ((_ref = options.axes[series.axis]) != null ? _ref.ticksFormatter : void 0)) { |
| v = options.axes[series.axis].ticksFormatter(v); |
| } |
| if (v == null) { |
| return; |
| } |
| if (('' + v).length > ('' + widest).length) { |
| return widest = v; |
| } |
| }); |
| }); |
| return widest; |
| }, |
| getDefaultOptions: function() { |
| return { |
| tooltip: { |
| mode: 'scrubber' |
| }, |
| lineMode: 'linear', |
| tension: 0.7, |
| margin: this.getDefaultMargins(), |
| axes: { |
| x: { |
| type: 'linear', |
| key: 'x' |
| }, |
| y: { |
| type: 'linear' |
| } |
| }, |
| series: [], |
| drawLegend: true, |
| drawDots: true, |
| stacks: [], |
| columnsHGap: 5, |
| hideOverflow: false |
| }; |
| }, |
| sanitizeOptions: function(options, mode) { |
| var defaultMargin; |
| if (options == null) { |
| options = {}; |
| } |
| if (mode === 'thumbnail') { |
| options.drawLegend = false; |
| options.drawDots = false; |
| options.tooltip = { |
| mode: 'none', |
| interpolate: false |
| }; |
| } |
| options.series = this.sanitizeSeriesOptions(options.series); |
| options.stacks = this.sanitizeSeriesStacks(options.stacks, options.series); |
| options.axes = this.sanitizeAxes(options.axes, this.haveSecondYAxis(options.series)); |
| options.tooltip = this.sanitizeTooltip(options.tooltip); |
| options.margin = this.sanitizeMargins(options.margin); |
| options.lineMode || (options.lineMode = this.getDefaultOptions().lineMode); |
| options.tension = /^\d+(\.\d+)?$/.test(options.tension) ? options.tension : this.getDefaultOptions().tension; |
| options.drawLegend = options.drawLegend !== false; |
| options.drawDots = options.drawDots !== false; |
| if (!angular.isNumber(options.columnsHGap)) { |
| options.columnsHGap = 5; |
| } |
| options.hideOverflow = options.hideOverflow || false; |
| defaultMargin = mode === 'thumbnail' ? this.getDefaultThumbnailMargins() : this.getDefaultMargins(); |
| options.series = angular.extend(this.getDefaultOptions().series, options.series); |
| options.axes = angular.extend(this.getDefaultOptions().axes, options.axes); |
| options.tooltip = angular.extend(this.getDefaultOptions().tooltip, options.tooltip); |
| options.margin = angular.extend(defaultMargin, options.margin); |
| return options; |
| }, |
| sanitizeMargins: function(options) { |
| var attrs, margin, opt, value; |
| attrs = ['top', 'right', 'bottom', 'left']; |
| margin = {}; |
| for (opt in options) { |
| value = options[opt]; |
| if (__indexOf.call(attrs, opt) >= 0) { |
| margin[opt] = parseFloat(value); |
| } |
| } |
| return margin; |
| }, |
| sanitizeSeriesStacks: function(stacks, series) { |
| var seriesKeys; |
| if (stacks == null) { |
| return []; |
| } |
| seriesKeys = {}; |
| series.forEach(function(s) { |
| return seriesKeys[s.id] = s; |
| }); |
| stacks.forEach(function(stack) { |
| return stack.series.forEach(function(id) { |
| var s; |
| s = seriesKeys[id]; |
| if (s != null) { |
| if (s.axis !== stack.axis) { |
| return $log.warn("Series " + id + " is not on the same axis as its stack"); |
| } |
| } else { |
| if (!s) { |
| return $log.warn("Unknown series found in stack : " + id); |
| } |
| } |
| }); |
| }); |
| return stacks; |
| }, |
| sanitizeTooltip: function(options) { |
| var _ref; |
| if (!options) { |
| return { |
| mode: 'scrubber' |
| }; |
| } |
| if ((_ref = options.mode) !== 'none' && _ref !== 'axes' && _ref !== 'scrubber') { |
| options.mode = 'scrubber'; |
| } |
| if (options.mode === 'scrubber') { |
| delete options.interpolate; |
| } else { |
| options.interpolate = !!options.interpolate; |
| } |
| if (options.mode === 'scrubber' && options.interpolate) { |
| throw new Error('Interpolation is not supported for scrubber tooltip mode.'); |
| } |
| return options; |
| }, |
| sanitizeSeriesOptions: function(options) { |
| var colors, knownIds; |
| if (options == null) { |
| return []; |
| } |
| colors = d3.scale.category10(); |
| knownIds = {}; |
| options.forEach(function(s, i) { |
| if (knownIds[s.id] != null) { |
| throw new Error("Twice the same ID (" + s.id + ") ? Really ?"); |
| } |
| if (s.id != null) { |
| return knownIds[s.id] = s; |
| } |
| }); |
| options.forEach(function(s, i) { |
| var cnt, _ref, _ref1, _ref2, _ref3; |
| s.axis = ((_ref = s.axis) != null ? _ref.toLowerCase() : void 0) !== 'y2' ? 'y' : 'y2'; |
| s.color || (s.color = colors(i)); |
| s.type = (_ref1 = s.type) === 'line' || _ref1 === 'area' || _ref1 === 'column' ? s.type : "line"; |
| if (s.type === 'column') { |
| delete s.thickness; |
| delete s.lineMode; |
| delete s.drawDots; |
| delete s.dotSize; |
| } else if (!/^\d+px$/.test(s.thickness)) { |
| s.thickness = '1px'; |
| } |
| if ((_ref2 = s.type) === 'line' || _ref2 === 'area') { |
| if ((_ref3 = s.lineMode) !== 'dashed') { |
| delete s.lineMode; |
| } |
| if (s.drawDots !== false && (s.dotSize == null)) { |
| s.dotSize = 2; |
| } |
| } |
| if (s.id == null) { |
| cnt = 0; |
| while (knownIds["series_" + cnt] != null) { |
| cnt++; |
| } |
| s.id = "series_" + cnt; |
| knownIds[s.id] = s; |
| } |
| if (s.drawDots === false) { |
| return delete s.dotSize; |
| } |
| }); |
| return options; |
| }, |
| sanitizeAxes: function(axesOptions, secondAxis) { |
| var _base; |
| if (axesOptions == null) { |
| axesOptions = {}; |
| } |
| axesOptions.x = this.sanitizeAxisOptions(axesOptions.x); |
| (_base = axesOptions.x).key || (_base.key = "x"); |
| axesOptions.y = this.sanitizeAxisOptions(axesOptions.y); |
| if (secondAxis) { |
| axesOptions.y2 = this.sanitizeAxisOptions(axesOptions.y2); |
| } |
| return axesOptions; |
| }, |
| sanitizeExtrema: function(options) { |
| var max, min; |
| min = this.getSanitizedNumber(options.min); |
| if (min != null) { |
| options.min = min; |
| } else { |
| delete options.min; |
| } |
| max = this.getSanitizedNumber(options.max); |
| if (max != null) { |
| return options.max = max; |
| } else { |
| return delete options.max; |
| } |
| }, |
| getSanitizedNumber: function(value) { |
| var number; |
| if (value == null) { |
| return void 0; |
| } |
| number = parseFloat(value); |
| if (isNaN(number)) { |
| $log.warn("Invalid extremum value : " + value + ", deleting it."); |
| return void 0; |
| } |
| return number; |
| }, |
| sanitizeAxisOptions: function(options) { |
| if (options == null) { |
| return { |
| type: 'linear' |
| }; |
| } |
| options.type || (options.type = 'linear'); |
| if (options.ticksRotate != null) { |
| options.ticksRotate = this.getSanitizedNumber(options.ticksRotate); |
| } |
| if (options.labelFunction != null) { |
| options.ticksFormatter = options.labelFunction; |
| } |
| if (options.ticksFormat != null) { |
| if (options.type === 'date') { |
| options.ticksFormatter = d3.time.format(options.ticksFormat); |
| } else { |
| options.ticksFormatter = d3.format(options.ticksFormat); |
| } |
| if (options.tooltipFormatter == null) { |
| options.tooltipFormatter = options.ticksFormatter; |
| } |
| } |
| if (options.tooltipFormat != null) { |
| if (options.type === 'date') { |
| options.tooltipFormatter = d3.time.format(options.tooltipFormat); |
| } else { |
| options.tooltipFormatter = d3.format(options.tooltipFormat); |
| } |
| } |
| if (options.ticksInterval != null) { |
| options.ticksInterval = this.getSanitizedNumber(options.ticksInterval); |
| } |
| this.sanitizeExtrema(options); |
| return options; |
| }, |
| createAxes: function(svg, dimensions, axesOptions) { |
| var createY2Axis, height, style, width, x, xAxis, y, y2, y2Axis, yAxis; |
| createY2Axis = axesOptions.y2 != null; |
| width = dimensions.width; |
| height = dimensions.height; |
| width = width - dimensions.left - dimensions.right; |
| height = height - dimensions.top - dimensions.bottom; |
| x = void 0; |
| if (axesOptions.x.type === 'date') { |
| x = d3.time.scale().rangeRound([0, width]); |
| } else { |
| x = d3.scale.linear().rangeRound([0, width]); |
| } |
| xAxis = this.createAxis(x, 'x', axesOptions); |
| y = void 0; |
| if (axesOptions.y.type === 'log') { |
| y = d3.scale.log().clamp(true).rangeRound([height, 0]); |
| } else { |
| y = d3.scale.linear().rangeRound([height, 0]); |
| } |
| y.clamp(true); |
| yAxis = this.createAxis(y, 'y', axesOptions); |
| y2 = void 0; |
| if (createY2Axis && axesOptions.y2.type === 'log') { |
| y2 = d3.scale.log().clamp(true).rangeRound([height, 0]); |
| } else { |
| y2 = d3.scale.linear().rangeRound([height, 0]); |
| } |
| y2.clamp(true); |
| y2Axis = this.createAxis(y2, 'y2', axesOptions); |
| style = function(group) { |
| group.style({ |
| 'font': '10px Courier', |
| 'shape-rendering': 'crispEdges' |
| }); |
| return group.selectAll('path').style({ |
| 'fill': 'none', |
| 'stroke': '#000' |
| }); |
| }; |
| return { |
| xScale: x, |
| yScale: y, |
| y2Scale: y2, |
| xAxis: xAxis, |
| yAxis: yAxis, |
| y2Axis: y2Axis, |
| andAddThemIf: function(conditions) { |
| if (!!conditions.all) { |
| if (!!conditions.x) { |
| svg.append('g').attr('class', 'x axis').attr('transform', 'translate(0,' + height + ')').call(xAxis).call(style); |
| } |
| if (!!conditions.y) { |
| svg.append('g').attr('class', 'y axis').call(yAxis).call(style); |
| } |
| if (createY2Axis && !!conditions.y2) { |
| svg.append('g').attr('class', 'y2 axis').attr('transform', 'translate(' + width + ', 0)').call(y2Axis).call(style); |
| } |
| } |
| return { |
| xScale: x, |
| yScale: y, |
| y2Scale: y2, |
| xAxis: xAxis, |
| yAxis: yAxis, |
| y2Axis: y2Axis |
| }; |
| } |
| }; |
| }, |
| createAxis: function(scale, key, options) { |
| var axis, o, sides; |
| sides = { |
| x: 'bottom', |
| y: 'left', |
| y2: 'right' |
| }; |
| o = options[key]; |
| axis = d3.svg.axis().scale(scale).orient(sides[key]).tickFormat(o != null ? o.ticksFormatter : void 0); |
| if (o == null) { |
| return axis; |
| } |
| if (angular.isArray(o.ticks)) { |
| axis.tickValues(o.ticks); |
| } else if (angular.isNumber(o.ticks)) { |
| axis.ticks(o.ticks); |
| } else if (angular.isFunction(o.ticks)) { |
| axis.ticks(o.ticks, o.ticksInterval); |
| } |
| return axis; |
| }, |
| setScalesDomain: function(scales, data, series, svg, options) { |
| var axis, y2Domain, yDomain; |
| this.setXScale(scales.xScale, data, series, options.axes); |
| axis = svg.selectAll('.x.axis').call(scales.xAxis); |
| if (options.axes.x.ticksRotate != null) { |
| 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'); |
| } |
| if ((series.filter(function(s) { |
| return s.axis === 'y' && s.visible !== false; |
| })).length > 0) { |
| yDomain = this.getVerticalDomain(options, data, series, 'y'); |
| scales.yScale.domain(yDomain).nice(); |
| axis = svg.selectAll('.y.axis').call(scales.yAxis); |
| if (options.axes.y.ticksRotate != null) { |
| axis.selectAll('.tick>text').attr('transform', 'rotate(' + options.axes.y.ticksRotate + ' -6,0)').style('text-anchor', 'end'); |
| } |
| } |
| if ((series.filter(function(s) { |
| return s.axis === 'y2' && s.visible !== false; |
| })).length > 0) { |
| y2Domain = this.getVerticalDomain(options, data, series, 'y2'); |
| scales.y2Scale.domain(y2Domain).nice(); |
| axis = svg.selectAll('.y2.axis').call(scales.y2Axis); |
| if (options.axes.y2.ticksRotate != null) { |
| return axis.selectAll('.tick>text').attr('transform', 'rotate(' + options.axes.y2.ticksRotate + ' 6,0)').style('text-anchor', 'start'); |
| } |
| } |
| }, |
| getVerticalDomain: function(options, data, series, key) { |
| var domain, mySeries, o; |
| if (!(o = options.axes[key])) { |
| return []; |
| } |
| if ((o.ticks != null) && angular.isArray(o.ticks)) { |
| return [o.ticks[0], o.ticks[o.ticks.length - 1]]; |
| } |
| mySeries = series.filter(function(s) { |
| return s.axis === key && s.visible !== false; |
| }); |
| domain = this.yExtent(series.filter(function(s) { |
| return s.axis === key && s.visible !== false; |
| }), data, options.stacks.filter(function(stack) { |
| return stack.axis === key; |
| })); |
| if (o.type === 'log') { |
| domain[0] = domain[0] === 0 ? 0.001 : domain[0]; |
| } |
| if (o.min != null) { |
| domain[0] = o.min; |
| } |
| if (o.max != null) { |
| domain[1] = o.max; |
| } |
| return domain; |
| }, |
| yExtent: function(series, data, stacks) { |
| var groups, maxY, minY; |
| minY = Number.POSITIVE_INFINITY; |
| maxY = Number.NEGATIVE_INFINITY; |
| groups = []; |
| stacks.forEach(function(stack) { |
| return groups.push(stack.series.map(function(id) { |
| return (series.filter(function(s) { |
| return s.id === id; |
| }))[0]; |
| })); |
| }); |
| series.forEach(function(series, i) { |
| var isInStack; |
| isInStack = false; |
| stacks.forEach(function(stack) { |
| var _ref; |
| if (_ref = series.id, __indexOf.call(stack.series, _ref) >= 0) { |
| return isInStack = true; |
| } |
| }); |
| if (!isInStack) { |
| return groups.push([series]); |
| } |
| }); |
| groups.forEach(function(group) { |
| group = group.filter(Boolean); |
| minY = Math.min(minY, d3.min(data, function(d) { |
| return group.reduce((function(a, s) { |
| return Math.min(a, d[s.y]); |
| }), Number.POSITIVE_INFINITY); |
| })); |
| return maxY = Math.max(maxY, d3.max(data, function(d) { |
| return group.reduce((function(a, s) { |
| return a + d[s.y]; |
| }), 0); |
| })); |
| }); |
| if (minY === maxY) { |
| if (minY > 0) { |
| return [0, minY * 2]; |
| } else { |
| return [minY * 2, 0]; |
| } |
| } |
| return [minY, maxY]; |
| }, |
| setXScale: function(xScale, data, series, axesOptions) { |
| var domain, o; |
| domain = this.xExtent(data, axesOptions.x.key); |
| if (series.filter(function(s) { |
| return s.type === 'column'; |
| }).length) { |
| this.adjustXDomainForColumns(domain, data, axesOptions.x.key); |
| } |
| o = axesOptions.x; |
| if (o.min != null) { |
| domain[0] = o.min; |
| } |
| if (o.max != null) { |
| domain[1] = o.max; |
| } |
| return xScale.domain(domain); |
| }, |
| xExtent: function(data, key) { |
| var from, to, _ref; |
| _ref = d3.extent(data, function(d) { |
| return d[key]; |
| }), from = _ref[0], to = _ref[1]; |
| if (from === to) { |
| if (from > 0) { |
| return [0, from * 2]; |
| } else { |
| return [from * 2, 0]; |
| } |
| } |
| return [from, to]; |
| }, |
| adjustXDomainForColumns: function(domain, data, field) { |
| var step; |
| step = this.getAverageStep(data, field); |
| if (angular.isDate(domain[0])) { |
| domain[0] = new Date(domain[0].getTime() - step); |
| return domain[1] = new Date(domain[1].getTime() + step); |
| } else { |
| domain[0] = domain[0] - step; |
| return domain[1] = domain[1] + step; |
| } |
| }, |
| getAverageStep: function(data, field) { |
| var i, n, sum; |
| if (!(data.length > 1)) { |
| return 0; |
| } |
| sum = 0; |
| n = data.length - 1; |
| i = 0; |
| while (i < n) { |
| sum += data[i + 1][field] - data[i][field]; |
| i++; |
| } |
| return sum / n; |
| }, |
| haveSecondYAxis: function(series) { |
| return !series.every(function(s) { |
| return s.axis !== 'y2'; |
| }); |
| }, |
| showScrubber: function(svg, glass, axes, data, options, dispatch, columnWidth) { |
| var that; |
| that = this; |
| glass.on('mousemove', function() { |
| svg.selectAll('.glass-container').attr('opacity', 1); |
| return that.updateScrubber(svg, d3.mouse(this), axes, data, options, dispatch, columnWidth); |
| }); |
| return glass.on('mouseout', function() { |
| glass.on('mousemove', null); |
| return svg.selectAll('.glass-container').attr('opacity', 0); |
| }); |
| }, |
| getClosestPoint: function(values, xValue) { |
| var d, d0, d1, i, xBisector; |
| xBisector = d3.bisector(function(d) { |
| return d.x; |
| }).left; |
| i = xBisector(values, xValue); |
| if (i === 0) { |
| return values[0]; |
| } |
| if (i > values.length - 1) { |
| return values[values.length - 1]; |
| } |
| d0 = values[i - 1]; |
| d1 = values[i]; |
| d = xValue - d0.x > d1.x - xValue ? d1 : d0; |
| return d; |
| }, |
| updateScrubber: function(svg, _arg, axes, data, options, dispatch, columnWidth) { |
| var ease, positions, that, tickLength, x, y; |
| x = _arg[0], y = _arg[1]; |
| ease = function(element) { |
| return element.transition().duration(50); |
| }; |
| that = this; |
| positions = []; |
| data.forEach(function(series, index) { |
| var color, item, lText, left, rText, right, side, sizes, text, v, xInvert, xPos, yInvert; |
| item = svg.select(".scrubberItem.series_" + index); |
| if (options.series[index].visible === false) { |
| item.attr('opacity', 0); |
| return; |
| } |
| item.attr('opacity', 1); |
| xInvert = axes.xScale.invert(x); |
| yInvert = axes.yScale.invert(y); |
| v = that.getClosestPoint(series.values, xInvert); |
| dispatch.focus(v, series.values.indexOf(v), [xInvert, yInvert]); |
| text = v.x + ' : ' + v.y; |
| if (options.tooltip.formatter) { |
| text = options.tooltip.formatter(v.x, v.y, options.series[index]); |
| } |
| right = item.select('.rightTT'); |
| rText = right.select('text'); |
| rText.text(text); |
| left = item.select('.leftTT'); |
| lText = left.select('text'); |
| lText.text(text); |
| sizes = { |
| right: that.getTextBBox(rText[0][0]).width + 5, |
| left: that.getTextBBox(lText[0][0]).width + 5 |
| }; |
| side = series.axis === 'y2' ? 'right' : 'left'; |
| xPos = axes.xScale(v.x); |
| if (side === 'left') { |
| if (xPos + that.getTextBBox(lText[0][0]).x - 10 < 0) { |
| side = 'right'; |
| } |
| } else if (side === 'right') { |
| if (xPos + sizes.right > that.getTextBBox(svg.select('.glass')[0][0]).width) { |
| side = 'left'; |
| } |
| } |
| if (side === 'left') { |
| ease(right).attr('opacity', 0); |
| ease(left).attr('opacity', 1); |
| } else { |
| ease(right).attr('opacity', 1); |
| ease(left).attr('opacity', 0); |
| } |
| positions[index] = { |
| index: index, |
| x: xPos, |
| y: axes[v.axis + 'Scale'](v.y + v.y0), |
| side: side, |
| sizes: sizes |
| }; |
| color = angular.isFunction(series.color) ? series.color(v, series.values.indexOf(v)) : series.color; |
| item.selectAll('circle').attr('stroke', color); |
| return item.selectAll('path').attr('fill', color); |
| }); |
| positions = this.preventOverlapping(positions); |
| tickLength = Math.max(15, 100 / columnWidth); |
| return data.forEach(function(series, index) { |
| var item, p, tt, xOffset; |
| if (options.series[index].visible === false) { |
| return; |
| } |
| p = positions[index]; |
| item = svg.select(".scrubberItem.series_" + index); |
| tt = item.select("." + p.side + "TT"); |
| xOffset = (p.side === 'left' ? series.xOffset : -series.xOffset); |
| tt.select('text').attr('transform', function() { |
| if (p.side === 'left') { |
| return "translate(" + (-3 - tickLength - xOffset) + ", " + (p.labelOffset + 3) + ")"; |
| } else { |
| return "translate(" + (4 + tickLength + xOffset) + ", " + (p.labelOffset + 3) + ")"; |
| } |
| }); |
| tt.select('path').attr('d', that.getScrubberPath(p.sizes[p.side] + 1, p.labelOffset, p.side, tickLength + xOffset)); |
| return ease(item).attr({ |
| 'transform': "translate(" + (positions[index].x + series.xOffset) + ", " + positions[index].y + ")" |
| }); |
| }); |
| }, |
| getScrubberPath: function(w, yOffset, side, padding) { |
| var h, p, xdir, ydir; |
| h = 18; |
| p = padding; |
| w = w; |
| xdir = side === 'left' ? 1 : -1; |
| ydir = 1; |
| if (yOffset !== 0) { |
| ydir = Math.abs(yOffset) / yOffset; |
| } |
| yOffset || (yOffset = 0); |
| 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(''); |
| }, |
| preventOverlapping: function(positions) { |
| var abscissas, getNeighbours, h, offset; |
| h = 18; |
| abscissas = {}; |
| positions.forEach(function(p) { |
| var _name; |
| abscissas[_name = p.x] || (abscissas[_name] = { |
| left: [], |
| right: [] |
| }); |
| return abscissas[p.x][p.side].push(p); |
| }); |
| getNeighbours = function(side) { |
| var foundNeighbour, neighbourhood, neighbours, neighboursForX, p, sides, x, y, _ref; |
| neighbours = []; |
| for (x in abscissas) { |
| sides = abscissas[x]; |
| if (sides[side].length === 0) { |
| continue; |
| } |
| neighboursForX = {}; |
| while (sides[side].length > 0) { |
| p = sides[side].pop(); |
| foundNeighbour = false; |
| for (y in neighboursForX) { |
| neighbourhood = neighboursForX[y]; |
| if ((+y - h <= (_ref = p.y) && _ref <= +y + h)) { |
| neighbourhood.push(p); |
| foundNeighbour = true; |
| } |
| } |
| if (!foundNeighbour) { |
| neighboursForX[p.y] = [p]; |
| } |
| } |
| neighbours.push(neighboursForX); |
| } |
| return neighbours; |
| }; |
| offset = function(neighboursForAbscissas) { |
| var abs, n, neighbours, start, step, xNeighbours, y; |
| step = 20; |
| for (abs in neighboursForAbscissas) { |
| xNeighbours = neighboursForAbscissas[abs]; |
| for (y in xNeighbours) { |
| neighbours = xNeighbours[y]; |
| n = neighbours.length; |
| if (n === 1) { |
| neighbours[0].labelOffset = 0; |
| continue; |
| } |
| neighbours = neighbours.sort(function(a, b) { |
| return a.y - b.y; |
| }); |
| if (n % 2 === 0) { |
| start = -(step / 2) * (n / 2); |
| } else { |
| start = -(n - 1) / 2 * step; |
| } |
| neighbours.forEach(function(neighbour, i) { |
| return neighbour.labelOffset = start + step * i; |
| }); |
| } |
| } |
| }; |
| offset(getNeighbours('left')); |
| offset(getNeighbours('right')); |
| return positions; |
| }, |
| getTooltipHandlers: function(options) { |
| if (options.tooltip.mode === 'scrubber') { |
| return { |
| onChartHover: angular.bind(this, this.showScrubber) |
| }; |
| } else { |
| return { |
| onMouseOver: angular.bind(this, this.onMouseOver), |
| onMouseOut: angular.bind(this, this.onMouseOut) |
| }; |
| } |
| }, |
| styleTooltip: function(d3TextElement) { |
| return d3TextElement.attr({ |
| 'font-family': 'monospace', |
| 'font-size': 10, |
| 'fill': 'white', |
| 'text-rendering': 'geometric-precision' |
| }); |
| }, |
| addTooltips: function(svg, dimensions, axesOptions) { |
| var h, height, p, w, width, xTooltip, y2Tooltip, yTooltip; |
| width = dimensions.width; |
| height = dimensions.height; |
| width = width - dimensions.left - dimensions.right; |
| height = height - dimensions.top - dimensions.bottom; |
| w = 24; |
| h = 18; |
| p = 5; |
| xTooltip = svg.append('g').attr({ |
| 'id': 'xTooltip', |
| 'class': 'xTooltip', |
| 'opacity': 0 |
| }); |
| xTooltip.append('path').attr('transform', "translate(0," + (height + 1) + ")"); |
| this.styleTooltip(xTooltip.append('text').style('text-anchor', 'middle').attr({ |
| 'width': w, |
| 'height': h, |
| 'transform': 'translate(0,' + (height + 19) + ')' |
| })); |
| yTooltip = svg.append('g').attr({ |
| id: 'yTooltip', |
| "class": 'yTooltip', |
| opacity: 0 |
| }); |
| yTooltip.append('path'); |
| this.styleTooltip(yTooltip.append('text').attr({ |
| 'width': h, |
| 'height': w |
| })); |
| if (axesOptions.y2 != null) { |
| y2Tooltip = svg.append('g').attr({ |
| 'id': 'y2Tooltip', |
| 'class': 'y2Tooltip', |
| 'opacity': 0, |
| 'transform': 'translate(' + width + ',0)' |
| }); |
| y2Tooltip.append('path'); |
| return this.styleTooltip(y2Tooltip.append('text').attr({ |
| 'width': h, |
| 'height': w |
| })); |
| } |
| }, |
| onMouseOver: function(svg, event, axesOptions) { |
| this.updateXTooltip(svg, event, axesOptions.x); |
| if (event.series.axis === 'y2') { |
| return this.updateY2Tooltip(svg, event, axesOptions.y2); |
| } else { |
| return this.updateYTooltip(svg, event, axesOptions.y); |
| } |
| }, |
| onMouseOut: function(svg) { |
| return this.hideTooltips(svg); |
| }, |
| updateXTooltip: function(svg, _arg, xAxisOptions) { |
| var color, datum, label, series, textX, x, xTooltip, _f; |
| x = _arg.x, datum = _arg.datum, series = _arg.series; |
| xTooltip = svg.select("#xTooltip"); |
| xTooltip.transition().attr({ |
| 'opacity': 1.0, |
| 'transform': "translate(" + x + ",0)" |
| }); |
| _f = xAxisOptions.tooltipFormatter; |
| textX = _f ? _f(datum.x) : datum.x; |
| label = xTooltip.select('text'); |
| label.text(textX); |
| color = angular.isFunction(series.color) ? series.color(datum, series.values.indexOf(datum)) : series.color; |
| return xTooltip.select('path').style('fill', color).attr('d', this.getXTooltipPath(label[0][0])); |
| }, |
| getXTooltipPath: function(textElement) { |
| var h, p, w; |
| w = Math.max(this.getTextBBox(textElement).width, 15); |
| h = 18; |
| p = 5; |
| 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'; |
| }, |
| updateYTooltip: function(svg, _arg, yAxisOptions) { |
| var color, datum, label, series, textY, w, y, yTooltip, _f; |
| y = _arg.y, datum = _arg.datum, series = _arg.series; |
| yTooltip = svg.select("#yTooltip"); |
| yTooltip.transition().attr({ |
| 'opacity': 1.0, |
| 'transform': "translate(0, " + y + ")" |
| }); |
| _f = yAxisOptions.tooltipFormatter; |
| textY = _f ? _f(datum.y) : datum.y; |
| label = yTooltip.select('text'); |
| label.text(textY); |
| w = this.getTextBBox(label[0][0]).width + 5; |
| label.attr({ |
| 'transform': 'translate(' + (-w - 2) + ',3)', |
| 'width': w |
| }); |
| color = angular.isFunction(series.color) ? series.color(datum, series.values.indexOf(datum)) : series.color; |
| return yTooltip.select('path').style('fill', color).attr('d', this.getYTooltipPath(w)); |
| }, |
| updateY2Tooltip: function(svg, _arg, yAxisOptions) { |
| var color, datum, label, series, textY, w, y, y2Tooltip, _f; |
| y = _arg.y, datum = _arg.datum, series = _arg.series; |
| y2Tooltip = svg.select("#y2Tooltip"); |
| y2Tooltip.transition().attr('opacity', 1.0); |
| _f = yAxisOptions.tooltipFormatter; |
| textY = _f ? _f(datum.y) : datum.y; |
| label = y2Tooltip.select('text'); |
| label.text(textY); |
| w = this.getTextBBox(label[0][0]).width + 5; |
| label.attr({ |
| 'transform': 'translate(7, ' + (parseFloat(y) + 3) + ')', |
| 'w': w |
| }); |
| color = angular.isFunction(series.color) ? series.color(datum, series.values.indexOf(datum)) : series.color; |
| return y2Tooltip.select('path').style('fill', color).attr({ |
| 'd': this.getY2TooltipPath(w), |
| 'transform': 'translate(0, ' + y + ')' |
| }); |
| }, |
| getYTooltipPath: function(w) { |
| var h, p; |
| h = 18; |
| p = 5; |
| 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'; |
| }, |
| getY2TooltipPath: function(w) { |
| var h, p; |
| h = 18; |
| p = 5; |
| 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'; |
| }, |
| hideTooltips: function(svg) { |
| svg.select("#xTooltip").transition().attr('opacity', 0); |
| svg.select("#yTooltip").transition().attr('opacity', 0); |
| return svg.select("#y2Tooltip").transition().attr('opacity', 0); |
| } |
| }; |
| } |
| ]); |