1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-26 00:32: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:
epriestley 2019-05-09 08:58:11 -07:00
parent c5a4dea8cf
commit f529abf900
4 changed files with 90 additions and 20 deletions

View file

@ -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',

View file

@ -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;
}

View file

@ -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};

View file

@ -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) {