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); | |||||
}); | }); | ||||
}); | }); |