mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-26 00:32:42 +01:00
Allow Fact app to draw charts
Summary: For any count fact, allow a chart to be drawn. INCREDIBLY POWERFUL DATA ANALYSIS PLATFORM. Test Plan: Drew a chart of object counts. Drew the Maniphest burn chart. Reviewers: vrana, btrahan Reviewed By: btrahan CC: aran Maniphest Tasks: T1562 Differential Revision: https://secure.phabricator.com/D3099
This commit is contained in:
parent
f0af273165
commit
fceabd42e8
11 changed files with 326 additions and 86 deletions
|
@ -1219,6 +1219,18 @@ celerity_register_resource_map(array(
|
||||||
),
|
),
|
||||||
'disk' => '/rsrc/js/application/herald/herald-rule-editor.js',
|
'disk' => '/rsrc/js/application/herald/herald-rule-editor.js',
|
||||||
),
|
),
|
||||||
|
'javelin-behavior-line-chart' =>
|
||||||
|
array(
|
||||||
|
'uri' => '/res/653743c8/rsrc/js/application/maniphest/behavior-line-chart.js',
|
||||||
|
'type' => 'js',
|
||||||
|
'requires' =>
|
||||||
|
array(
|
||||||
|
0 => 'javelin-behavior',
|
||||||
|
1 => 'javelin-dom',
|
||||||
|
2 => 'javelin-vector',
|
||||||
|
),
|
||||||
|
'disk' => '/rsrc/js/application/maniphest/behavior-line-chart.js',
|
||||||
|
),
|
||||||
'javelin-behavior-maniphest-batch-editor' =>
|
'javelin-behavior-maniphest-batch-editor' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/d22661be/rsrc/js/application/maniphest/behavior-batch-editor.js',
|
'uri' => '/res/d22661be/rsrc/js/application/maniphest/behavior-batch-editor.js',
|
||||||
|
|
|
@ -532,6 +532,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorAccessLog' => 'infrastructure/PhabricatorAccessLog.php',
|
'PhabricatorAccessLog' => 'infrastructure/PhabricatorAccessLog.php',
|
||||||
'PhabricatorApplication' => 'applications/base/PhabricatorApplication.php',
|
'PhabricatorApplication' => 'applications/base/PhabricatorApplication.php',
|
||||||
'PhabricatorApplicationDifferential' => 'applications/differential/application/PhabricatorApplicationDifferential.php',
|
'PhabricatorApplicationDifferential' => 'applications/differential/application/PhabricatorApplicationDifferential.php',
|
||||||
|
'PhabricatorApplicationFact' => 'applications/fact/application/PhabricatorApplicationFact.php',
|
||||||
'PhabricatorApplicationManiphest' => 'applications/maniphest/application/PhabricatorApplicationManiphest.php',
|
'PhabricatorApplicationManiphest' => 'applications/maniphest/application/PhabricatorApplicationManiphest.php',
|
||||||
'PhabricatorAuditActionConstants' => 'applications/audit/constants/PhabricatorAuditActionConstants.php',
|
'PhabricatorAuditActionConstants' => 'applications/audit/constants/PhabricatorAuditActionConstants.php',
|
||||||
'PhabricatorAuditAddCommentController' => 'applications/audit/controller/PhabricatorAuditAddCommentController.php',
|
'PhabricatorAuditAddCommentController' => 'applications/audit/controller/PhabricatorAuditAddCommentController.php',
|
||||||
|
@ -628,6 +629,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorEventType' => 'infrastructure/events/constant/PhabricatorEventType.php',
|
'PhabricatorEventType' => 'infrastructure/events/constant/PhabricatorEventType.php',
|
||||||
'PhabricatorExampleEventListener' => 'infrastructure/events/PhabricatorExampleEventListener.php',
|
'PhabricatorExampleEventListener' => 'infrastructure/events/PhabricatorExampleEventListener.php',
|
||||||
'PhabricatorFactAggregate' => 'applications/fact/storage/PhabricatorFactAggregate.php',
|
'PhabricatorFactAggregate' => 'applications/fact/storage/PhabricatorFactAggregate.php',
|
||||||
|
'PhabricatorFactChartController' => 'applications/fact/controller/PhabricatorFactChartController.php',
|
||||||
'PhabricatorFactController' => 'applications/fact/controller/PhabricatorFactController.php',
|
'PhabricatorFactController' => 'applications/fact/controller/PhabricatorFactController.php',
|
||||||
'PhabricatorFactCountEngine' => 'applications/fact/engine/PhabricatorFactCountEngine.php',
|
'PhabricatorFactCountEngine' => 'applications/fact/engine/PhabricatorFactCountEngine.php',
|
||||||
'PhabricatorFactCursor' => 'applications/fact/storage/PhabricatorFactCursor.php',
|
'PhabricatorFactCursor' => 'applications/fact/storage/PhabricatorFactCursor.php',
|
||||||
|
@ -1585,6 +1587,7 @@ phutil_register_library_map(array(
|
||||||
'PackageModifyMail' => 'PackageMail',
|
'PackageModifyMail' => 'PackageMail',
|
||||||
'Phabricator404Controller' => 'PhabricatorController',
|
'Phabricator404Controller' => 'PhabricatorController',
|
||||||
'PhabricatorApplicationDifferential' => 'PhabricatorApplication',
|
'PhabricatorApplicationDifferential' => 'PhabricatorApplication',
|
||||||
|
'PhabricatorApplicationFact' => 'PhabricatorApplication',
|
||||||
'PhabricatorApplicationManiphest' => 'PhabricatorApplication',
|
'PhabricatorApplicationManiphest' => 'PhabricatorApplication',
|
||||||
'PhabricatorAuditAddCommentController' => 'PhabricatorAuditController',
|
'PhabricatorAuditAddCommentController' => 'PhabricatorAuditController',
|
||||||
'PhabricatorAuditComment' => 'PhabricatorAuditDAO',
|
'PhabricatorAuditComment' => 'PhabricatorAuditDAO',
|
||||||
|
@ -1674,6 +1677,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorEventType' => 'PhutilEventType',
|
'PhabricatorEventType' => 'PhutilEventType',
|
||||||
'PhabricatorExampleEventListener' => 'PhutilEventListener',
|
'PhabricatorExampleEventListener' => 'PhutilEventListener',
|
||||||
'PhabricatorFactAggregate' => 'PhabricatorFactDAO',
|
'PhabricatorFactAggregate' => 'PhabricatorFactDAO',
|
||||||
|
'PhabricatorFactChartController' => 'PhabricatorFactController',
|
||||||
'PhabricatorFactController' => 'PhabricatorController',
|
'PhabricatorFactController' => 'PhabricatorController',
|
||||||
'PhabricatorFactCountEngine' => 'PhabricatorFactEngine',
|
'PhabricatorFactCountEngine' => 'PhabricatorFactEngine',
|
||||||
'PhabricatorFactCursor' => 'PhabricatorFactDAO',
|
'PhabricatorFactCursor' => 'PhabricatorFactDAO',
|
||||||
|
|
|
@ -464,10 +464,7 @@ class AphrontDefaultApplicationConfiguration
|
||||||
'/emailverify/(?P<code>[^/]+)/' =>
|
'/emailverify/(?P<code>[^/]+)/' =>
|
||||||
'PhabricatorEmailVerificationController',
|
'PhabricatorEmailVerificationController',
|
||||||
|
|
||||||
'/fact/' => array(
|
) + $this->getApplicationRoutes();
|
||||||
'' => 'PhabricatorFactHomeController',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getResourceURIMapRules() {
|
protected function getResourceURIMapRules() {
|
||||||
|
@ -481,6 +478,15 @@ class AphrontDefaultApplicationConfiguration
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getApplicationRoutes() {
|
||||||
|
$applications = PhabricatorApplication::getAllInstalledApplications();
|
||||||
|
$routes = array();
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
$routes += $application->getRoutes();
|
||||||
|
}
|
||||||
|
return $routes;
|
||||||
|
}
|
||||||
|
|
||||||
public function buildRequest() {
|
public function buildRequest() {
|
||||||
$request = new AphrontRequest($this->getHost(), $this->getPath());
|
$request = new AphrontRequest($this->getHost(), $this->getPath());
|
||||||
$request->setRequestData($_GET + $_POST);
|
$request->setRequestData($_GET + $_POST);
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @task info Application Information
|
* @task info Application Information
|
||||||
|
* @task uri URI Routing
|
||||||
* @task fact Fact Integration
|
* @task fact Fact Integration
|
||||||
* @task meta Application Management
|
* @task meta Application Management
|
||||||
* @group apps
|
* @group apps
|
||||||
|
@ -37,6 +38,14 @@ abstract class PhabricatorApplication {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( Application Information )-------------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
public function getRoutes() {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( Fact Integration )--------------------------------------------------- */
|
/* -( Fact Integration )--------------------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2012 Facebook, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
final class PhabricatorApplicationFact extends PhabricatorApplication {
|
||||||
|
|
||||||
|
public function getRoutes() {
|
||||||
|
return array(
|
||||||
|
'/fact/' => array(
|
||||||
|
'' => 'PhabricatorFactHomeController',
|
||||||
|
'chart/' => 'PhabricatorFactChartController',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2012 Facebook, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
final class PhabricatorFactChartController extends PhabricatorFactController {
|
||||||
|
|
||||||
|
public function processRequest() {
|
||||||
|
$request = $this->getRequest();
|
||||||
|
$user = $request->getUser();
|
||||||
|
|
||||||
|
$table = new PhabricatorFactRaw();
|
||||||
|
$conn_r = $table->establishConnection('r');
|
||||||
|
$table_name = $table->getTableName();
|
||||||
|
|
||||||
|
$series = $request->getStr('y1');
|
||||||
|
|
||||||
|
$specs = PhabricatorFactSpec::newSpecsForFactTypes(
|
||||||
|
PhabricatorFactEngine::loadAllEngines(),
|
||||||
|
array($series));
|
||||||
|
$spec = idx($specs, $series);
|
||||||
|
|
||||||
|
$data = queryfx_all(
|
||||||
|
$conn_r,
|
||||||
|
'SELECT valueX, epoch FROM %T WHERE factType = %s ORDER BY epoch ASC',
|
||||||
|
$table_name,
|
||||||
|
$series);
|
||||||
|
|
||||||
|
$points = array();
|
||||||
|
$sum = 0;
|
||||||
|
foreach ($data as $key => $row) {
|
||||||
|
$sum += (int)$row['valueX'];
|
||||||
|
$points[(int)$row['epoch']] = $sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$points) {
|
||||||
|
// NOTE: Raphael crashes Safari if you hand it series with no points.
|
||||||
|
throw new Exception("No data to show!");
|
||||||
|
}
|
||||||
|
|
||||||
|
$x = array_keys($points);
|
||||||
|
$y = array_values($points);
|
||||||
|
|
||||||
|
$id = celerity_generate_unique_node_id();
|
||||||
|
$chart = phutil_render_tag(
|
||||||
|
'div',
|
||||||
|
array(
|
||||||
|
'id' => $id,
|
||||||
|
'style' => 'border: 1px solid #6f6f6f; '.
|
||||||
|
'margin: 1em 2em; '.
|
||||||
|
'background: #ffffff; '.
|
||||||
|
'height: 400px; ',
|
||||||
|
),
|
||||||
|
'');
|
||||||
|
|
||||||
|
require_celerity_resource('raphael-core');
|
||||||
|
require_celerity_resource('raphael-g');
|
||||||
|
require_celerity_resource('raphael-g-line');
|
||||||
|
|
||||||
|
Javelin::initBehavior('line-chart', array(
|
||||||
|
'hardpoint' => $id,
|
||||||
|
'x' => array($x),
|
||||||
|
'y' => array($y),
|
||||||
|
'xformat' => 'epoch',
|
||||||
|
'colors' => array('#0000ff'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$panel = new AphrontPanelView();
|
||||||
|
$panel->setHeader('Count of '.$spec->getName());
|
||||||
|
$panel->appendChild($chart);
|
||||||
|
|
||||||
|
return $this->buildStandardPageResponse(
|
||||||
|
$panel,
|
||||||
|
array(
|
||||||
|
'title' => 'Chart',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -22,6 +22,12 @@ final class PhabricatorFactHomeController extends PhabricatorFactController {
|
||||||
$request = $this->getRequest();
|
$request = $this->getRequest();
|
||||||
$user = $request->getUser();
|
$user = $request->getUser();
|
||||||
|
|
||||||
|
if ($request->isFormPost()) {
|
||||||
|
$uri = new PhutilURI('/fact/chart/');
|
||||||
|
$uri->setQueryParam('y1', $request->getStr('y1'));
|
||||||
|
return id(new AphrontRedirectResponse())->setURI($uri);
|
||||||
|
}
|
||||||
|
|
||||||
$types = array(
|
$types = array(
|
||||||
'+N:*',
|
'+N:*',
|
||||||
'+N:DREV',
|
'+N:DREV',
|
||||||
|
@ -64,11 +70,68 @@ final class PhabricatorFactHomeController extends PhabricatorFactController {
|
||||||
$panel->setHeader('Facts!');
|
$panel->setHeader('Facts!');
|
||||||
$panel->appendChild($table);
|
$panel->appendChild($table);
|
||||||
|
|
||||||
|
$chart_form = $this->buildChartForm();
|
||||||
|
|
||||||
return $this->buildStandardPageResponse(
|
return $this->buildStandardPageResponse(
|
||||||
$panel,
|
array(
|
||||||
|
$chart_form,
|
||||||
|
$panel,
|
||||||
|
),
|
||||||
array(
|
array(
|
||||||
'title' => 'Facts!',
|
'title' => 'Facts!',
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildChartForm() {
|
||||||
|
$request = $this->getRequest();
|
||||||
|
$user = $request->getUser();
|
||||||
|
|
||||||
|
$table = new PhabricatorFactRaw();
|
||||||
|
$conn_r = $table->establishConnection('r');
|
||||||
|
$table_name = $table->getTableName();
|
||||||
|
|
||||||
|
$facts = queryfx_all(
|
||||||
|
$conn_r,
|
||||||
|
'SELECT DISTINCT factType from %T',
|
||||||
|
$table_name);
|
||||||
|
|
||||||
|
$specs = PhabricatorFactSpec::newSpecsForFactTypes(
|
||||||
|
PhabricatorFactEngine::loadAllEngines(),
|
||||||
|
ipull($facts, 'factType'));
|
||||||
|
|
||||||
|
$options = array();
|
||||||
|
foreach ($specs as $spec) {
|
||||||
|
if ($spec->getUnit() == PhabricatorFactSpec::UNIT_COUNT) {
|
||||||
|
$options[$spec->getType()] = $spec->getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$options) {
|
||||||
|
return id(new AphrontErrorView())
|
||||||
|
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
|
||||||
|
->setTitle(pht('No Chartable Facts'))
|
||||||
|
->appendChild(
|
||||||
|
'<p>'.pht(
|
||||||
|
'There are no facts that can be plotted yet.').'</p>');
|
||||||
|
}
|
||||||
|
|
||||||
|
$form = id(new AphrontFormView())
|
||||||
|
->setUser($user)
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormSelectControl())
|
||||||
|
->setLabel('Y-Axis')
|
||||||
|
->setName('y1')
|
||||||
|
->setOptions($options))
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormSubmitControl())
|
||||||
|
->setValue('Plot Chart'));
|
||||||
|
|
||||||
|
$panel = new AphrontPanelView();
|
||||||
|
$panel->appendChild($form);
|
||||||
|
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
|
||||||
|
$panel->setHeader('Plot Chart');
|
||||||
|
|
||||||
|
return $panel;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,18 @@ final class PhabricatorFactCountEngine extends PhabricatorFactEngine {
|
||||||
->setName($name)
|
->setName($name)
|
||||||
->setUnit(PhabricatorFactSimpleSpec::UNIT_COUNT);
|
->setUnit(PhabricatorFactSimpleSpec::UNIT_COUNT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!strncmp($type, 'N:', 2)) {
|
||||||
|
if ($type == 'N:*') {
|
||||||
|
$name = 'Objects';
|
||||||
|
} else {
|
||||||
|
$name = 'Objects of type '.substr($type, 2);
|
||||||
|
}
|
||||||
|
$results[] = id(new PhabricatorFactSimpleSpec($type))
|
||||||
|
->setName($name)
|
||||||
|
->setUnit(PhabricatorFactSimpleSpec::UNIT_COUNT);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return $results;
|
return $results;
|
||||||
}
|
}
|
||||||
|
@ -53,6 +65,7 @@ final class PhabricatorFactCountEngine extends PhabricatorFactEngine {
|
||||||
$facts[] = id(new PhabricatorFactRaw())
|
$facts[] = id(new PhabricatorFactRaw())
|
||||||
->setFactType($fact_type)
|
->setFactType($fact_type)
|
||||||
->setObjectPHID($phid)
|
->setObjectPHID($phid)
|
||||||
|
->setValueX(1)
|
||||||
->setEpoch($object->getDateCreated());
|
->setEpoch($object->getDateCreated());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +83,7 @@ final class PhabricatorFactCountEngine extends PhabricatorFactEngine {
|
||||||
|
|
||||||
$counts = queryfx_all(
|
$counts = queryfx_all(
|
||||||
$conn,
|
$conn,
|
||||||
'SELECT factType, count(*) N FROM %T WHERE factType LIKE %>
|
'SELECT factType, SUM(valueX) N FROM %T WHERE factType LIKE %>
|
||||||
GROUP BY factType',
|
GROUP BY factType',
|
||||||
$table_name,
|
$table_name,
|
||||||
'N:');
|
'N:');
|
||||||
|
|
|
@ -304,7 +304,7 @@ final class ManiphestReportController extends ManiphestController {
|
||||||
require_celerity_resource('raphael-g');
|
require_celerity_resource('raphael-g');
|
||||||
require_celerity_resource('raphael-g-line');
|
require_celerity_resource('raphael-g-line');
|
||||||
|
|
||||||
Javelin::initBehavior('burn-chart', array(
|
Javelin::initBehavior('line-chart', array(
|
||||||
'hardpoint' => $id,
|
'hardpoint' => $id,
|
||||||
'x' => array(
|
'x' => array(
|
||||||
$burn_x,
|
$burn_x,
|
||||||
|
@ -312,6 +312,7 @@ final class ManiphestReportController extends ManiphestController {
|
||||||
'y' => array(
|
'y' => array(
|
||||||
$burn_y,
|
$burn_y,
|
||||||
),
|
),
|
||||||
|
'xformat' => 'epoch',
|
||||||
));
|
));
|
||||||
|
|
||||||
return array($filter, $chart, $panel);
|
return array($filter, $chart, $panel);
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
/**
|
|
||||||
* @provides javelin-behavior-burn-chart
|
|
||||||
* @requires javelin-behavior
|
|
||||||
* javelin-dom
|
|
||||||
* javelin-vector
|
|
||||||
*/
|
|
||||||
|
|
||||||
JX.behavior('burn-chart', function(config) {
|
|
||||||
|
|
||||||
|
|
||||||
var h = JX.$(config.hardpoint);
|
|
||||||
var p = JX.$V(h);
|
|
||||||
var d = JX.Vector.getDim(h);
|
|
||||||
var mx = 60;
|
|
||||||
var my = 30;
|
|
||||||
|
|
||||||
var r = Raphael(p.x, p.y, d.x, d.y);
|
|
||||||
|
|
||||||
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: ['#d06']
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// Convert the epoch timestamps on the X axis into readable dates.
|
|
||||||
|
|
||||||
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 date = new Date(parseInt(cur, 10) * 1000);
|
|
||||||
var str = date.toLocaleDateString();
|
|
||||||
text[k].attr({text: str});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
l.hoverColumn(function() {
|
|
||||||
|
|
||||||
var open = 0;
|
|
||||||
for (var ii = 0; ii < config.x[0].length; ii++) {
|
|
||||||
if (config.x[0][ii] > this.axis) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
open = config.y[0][ii];
|
|
||||||
}
|
|
||||||
|
|
||||||
var date = new Date(parseInt(this.axis, 10) * 1000).toLocaleDateString();
|
|
||||||
var total = open + " Open Tasks";
|
|
||||||
|
|
||||||
var tag = r.tag(
|
|
||||||
this.x,
|
|
||||||
this.y[0],
|
|
||||||
[date, total].join("\n"),
|
|
||||||
180,
|
|
||||||
24);
|
|
||||||
tag
|
|
||||||
.insertBefore(this)
|
|
||||||
.attr([{fill : '#fff'}, {fill: '#000'}]);
|
|
||||||
|
|
||||||
this.tags = r.set();
|
|
||||||
this.tags.push(tag);
|
|
||||||
}, function() {
|
|
||||||
this.tags && this.tags.remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
89
webroot/rsrc/js/application/maniphest/behavior-line-chart.js
Normal file
89
webroot/rsrc/js/application/maniphest/behavior-line-chart.js
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
/**
|
||||||
|
* @provides javelin-behavior-line-chart
|
||||||
|
* @requires javelin-behavior
|
||||||
|
* javelin-dom
|
||||||
|
* javelin-vector
|
||||||
|
*/
|
||||||
|
|
||||||
|
JX.behavior('line-chart', function(config) {
|
||||||
|
|
||||||
|
var h = JX.$(config.hardpoint);
|
||||||
|
var p = JX.$V(h);
|
||||||
|
var d = JX.Vector.getDim(h);
|
||||||
|
var mx = 60;
|
||||||
|
var my = 30;
|
||||||
|
|
||||||
|
var r = Raphael(p.x, p.y, d.x, d.y);
|
||||||
|
|
||||||
|
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 || ['#d06']
|
||||||
|
});
|
||||||
|
|
||||||
|
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');
|
||||||
|
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 || [])[yy]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var xvalue = format(this.axis, config.xformat);
|
||||||
|
|
||||||
|
var tag = r.tag(
|
||||||
|
this.x,
|
||||||
|
this.y[yy],
|
||||||
|
[xvalue, yvalue].join("\n"),
|
||||||
|
180,
|
||||||
|
24);
|
||||||
|
tag
|
||||||
|
.insertBefore(this)
|
||||||
|
.attr([{fill : '#fff'}, {fill: '#000'}]);
|
||||||
|
|
||||||
|
this.tags.push(tag);
|
||||||
|
}
|
||||||
|
}, function() {
|
||||||
|
this.tags && this.tags.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in a new issue