Page MenuHomePhabricator

D20440.id.diff
No OneTemporary

D20440.id.diff

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

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)

Event Timeline