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