diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -141,7 +141,7 @@ '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 @@ '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 @@ '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 @@ '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 @@ '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 @@ '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', diff --git a/src/applications/fact/chart/PhabricatorChartStackedAreaDataset.php b/src/applications/fact/chart/PhabricatorChartStackedAreaDataset.php --- a/src/applications/fact/chart/PhabricatorChartStackedAreaDataset.php +++ b/src/applications/fact/chart/PhabricatorChartStackedAreaDataset.php @@ -17,8 +17,8 @@ $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 @@ $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; } diff --git a/webroot/rsrc/css/phui/phui-chart.css b/webroot/rsrc/css/phui/phui-chart.css --- a/webroot/rsrc/css/phui/phui-chart.css +++ b/webroot/rsrc/css/phui/phui-chart.css @@ -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}; diff --git a/webroot/rsrc/js/application/fact/Chart.js b/webroot/rsrc/js/application/fact/Chart.js --- a/webroot/rsrc/js/application/fact/Chart.js +++ b/webroot/rsrc/js/application/fact/Chart.js @@ -133,6 +133,8 @@ }, _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 @@ .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 @@ .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 @@ var d_d = dd.getDate(); + var y = parseInt(d.y1); + + var label = d.n + ' Points'; + + var view = + d_y + '-' + d_m + '-' + d_d + ': ' + y + '
' + + 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 @@ .on('mouseout', function() { div.style('opacity', 0); }); - - curtain.addFunctionLabel(label); } + }, _newDate: function(epoch) {