Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15369846
D20440.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
14 KB
Referenced Files
None
Subscribers
None
D20440.id.diff
View Options
diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -389,6 +389,7 @@
'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'c715c123',
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '6a85bc5a',
'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-icon-composer.js' => '38a6cedb',
'rsrc/js/application/files/behavior-launch-icon-composer.js' => 'a17b84f1',
@@ -397,7 +398,7 @@
'rsrc/js/application/herald/PathTypeahead.js' => 'ad486db3',
'rsrc/js/application/herald/herald-rule-editor.js' => '0922e81d',
'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/owners/OwnersPathEditor.js' => '2a8b62d9',
'rsrc/js/application/owners/owners-path-editor.js' => 'ff688a7a',
@@ -625,7 +626,7 @@
'javelin-behavior-icon-composer' => '38a6cedb',
'javelin-behavior-launch-icon-composer' => 'a17b84f1',
'javelin-behavior-lightbox-attachments' => 'c7e748bf',
- 'javelin-behavior-line-chart' => '495cf14d',
+ 'javelin-behavior-line-chart' => 'ad258e28',
'javelin-behavior-linked-container' => '74446546',
'javelin-behavior-maniphest-batch-selector' => '139ef688',
'javelin-behavior-maniphest-list-editor' => 'c687e867',
@@ -695,6 +696,7 @@
'javelin-behavior-user-menu' => '60cd9241',
'javelin-behavior-view-placeholder' => 'a9942052',
'javelin-behavior-workflow' => '9623adc1',
+ 'javelin-chart' => 'fcb0c07d',
'javelin-color' => '78f811c9',
'javelin-cookie' => '05d290ef',
'javelin-diffusion-locate-file-source' => '94243d89',
@@ -1319,12 +1321,6 @@
'490e2e2e' => array(
'phui-oi-list-view-css',
),
- '495cf14d' => array(
- 'javelin-behavior',
- 'javelin-dom',
- 'javelin-vector',
- 'phui-chart-css',
- ),
'4a7fb02b' => array(
'javelin-behavior',
'javelin-dom',
@@ -1861,6 +1857,11 @@
'javelin-request',
'javelin-router',
),
+ 'ad258e28' => array(
+ 'javelin-behavior',
+ 'javelin-dom',
+ 'javelin-chart',
+ ),
'ad486db3' => array(
'javelin-install',
'javelin-typeahead',
@@ -2179,6 +2180,10 @@
'fa74cc35' => array(
'phui-oi-list-view-css',
),
+ 'fcb0c07d' => array(
+ 'phui-chart-css',
+ 'd3',
+ ),
'fdc13e4e' => array(
'javelin-install',
),
diff --git a/src/applications/fact/application/PhabricatorFactApplication.php b/src/applications/fact/application/PhabricatorFactApplication.php
--- a/src/applications/fact/application/PhabricatorFactApplication.php
+++ b/src/applications/fact/application/PhabricatorFactApplication.php
@@ -30,7 +30,7 @@
return array(
'/fact/' => array(
'' => 'PhabricatorFactHomeController',
- 'chart/' => 'PhabricatorFactChartController',
+ '(?<mode>chart|draw)/' => 'PhabricatorFactChartController',
'object/(?<phid>[^/]+)/' => 'PhabricatorFactObjectController',
),
);
diff --git a/src/applications/fact/controller/PhabricatorFactChartController.php b/src/applications/fact/controller/PhabricatorFactChartController.php
--- a/src/applications/fact/controller/PhabricatorFactChartController.php
+++ b/src/applications/fact/controller/PhabricatorFactChartController.php
@@ -5,6 +5,13 @@
public function handleRequest(AphrontRequest $request) {
$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');
$facts = PhabricatorFact::getAllFacts();
@@ -20,6 +27,10 @@
return new Aphront404Response();
}
+ if ($is_chart_mode) {
+ return $this->newChartResponse();
+ }
+
$table = $fact->newDatapoint();
$conn_r = $table->establishConnection('r');
$table_name = $table->getTableName();
@@ -63,14 +74,13 @@
'color' => '#ff0000',
);
-
// Add a dummy "y = x" dataset to prove we can draw multiple datasets.
$x_min = min(array_keys($points));
$x_max = max(array_keys($points));
$x_range = ($x_max - $x_min) / 4;
$linear = array();
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(
'x' => array_keys($linear),
@@ -78,19 +88,6 @@
'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_max = 0;
$x_min = null;
@@ -112,21 +109,43 @@
$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(
'line-chart',
array(
- 'hardpoint' => $id,
- 'datasets' => $datasets,
- 'xMin' => $x_min,
- 'xMax' => $x_max,
- 'yMin' => $y_min,
- 'yMax' => $y_max,
- 'xformat' => 'epoch',
+ 'chartNodeID' => $chart_node_id,
+ 'dataURI' => (string)$data_uri,
));
$box = id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Count of %s', $fact->getName()))
- ->appendChild($chart);
+ ->setHeaderText(pht('Chart'))
+ ->appendChild($chart_view);
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Chart'))
diff --git a/webroot/rsrc/js/application/fact/Chart.js b/webroot/rsrc/js/application/fact/Chart.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/application/fact/Chart.js
@@ -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);
+ }
+ }
+
+});
diff --git a/webroot/rsrc/js/application/maniphest/behavior-line-chart.js b/webroot/rsrc/js/application/maniphest/behavior-line-chart.js
--- a/webroot/rsrc/js/application/maniphest/behavior-line-chart.js
+++ b/webroot/rsrc/js/application/maniphest/behavior-line-chart.js
@@ -2,130 +2,18 @@
* @provides javelin-behavior-line-chart
* @requires javelin-behavior
* javelin-dom
- * javelin-vector
- * phui-chart-css
+ * javelin-chart
*/
JX.behavior('line-chart', function(config) {
+ var chart_node = JX.$(config.chartNodeID);
- function css_function(n) {
- return n + '(' + JX.$A(arguments).slice(1).join(', ') + ')';
- }
-
- var h = JX.$(config.hardpoint);
- var d = JX.Vector.getDim(h);
-
- 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 chart = new JX.Chart(chart_node);
- 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);
+ function onresponse(r) {
+ chart.setData(r);
}
- 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);
-
+ new JX.Request(config.dataURI, onresponse)
+ .send();
});
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Mar 13, 8:24 AM (1 w, 6 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7612825
Default Alt Text
D20440.id.diff (14 KB)
Attached To
Mode
D20440: Fetch chart data via async request and redraw charts when the window is resized
Attached
Detach File
Event Timeline
Log In to Comment