1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-09 16:32:39 +01:00

Swap charts from gRaphael to D3

Summary:
Mostly, this has just been sitting in my sandbox for a long time. I may also touch some charting stuff with subprojects/milestones, but don't have particular plans to do that.

D3 seems a bit more flexible, and it's easier to push more of the style logic into CSS so you can fix my design atrocities. gRaphael also hasn't been updated in ~3+ years.

Test Plan:
{F1085433}

{F1085434}

Reviewers: chad

Reviewed By: chad

Subscribers: cburroughs, yelirekim

Differential Revision: https://secure.phabricator.com/D15155
This commit is contained in:
epriestley 2016-01-28 13:29:27 -08:00
parent 18f34fab73
commit f5c686d6a4
13 changed files with 235 additions and 141 deletions

View file

@ -125,6 +125,7 @@ return array(
'rsrc/css/phui/phui-big-info-view.css' => 'bd903741',
'rsrc/css/phui/phui-box.css' => '6e8ac7fd',
'rsrc/css/phui/phui-button.css' => 'd6ac72db',
'rsrc/css/phui/phui-chart.css' => '6bf6f78e',
'rsrc/css/phui/phui-crumbs-view.css' => '414406b5',
'rsrc/css/phui/phui-document-pro.css' => '8799acf7',
'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf',
@ -158,6 +159,7 @@ return array(
'rsrc/css/sprite-login.css' => '60e8560e',
'rsrc/css/sprite-menu.css' => '9dd65b92',
'rsrc/css/sprite-tokens.css' => '4f399012',
'rsrc/externals/d3/d3.min.js' => 'a11a5ff2',
'rsrc/externals/font/aleo/aleo-bold.eot' => 'd3d3bed7',
'rsrc/externals/font/aleo/aleo-bold.svg' => '45899c8e',
'rsrc/externals/font/aleo/aleo-bold.ttf' => '4b08bef0',
@ -252,9 +254,6 @@ return array(
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js' => '54f314a0',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => '1bc11c4a',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js' => '6c0e62fa',
'rsrc/externals/raphael/g.raphael.js' => '40dde778',
'rsrc/externals/raphael/g.raphael.line.js' => '40da039e',
'rsrc/externals/raphael/raphael.js' => '51ee6b43',
'rsrc/favicons/apple-touch-icon-120x120.png' => '43742962',
'rsrc/favicons/apple-touch-icon-152x152.png' => '669eaec3',
'rsrc/favicons/apple-touch-icon-76x76.png' => 'ecdef672',
@ -401,7 +400,7 @@ return array(
'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3',
'rsrc/js/application/maniphest/behavior-batch-editor.js' => '782ab6e7',
'rsrc/js/application/maniphest/behavior-batch-selector.js' => '7b98d7c5',
'rsrc/js/application/maniphest/behavior-line-chart.js' => '88f0c5b3',
'rsrc/js/application/maniphest/behavior-line-chart.js' => 'e4232876',
'rsrc/js/application/maniphest/behavior-list-edit.js' => 'a9f88de2',
'rsrc/js/application/maniphest/behavior-subpriorityeditor.js' => '71237763',
'rsrc/js/application/owners/OwnersPathEditor.js' => 'aa1733d0',
@ -535,6 +534,7 @@ return array(
'conpherence-transaction-css' => '85d0974c',
'conpherence-update-css' => 'faf6be09',
'conpherence-widget-pane-css' => '775eaaba',
'd3' => 'a11a5ff2',
'differential-changeset-view-css' => 'b6b0d1bb',
'differential-core-view-css' => '7ac3cabc',
'differential-inline-comment-editor' => '64a5550f',
@ -615,7 +615,7 @@ return array(
'javelin-behavior-icon-composer' => '8499b6ab',
'javelin-behavior-launch-icon-composer' => '48086888',
'javelin-behavior-lightbox-attachments' => 'f8ba29d7',
'javelin-behavior-line-chart' => '88f0c5b3',
'javelin-behavior-line-chart' => 'e4232876',
'javelin-behavior-load-blame' => '42126667',
'javelin-behavior-maniphest-batch-editor' => '782ab6e7',
'javelin-behavior-maniphest-batch-selector' => '7b98d7c5',
@ -799,6 +799,7 @@ return array(
'phui-calendar-day-css' => 'd1cf6f93',
'phui-calendar-list-css' => 'c1c7f338',
'phui-calendar-month-css' => '476be7e0',
'phui-chart-css' => '6bf6f78e',
'phui-crumbs-view-css' => '414406b5',
'phui-document-summary-view-css' => '9ca48bdf',
'phui-document-view-css' => '9c71d2bf',
@ -843,9 +844,6 @@ return array(
'policy-transaction-detail-css' => '82100a43',
'ponder-view-css' => '7b0df4da',
'project-view-css' => 'c6387c87',
'raphael-core' => '51ee6b43',
'raphael-g' => '40dde778',
'raphael-g-line' => '40da039e',
'releeph-core' => '9b3c5733',
'releeph-preview-branch' => 'b7a6f4a5',
'releeph-request-differential-create-dialog' => '8d8b92cd',
@ -1477,11 +1475,6 @@ return array(
'javelin-stratcom',
'javelin-dom',
),
'88f0c5b3' => array(
'javelin-behavior',
'javelin-dom',
'javelin-vector',
),
'8a41885b' => array(
'javelin-install',
'javelin-dom',
@ -1950,6 +1943,12 @@ return array(
'javelin-dom',
'javelin-uri',
),
'e4232876' => array(
'javelin-behavior',
'javelin-dom',
'javelin-vector',
'phui-chart-css',
),
'e4cc26b3' => array(
'javelin-behavior',
'javelin-dom',

View file

@ -66,7 +66,7 @@ final class CelerityResourceTransformer extends Phobject {
return $data;
}
// Some resources won't survive minification (like Raphael.js), and are
// Some resources won't survive minification (like d3.min.js), and are
// marked so as not to be minified.
if (strpos($data, '@'.'do-not-minify') !== false) {
return $data;

View file

@ -30,8 +30,7 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
}
if (!$points) {
// NOTE: Raphael crashes Safari if you hand it series with no points.
throw new Exception(pht('No data to show!'));
throw new Exception('No data to show!');
}
// Limit amount of data passed to browser.
@ -56,16 +55,12 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
'div',
array(
'id' => $id,
'style' => 'border: 1px solid #6f6f6f; '.
'margin: 1em 2em; '.
'background: #ffffff; '.
'height: 400px; ',
'style' => 'background: #ffffff; '.
'height: 480px; ',
),
'');
require_celerity_resource('raphael-core');
require_celerity_resource('raphael-g');
require_celerity_resource('raphael-g-line');
require_celerity_resource('d3');
Javelin::initBehavior('line-chart', array(
'hardpoint' => $id,
@ -75,9 +70,9 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
'colors' => array('#0000ff'),
));
$panel = new PHUIObjectBoxView();
$panel->setHeaderText(pht('Count of %s', $spec->getName()));
$panel->appendChild($chart);
$box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Count of %s', $spec->getName()))
->appendChild($chart);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Chart'));
@ -85,7 +80,7 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
return $this->buildApplicationPage(
array(
$crumbs,
$panel,
$box,
),
array(
'title' => pht('Chart'),

View file

@ -290,9 +290,8 @@ final class ManiphestReportController extends ManiphestController {
list($burn_x, $burn_y) = $this->buildSeries($data);
require_celerity_resource('raphael-core');
require_celerity_resource('raphael-g');
require_celerity_resource('raphael-g-line');
require_celerity_resource('d3');
require_celerity_resource('phui-chart-css');
Javelin::initBehavior('line-chart', array(
'hardpoint' => $id,
@ -306,7 +305,11 @@ final class ManiphestReportController extends ManiphestController {
'yformat' => 'int',
));
return array($filter, $chart, $panel);
$box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Burnup Rate'))
->appendChild($chart);
return array($filter, $box, $panel);
}
private function renderReportFilters(array $tokens, $has_window) {

View file

@ -17,8 +17,9 @@
"globals": {
"JX": false,
"Raphael": false,
"d3": false,
"__DEV__": false
},
"browser": true
}

View file

@ -0,0 +1,54 @@
/**
* @provides phui-chart-css
*/
.chart .axis line,
.chart .axis path {
fill: none;
stroke: {$blueborder};
shape-rendering: crispEdges;
}
.chart .axis text {
fill: {$darkgreytext};
}
.chart .outer,
.chart .inner {
shape-rendering: crispEdges;
}
.chart .outer {
fill: none;
stroke: none;
}
.chart .inner {
fill: {$lightbluebackground};
stroke: {$lightblueborder};
}
.chart .line {
fill: none;
stroke: {$blue};
stroke-width: 2px;
}
.chart .point {
fill: {$lightblue};
stroke: {$blue};
stroke-width: 1px;
}
.chart-tooltip {
position: absolute;
text-align: center;
width: 120px;
height: 16px;
overflow: hidden;
padding: 2px;
background: {$lightbluebackground};
border: 1px solid {$blueborder};
border-radius: 8px;
pointer-events: none;
}

26
webroot/rsrc/externals/d3/LICENSE vendored Normal file
View file

@ -0,0 +1,26 @@
Copyright (c) 2010-2014, Michael Bostock
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* The name Michael Bostock may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

9
webroot/rsrc/externals/d3/README.md vendored Normal file
View file

@ -0,0 +1,9 @@
# Data-Driven Documents
<a href="http://d3js.org"><img src="http://d3js.org/logo.svg" align="left" hspace="10" vspace="6"></a>
**D3.js** is a JavaScript library for manipulating documents based on data. **D3** helps you bring data to life using HTML, SVG and CSS. D3s emphasis on web standards gives you the full capabilities of modern browsers without tying yourself to a proprietary framework, combining powerful visualization components and a data-driven approach to DOM manipulation.
Want to learn more? [See the wiki.](https://github.com/mbostock/d3/wiki)
For examples, [see the gallery](https://github.com/mbostock/d3/wiki/Gallery) and [mbostocks bl.ocks](http://bl.ocks.org/mbostock).

9
webroot/rsrc/externals/d3/d3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,12 +0,0 @@
/**
* @provides raphael-g-line
* @do-not-minify
* @nolint
*/
/*!
* g.Raphael 0.5 - Charting library, based on Raphaël
*
* Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
* Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
*/
(function(){function a(g,n){var f=g.length/n,h=0,e=f,m=0,i=[];while(h<g.length){e--;if(e<0){m+=g[h]*(1+e);i.push(m/f);m=g[h++]*-e;e+=f}else{m+=g[h++]}}return i}function d(f,e,p,n,k,j){var h=(p-f)/2,g=(k-p)/2,q=Math.atan((p-f)/Math.abs(n-e)),o=Math.atan((k-p)/Math.abs(n-j));q=e<n?Math.PI-q:q;o=j<n?Math.PI-o:o;var i=Math.PI/2-((q+o)%(Math.PI*2))/2,s=h*Math.sin(i+q),m=h*Math.cos(i+q),r=g*Math.sin(i+o),l=g*Math.cos(i+o);return{x1:p-s,y1:n+m,x2:p+r,y2:n+l}}function b(f,P,O,e,h,A,z,J){var s=this;J=J||{};if(!f.raphael.is(A[0],"array")){A=[A]}if(!f.raphael.is(z[0],"array")){z=[z]}var q=J.gutter||10,B=Math.max(A[0].length,z[0].length),t=J.symbol||"",S=J.colors||s.colors,v=null,p=null,ad=f.set(),T=[];for(var ac=0,L=z.length;ac<L;ac++){B=Math.max(B,z[ac].length)}var ae=f.set();for(ac=0,L=z.length;ac<L;ac++){if(J.shade){ae.push(f.path().attr({stroke:"none",fill:S[ac],opacity:J.nostroke?1:0.3}))}if(z[ac].length>e-2*q){z[ac]=a(z[ac],e-2*q);B=e-2*q}if(A[ac]&&A[ac].length>e-2*q){A[ac]=a(A[ac],e-2*q)}}var W=Array.prototype.concat.apply([],A),U=Array.prototype.concat.apply([],z),u=s.snapEnds(Math.min.apply(Math,W),Math.max.apply(Math,W),A[0].length-1),E=u.from,o=u.to,N=s.snapEnds(Math.min.apply(Math,U),Math.max.apply(Math,U),z[0].length-1),C=N.from,n=N.to,Z=(e-q*2)/((o-E)||1),V=(h-q*2)/((n-C)||1);var G=f.set();if(J.axis){var m=(J.axis+"").split(/[,\s]+/);+m[0]&&G.push(s.axis(P+q,O+q,e-2*q,E,o,J.axisxstep||Math.floor((e-2*q)/20),2,f));+m[1]&&G.push(s.axis(P+e-q,O+h-q,h-2*q,C,n,J.axisystep||Math.floor((h-2*q)/20),3,f));+m[2]&&G.push(s.axis(P+q,O+h-q,e-2*q,E,o,J.axisxstep||Math.floor((e-2*q)/20),0,f));+m[3]&&G.push(s.axis(P+q,O+h-q,h-2*q,C,n,J.axisystep||Math.floor((h-2*q)/20),1,f))}var M=f.set(),aa=f.set(),r;for(ac=0,L=z.length;ac<L;ac++){if(!J.nostroke){M.push(r=f.path().attr({stroke:S[ac],"stroke-width":J.width||2,"stroke-linejoin":"round","stroke-linecap":"round","stroke-dasharray":J.dash||""}))}var g=Raphael.is(t,"array")?t[ac]:t,H=f.set();T=[];for(var ab=0,w=z[ac].length;ab<w;ab++){var l=P+q+((A[ac]||A[0])[ab]-E)*Z,k=O+h-q-(z[ac][ab]-C)*V;(Raphael.is(g,"array")?g[ab]:g)&&H.push(f[Raphael.is(g,"array")?g[ab]:g](l,k,(J.width||2)*3).attr({fill:S[ac],stroke:"none"}));if(J.smooth){if(ab&&ab!=w-1){var R=P+q+((A[ac]||A[0])[ab-1]-E)*Z,F=O+h-q-(z[ac][ab-1]-C)*V,Q=P+q+((A[ac]||A[0])[ab+1]-E)*Z,D=O+h-q-(z[ac][ab+1]-C)*V,af=d(R,F,l,k,Q,D);T=T.concat([af.x1,af.y1,l,k,af.x2,af.y2])}if(!ab){T=["M",l,k,"C",l,k]}}else{T=T.concat([ab?"L":"M",l,k])}}if(J.smooth){T=T.concat([l,k,l,k])}aa.push(H);if(J.shade){ae[ac].attr({path:T.concat(["L",l,O+h-q,"L",P+q+((A[ac]||A[0])[0]-E)*Z,O+h-q,"z"]).join(",")})}!J.nostroke&&r.attr({path:T.join(",")})}function K(an){var ak=[];for(var al=0,ap=A.length;al<ap;al++){ak=ak.concat(A[al])}ak.sort();var aq=[],ah=[];for(al=0,ap=ak.length;al<ap;al++){ak[al]!=ak[al-1]&&aq.push(ak[al])&&ah.push(P+q+(ak[al]-E)*Z)}ak=aq;ap=ak.length;var ag=an||f.set();for(al=0;al<ap;al++){var Y=ah[al]-(ah[al]-(ah[al-1]||P))/2,ao=((ah[al+1]||P+e)-ah[al])/2+(ah[al]-(ah[al-1]||P))/2,x;an?(x={}):ag.push(x=f.rect(Y-1,O,Math.max(ao+1,1),h).attr({stroke:"none",fill:"#000",opacity:0}));x.values=[];x.symbols=f.set();x.y=[];x.x=ah[al];x.axis=ak[al];for(var aj=0,am=z.length;aj<am;aj++){aq=A[aj]||A[0];for(var ai=0,y=aq.length;ai<y;ai++){if(aq[ai]==ak[al]){x.values.push(z[aj][ai]);x.y.push(O+h-q-(z[aj][ai]-C)*V);x.symbols.push(ad.symbols[aj][ai])}}}an&&an.call(x)}!an&&(v=ag)}function I(al){var ah=al||f.set(),x;for(var aj=0,an=z.length;aj<an;aj++){for(var ai=0,ak=z[aj].length;ai<ak;ai++){var ag=P+q+((A[aj]||A[0])[ai]-E)*Z,am=P+q+((A[aj]||A[0])[ai?ai-1:1]-E)*Z,y=O+h-q-(z[aj][ai]-C)*V;al?(x={}):ah.push(x=f.circle(ag,y,Math.abs(am-ag)/2).attr({stroke:"none",fill:"#000",opacity:0}));x.x=ag;x.y=y;x.value=z[aj][ai];x.line=ad.lines[aj];x.shade=ad.shades[aj];x.symbol=ad.symbols[aj][ai];x.symbols=ad.symbols[aj];x.axis=(A[aj]||A[0])[ai];al&&al.call(x)}}!al&&(p=ah)}ad.push(M,ae,aa,G,v,p);ad.lines=M;ad.shades=ae;ad.symbols=aa;ad.axis=G;ad.hoverColumn=function(j,i){!v&&K();v.mouseover(j).mouseout(i);return this};ad.clickColumn=function(i){!v&&K();v.click(i);return this};ad.hrefColumn=function(Y){var ag=f.raphael.is(arguments[0],"array")?arguments[0]:arguments;if(!(arguments.length-1)&&typeof Y=="object"){for(var j in Y){for(var y=0,X=v.length;y<X;y++){if(v[y].axis==j){v[y].attr("href",Y[j])}}}}!v&&K();for(y=0,X=ag.length;y<X;y++){v[y]&&v[y].attr("href",ag[y])}return this};ad.hover=function(j,i){!p&&I();p.mouseover(j).mouseout(i);return this};ad.click=function(i){!p&&I();p.click(i);return this};ad.each=function(i){I(i);return this};ad.eachColumn=function(i){K(i);return this};return ad}var c=function(){};c.prototype=Raphael.g;b.prototype=new c;Raphael.fn.linechart=function(f,k,g,e,j,i,h){return new b(this,f,k,g,e,j,i,h)}})();

File diff suppressed because one or more lines are too long

View file

@ -3,86 +3,121 @@
* @requires javelin-behavior
* javelin-dom
* javelin-vector
* phui-chart-css
*/
JX.behavior('line-chart', function(config) {
function fn(n) {
return n + '(' + JX.$A(arguments).slice(1).join(', ') + ')';
}
var h = JX.$(config.hardpoint);
var p = JX.$V(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 l = r.linechart(
mx, my,
d.x - (2 * mx), d.y - (2 * my),
config.x,
config.y,
{
nostroke: false,
axis: '0 0 1 1',
shade: true,
gutter: 1,
colors: config.colors || ['#2980b9']
});
var size = {
frameWidth: d.x,
frameHeight: d.y,
};
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;
}
}
size.width = size.frameWidth - padding.left - padding.right;
size.height = size.frameHeight - padding.top - padding.bottom;
// Format the X axis.
var x = d3.time.scale()
.range([0, size.width]);
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});
}
}
var y = d3.scale.linear()
.range([size.height, 0]);
// Show values on hover.
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
l.hoverColumn(function() {
this.tags = r.set();
for (var yy = 0; yy < config.y.length; yy++) {
var yvalue = 0;
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 data = [];
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);
data.push(
{
date: new Date(config.x[0][ii] * 1000),
count: +config.y[0][ii]
});
}
var xvalue = format(this.axis, config.xformat);
x.domain(d3.extent(data, function(d) { return d.date; }));
var tag = r.tag(
this.x,
this.y[yy],
[xvalue, yvalue].join('\n'),
180,
24);
tag
.insertBefore(this)
.attr([{fill : '#fff'}, {fill: '#000'}]);
var yex = d3.extent(data, function(d) { return d.count; });
yex[0] = 0;
yex[1] = yex[1] * 1.05;
y.domain(yex);
this.tags.push(tag);
}
}, function() {
this.tags && this.tags.remove();
g.append('path')
.datum(data)
.attr('class', 'line')
.attr('d', line);
g.append('g')
.attr('class', 'x axis')
.attr('transform', fn('translate', 0, size.height))
.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);
});
});