Changeset View
Changeset View
Standalone View
Standalone View
webroot/rsrc/js/application/maniphest/behavior-line-chart.js
| /** | /** | ||||
| * @provides javelin-behavior-line-chart | * @provides javelin-behavior-line-chart | ||||
| * @requires javelin-behavior | * @requires javelin-behavior | ||||
| * javelin-dom | * javelin-dom | ||||
| * javelin-vector | * javelin-vector | ||||
| * phui-chart-css | |||||
| */ | */ | ||||
| JX.behavior('line-chart', function(config) { | JX.behavior('line-chart', function(config) { | ||||
| function fn(n) { | |||||
| return n + '(' + JX.$A(arguments).slice(1).join(', ') + ')'; | |||||
| } | |||||
| var h = JX.$(config.hardpoint); | var h = JX.$(config.hardpoint); | ||||
| var p = JX.$V(h); | |||||
| var d = JX.Vector.getDim(h); | var d = JX.Vector.getDim(h); | ||||
| var mx = 60; | |||||
| var my = 30; | |||||
| var r = new Raphael(h, d.x, d.y); | var padding = { | ||||
| top: 24, | |||||
| left: 48, | |||||
| bottom: 48, | |||||
| right: 32 | |||||
| }; | |||||
| var size = { | |||||
| frameWidth: d.x, | |||||
| frameHeight: d.y, | |||||
| }; | |||||
| size.width = size.frameWidth - padding.left - padding.right; | |||||
| size.height = size.frameHeight - padding.top - padding.bottom; | |||||
| var x = d3.time.scale() | |||||
| .range([0, size.width]); | |||||
| var y = d3.scale.linear() | |||||
| .range([size.height, 0]); | |||||
| var xAxis = d3.svg.axis() | |||||
| .scale(x) | |||||
| .orient('bottom'); | |||||
| var yAxis = d3.svg.axis() | |||||
| .scale(y) | |||||
| .orient('left'); | |||||
| var svg = d3.select('#' + config.hardpoint).append('svg') | |||||
| .attr('width', size.frameWidth) | |||||
| .attr('height', size.frameHeight) | |||||
| .attr('class', 'chart'); | |||||
| var g = svg.append('g') | |||||
| .attr('transform', fn('translate', padding.left, padding.top)); | |||||
| g.append('rect') | |||||
| .attr('class', 'inner') | |||||
| .attr('width', size.width) | |||||
| .attr('height', size.height); | |||||
| var line = d3.svg.line() | |||||
| .x(function(d) { return x(d.date); }) | |||||
| .y(function(d) { return y(d.count); }); | |||||
| var l = r.linechart( | var data = []; | ||||
| mx, my, | for (var ii = 0; ii < config.x[0].length; ii++) { | ||||
| d.x - (2 * mx), d.y - (2 * my), | data.push( | ||||
| config.x, | |||||
| config.y, | |||||
| { | { | ||||
| nostroke: false, | date: new Date(config.x[0][ii] * 1000), | ||||
| axis: '0 0 1 1', | count: +config.y[0][ii] | ||||
| shade: true, | |||||
| gutter: 1, | |||||
| colors: config.colors || ['#2980b9'] | |||||
| }); | }); | ||||
| function format(value, type) { | |||||
| switch (type) { | |||||
| case 'epoch': | |||||
| return new Date(parseInt(value, 10) * 1000).toLocaleDateString(); | |||||
| case 'int': | |||||
| return parseInt(value, 10); | |||||
| default: | |||||
| return value; | |||||
| } | |||||
| } | |||||
| // Format the X axis. | |||||
| var n = 2; | |||||
| var ii = 0; | |||||
| var text = l.axis[0].text.items; | |||||
| for (var k in text) { | |||||
| if (ii++ % n) { | |||||
| text[k].attr({text: ''}); | |||||
| } else { | |||||
| var cur = text[k].attr('text'); | |||||
| var str = format(cur, config.xformat); | |||||
| text[k].attr({text: str}); | |||||
| } | |||||
| } | |||||
| // Show values on hover. | |||||
| l.hoverColumn(function() { | |||||
| this.tags = r.set(); | |||||
| for (var yy = 0; yy < config.y.length; yy++) { | |||||
| var yvalue = 0; | |||||
| for (var ii = 0; ii < config.x[0].length; ii++) { | |||||
| if (config.x[0][ii] > this.axis) { | |||||
| break; | |||||
| } | |||||
| yvalue = format(config.y[yy][ii], config.yformat); | |||||
| } | } | ||||
| var xvalue = format(this.axis, config.xformat); | x.domain(d3.extent(data, function(d) { return d.date; })); | ||||
| var tag = r.tag( | var yex = d3.extent(data, function(d) { return d.count; }); | ||||
| this.x, | yex[0] = 0; | ||||
| this.y[yy], | yex[1] = yex[1] * 1.05; | ||||
| [xvalue, yvalue].join('\n'), | y.domain(yex); | ||||
| 180, | |||||
| 24); | g.append('path') | ||||
| tag | .datum(data) | ||||
| .insertBefore(this) | .attr('class', 'line') | ||||
| .attr([{fill : '#fff'}, {fill: '#000'}]); | .attr('d', line); | ||||
| this.tags.push(tag); | g.append('g') | ||||
| } | .attr('class', 'x axis') | ||||
| }, function() { | .attr('transform', fn('translate', 0, size.height)) | ||||
| this.tags && this.tags.remove(); | .call(xAxis); | ||||
| g.append('g') | |||||
| .attr('class', 'y axis') | |||||
| .attr('transform', fn('translate', 0, 0)) | |||||
| .call(yAxis); | |||||
| var div = d3.select('body') | |||||
| .append('div') | |||||
| .attr('class', 'chart-tooltip') | |||||
| .style('opacity', 0); | |||||
| g.selectAll('dot') | |||||
| .data(data) | |||||
| .enter() | |||||
| .append('circle') | |||||
| .attr('class', 'point') | |||||
| .attr('r', 3) | |||||
| .attr('cx', function(d) { return x(d.date); }) | |||||
| .attr('cy', function(d) { return y(d.count); }) | |||||
| .on('mouseover', function(d) { | |||||
| var d_y = d.date.getFullYear(); | |||||
| var d_m = d.date.getMonth(); | |||||
| var d_d = d.date.getDate(); | |||||
| div | |||||
| .html(d_y + '-' + d_m + '-' + d_d + ': ' + d.count) | |||||
| .style('opacity', 0.9) | |||||
| .style('left', (d3.event.pageX - 60) + 'px') | |||||
| .style('top', (d3.event.pageY - 38) + 'px'); | |||||
| }) | |||||
| .on('mouseout', function() { | |||||
| div.style('opacity', 0); | |||||
| }); | }); | ||||
| }); | }); | ||||