mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-26 08:42:41 +01:00
(stable) Promote 2019 Week 38
This commit is contained in:
commit
f0f352ffd0
32 changed files with 924 additions and 371 deletions
|
@ -141,7 +141,7 @@ return array(
|
|||
'rsrc/css/phui/phui-big-info-view.css' => '362ad37b',
|
||||
'rsrc/css/phui/phui-box.css' => '5ed3b8cb',
|
||||
'rsrc/css/phui/phui-bulk-editor.css' => '374d5e30',
|
||||
'rsrc/css/phui/phui-chart.css' => '10135a9d',
|
||||
'rsrc/css/phui/phui-chart.css' => '14df9ae3',
|
||||
'rsrc/css/phui/phui-cms.css' => '8c05c41e',
|
||||
'rsrc/css/phui/phui-comment-form.css' => '68a2d99a',
|
||||
'rsrc/css/phui/phui-comment-panel.css' => 'ec4e31c0',
|
||||
|
@ -390,7 +390,7 @@ return array(
|
|||
'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' => 'eec96de0',
|
||||
'rsrc/js/application/fact/Chart.js' => '52e3ff03',
|
||||
'rsrc/js/application/fact/ChartCurtainView.js' => '86954222',
|
||||
'rsrc/js/application/fact/ChartFunctionLabel.js' => '81de1dab',
|
||||
'rsrc/js/application/files/behavior-document-engine.js' => '243d6c22',
|
||||
|
@ -699,7 +699,7 @@ return array(
|
|||
'javelin-behavior-user-menu' => '60cd9241',
|
||||
'javelin-behavior-view-placeholder' => 'a9942052',
|
||||
'javelin-behavior-workflow' => '9623adc1',
|
||||
'javelin-chart' => 'eec96de0',
|
||||
'javelin-chart' => '52e3ff03',
|
||||
'javelin-chart-curtain-view' => '86954222',
|
||||
'javelin-chart-function-label' => '81de1dab',
|
||||
'javelin-color' => '78f811c9',
|
||||
|
@ -828,7 +828,7 @@ return array(
|
|||
'phui-calendar-day-css' => '9597d706',
|
||||
'phui-calendar-list-css' => 'ccd7e4e2',
|
||||
'phui-calendar-month-css' => 'cb758c42',
|
||||
'phui-chart-css' => '10135a9d',
|
||||
'phui-chart-css' => '14df9ae3',
|
||||
'phui-cms-css' => '8c05c41e',
|
||||
'phui-comment-form-css' => '68a2d99a',
|
||||
'phui-comment-panel-css' => 'ec4e31c0',
|
||||
|
@ -1369,6 +1369,12 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-fx',
|
||||
),
|
||||
'52e3ff03' => array(
|
||||
'phui-chart-css',
|
||||
'd3',
|
||||
'javelin-chart-curtain-view',
|
||||
'javelin-chart-function-label',
|
||||
),
|
||||
'541f81c3' => array(
|
||||
'javelin-install',
|
||||
),
|
||||
|
@ -2127,12 +2133,6 @@ return array(
|
|||
'phabricator-keyboard-shortcut',
|
||||
'javelin-stratcom',
|
||||
),
|
||||
'eec96de0' => array(
|
||||
'phui-chart-css',
|
||||
'd3',
|
||||
'javelin-chart-curtain-view',
|
||||
'javelin-chart-function-label',
|
||||
),
|
||||
'ef836bf2' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
|
|
|
@ -3104,6 +3104,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDefaultRequestExceptionHandler' => 'aphront/handler/PhabricatorDefaultRequestExceptionHandler.php',
|
||||
'PhabricatorDefaultSyntaxStyle' => 'infrastructure/syntax/PhabricatorDefaultSyntaxStyle.php',
|
||||
'PhabricatorDefaultUnlockEngine' => 'applications/system/engine/PhabricatorDefaultUnlockEngine.php',
|
||||
'PhabricatorDemoChartEngine' => 'applications/fact/engine/PhabricatorDemoChartEngine.php',
|
||||
'PhabricatorDestructibleCodex' => 'applications/system/codex/PhabricatorDestructibleCodex.php',
|
||||
'PhabricatorDestructibleCodexInterface' => 'applications/system/interface/PhabricatorDestructibleCodexInterface.php',
|
||||
'PhabricatorDestructibleInterface' => 'applications/system/interface/PhabricatorDestructibleInterface.php',
|
||||
|
@ -4223,6 +4224,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProfileMenuItemView' => 'applications/search/engine/PhabricatorProfileMenuItemView.php',
|
||||
'PhabricatorProfileMenuItemViewList' => 'applications/search/engine/PhabricatorProfileMenuItemViewList.php',
|
||||
'PhabricatorProject' => 'applications/project/storage/PhabricatorProject.php',
|
||||
'PhabricatorProjectActivityChartEngine' => 'applications/project/chart/PhabricatorProjectActivityChartEngine.php',
|
||||
'PhabricatorProjectAddHeraldAction' => 'applications/project/herald/PhabricatorProjectAddHeraldAction.php',
|
||||
'PhabricatorProjectApplication' => 'applications/project/application/PhabricatorProjectApplication.php',
|
||||
'PhabricatorProjectArchiveController' => 'applications/project/controller/PhabricatorProjectArchiveController.php',
|
||||
|
@ -4422,6 +4424,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectsWatchersSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsWatchersSearchEngineAttachment.php',
|
||||
'PhabricatorPronounSetting' => 'applications/settings/setting/PhabricatorPronounSetting.php',
|
||||
'PhabricatorProtocolLog' => 'infrastructure/log/PhabricatorProtocolLog.php',
|
||||
'PhabricatorPureChartFunction' => 'applications/fact/chart/PhabricatorPureChartFunction.php',
|
||||
'PhabricatorPygmentSetupCheck' => 'applications/config/check/PhabricatorPygmentSetupCheck.php',
|
||||
'PhabricatorQuery' => 'infrastructure/query/PhabricatorQuery.php',
|
||||
'PhabricatorQueryConstraint' => 'infrastructure/query/constraint/PhabricatorQueryConstraint.php',
|
||||
|
@ -8300,7 +8303,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorAccessLog' => 'Phobject',
|
||||
'PhabricatorAccessLogConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
'PhabricatorAccessibilitySetting' => 'PhabricatorSelectSetting',
|
||||
'PhabricatorAccumulateChartFunction' => 'PhabricatorChartFunction',
|
||||
'PhabricatorAccumulateChartFunction' => 'PhabricatorHigherOrderChartFunction',
|
||||
'PhabricatorActionListView' => 'AphrontTagView',
|
||||
'PhabricatorActionView' => 'AphrontView',
|
||||
'PhabricatorActivitySettingsPanel' => 'PhabricatorSettingsPanel',
|
||||
|
@ -9155,7 +9158,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorConpherenceWidgetVisibleSetting' => 'PhabricatorInternalSetting',
|
||||
'PhabricatorConsoleApplication' => 'PhabricatorApplication',
|
||||
'PhabricatorConsoleContentSource' => 'PhabricatorContentSource',
|
||||
'PhabricatorConstantChartFunction' => 'PhabricatorChartFunction',
|
||||
'PhabricatorConstantChartFunction' => 'PhabricatorPureChartFunction',
|
||||
'PhabricatorContactNumbersSettingsPanel' => 'PhabricatorSettingsPanel',
|
||||
'PhabricatorContentSource' => 'Phobject',
|
||||
'PhabricatorContentSourceModule' => 'PhabricatorConfigModule',
|
||||
|
@ -9167,7 +9170,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorCoreCreateTransaction' => 'PhabricatorCoreTransactionType',
|
||||
'PhabricatorCoreTransactionType' => 'PhabricatorModularTransactionType',
|
||||
'PhabricatorCoreVoidTransaction' => 'PhabricatorModularTransactionType',
|
||||
'PhabricatorCosChartFunction' => 'PhabricatorChartFunction',
|
||||
'PhabricatorCosChartFunction' => 'PhabricatorPureChartFunction',
|
||||
'PhabricatorCountFact' => 'PhabricatorFact',
|
||||
'PhabricatorCountdown' => array(
|
||||
'PhabricatorCountdownDAO',
|
||||
|
@ -9433,6 +9436,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDefaultRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
|
||||
'PhabricatorDefaultSyntaxStyle' => 'PhabricatorSyntaxStyle',
|
||||
'PhabricatorDefaultUnlockEngine' => 'PhabricatorUnlockEngine',
|
||||
'PhabricatorDemoChartEngine' => 'PhabricatorChartEngine',
|
||||
'PhabricatorDestructibleCodex' => 'Phobject',
|
||||
'PhabricatorDestructionEngine' => 'Phobject',
|
||||
'PhabricatorDestructionEngineExtension' => 'Phobject',
|
||||
|
@ -10068,7 +10072,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMarkupInterface',
|
||||
),
|
||||
'PhabricatorMarkupPreviewController' => 'PhabricatorController',
|
||||
'PhabricatorMaxChartFunction' => 'PhabricatorChartFunction',
|
||||
'PhabricatorMaxChartFunction' => 'PhabricatorPureChartFunction',
|
||||
'PhabricatorMemeEngine' => 'Phobject',
|
||||
'PhabricatorMemeRemarkupRule' => 'PhutilRemarkupRule',
|
||||
'PhabricatorMentionRemarkupRule' => 'PhutilRemarkupRule',
|
||||
|
@ -10135,7 +10139,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMetronome' => 'Phobject',
|
||||
'PhabricatorMetronomeTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorMetronomicTriggerClock' => 'PhabricatorTriggerClock',
|
||||
'PhabricatorMinChartFunction' => 'PhabricatorChartFunction',
|
||||
'PhabricatorMinChartFunction' => 'PhabricatorPureChartFunction',
|
||||
'PhabricatorModularTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'PhabricatorModularTransactionType' => 'Phobject',
|
||||
'PhabricatorMonogramDatasourceEngineExtension' => 'PhabricatorDatasourceEngineExtension',
|
||||
|
@ -10730,6 +10734,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSpacesInterface',
|
||||
'PhabricatorEditEngineSubtypeInterface',
|
||||
),
|
||||
'PhabricatorProjectActivityChartEngine' => 'PhabricatorChartEngine',
|
||||
'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction',
|
||||
'PhabricatorProjectApplication' => 'PhabricatorApplication',
|
||||
'PhabricatorProjectArchiveController' => 'PhabricatorProjectController',
|
||||
|
@ -10950,6 +10955,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorProjectsWatchersSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
|
||||
'PhabricatorPronounSetting' => 'PhabricatorSelectSetting',
|
||||
'PhabricatorProtocolLog' => 'Phobject',
|
||||
'PhabricatorPureChartFunction' => 'PhabricatorChartFunction',
|
||||
'PhabricatorPygmentSetupCheck' => 'PhabricatorSetupCheck',
|
||||
'PhabricatorQuery' => 'Phobject',
|
||||
'PhabricatorQueryConstraint' => 'Phobject',
|
||||
|
@ -11208,7 +11214,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPolicyInterface',
|
||||
),
|
||||
'PhabricatorSavedQueryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorScaleChartFunction' => 'PhabricatorChartFunction',
|
||||
'PhabricatorScaleChartFunction' => 'PhabricatorPureChartFunction',
|
||||
'PhabricatorScheduleTaskTriggerAction' => 'PhabricatorTriggerAction',
|
||||
'PhabricatorScopedEnv' => 'Phobject',
|
||||
'PhabricatorSearchAbstractDocument' => 'Phobject',
|
||||
|
@ -11299,12 +11305,12 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSetupIssue' => 'Phobject',
|
||||
'PhabricatorSetupIssueUIExample' => 'PhabricatorUIExample',
|
||||
'PhabricatorSetupIssueView' => 'AphrontView',
|
||||
'PhabricatorShiftChartFunction' => 'PhabricatorChartFunction',
|
||||
'PhabricatorShiftChartFunction' => 'PhabricatorPureChartFunction',
|
||||
'PhabricatorShortSite' => 'PhabricatorSite',
|
||||
'PhabricatorShowFiletreeSetting' => 'PhabricatorSelectSetting',
|
||||
'PhabricatorSignDocumentsUserLogType' => 'PhabricatorUserLogType',
|
||||
'PhabricatorSimpleEditType' => 'PhabricatorEditType',
|
||||
'PhabricatorSinChartFunction' => 'PhabricatorChartFunction',
|
||||
'PhabricatorSinChartFunction' => 'PhabricatorPureChartFunction',
|
||||
'PhabricatorSite' => 'AphrontSite',
|
||||
'PhabricatorSlackAuthProvider' => 'PhabricatorOAuth2AuthProvider',
|
||||
'PhabricatorSlowvoteApplication' => 'PhabricatorApplication',
|
||||
|
|
|
@ -22,15 +22,10 @@ final class PhabricatorFactApplication extends PhabricatorApplication {
|
|||
return self::GROUP_UTILITIES;
|
||||
}
|
||||
|
||||
public function isPrototype() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getRoutes() {
|
||||
return array(
|
||||
'/fact/' => array(
|
||||
'' => 'PhabricatorFactHomeController',
|
||||
'chart/' => 'PhabricatorFactChartController',
|
||||
'chart/(?P<chartKey>[^/]+)/(?:(?P<mode>draw)/)?' =>
|
||||
'PhabricatorFactChartController',
|
||||
'object/(?<phid>[^/]+)/' => 'PhabricatorFactObjectController',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorAccumulateChartFunction
|
||||
extends PhabricatorChartFunction {
|
||||
extends PhabricatorHigherOrderChartFunction {
|
||||
|
||||
const FUNCTIONKEY = 'accumulate';
|
||||
|
||||
|
@ -13,14 +13,6 @@ final class PhabricatorAccumulateChartFunction
|
|||
);
|
||||
}
|
||||
|
||||
public function getDomain() {
|
||||
return $this->getArgument('x')->getDomain();
|
||||
}
|
||||
|
||||
public function newInputValues(PhabricatorChartDataQuery $query) {
|
||||
return $this->getArgument('x')->newInputValues($query);
|
||||
}
|
||||
|
||||
public function evaluateFunction(array $xv) {
|
||||
// First, we're going to accumulate the underlying function. Then
|
||||
// we'll map the inputs through the accumulation.
|
||||
|
|
|
@ -59,13 +59,6 @@ abstract class PhabricatorChartDataset
|
|||
return $dataset;
|
||||
}
|
||||
|
||||
final public function toDictionary() {
|
||||
return array(
|
||||
'type' => $this->getDatasetTypeKey(),
|
||||
'functions' => mpull($this->getFunctions(), 'toDictionary'),
|
||||
);
|
||||
}
|
||||
|
||||
final public function getChartDisplayData(
|
||||
PhabricatorChartDataQuery $data_query) {
|
||||
return $this->newChartDisplayData($data_query);
|
||||
|
@ -75,4 +68,35 @@ abstract class PhabricatorChartDataset
|
|||
PhabricatorChartDataQuery $data_query);
|
||||
|
||||
|
||||
final public function getTabularDisplayData(
|
||||
PhabricatorChartDataQuery $data_query) {
|
||||
$results = array();
|
||||
|
||||
$functions = $this->getFunctions();
|
||||
foreach ($functions as $function) {
|
||||
$datapoints = $function->newDatapoints($data_query);
|
||||
|
||||
$refs = $function->getDataRefs(ipull($datapoints, 'x'));
|
||||
|
||||
foreach ($datapoints as $key => $point) {
|
||||
$x = $point['x'];
|
||||
|
||||
if (isset($refs[$x])) {
|
||||
$xrefs = $refs[$x];
|
||||
} else {
|
||||
$xrefs = array();
|
||||
}
|
||||
|
||||
$datapoints[$key]['refs'] = $xrefs;
|
||||
}
|
||||
|
||||
$results[] = array(
|
||||
'data' => $datapoints,
|
||||
);
|
||||
}
|
||||
|
||||
return id(new PhabricatorChartDisplayData())
|
||||
->setWireData($results);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -60,6 +60,10 @@ abstract class PhabricatorChartFunction
|
|||
return $this->functionLabel;
|
||||
}
|
||||
|
||||
final public function getKey() {
|
||||
return $this->getFunctionLabel()->getKey();
|
||||
}
|
||||
|
||||
final public static function newFromDictionary(array $map) {
|
||||
PhutilTypeSpec::checkMap(
|
||||
$map,
|
||||
|
@ -86,13 +90,6 @@ abstract class PhabricatorChartFunction
|
|||
return $function;
|
||||
}
|
||||
|
||||
public function toDictionary() {
|
||||
return array(
|
||||
'function' => $this->getFunctionKey(),
|
||||
'arguments' => $this->getArgumentParser()->getRawArguments(),
|
||||
);
|
||||
}
|
||||
|
||||
public function getSubfunctions() {
|
||||
$result = array();
|
||||
$result[] = $this;
|
||||
|
@ -180,6 +177,8 @@ abstract class PhabricatorChartFunction
|
|||
}
|
||||
|
||||
abstract public function evaluateFunction(array $xv);
|
||||
abstract public function getDataRefs(array $xv);
|
||||
abstract public function loadRefs(array $refs);
|
||||
|
||||
public function getDomain() {
|
||||
return null;
|
||||
|
|
|
@ -3,11 +3,21 @@
|
|||
final class PhabricatorChartFunctionLabel
|
||||
extends Phobject {
|
||||
|
||||
private $key;
|
||||
private $name;
|
||||
private $color;
|
||||
private $icon;
|
||||
private $fillColor;
|
||||
|
||||
public function setKey($key) {
|
||||
$this->key = $key;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getKey() {
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
public function setName($name) {
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
|
@ -46,6 +56,7 @@ final class PhabricatorChartFunctionLabel
|
|||
|
||||
public function toWireFormat() {
|
||||
return array(
|
||||
'key' => $this->getKey(),
|
||||
'name' => $this->getName(),
|
||||
'color' => $this->getColor(),
|
||||
'icon' => $this->getIcon(),
|
||||
|
|
|
@ -5,24 +5,187 @@ final class PhabricatorChartStackedAreaDataset
|
|||
|
||||
const DATASETKEY = 'stacked-area';
|
||||
|
||||
private $stacks;
|
||||
|
||||
public function setStacks(array $stacks) {
|
||||
$this->stacks = $stacks;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getStacks() {
|
||||
return $this->stacks;
|
||||
}
|
||||
|
||||
protected function newChartDisplayData(
|
||||
PhabricatorChartDataQuery $data_query) {
|
||||
|
||||
$functions = $this->getFunctions();
|
||||
$functions = mpull($functions, null, 'getKey');
|
||||
|
||||
$reversed_functions = array_reverse($functions, true);
|
||||
$stacks = $this->getStacks();
|
||||
|
||||
$function_points = array();
|
||||
foreach ($reversed_functions as $function_idx => $function) {
|
||||
$function_points[$function_idx] = array();
|
||||
if (!$stacks) {
|
||||
$stacks = array(
|
||||
array_reverse(array_keys($functions), true),
|
||||
);
|
||||
}
|
||||
|
||||
$datapoints = $function->newDatapoints($data_query);
|
||||
foreach ($datapoints as $point) {
|
||||
$x = $point['x'];
|
||||
$function_points[$function_idx][$x] = $point;
|
||||
$series = array();
|
||||
$raw_points = array();
|
||||
|
||||
foreach ($stacks as $stack) {
|
||||
$stack_functions = array_select_keys($functions, $stack);
|
||||
|
||||
$function_points = $this->getFunctionDatapoints(
|
||||
$data_query,
|
||||
$stack_functions);
|
||||
|
||||
$stack_points = $function_points;
|
||||
|
||||
$function_points = $this->getGeometry(
|
||||
$data_query,
|
||||
$function_points);
|
||||
|
||||
$baseline = array();
|
||||
foreach ($function_points as $function_idx => $points) {
|
||||
$bounds = array();
|
||||
foreach ($points as $x => $point) {
|
||||
if (!isset($baseline[$x])) {
|
||||
$baseline[$x] = 0;
|
||||
}
|
||||
|
||||
$y0 = $baseline[$x];
|
||||
$baseline[$x] += $point['y'];
|
||||
$y1 = $baseline[$x];
|
||||
|
||||
$bounds[] = array(
|
||||
'x' => $x,
|
||||
'y0' => $y0,
|
||||
'y1' => $y1,
|
||||
);
|
||||
|
||||
if (isset($stack_points[$function_idx][$x])) {
|
||||
$stack_points[$function_idx][$x]['y1'] = $y1;
|
||||
}
|
||||
}
|
||||
|
||||
$series[$function_idx] = $bounds;
|
||||
}
|
||||
|
||||
$raw_points += $stack_points;
|
||||
}
|
||||
|
||||
$series = array_select_keys($series, array_keys($functions));
|
||||
$series = array_values($series);
|
||||
|
||||
$raw_points = array_select_keys($raw_points, array_keys($functions));
|
||||
$raw_points = array_values($raw_points);
|
||||
|
||||
$range_min = null;
|
||||
$range_max = null;
|
||||
|
||||
foreach ($series as $geometry_list) {
|
||||
foreach ($geometry_list as $geometry_item) {
|
||||
$y0 = $geometry_item['y0'];
|
||||
$y1 = $geometry_item['y1'];
|
||||
|
||||
if ($range_min === null) {
|
||||
$range_min = $y0;
|
||||
}
|
||||
$range_min = min($range_min, $y0, $y1);
|
||||
|
||||
if ($range_max === null) {
|
||||
$range_max = $y1;
|
||||
}
|
||||
$range_max = max($range_max, $y0, $y1);
|
||||
}
|
||||
}
|
||||
|
||||
$raw_points = $function_points;
|
||||
// We're going to group multiple events into a single point if they have
|
||||
// X values that are very close to one another.
|
||||
//
|
||||
// If the Y values are also close to one another (these points are near
|
||||
// one another in a horizontal line), it can be hard to select any
|
||||
// individual point with the mouse.
|
||||
//
|
||||
// Even if the Y values are not close together (the points are on a
|
||||
// fairly steep slope up or down), it's usually better to be able to
|
||||
// mouse over a single point at the top or bottom of the slope and get
|
||||
// a summary of what's going on.
|
||||
|
||||
$domain_max = $data_query->getMaximumValue();
|
||||
$domain_min = $data_query->getMinimumValue();
|
||||
$resolution = ($domain_max - $domain_min) / 100;
|
||||
|
||||
$events = array();
|
||||
foreach ($raw_points as $function_idx => $points) {
|
||||
$event_list = array();
|
||||
|
||||
$event_group = array();
|
||||
$head_event = null;
|
||||
foreach ($points as $point) {
|
||||
$x = $point['x'];
|
||||
|
||||
if ($head_event === null) {
|
||||
// We don't have any points yet, so start a new group.
|
||||
$head_event = $x;
|
||||
$event_group[] = $point;
|
||||
} else if (($x - $head_event) <= $resolution) {
|
||||
// This point is close to the first point in this group, so
|
||||
// add it to the existing group.
|
||||
$event_group[] = $point;
|
||||
} else {
|
||||
// This point is not close to the first point in the group,
|
||||
// so create a new group.
|
||||
$event_list[] = $event_group;
|
||||
$head_event = $x;
|
||||
$event_group = array($point);
|
||||
}
|
||||
}
|
||||
|
||||
if ($event_group) {
|
||||
$event_list[] = $event_group;
|
||||
}
|
||||
|
||||
$event_spec = array();
|
||||
foreach ($event_list as $key => $event_points) {
|
||||
// NOTE: We're using the last point as the representative point so
|
||||
// that you can learn about a section of a chart by hovering over
|
||||
// the point to right of the section, which is more intuitive than
|
||||
// other points.
|
||||
$event = last($event_points);
|
||||
|
||||
$event = $event + array(
|
||||
'n' => count($event_points),
|
||||
);
|
||||
|
||||
$event_list[$key] = $event;
|
||||
}
|
||||
|
||||
$events[] = $event_list;
|
||||
}
|
||||
|
||||
$wire_labels = array();
|
||||
foreach ($functions as $function_key => $function) {
|
||||
$label = $function->getFunctionLabel();
|
||||
$wire_labels[] = $label->toWireFormat();
|
||||
}
|
||||
|
||||
$result = array(
|
||||
'type' => $this->getDatasetTypeKey(),
|
||||
'data' => $series,
|
||||
'events' => $events,
|
||||
'labels' => $wire_labels,
|
||||
);
|
||||
|
||||
return id(new PhabricatorChartDisplayData())
|
||||
->setWireData($result)
|
||||
->setRange(new PhabricatorChartInterval($range_min, $range_max));
|
||||
}
|
||||
|
||||
private function getAllXValuesAsMap(
|
||||
PhabricatorChartDataQuery $data_query,
|
||||
array $point_lists) {
|
||||
|
||||
// We need to define every function we're drawing at every point where
|
||||
// any of the functions we're drawing are defined. If we don't, we'll
|
||||
|
@ -31,17 +194,54 @@ final class PhabricatorChartStackedAreaDataset
|
|||
// stacking the functions on top of one another.
|
||||
|
||||
$must_define = array();
|
||||
foreach ($function_points as $function_idx => $points) {
|
||||
foreach ($points as $x => $point) {
|
||||
|
||||
$min = $data_query->getMinimumValue();
|
||||
$max = $data_query->getMaximumValue();
|
||||
$must_define[$max] = $max;
|
||||
$must_define[$min] = $min;
|
||||
|
||||
foreach ($point_lists as $point_list) {
|
||||
foreach ($point_list as $x => $point) {
|
||||
$must_define[$x] = $x;
|
||||
}
|
||||
}
|
||||
|
||||
ksort($must_define);
|
||||
|
||||
foreach ($reversed_functions as $function_idx => $function) {
|
||||
return $must_define;
|
||||
}
|
||||
|
||||
private function getFunctionDatapoints(
|
||||
PhabricatorChartDataQuery $data_query,
|
||||
array $functions) {
|
||||
|
||||
assert_instances_of($functions, 'PhabricatorChartFunction');
|
||||
|
||||
$points = array();
|
||||
foreach ($functions as $idx => $function) {
|
||||
$points[$idx] = array();
|
||||
|
||||
$datapoints = $function->newDatapoints($data_query);
|
||||
foreach ($datapoints as $point) {
|
||||
$x_value = $point['x'];
|
||||
$points[$idx][$x_value] = $point;
|
||||
}
|
||||
}
|
||||
|
||||
return $points;
|
||||
}
|
||||
|
||||
private function getGeometry(
|
||||
PhabricatorChartDataQuery $data_query,
|
||||
array $point_lists) {
|
||||
|
||||
$must_define = $this->getAllXValuesAsMap($data_query, $point_lists);
|
||||
|
||||
foreach ($point_lists as $idx => $points) {
|
||||
|
||||
$missing = array();
|
||||
foreach ($must_define as $x) {
|
||||
if (!isset($function_points[$function_idx][$x])) {
|
||||
if (!isset($points[$x])) {
|
||||
$missing[$x] = true;
|
||||
}
|
||||
}
|
||||
|
@ -50,8 +250,6 @@ final class PhabricatorChartStackedAreaDataset
|
|||
continue;
|
||||
}
|
||||
|
||||
$points = $function_points[$function_idx];
|
||||
|
||||
$values = array_keys($points);
|
||||
$cursor = -1;
|
||||
$length = count($values);
|
||||
|
@ -84,88 +282,19 @@ final class PhabricatorChartStackedAreaDataset
|
|||
$y = $ymin + (($ymax - $ymin) * $distance);
|
||||
} else {
|
||||
$xmin = $values[$cursor];
|
||||
$y = $function_points[$function_idx][$xmin]['y'];
|
||||
$y = $points[$xmin]['y'];
|
||||
}
|
||||
|
||||
$function_points[$function_idx][$x] = array(
|
||||
$point_lists[$idx][$x] = array(
|
||||
'x' => $x,
|
||||
'y' => $y,
|
||||
);
|
||||
}
|
||||
|
||||
ksort($function_points[$function_idx]);
|
||||
ksort($point_lists[$idx]);
|
||||
}
|
||||
|
||||
$range_min = null;
|
||||
$range_max = null;
|
||||
|
||||
$series = array();
|
||||
$baseline = array();
|
||||
foreach ($function_points as $function_idx => $points) {
|
||||
$below = idx($function_points, $function_idx - 1);
|
||||
|
||||
$bounds = array();
|
||||
foreach ($points as $x => $point) {
|
||||
if (!isset($baseline[$x])) {
|
||||
$baseline[$x] = 0;
|
||||
}
|
||||
|
||||
$y0 = $baseline[$x];
|
||||
$baseline[$x] += $point['y'];
|
||||
$y1 = $baseline[$x];
|
||||
|
||||
$bounds[] = array(
|
||||
'x' => $x,
|
||||
'y0' => $y0,
|
||||
'y1' => $y1,
|
||||
);
|
||||
|
||||
if (isset($raw_points[$function_idx][$x])) {
|
||||
$raw_points[$function_idx][$x]['y1'] = $y1;
|
||||
}
|
||||
|
||||
if ($range_min === null) {
|
||||
$range_min = $y0;
|
||||
}
|
||||
$range_min = min($range_min, $y0, $y1);
|
||||
|
||||
if ($range_max === null) {
|
||||
$range_max = $y1;
|
||||
}
|
||||
$range_max = max($range_max, $y0, $y1);
|
||||
}
|
||||
|
||||
$series[] = $bounds;
|
||||
}
|
||||
|
||||
$series = array_reverse($series);
|
||||
|
||||
$events = array();
|
||||
foreach ($raw_points as $function_idx => $points) {
|
||||
$event_list = array();
|
||||
foreach ($points as $point) {
|
||||
$event_list[] = $point;
|
||||
}
|
||||
$events[] = $event_list;
|
||||
}
|
||||
|
||||
$wire_labels = array();
|
||||
foreach ($functions as $function_key => $function) {
|
||||
$label = $function->getFunctionLabel();
|
||||
$wire_labels[] = $label->toWireFormat();
|
||||
}
|
||||
|
||||
$result = array(
|
||||
'type' => $this->getDatasetTypeKey(),
|
||||
'data' => $series,
|
||||
'events' => $events,
|
||||
'labels' => $wire_labels,
|
||||
);
|
||||
|
||||
return id(new PhabricatorChartDisplayData())
|
||||
->setWireData($result)
|
||||
->setRange(new PhabricatorChartInterval($range_min, $range_max));
|
||||
return $point_lists;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -70,4 +70,22 @@ final class PhabricatorComposeChartFunction
|
|||
return $yv;
|
||||
}
|
||||
|
||||
public function getDataRefs(array $xv) {
|
||||
// TODO: This is not entirely correct. The correct implementation would
|
||||
// map "x -> y" at each stage of composition and pull and aggregate all
|
||||
// the datapoint refs. In practice, we currently never compose functions
|
||||
// with a data function somewhere in the middle, so just grabbing the first
|
||||
// result is close enough.
|
||||
|
||||
// In the future, we may: notably, "x -> shift(-1 month) -> ..." to
|
||||
// generate a month-over-month overlay is a sensible operation which will
|
||||
// source data from the middle of a function composition.
|
||||
|
||||
foreach ($this->getFunctionArguments() as $function) {
|
||||
return $function->getDataRefs($xv);
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorConstantChartFunction
|
||||
extends PhabricatorChartFunction {
|
||||
extends PhabricatorPureChartFunction {
|
||||
|
||||
const FUNCTIONKEY = 'constant';
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCosChartFunction
|
||||
extends PhabricatorChartFunction {
|
||||
extends PhabricatorPureChartFunction {
|
||||
|
||||
const FUNCTIONKEY = 'cos';
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ final class PhabricatorFactChartFunction
|
|||
|
||||
private $fact;
|
||||
private $map;
|
||||
private $refs;
|
||||
|
||||
protected function newArguments() {
|
||||
$key_argument = $this->newArgument()
|
||||
|
@ -51,13 +52,15 @@ final class PhabricatorFactChartFunction
|
|||
|
||||
$data = queryfx_all(
|
||||
$conn,
|
||||
'SELECT value, epoch FROM %T WHERE %LA ORDER BY epoch ASC',
|
||||
'SELECT id, value, epoch FROM %T WHERE %LA ORDER BY epoch ASC',
|
||||
$table_name,
|
||||
$where);
|
||||
|
||||
$map = array();
|
||||
$refs = array();
|
||||
if ($data) {
|
||||
foreach ($data as $row) {
|
||||
$ref = (string)$row['id'];
|
||||
$value = (int)$row['value'];
|
||||
$epoch = (int)$row['epoch'];
|
||||
|
||||
|
@ -66,10 +69,17 @@ final class PhabricatorFactChartFunction
|
|||
}
|
||||
|
||||
$map[$epoch] += $value;
|
||||
|
||||
if (!isset($refs[$epoch])) {
|
||||
$refs[$epoch] = array();
|
||||
}
|
||||
|
||||
$refs[$epoch][] = $ref;
|
||||
}
|
||||
}
|
||||
|
||||
$this->map = $map;
|
||||
$this->refs = $refs;
|
||||
}
|
||||
|
||||
public function getDomain() {
|
||||
|
@ -99,4 +109,60 @@ final class PhabricatorFactChartFunction
|
|||
return $yv;
|
||||
}
|
||||
|
||||
public function getDataRefs(array $xv) {
|
||||
return array_select_keys($this->refs, $xv);
|
||||
}
|
||||
|
||||
public function loadRefs(array $refs) {
|
||||
$fact = $this->fact;
|
||||
|
||||
$datapoint_table = $fact->newDatapoint();
|
||||
$conn = $datapoint_table->establishConnection('r');
|
||||
|
||||
$dimension_table = new PhabricatorFactObjectDimension();
|
||||
|
||||
$where = array();
|
||||
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'p.id IN (%Ld)',
|
||||
$refs);
|
||||
|
||||
|
||||
$rows = queryfx_all(
|
||||
$conn,
|
||||
'SELECT
|
||||
p.id id,
|
||||
p.value,
|
||||
od.objectPHID objectPHID,
|
||||
dd.objectPHID dimensionPHID
|
||||
FROM %R p
|
||||
LEFT JOIN %R od ON od.id = p.objectID
|
||||
LEFT JOIN %R dd ON dd.id = p.dimensionID
|
||||
WHERE %LA',
|
||||
$datapoint_table,
|
||||
$dimension_table,
|
||||
$dimension_table,
|
||||
$where);
|
||||
$rows = ipull($rows, null, 'id');
|
||||
|
||||
$results = array();
|
||||
|
||||
foreach ($refs as $ref) {
|
||||
if (!isset($rows[$ref])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$row = $rows[$ref];
|
||||
|
||||
$results[$ref] = array(
|
||||
'objectPHID' => $row['objectPHID'],
|
||||
'dimensionPHID' => $row['dimensionPHID'],
|
||||
'value' => (float)$row['value'],
|
||||
);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,4 +32,38 @@ abstract class PhabricatorHigherOrderChartFunction
|
|||
return array_keys($map);
|
||||
}
|
||||
|
||||
public function getDataRefs(array $xv) {
|
||||
$refs = array();
|
||||
|
||||
foreach ($this->getFunctionArguments() as $function) {
|
||||
$function_refs = $function->getDataRefs($xv);
|
||||
|
||||
$function_refs = array_select_keys($function_refs, $xv);
|
||||
if (!$function_refs) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($function_refs as $x => $ref_list) {
|
||||
if (!isset($refs[$x])) {
|
||||
$refs[$x] = array();
|
||||
}
|
||||
foreach ($ref_list as $ref) {
|
||||
$refs[$x][] = $ref;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $refs;
|
||||
}
|
||||
|
||||
public function loadRefs(array $refs) {
|
||||
$results = array();
|
||||
|
||||
foreach ($this->getFunctionArguments() as $function) {
|
||||
$results += $function->loadRefs($refs);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,36 +1,27 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorMaxChartFunction
|
||||
extends PhabricatorChartFunction {
|
||||
extends PhabricatorPureChartFunction {
|
||||
|
||||
const FUNCTIONKEY = 'max';
|
||||
|
||||
protected function newArguments() {
|
||||
return array(
|
||||
$this->newArgument()
|
||||
->setName('x')
|
||||
->setType('function'),
|
||||
$this->newArgument()
|
||||
->setName('max')
|
||||
->setType('number'),
|
||||
);
|
||||
}
|
||||
|
||||
public function getDomain() {
|
||||
return $this->getArgument('x')->getDomain();
|
||||
}
|
||||
|
||||
public function newInputValues(PhabricatorChartDataQuery $query) {
|
||||
return $this->getArgument('x')->newInputValues($query);
|
||||
}
|
||||
|
||||
public function evaluateFunction(array $xv) {
|
||||
$yv = $this->getArgument('x')->evaluateFunction($xv);
|
||||
$max = $this->getArgument('max');
|
||||
|
||||
foreach ($yv as $k => $y) {
|
||||
if ($y > $max) {
|
||||
$yv[$k] = null;
|
||||
$yv = array();
|
||||
foreach ($xv as $x) {
|
||||
if ($x > $max) {
|
||||
$yv[] = null;
|
||||
} else {
|
||||
$yv[] = $x;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,36 +1,27 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorMinChartFunction
|
||||
extends PhabricatorChartFunction {
|
||||
extends PhabricatorPureChartFunction {
|
||||
|
||||
const FUNCTIONKEY = 'min';
|
||||
|
||||
protected function newArguments() {
|
||||
return array(
|
||||
$this->newArgument()
|
||||
->setName('x')
|
||||
->setType('function'),
|
||||
$this->newArgument()
|
||||
->setName('min')
|
||||
->setType('number'),
|
||||
);
|
||||
}
|
||||
|
||||
public function getDomain() {
|
||||
return $this->getArgument('x')->getDomain();
|
||||
}
|
||||
|
||||
public function newInputValues(PhabricatorChartDataQuery $query) {
|
||||
return $this->getArgument('x')->newInputValues($query);
|
||||
}
|
||||
|
||||
public function evaluateFunction(array $xv) {
|
||||
$yv = $this->getArgument('x')->evaluateFunction($xv);
|
||||
$min = $this->getArgument('min');
|
||||
|
||||
foreach ($yv as $k => $y) {
|
||||
if ($y < $min) {
|
||||
$yv[$k] = null;
|
||||
$yv = array();
|
||||
foreach ($xv as $x) {
|
||||
if ($x < $min) {
|
||||
$yv[] = null;
|
||||
} else {
|
||||
$yv[] = $x;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
14
src/applications/fact/chart/PhabricatorPureChartFunction.php
Normal file
14
src/applications/fact/chart/PhabricatorPureChartFunction.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorPureChartFunction
|
||||
extends PhabricatorChartFunction {
|
||||
|
||||
public function getDataRefs(array $xv) {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function loadRefs(array $refs) {
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorScaleChartFunction
|
||||
extends PhabricatorChartFunction {
|
||||
extends PhabricatorPureChartFunction {
|
||||
|
||||
const FUNCTIONKEY = 'scale';
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorShiftChartFunction
|
||||
extends PhabricatorChartFunction {
|
||||
extends PhabricatorPureChartFunction {
|
||||
|
||||
const FUNCTIONKEY = 'shift';
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorSinChartFunction
|
||||
extends PhabricatorChartFunction {
|
||||
extends PhabricatorPureChartFunction {
|
||||
|
||||
const FUNCTIONKEY = 'sin';
|
||||
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFactChartController extends PhabricatorFactController {
|
||||
final class PhabricatorFactChartController
|
||||
extends PhabricatorFactController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
|
||||
$chart_key = $request->getURIData('chartKey');
|
||||
if ($chart_key === null) {
|
||||
return $this->newDemoChart();
|
||||
if (!$chart_key) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$engine = id(new PhabricatorChartRenderingEngine())
|
||||
|
@ -24,15 +25,24 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
|
|||
$mode = $request->getURIData('mode');
|
||||
$is_draw_mode = ($mode === 'draw');
|
||||
|
||||
// TODO: For now, always pull the data. We'll throw it away if we're just
|
||||
// drawing the frame, but this makes errors easier to debug.
|
||||
$chart_data = $engine->newChartData();
|
||||
$want_data = $is_draw_mode;
|
||||
|
||||
if ($is_draw_mode) {
|
||||
return id(new AphrontAjaxResponse())->setContent($chart_data);
|
||||
// In developer mode, always pull the data in the main request. We'll
|
||||
// throw it away if we're just drawing the chart frame, but this currently
|
||||
// makes errors quite a bit easier to debug.
|
||||
if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) {
|
||||
$want_data = true;
|
||||
}
|
||||
|
||||
if ($want_data) {
|
||||
$chart_data = $engine->newChartData();
|
||||
if ($is_draw_mode) {
|
||||
return id(new AphrontAjaxResponse())->setContent($chart_data);
|
||||
}
|
||||
}
|
||||
|
||||
$chart_view = $engine->newChartView();
|
||||
|
||||
return $this->newChartResponse($chart_view);
|
||||
}
|
||||
|
||||
|
@ -50,58 +60,10 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
|
|||
return $this->newPage()
|
||||
->setTitle($title)
|
||||
->setCrumbs($crumbs)
|
||||
->appendChild($box);
|
||||
}
|
||||
|
||||
private function newDemoChart() {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$argvs = array();
|
||||
|
||||
$argvs[] = array('fact', 'tasks.count.create');
|
||||
|
||||
$argvs[] = array('constant', 360);
|
||||
|
||||
$argvs[] = array('fact', 'tasks.open-count.create');
|
||||
|
||||
$argvs[] = array(
|
||||
'sum',
|
||||
array(
|
||||
'accumulate',
|
||||
array('fact', 'tasks.count.create'),
|
||||
),
|
||||
array(
|
||||
'accumulate',
|
||||
array('fact', 'tasks.open-count.create'),
|
||||
),
|
||||
);
|
||||
|
||||
$argvs[] = array(
|
||||
'compose',
|
||||
array('scale', 0.001),
|
||||
array('cos'),
|
||||
array('scale', 100),
|
||||
array('shift', 800),
|
||||
);
|
||||
|
||||
$datasets = array();
|
||||
foreach ($argvs as $argv) {
|
||||
$datasets[] = PhabricatorChartDataset::newFromDictionary(
|
||||
->appendChild(
|
||||
array(
|
||||
'function' => $argv,
|
||||
$box,
|
||||
));
|
||||
}
|
||||
|
||||
$chart = id(new PhabricatorFactChart())
|
||||
->setDatasets($datasets);
|
||||
|
||||
$engine = id(new PhabricatorChartRenderingEngine())
|
||||
->setViewer($viewer)
|
||||
->setChart($chart);
|
||||
|
||||
$chart = $engine->getStoredChart();
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($chart->getURI());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,59 +1,20 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFactHomeController extends PhabricatorFactController {
|
||||
final class PhabricatorFactHomeController
|
||||
extends PhabricatorFactController {
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$uri = new PhutilURI('/fact/chart/');
|
||||
$uri->replaceQueryParam('y1', $request->getStr('y1'));
|
||||
return id(new AphrontRedirectResponse())->setURI($uri);
|
||||
}
|
||||
$chart = id(new PhabricatorDemoChartEngine())
|
||||
->setViewer($viewer)
|
||||
->newStoredChart();
|
||||
|
||||
$chart_form = $this->buildChartForm();
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(pht('Home'));
|
||||
|
||||
$title = pht('Facts');
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle($title)
|
||||
->setCrumbs($crumbs)
|
||||
->appendChild(
|
||||
array(
|
||||
$chart_form,
|
||||
));
|
||||
}
|
||||
|
||||
private function buildChartForm() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$specs = PhabricatorFact::getAllFacts();
|
||||
$options = mpull($specs, 'getName', 'getKey');
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->appendChild(
|
||||
id(new AphrontFormSelectControl())
|
||||
->setLabel(pht('Y-Axis'))
|
||||
->setName('y1')
|
||||
->setOptions($options))
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue(pht('Plot Chart')));
|
||||
|
||||
$panel = new PHUIObjectBoxView();
|
||||
$panel->setForm($form);
|
||||
$panel->setHeaderText(pht('Plot Chart'));
|
||||
|
||||
return $panel;
|
||||
return id(new AphrontRedirectResponse())->setURI($chart->getURI());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ abstract class PhabricatorChartEngine
|
|||
|
||||
abstract protected function newChart(PhabricatorFactChart $chart, array $map);
|
||||
|
||||
final public function buildChartPanel() {
|
||||
final public function newStoredChart() {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$parameters = $this->getEngineParameters();
|
||||
|
@ -76,7 +76,11 @@ abstract class PhabricatorChartEngine
|
|||
->setViewer($viewer)
|
||||
->setChart($chart);
|
||||
|
||||
$chart = $rendering_engine->getStoredChart();
|
||||
return $rendering_engine->getStoredChart();
|
||||
}
|
||||
|
||||
final public function buildChartPanel() {
|
||||
$chart = $this->newStoredChart();
|
||||
|
||||
$panel_type = id(new PhabricatorDashboardChartPanelType())
|
||||
->getPanelTypeKey();
|
||||
|
@ -91,7 +95,7 @@ abstract class PhabricatorChartEngine
|
|||
final protected function newFunction($name /* , ... */) {
|
||||
$argv = func_get_args();
|
||||
return id(new PhabricatorComposeChartFunction())
|
||||
->setArguments(array($argv));
|
||||
->setArguments($argv);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -109,7 +109,146 @@ final class PhabricatorChartRenderingEngine
|
|||
return $chart_view;
|
||||
}
|
||||
|
||||
public function newTabularView() {
|
||||
$viewer = $this->getViewer();
|
||||
$tabular_data = $this->newTabularData();
|
||||
|
||||
$ref_keys = array();
|
||||
foreach ($tabular_data['datasets'] as $tabular_dataset) {
|
||||
foreach ($tabular_dataset as $function) {
|
||||
foreach ($function['data'] as $point) {
|
||||
foreach ($point['refs'] as $ref) {
|
||||
$ref_keys[$ref] = $ref;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$chart = $this->getStoredChart();
|
||||
|
||||
$ref_map = array();
|
||||
foreach ($chart->getDatasets() as $dataset) {
|
||||
foreach ($dataset->getFunctions() as $function) {
|
||||
// If we aren't looking for anything else, bail out.
|
||||
if (!$ref_keys) {
|
||||
break 2;
|
||||
}
|
||||
|
||||
$function_refs = $function->loadRefs($ref_keys);
|
||||
|
||||
$ref_map += $function_refs;
|
||||
|
||||
// Remove the ref keys that we found data for from the list of keys
|
||||
// we are looking for. If any function gives us data for a given ref,
|
||||
// that's satisfactory.
|
||||
foreach ($function_refs as $ref_key => $ref_data) {
|
||||
unset($ref_keys[$ref_key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$phids = array();
|
||||
foreach ($ref_map as $ref => $ref_data) {
|
||||
if (isset($ref_data['objectPHID'])) {
|
||||
$phids[] = $ref_data['objectPHID'];
|
||||
}
|
||||
}
|
||||
|
||||
$handles = $viewer->loadHandles($phids);
|
||||
|
||||
$tabular_view = array();
|
||||
foreach ($tabular_data['datasets'] as $tabular_data) {
|
||||
foreach ($tabular_data as $function) {
|
||||
$rows = array();
|
||||
foreach ($function['data'] as $point) {
|
||||
$ref_views = array();
|
||||
|
||||
$xv = date('Y-m-d h:i:s', $point['x']);
|
||||
$yv = $point['y'];
|
||||
|
||||
$point_refs = array();
|
||||
foreach ($point['refs'] as $ref) {
|
||||
if (!isset($ref_map[$ref])) {
|
||||
continue;
|
||||
}
|
||||
$point_refs[$ref] = $ref_map[$ref];
|
||||
}
|
||||
|
||||
if (!$point_refs) {
|
||||
$rows[] = array(
|
||||
$xv,
|
||||
$yv,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
} else {
|
||||
foreach ($point_refs as $ref => $ref_data) {
|
||||
$ref_value = $ref_data['value'];
|
||||
$ref_link = $handles[$ref_data['objectPHID']]
|
||||
->renderLink();
|
||||
|
||||
$view_uri = urisprintf(
|
||||
'/fact/object/%s/',
|
||||
$ref_data['objectPHID']);
|
||||
|
||||
$ref_button = id(new PHUIButtonView())
|
||||
->setIcon('fa-table')
|
||||
->setTag('a')
|
||||
->setColor('grey')
|
||||
->setHref($view_uri)
|
||||
->setText(pht('View Data'));
|
||||
|
||||
$rows[] = array(
|
||||
$xv,
|
||||
$yv,
|
||||
$ref_value,
|
||||
$ref_link,
|
||||
$ref_button,
|
||||
);
|
||||
|
||||
$xv = null;
|
||||
$yv = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$table = id(new AphrontTableView($rows))
|
||||
->setHeaders(
|
||||
array(
|
||||
pht('X'),
|
||||
pht('Y'),
|
||||
pht('Raw'),
|
||||
pht('Refs'),
|
||||
null,
|
||||
))
|
||||
->setColumnClasses(
|
||||
array(
|
||||
'n',
|
||||
'n',
|
||||
'n',
|
||||
'wide',
|
||||
null,
|
||||
));
|
||||
|
||||
$tabular_view[] = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Function'))
|
||||
->setTable($table);
|
||||
}
|
||||
}
|
||||
|
||||
return $tabular_view;
|
||||
}
|
||||
|
||||
public function newChartData() {
|
||||
return $this->newWireData(false);
|
||||
}
|
||||
|
||||
public function newTabularData() {
|
||||
return $this->newWireData(true);
|
||||
}
|
||||
|
||||
private function newWireData($is_tabular) {
|
||||
$chart = $this->getStoredChart();
|
||||
$chart_key = $chart->getChartKey();
|
||||
|
||||
|
@ -151,7 +290,11 @@ final class PhabricatorChartRenderingEngine
|
|||
$wire_datasets = array();
|
||||
$ranges = array();
|
||||
foreach ($datasets as $dataset) {
|
||||
$display_data = $dataset->getChartDisplayData($data_query);
|
||||
if ($is_tabular) {
|
||||
$display_data = $dataset->getTabularDisplayData($data_query);
|
||||
} else {
|
||||
$display_data = $dataset->getChartDisplayData($data_query);
|
||||
}
|
||||
|
||||
$ranges[] = $display_data->getRange();
|
||||
$wire_datasets[] = $display_data->getWireData();
|
||||
|
|
46
src/applications/fact/engine/PhabricatorDemoChartEngine.php
Normal file
46
src/applications/fact/engine/PhabricatorDemoChartEngine.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDemoChartEngine
|
||||
extends PhabricatorChartEngine {
|
||||
|
||||
const CHARTENGINEKEY = 'facts.demo';
|
||||
|
||||
protected function newChart(PhabricatorFactChart $chart, array $map) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$functions = array();
|
||||
|
||||
$function = $this->newFunction(
|
||||
array('scale', 0.0001),
|
||||
array('cos'),
|
||||
array('scale', 128),
|
||||
array('shift', 256));
|
||||
|
||||
$function->getFunctionLabel()
|
||||
->setKey('cos-x')
|
||||
->setName(pht('cos(x)'))
|
||||
->setColor('rgba(0, 200, 0, 1)')
|
||||
->setFillColor('rgba(0, 200, 0, 0.15)');
|
||||
|
||||
$functions[] = $function;
|
||||
|
||||
$function = $this->newFunction(
|
||||
array('constant', 345));
|
||||
|
||||
$function->getFunctionLabel()
|
||||
->setKey('constant-345')
|
||||
->setName(pht('constant(345)'))
|
||||
->setColor('rgba(0, 0, 200, 1)')
|
||||
->setFillColor('rgba(0, 0, 200, 0.15)');
|
||||
|
||||
$functions[] = $function;
|
||||
|
||||
$datasets = array();
|
||||
|
||||
$datasets[] = id(new PhabricatorChartStackedAreaDataset())
|
||||
->setFunctions($functions);
|
||||
|
||||
$chart->attachDatasets($datasets);
|
||||
}
|
||||
|
||||
}
|
|
@ -35,8 +35,12 @@ final class ManiphestReportController extends ManiphestController {
|
|||
$nav->addLabel(pht('Open Tasks'));
|
||||
$nav->addFilter('user', pht('By User'));
|
||||
$nav->addFilter('project', pht('By Project'));
|
||||
$nav->addLabel(pht('Burnup'));
|
||||
$nav->addFilter('burn', pht('Burnup Rate'));
|
||||
|
||||
$class = 'PhabricatorFactApplication';
|
||||
if (PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
|
||||
$nav->addLabel(pht('Burnup'));
|
||||
$nav->addFilter('burn', pht('Burnup Rate'));
|
||||
}
|
||||
|
||||
$this->view = $nav->selectFilter($this->view, 'user');
|
||||
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorProjectActivityChartEngine
|
||||
extends PhabricatorChartEngine {
|
||||
|
||||
const CHARTENGINEKEY = 'project.activity';
|
||||
|
||||
public function setProjects(array $projects) {
|
||||
assert_instances_of($projects, 'PhabricatorProject');
|
||||
$project_phids = mpull($projects, 'getPHID');
|
||||
return $this->setEngineParameter('projectPHIDs', $project_phids);
|
||||
}
|
||||
|
||||
protected function newChart(PhabricatorFactChart $chart, array $map) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$map = $map + array(
|
||||
'projectPHIDs' => array(),
|
||||
);
|
||||
|
||||
if ($map['projectPHIDs']) {
|
||||
$projects = id(new PhabricatorProjectQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs($map['projectPHIDs'])
|
||||
->execute();
|
||||
$project_phids = mpull($projects, 'getPHID');
|
||||
} else {
|
||||
$project_phids = array();
|
||||
}
|
||||
|
||||
$project_phid = head($project_phids);
|
||||
|
||||
$functions = array();
|
||||
$stacks = array();
|
||||
|
||||
$function = $this->newFunction(
|
||||
array(
|
||||
'accumulate',
|
||||
array(
|
||||
'compose',
|
||||
array('fact', 'tasks.open-count.assign.project', $project_phid),
|
||||
array('min', 0),
|
||||
),
|
||||
));
|
||||
|
||||
$function->getFunctionLabel()
|
||||
->setKey('moved-in')
|
||||
->setName(pht('Tasks Moved Into Project'))
|
||||
->setColor('rgba(128, 128, 200, 1)')
|
||||
->setFillColor('rgba(128, 128, 200, 0.15)');
|
||||
|
||||
$functions[] = $function;
|
||||
|
||||
$function = $this->newFunction(
|
||||
array(
|
||||
'accumulate',
|
||||
array(
|
||||
'compose',
|
||||
array('fact', 'tasks.open-count.status.project', $project_phid),
|
||||
array('min', 0),
|
||||
),
|
||||
));
|
||||
|
||||
$function->getFunctionLabel()
|
||||
->setKey('reopened')
|
||||
->setName(pht('Tasks Reopened'))
|
||||
->setColor('rgba(128, 128, 200, 1)')
|
||||
->setFillColor('rgba(128, 128, 200, 0.15)');
|
||||
|
||||
$functions[] = $function;
|
||||
|
||||
$function = $this->newFunction(
|
||||
array(
|
||||
'accumulate',
|
||||
array('fact', 'tasks.open-count.create.project', $project_phid),
|
||||
));
|
||||
|
||||
$function->getFunctionLabel()
|
||||
->setKey('created')
|
||||
->setName(pht('Tasks Created'))
|
||||
->setColor('rgba(0, 0, 200, 1)')
|
||||
->setFillColor('rgba(0, 0, 200, 0.15)');
|
||||
|
||||
$functions[] = $function;
|
||||
|
||||
$function = $this->newFunction(
|
||||
array(
|
||||
'accumulate',
|
||||
array(
|
||||
'compose',
|
||||
array('fact', 'tasks.open-count.status.project', $project_phid),
|
||||
array('max', 0),
|
||||
),
|
||||
));
|
||||
|
||||
$function->getFunctionLabel()
|
||||
->setKey('closed')
|
||||
->setName(pht('Tasks Closed'))
|
||||
->setColor('rgba(0, 200, 0, 1)')
|
||||
->setFillColor('rgba(0, 200, 0, 0.15)');
|
||||
|
||||
$functions[] = $function;
|
||||
|
||||
$function = $this->newFunction(
|
||||
array(
|
||||
'accumulate',
|
||||
array(
|
||||
'compose',
|
||||
array('fact', 'tasks.open-count.assign.project', $project_phid),
|
||||
array('max', 0),
|
||||
),
|
||||
));
|
||||
|
||||
$function->getFunctionLabel()
|
||||
->setKey('moved-out')
|
||||
->setName(pht('Tasks Moved Out of Project'))
|
||||
->setColor('rgba(128, 200, 128, 1)')
|
||||
->setFillColor('rgba(128, 200, 128, 0.15)');
|
||||
|
||||
$functions[] = $function;
|
||||
|
||||
$stacks[] = array('created', 'reopened', 'moved-in');
|
||||
$stacks[] = array('closed', 'moved-out');
|
||||
|
||||
$datasets = array();
|
||||
|
||||
$dataset = id(new PhabricatorChartStackedAreaDataset())
|
||||
->setFunctions($functions)
|
||||
->setStacks($stacks);
|
||||
|
||||
$datasets[] = $dataset;
|
||||
$chart->attachDatasets($datasets);
|
||||
}
|
||||
|
||||
}
|
|
@ -30,97 +30,78 @@ final class PhabricatorProjectBurndownChartEngine
|
|||
|
||||
$functions = array();
|
||||
if ($project_phids) {
|
||||
foreach ($project_phids as $project_phid) {
|
||||
$function = $this->newFunction(
|
||||
'min',
|
||||
$open_function = $this->newFunction(
|
||||
array(
|
||||
'accumulate',
|
||||
array(
|
||||
'accumulate',
|
||||
array('fact', 'tasks.open-count.assign.project', $project_phid),
|
||||
'sum',
|
||||
$this->newFactSum(
|
||||
'tasks.open-count.create.project', $project_phids),
|
||||
$this->newFactSum(
|
||||
'tasks.open-count.status.project', $project_phids),
|
||||
$this->newFactSum(
|
||||
'tasks.open-count.assign.project', $project_phids),
|
||||
),
|
||||
0);
|
||||
));
|
||||
|
||||
$function->getFunctionLabel()
|
||||
->setName(pht('Tasks Moved Into Project'))
|
||||
->setColor('rgba(0, 200, 200, 1)')
|
||||
->setFillColor('rgba(0, 200, 200, 0.15)');
|
||||
|
||||
$functions[] = $function;
|
||||
|
||||
$function = $this->newFunction(
|
||||
'min',
|
||||
array(
|
||||
'accumulate',
|
||||
array('fact', 'tasks.open-count.status.project', $project_phid),
|
||||
),
|
||||
0);
|
||||
|
||||
$function->getFunctionLabel()
|
||||
->setName(pht('Tasks Reopened'))
|
||||
->setColor('rgba(200, 0, 200, 1)')
|
||||
->setFillColor('rgba(200, 0, 200, 0.15)');
|
||||
|
||||
$functions[] = $function;
|
||||
|
||||
$function = $this->newFunction(
|
||||
'sum',
|
||||
array(
|
||||
'accumulate',
|
||||
array('fact', 'tasks.open-count.create.project', $project_phid),
|
||||
),
|
||||
array(
|
||||
'max',
|
||||
array(
|
||||
'accumulate',
|
||||
array('fact', 'tasks.open-count.status.project', $project_phid),
|
||||
),
|
||||
0,
|
||||
),
|
||||
array(
|
||||
'max',
|
||||
array(
|
||||
'accumulate',
|
||||
array('fact', 'tasks.open-count.assign.project', $project_phid),
|
||||
),
|
||||
0,
|
||||
));
|
||||
|
||||
$function->getFunctionLabel()
|
||||
->setName(pht('Tasks Created'))
|
||||
->setColor('rgba(0, 0, 200, 1)')
|
||||
->setFillColor('rgba(0, 0, 200, 0.15)');
|
||||
|
||||
$functions[] = $function;
|
||||
}
|
||||
$closed_function = $this->newFunction(
|
||||
array(
|
||||
'accumulate',
|
||||
$this->newFactSum('tasks.open-count.status.project', $project_phids),
|
||||
));
|
||||
} else {
|
||||
$function = $this->newFunction(
|
||||
'accumulate',
|
||||
array('fact', 'tasks.open-count.create'));
|
||||
$open_function = $this->newFunction(
|
||||
array(
|
||||
'accumulate',
|
||||
array('fact', 'tasks.open-count.create'),
|
||||
));
|
||||
|
||||
$function->getFunctionLabel()
|
||||
->setName(pht('Tasks Created'))
|
||||
->setColor('rgba(0, 200, 200, 1)')
|
||||
->setFillColor('rgba(0, 200, 200, 0.15)');
|
||||
|
||||
$functions[] = $function;
|
||||
|
||||
$function = $this->newFunction(
|
||||
'accumulate',
|
||||
array('fact', 'tasks.open-count.status'));
|
||||
|
||||
$function->getFunctionLabel()
|
||||
->setName(pht('Tasks Closed / Reopened'))
|
||||
->setColor('rgba(200, 0, 200, 1)')
|
||||
->setFillColor('rgba(200, 0, 200, 0.15)');
|
||||
|
||||
$functions[] = $function;
|
||||
$closed_function = $this->newFunction(
|
||||
array(
|
||||
'accumulate',
|
||||
array('fact', 'tasks.open-count.status'),
|
||||
));
|
||||
}
|
||||
|
||||
$open_function->getFunctionLabel()
|
||||
->setKey('open')
|
||||
->setName(pht('Open Tasks'))
|
||||
->setColor('rgba(0, 0, 200, 1)')
|
||||
->setFillColor('rgba(0, 0, 200, 0.15)');
|
||||
|
||||
$closed_function->getFunctionLabel()
|
||||
->setKey('closed')
|
||||
->setName(pht('Closed Tasks'))
|
||||
->setColor('rgba(0, 200, 0, 1)')
|
||||
->setFillColor('rgba(0, 200, 0, 0.15)');
|
||||
|
||||
$datasets = array();
|
||||
|
||||
$datasets[] = id(new PhabricatorChartStackedAreaDataset())
|
||||
->setFunctions($functions);
|
||||
$dataset = id(new PhabricatorChartStackedAreaDataset())
|
||||
->setFunctions(
|
||||
array(
|
||||
$open_function,
|
||||
$closed_function,
|
||||
))
|
||||
->setStacks(
|
||||
array(
|
||||
array('open'),
|
||||
array('closed'),
|
||||
));
|
||||
|
||||
$datasets[] = $dataset;
|
||||
$chart->attachDatasets($datasets);
|
||||
}
|
||||
|
||||
private function newFactSum($fact_key, array $phids) {
|
||||
$result = array();
|
||||
$result[] = 'sum';
|
||||
|
||||
foreach ($phids as $phid) {
|
||||
$result[] = array('fact', $fact_key, $phid);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -44,10 +44,24 @@ final class PhabricatorProjectReportsController
|
|||
->setParentPanelPHIDs(array())
|
||||
->renderPanel();
|
||||
|
||||
$activity_panel = id(new PhabricatorProjectActivityChartEngine())
|
||||
->setViewer($viewer)
|
||||
->setProjects(array($project))
|
||||
->buildChartPanel();
|
||||
|
||||
$activity_panel->setName(pht('%s: Activity', $project->getName()));
|
||||
|
||||
$activity_view = id(new PhabricatorDashboardPanelRenderingEngine())
|
||||
->setViewer($viewer)
|
||||
->setPanel($activity_panel)
|
||||
->setParentPanelPHIDs(array())
|
||||
->renderPanel();
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setFooter(
|
||||
array(
|
||||
$chart_view,
|
||||
$activity_view,
|
||||
));
|
||||
|
||||
return $this->newPage()
|
||||
|
|
|
@ -34,6 +34,11 @@ final class PhabricatorProjectReportsProfileMenuItem
|
|||
return false;
|
||||
}
|
||||
|
||||
$class = 'PhabricatorFactApplication';
|
||||
if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@ if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
|||
|
||||
$real_address = your_custom_parsing_function($raw_header);
|
||||
|
||||
$_SERVER['REMOTE_ADDR'] = $raw_header;
|
||||
$_SERVER['REMOTE_ADDR'] = $real_address;
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -36,16 +36,17 @@
|
|||
}
|
||||
|
||||
.chart .point {
|
||||
fill: {$lightblue};
|
||||
fill: #ffffff;
|
||||
stroke: {$blue};
|
||||
stroke-width: 1px;
|
||||
stroke-width: 2px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chart-tooltip {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 120px;
|
||||
height: 16px;
|
||||
overflow: hidden;
|
||||
padding: 2px;
|
||||
background: {$lightbluebackground};
|
||||
|
|
37
webroot/rsrc/js/application/fact/Chart.js
vendored
37
webroot/rsrc/js/application/fact/Chart.js
vendored
|
@ -133,18 +133,33 @@ JX.install('Chart', {
|
|||
},
|
||||
|
||||
_newStackedArea: function(g, dataset, x, y, div, curtain) {
|
||||
var ii;
|
||||
|
||||
var to_date = JX.bind(this, this._newDate);
|
||||
|
||||
var area = d3.area()
|
||||
.x(function(d) { return x(to_date(d.x)); })
|
||||
.y0(function(d) { return y(d.y0); })
|
||||
.y0(function(d) {
|
||||
// When the area is positive, draw it above the X axis. When the area
|
||||
// is negative, draw it below the X axis. We currently avoid having
|
||||
// functions which cross the X axis by clever construction.
|
||||
if (d.y0 >= 0 && d.y1 >= 0) {
|
||||
return y(d.y0);
|
||||
}
|
||||
|
||||
if (d.y0 <= 0 && d.y1 <= 0) {
|
||||
return y(d.y0);
|
||||
}
|
||||
|
||||
return y(0);
|
||||
})
|
||||
.y1(function(d) { return y(d.y1); });
|
||||
|
||||
var line = d3.line()
|
||||
.x(function(d) { return x(to_date(d.x)); })
|
||||
.y(function(d) { return y(d.y1); });
|
||||
|
||||
for (var ii = 0; ii < dataset.data.length; ii++) {
|
||||
for (ii = 0; ii < dataset.data.length; ii++) {
|
||||
var label = new JX.ChartFunctionLabel(dataset.labels[ii]);
|
||||
|
||||
var fill_color = label.getFillColor() || label.getColor();
|
||||
|
@ -160,6 +175,11 @@ JX.install('Chart', {
|
|||
.style('stroke', stroke_color)
|
||||
.attr('d', line(dataset.data[ii]));
|
||||
|
||||
curtain.addFunctionLabel(label);
|
||||
}
|
||||
|
||||
// Now that we've drawn all the areas and lines, draw the dots.
|
||||
for (ii = 0; ii < dataset.data.length; ii++) {
|
||||
g.selectAll('dot')
|
||||
.data(dataset.events[ii])
|
||||
.enter()
|
||||
|
@ -178,8 +198,16 @@ JX.install('Chart', {
|
|||
|
||||
var d_d = dd.getDate();
|
||||
|
||||
var y = parseInt(d.y1);
|
||||
|
||||
var label = d.n + ' Points';
|
||||
|
||||
var view =
|
||||
d_y + '-' + d_m + '-' + d_d + ': ' + y + '<br />' +
|
||||
label;
|
||||
|
||||
div
|
||||
.html(d_y + '-' + d_m + '-' + d_d + ': ' + d.y1)
|
||||
.html(view)
|
||||
.style('opacity', 0.9)
|
||||
.style('left', (d3.event.pageX - 60) + 'px')
|
||||
.style('top', (d3.event.pageY - 38) + 'px');
|
||||
|
@ -187,9 +215,8 @@ JX.install('Chart', {
|
|||
.on('mouseout', function() {
|
||||
div.style('opacity', 0);
|
||||
});
|
||||
|
||||
curtain.addFunctionLabel(label);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_newDate: function(epoch) {
|
||||
|
|
Loading…
Reference in a new issue