--- /dev/null
+
+/*
+ *
+ * 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);
+ }
+ };
+ }
+]);