mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-29 10:12:41 +01:00
Fetch chart data via async request and redraw charts when the window is resized
Summary: Depends on D20439. Ref T13279. Some day, charts will probably need to reload themselves or do a bunch of defer/request-shaping magic when they're on a dashboard with 900 other charts. Give the controller separate "HTML placeholder" and "actual data" modes, and make the placeholder fetch the data in a separate request. Then, make the chart redraw if you resize the window instead of staying at whatever size it started as. Test Plan: - Loaded a chart, saw it load data asynchronously. - Resized the window, saw the chart resize. Reviewers: amckinley Reviewed By: amckinley Subscribers: yelirekim Maniphest Tasks: T13279 Differential Revision: https://secure.phabricator.com/D20440
This commit is contained in:
parent
044c6fbc19
commit
45b3c23148
5 changed files with 221 additions and 153 deletions
|
@ -389,6 +389,7 @@ return array(
|
||||||
'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'c715c123',
|
'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'c715c123',
|
||||||
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '6a85bc5a',
|
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '6a85bc5a',
|
||||||
'rsrc/js/application/drydock/drydock-live-operation-status.js' => '47a0728b',
|
'rsrc/js/application/drydock/drydock-live-operation-status.js' => '47a0728b',
|
||||||
|
'rsrc/js/application/fact/Chart.js' => 'fcb0c07d',
|
||||||
'rsrc/js/application/files/behavior-document-engine.js' => '243d6c22',
|
'rsrc/js/application/files/behavior-document-engine.js' => '243d6c22',
|
||||||
'rsrc/js/application/files/behavior-icon-composer.js' => '38a6cedb',
|
'rsrc/js/application/files/behavior-icon-composer.js' => '38a6cedb',
|
||||||
'rsrc/js/application/files/behavior-launch-icon-composer.js' => 'a17b84f1',
|
'rsrc/js/application/files/behavior-launch-icon-composer.js' => 'a17b84f1',
|
||||||
|
@ -397,7 +398,7 @@ return array(
|
||||||
'rsrc/js/application/herald/PathTypeahead.js' => 'ad486db3',
|
'rsrc/js/application/herald/PathTypeahead.js' => 'ad486db3',
|
||||||
'rsrc/js/application/herald/herald-rule-editor.js' => '0922e81d',
|
'rsrc/js/application/herald/herald-rule-editor.js' => '0922e81d',
|
||||||
'rsrc/js/application/maniphest/behavior-batch-selector.js' => '139ef688',
|
'rsrc/js/application/maniphest/behavior-batch-selector.js' => '139ef688',
|
||||||
'rsrc/js/application/maniphest/behavior-line-chart.js' => '495cf14d',
|
'rsrc/js/application/maniphest/behavior-line-chart.js' => 'ad258e28',
|
||||||
'rsrc/js/application/maniphest/behavior-list-edit.js' => 'c687e867',
|
'rsrc/js/application/maniphest/behavior-list-edit.js' => 'c687e867',
|
||||||
'rsrc/js/application/owners/OwnersPathEditor.js' => '2a8b62d9',
|
'rsrc/js/application/owners/OwnersPathEditor.js' => '2a8b62d9',
|
||||||
'rsrc/js/application/owners/owners-path-editor.js' => 'ff688a7a',
|
'rsrc/js/application/owners/owners-path-editor.js' => 'ff688a7a',
|
||||||
|
@ -625,7 +626,7 @@ return array(
|
||||||
'javelin-behavior-icon-composer' => '38a6cedb',
|
'javelin-behavior-icon-composer' => '38a6cedb',
|
||||||
'javelin-behavior-launch-icon-composer' => 'a17b84f1',
|
'javelin-behavior-launch-icon-composer' => 'a17b84f1',
|
||||||
'javelin-behavior-lightbox-attachments' => 'c7e748bf',
|
'javelin-behavior-lightbox-attachments' => 'c7e748bf',
|
||||||
'javelin-behavior-line-chart' => '495cf14d',
|
'javelin-behavior-line-chart' => 'ad258e28',
|
||||||
'javelin-behavior-linked-container' => '74446546',
|
'javelin-behavior-linked-container' => '74446546',
|
||||||
'javelin-behavior-maniphest-batch-selector' => '139ef688',
|
'javelin-behavior-maniphest-batch-selector' => '139ef688',
|
||||||
'javelin-behavior-maniphest-list-editor' => 'c687e867',
|
'javelin-behavior-maniphest-list-editor' => 'c687e867',
|
||||||
|
@ -695,6 +696,7 @@ return array(
|
||||||
'javelin-behavior-user-menu' => '60cd9241',
|
'javelin-behavior-user-menu' => '60cd9241',
|
||||||
'javelin-behavior-view-placeholder' => 'a9942052',
|
'javelin-behavior-view-placeholder' => 'a9942052',
|
||||||
'javelin-behavior-workflow' => '9623adc1',
|
'javelin-behavior-workflow' => '9623adc1',
|
||||||
|
'javelin-chart' => 'fcb0c07d',
|
||||||
'javelin-color' => '78f811c9',
|
'javelin-color' => '78f811c9',
|
||||||
'javelin-cookie' => '05d290ef',
|
'javelin-cookie' => '05d290ef',
|
||||||
'javelin-diffusion-locate-file-source' => '94243d89',
|
'javelin-diffusion-locate-file-source' => '94243d89',
|
||||||
|
@ -1319,12 +1321,6 @@ return array(
|
||||||
'490e2e2e' => array(
|
'490e2e2e' => array(
|
||||||
'phui-oi-list-view-css',
|
'phui-oi-list-view-css',
|
||||||
),
|
),
|
||||||
'495cf14d' => array(
|
|
||||||
'javelin-behavior',
|
|
||||||
'javelin-dom',
|
|
||||||
'javelin-vector',
|
|
||||||
'phui-chart-css',
|
|
||||||
),
|
|
||||||
'4a7fb02b' => array(
|
'4a7fb02b' => array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
|
@ -1861,6 +1857,11 @@ return array(
|
||||||
'javelin-request',
|
'javelin-request',
|
||||||
'javelin-router',
|
'javelin-router',
|
||||||
),
|
),
|
||||||
|
'ad258e28' => array(
|
||||||
|
'javelin-behavior',
|
||||||
|
'javelin-dom',
|
||||||
|
'javelin-chart',
|
||||||
|
),
|
||||||
'ad486db3' => array(
|
'ad486db3' => array(
|
||||||
'javelin-install',
|
'javelin-install',
|
||||||
'javelin-typeahead',
|
'javelin-typeahead',
|
||||||
|
@ -2179,6 +2180,10 @@ return array(
|
||||||
'fa74cc35' => array(
|
'fa74cc35' => array(
|
||||||
'phui-oi-list-view-css',
|
'phui-oi-list-view-css',
|
||||||
),
|
),
|
||||||
|
'fcb0c07d' => array(
|
||||||
|
'phui-chart-css',
|
||||||
|
'd3',
|
||||||
|
),
|
||||||
'fdc13e4e' => array(
|
'fdc13e4e' => array(
|
||||||
'javelin-install',
|
'javelin-install',
|
||||||
),
|
),
|
||||||
|
|
|
@ -30,7 +30,7 @@ final class PhabricatorFactApplication extends PhabricatorApplication {
|
||||||
return array(
|
return array(
|
||||||
'/fact/' => array(
|
'/fact/' => array(
|
||||||
'' => 'PhabricatorFactHomeController',
|
'' => 'PhabricatorFactHomeController',
|
||||||
'chart/' => 'PhabricatorFactChartController',
|
'(?<mode>chart|draw)/' => 'PhabricatorFactChartController',
|
||||||
'object/(?<phid>[^/]+)/' => 'PhabricatorFactObjectController',
|
'object/(?<phid>[^/]+)/' => 'PhabricatorFactObjectController',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,6 +5,13 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
|
||||||
public function handleRequest(AphrontRequest $request) {
|
public function handleRequest(AphrontRequest $request) {
|
||||||
$viewer = $request->getViewer();
|
$viewer = $request->getViewer();
|
||||||
|
|
||||||
|
// When drawing a chart, we send down a placeholder piece of HTML first,
|
||||||
|
// then fetch the data via async request. Determine if we're drawing
|
||||||
|
// the structure or actually pulling the data.
|
||||||
|
$mode = $request->getURIData('mode');
|
||||||
|
$is_chart_mode = ($mode === 'chart');
|
||||||
|
$is_draw_mode = ($mode === 'draw');
|
||||||
|
|
||||||
$series = $request->getStr('y1');
|
$series = $request->getStr('y1');
|
||||||
|
|
||||||
$facts = PhabricatorFact::getAllFacts();
|
$facts = PhabricatorFact::getAllFacts();
|
||||||
|
@ -20,6 +27,10 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
|
||||||
return new Aphront404Response();
|
return new Aphront404Response();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($is_chart_mode) {
|
||||||
|
return $this->newChartResponse();
|
||||||
|
}
|
||||||
|
|
||||||
$table = $fact->newDatapoint();
|
$table = $fact->newDatapoint();
|
||||||
$conn_r = $table->establishConnection('r');
|
$conn_r = $table->establishConnection('r');
|
||||||
$table_name = $table->getTableName();
|
$table_name = $table->getTableName();
|
||||||
|
@ -63,14 +74,13 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
|
||||||
'color' => '#ff0000',
|
'color' => '#ff0000',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
// Add a dummy "y = x" dataset to prove we can draw multiple datasets.
|
// Add a dummy "y = x" dataset to prove we can draw multiple datasets.
|
||||||
$x_min = min(array_keys($points));
|
$x_min = min(array_keys($points));
|
||||||
$x_max = max(array_keys($points));
|
$x_max = max(array_keys($points));
|
||||||
$x_range = ($x_max - $x_min) / 4;
|
$x_range = ($x_max - $x_min) / 4;
|
||||||
$linear = array();
|
$linear = array();
|
||||||
foreach ($points as $x => $y) {
|
foreach ($points as $x => $y) {
|
||||||
$linear[$x] = count($points) * (($x - $x_min) / $x_range);
|
$linear[$x] = round(count($points) * (($x - $x_min) / $x_range));
|
||||||
}
|
}
|
||||||
$datasets[] = array(
|
$datasets[] = array(
|
||||||
'x' => array_keys($linear),
|
'x' => array_keys($linear),
|
||||||
|
@ -78,19 +88,6 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
|
||||||
'color' => '#0000ff',
|
'color' => '#0000ff',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
$id = celerity_generate_unique_node_id();
|
|
||||||
$chart = phutil_tag(
|
|
||||||
'div',
|
|
||||||
array(
|
|
||||||
'id' => $id,
|
|
||||||
'style' => 'background: #ffffff; '.
|
|
||||||
'height: 480px; ',
|
|
||||||
),
|
|
||||||
'');
|
|
||||||
|
|
||||||
require_celerity_resource('d3');
|
|
||||||
|
|
||||||
$y_min = 0;
|
$y_min = 0;
|
||||||
$y_max = 0;
|
$y_max = 0;
|
||||||
$x_min = null;
|
$x_min = null;
|
||||||
|
@ -112,21 +109,43 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
|
||||||
$x_max = max($x_max, max($dataset['x']));
|
$x_max = max($x_max, max($dataset['x']));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$chart_data = array(
|
||||||
|
'datasets' => $datasets,
|
||||||
|
'xMin' => $x_min,
|
||||||
|
'xMax' => $x_max,
|
||||||
|
'yMin' => $y_min,
|
||||||
|
'yMax' => $y_max,
|
||||||
|
);
|
||||||
|
|
||||||
|
return id(new AphrontAjaxResponse())->setContent($chart_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function newChartResponse() {
|
||||||
|
$request = $this->getRequest();
|
||||||
|
$chart_node_id = celerity_generate_unique_node_id();
|
||||||
|
|
||||||
|
$chart_view = phutil_tag(
|
||||||
|
'div',
|
||||||
|
array(
|
||||||
|
'id' => $chart_node_id,
|
||||||
|
'style' => 'background: #ffffff; '.
|
||||||
|
'height: 480px; ',
|
||||||
|
),
|
||||||
|
'');
|
||||||
|
|
||||||
|
$data_uri = $request->getRequestURI();
|
||||||
|
$data_uri->setPath('/fact/draw/');
|
||||||
|
|
||||||
Javelin::initBehavior(
|
Javelin::initBehavior(
|
||||||
'line-chart',
|
'line-chart',
|
||||||
array(
|
array(
|
||||||
'hardpoint' => $id,
|
'chartNodeID' => $chart_node_id,
|
||||||
'datasets' => $datasets,
|
'dataURI' => (string)$data_uri,
|
||||||
'xMin' => $x_min,
|
|
||||||
'xMax' => $x_max,
|
|
||||||
'yMin' => $y_min,
|
|
||||||
'yMax' => $y_max,
|
|
||||||
'xformat' => 'epoch',
|
|
||||||
));
|
));
|
||||||
|
|
||||||
$box = id(new PHUIObjectBoxView())
|
$box = id(new PHUIObjectBoxView())
|
||||||
->setHeaderText(pht('Count of %s', $fact->getName()))
|
->setHeaderText(pht('Chart'))
|
||||||
->appendChild($chart);
|
->appendChild($chart_view);
|
||||||
|
|
||||||
$crumbs = $this->buildApplicationCrumbs()
|
$crumbs = $this->buildApplicationCrumbs()
|
||||||
->addTextCrumb(pht('Chart'))
|
->addTextCrumb(pht('Chart'))
|
||||||
|
|
156
webroot/rsrc/js/application/fact/Chart.js
vendored
Normal file
156
webroot/rsrc/js/application/fact/Chart.js
vendored
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
/**
|
||||||
|
* @provides javelin-chart
|
||||||
|
* @requires phui-chart-css
|
||||||
|
* d3
|
||||||
|
*/
|
||||||
|
JX.install('Chart', {
|
||||||
|
|
||||||
|
construct: function(root_node) {
|
||||||
|
this._rootNode = root_node;
|
||||||
|
|
||||||
|
JX.Stratcom.listen('resize', null, JX.bind(this, this._redraw));
|
||||||
|
},
|
||||||
|
|
||||||
|
members: {
|
||||||
|
_rootNode: null,
|
||||||
|
_data: null,
|
||||||
|
|
||||||
|
setData: function(blob) {
|
||||||
|
this._data = blob;
|
||||||
|
this._redraw();
|
||||||
|
},
|
||||||
|
|
||||||
|
_redraw: function() {
|
||||||
|
if (!this._data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hardpoint = this._rootNode;
|
||||||
|
var viewport = JX.Vector.getDim(hardpoint);
|
||||||
|
var config = this._data;
|
||||||
|
|
||||||
|
function css_function(n) {
|
||||||
|
return n + '(' + JX.$A(arguments).slice(1).join(', ') + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
var padding = {
|
||||||
|
top: 24,
|
||||||
|
left: 48,
|
||||||
|
bottom: 48,
|
||||||
|
right: 32
|
||||||
|
};
|
||||||
|
|
||||||
|
var size = {
|
||||||
|
frameWidth: viewport.x,
|
||||||
|
frameHeight: viewport.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');
|
||||||
|
|
||||||
|
// Remove the old chart (if one exists) before drawing the new chart.
|
||||||
|
JX.DOM.setContent(hardpoint, []);
|
||||||
|
|
||||||
|
var svg = d3.select('#' + hardpoint.id).append('svg')
|
||||||
|
.attr('width', size.frameWidth)
|
||||||
|
.attr('height', size.frameHeight)
|
||||||
|
.attr('class', 'chart');
|
||||||
|
|
||||||
|
var g = svg.append('g')
|
||||||
|
.attr(
|
||||||
|
'transform',
|
||||||
|
css_function('translate', padding.left, padding.top));
|
||||||
|
|
||||||
|
g.append('rect')
|
||||||
|
.attr('class', 'inner')
|
||||||
|
.attr('width', size.width)
|
||||||
|
.attr('height', size.height);
|
||||||
|
|
||||||
|
function as_date(value) {
|
||||||
|
return new Date(value * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
x.domain([as_date(config.xMin), as_date(config.xMax)]);
|
||||||
|
y.domain([config.yMin, config.yMax]);
|
||||||
|
|
||||||
|
var div = d3.select('body')
|
||||||
|
.append('div')
|
||||||
|
.attr('class', 'chart-tooltip')
|
||||||
|
.style('opacity', 0);
|
||||||
|
|
||||||
|
for (var idx = 0; idx < config.datasets.length; idx++) {
|
||||||
|
var dataset = config.datasets[idx];
|
||||||
|
|
||||||
|
var line = d3.svg.line()
|
||||||
|
.x(function(d) { return x(d.xvalue); })
|
||||||
|
.y(function(d) { return y(d.yvalue); });
|
||||||
|
|
||||||
|
var data = [];
|
||||||
|
for (var ii = 0; ii < dataset.x.length; ii++) {
|
||||||
|
data.push(
|
||||||
|
{
|
||||||
|
xvalue: as_date(dataset.x[ii]),
|
||||||
|
yvalue: dataset.y[ii]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
g.append('path')
|
||||||
|
.datum(data)
|
||||||
|
.attr('class', 'line')
|
||||||
|
.style('stroke', dataset.color)
|
||||||
|
.attr('d', line);
|
||||||
|
|
||||||
|
g.selectAll('dot')
|
||||||
|
.data(data)
|
||||||
|
.enter()
|
||||||
|
.append('circle')
|
||||||
|
.attr('class', 'point')
|
||||||
|
.attr('r', 3)
|
||||||
|
.attr('cx', function(d) { return x(d.xvalue); })
|
||||||
|
.attr('cy', function(d) { return y(d.yvalue); })
|
||||||
|
.on('mouseover', function(d) {
|
||||||
|
var d_y = d.xvalue.getFullYear();
|
||||||
|
|
||||||
|
// NOTE: Javascript months are zero-based. See PHI1017.
|
||||||
|
var d_m = d.xvalue.getMonth() + 1;
|
||||||
|
|
||||||
|
var d_d = d.xvalue.getDate();
|
||||||
|
|
||||||
|
div
|
||||||
|
.html(d_y + '-' + d_m + '-' + d_d + ': ' + d.yvalue)
|
||||||
|
.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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
g.append('g')
|
||||||
|
.attr('class', 'x axis')
|
||||||
|
.attr('transform', css_function('translate', 0, size.height))
|
||||||
|
.call(xAxis);
|
||||||
|
|
||||||
|
g.append('g')
|
||||||
|
.attr('class', 'y axis')
|
||||||
|
.attr('transform', css_function('translate', 0, 0))
|
||||||
|
.call(yAxis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
|
@ -2,130 +2,18 @@
|
||||||
* @provides javelin-behavior-line-chart
|
* @provides javelin-behavior-line-chart
|
||||||
* @requires javelin-behavior
|
* @requires javelin-behavior
|
||||||
* javelin-dom
|
* javelin-dom
|
||||||
* javelin-vector
|
* javelin-chart
|
||||||
* phui-chart-css
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
JX.behavior('line-chart', function(config) {
|
JX.behavior('line-chart', function(config) {
|
||||||
|
var chart_node = JX.$(config.chartNodeID);
|
||||||
|
|
||||||
function css_function(n) {
|
var chart = new JX.Chart(chart_node);
|
||||||
return n + '(' + JX.$A(arguments).slice(1).join(', ') + ')';
|
|
||||||
|
function onresponse(r) {
|
||||||
|
chart.setData(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
var h = JX.$(config.hardpoint);
|
new JX.Request(config.dataURI, onresponse)
|
||||||
var d = JX.Vector.getDim(h);
|
.send();
|
||||||
|
|
||||||
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', css_function('translate', padding.left, padding.top));
|
|
||||||
|
|
||||||
g.append('rect')
|
|
||||||
.attr('class', 'inner')
|
|
||||||
.attr('width', size.width)
|
|
||||||
.attr('height', size.height);
|
|
||||||
|
|
||||||
function as_date(value) {
|
|
||||||
return new Date(value * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
x.domain([as_date(config.xMin), as_date(config.xMax)]);
|
|
||||||
y.domain([config.yMin, config.yMax]);
|
|
||||||
|
|
||||||
for (var idx = 0; idx < config.datasets.length; idx++) {
|
|
||||||
var dataset = config.datasets[idx];
|
|
||||||
|
|
||||||
var line = d3.svg.line()
|
|
||||||
.x(function(d) { return x(d.xvalue); })
|
|
||||||
.y(function(d) { return y(d.yvalue); });
|
|
||||||
|
|
||||||
var data = [];
|
|
||||||
for (var ii = 0; ii < dataset.x.length; ii++) {
|
|
||||||
data.push(
|
|
||||||
{
|
|
||||||
xvalue: as_date(dataset.x[ii]),
|
|
||||||
yvalue: dataset.y[ii]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
g.append('path')
|
|
||||||
.datum(data)
|
|
||||||
.attr('class', 'line')
|
|
||||||
.style('stroke', dataset.color)
|
|
||||||
.attr('d', line);
|
|
||||||
|
|
||||||
g.selectAll('dot')
|
|
||||||
.data(data)
|
|
||||||
.enter()
|
|
||||||
.append('circle')
|
|
||||||
.attr('class', 'point')
|
|
||||||
.attr('r', 3)
|
|
||||||
.attr('cx', function(d) { return x(d.xvalue); })
|
|
||||||
.attr('cy', function(d) { return y(d.yvalue); })
|
|
||||||
.on('mouseover', function(d) {
|
|
||||||
var d_y = d.xvalue.getFullYear();
|
|
||||||
|
|
||||||
// NOTE: Javascript months are zero-based. See PHI1017.
|
|
||||||
var d_m = d.xvalue.getMonth() + 1;
|
|
||||||
|
|
||||||
var d_d = d.xvalue.getDate();
|
|
||||||
|
|
||||||
div
|
|
||||||
.html(d_y + '-' + d_m + '-' + d_d + ': ' + d.yvalue)
|
|
||||||
.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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
g.append('g')
|
|
||||||
.attr('class', 'x axis')
|
|
||||||
.attr('transform', css_function('translate', 0, size.height))
|
|
||||||
.call(xAxis);
|
|
||||||
|
|
||||||
g.append('g')
|
|
||||||
.attr('class', 'y axis')
|
|
||||||
.attr('transform', css_function('translate', 0, 0))
|
|
||||||
.call(yAxis);
|
|
||||||
|
|
||||||
var div = d3.select('body')
|
|
||||||
.append('div')
|
|
||||||
.attr('class', 'chart-tooltip')
|
|
||||||
.style('opacity', 0);
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue