mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 06:42:42 +01:00
In stacked area charts, group nearby points so they don't overlap
Summary: Ref T13279. We currently draw a point on the chart for each datapoint, but this leads to many overlapping circles. Instead, aggregate the raw points into display points ("events") at the end. Test Plan: Viewed a stacked area chart with many points, saw a more palatable number of drawn dots. Subscribers: yelirekim Maniphest Tasks: T13279 Differential Revision: https://secure.phabricator.com/D20814
This commit is contained in:
parent
c5a4dea8cf
commit
f529abf900
4 changed files with 90 additions and 20 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' => 'ddb9dd1f',
|
||||
'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' => 'ddb9dd1f',
|
||||
'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',
|
||||
|
@ -2066,6 +2066,12 @@ return array(
|
|||
'javelin-uri',
|
||||
'phabricator-notification',
|
||||
),
|
||||
'ddb9dd1f' => array(
|
||||
'phui-chart-css',
|
||||
'd3',
|
||||
'javelin-chart-curtain-view',
|
||||
'javelin-chart-function-label',
|
||||
),
|
||||
'dfa1d313' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
|
@ -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',
|
||||
|
|
|
@ -17,8 +17,8 @@ final class PhabricatorChartStackedAreaDataset
|
|||
|
||||
$datapoints = $function->newDatapoints($data_query);
|
||||
foreach ($datapoints as $point) {
|
||||
$x = $point['x'];
|
||||
$function_points[$function_idx][$x] = $point;
|
||||
$x_value = $point['x'];
|
||||
$function_points[$function_idx][$x_value] = $point;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,12 +140,67 @@ final class PhabricatorChartStackedAreaDataset
|
|||
|
||||
$series = array_reverse($series);
|
||||
|
||||
// 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) {
|
||||
$event_list[] = $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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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};
|
||||
|
|
22
webroot/rsrc/js/application/fact/Chart.js
vendored
22
webroot/rsrc/js/application/fact/Chart.js
vendored
|
@ -133,6 +133,8 @@ 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()
|
||||
|
@ -144,7 +146,7 @@ JX.install('Chart', {
|
|||
.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 +162,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 +185,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 +202,8 @@ JX.install('Chart', {
|
|||
.on('mouseout', function() {
|
||||
div.style('opacity', 0);
|
||||
});
|
||||
|
||||
curtain.addFunctionLabel(label);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_newDate: function(epoch) {
|
||||
|
|
Loading…
Reference in a new issue