@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+90 -20
+10 -10
resources/celerity/map.php
··· 141 141 'rsrc/css/phui/phui-big-info-view.css' => '362ad37b', 142 142 'rsrc/css/phui/phui-box.css' => '5ed3b8cb', 143 143 'rsrc/css/phui/phui-bulk-editor.css' => '374d5e30', 144 - 'rsrc/css/phui/phui-chart.css' => '10135a9d', 144 + 'rsrc/css/phui/phui-chart.css' => '14df9ae3', 145 145 'rsrc/css/phui/phui-cms.css' => '8c05c41e', 146 146 'rsrc/css/phui/phui-comment-form.css' => '68a2d99a', 147 147 'rsrc/css/phui/phui-comment-panel.css' => 'ec4e31c0', ··· 390 390 'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'c715c123', 391 391 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '6a85bc5a', 392 392 'rsrc/js/application/drydock/drydock-live-operation-status.js' => '47a0728b', 393 - 'rsrc/js/application/fact/Chart.js' => 'eec96de0', 393 + 'rsrc/js/application/fact/Chart.js' => 'ddb9dd1f', 394 394 'rsrc/js/application/fact/ChartCurtainView.js' => '86954222', 395 395 'rsrc/js/application/fact/ChartFunctionLabel.js' => '81de1dab', 396 396 'rsrc/js/application/files/behavior-document-engine.js' => '243d6c22', ··· 699 699 'javelin-behavior-user-menu' => '60cd9241', 700 700 'javelin-behavior-view-placeholder' => 'a9942052', 701 701 'javelin-behavior-workflow' => '9623adc1', 702 - 'javelin-chart' => 'eec96de0', 702 + 'javelin-chart' => 'ddb9dd1f', 703 703 'javelin-chart-curtain-view' => '86954222', 704 704 'javelin-chart-function-label' => '81de1dab', 705 705 'javelin-color' => '78f811c9', ··· 828 828 'phui-calendar-day-css' => '9597d706', 829 829 'phui-calendar-list-css' => 'ccd7e4e2', 830 830 'phui-calendar-month-css' => 'cb758c42', 831 - 'phui-chart-css' => '10135a9d', 831 + 'phui-chart-css' => '14df9ae3', 832 832 'phui-cms-css' => '8c05c41e', 833 833 'phui-comment-form-css' => '68a2d99a', 834 834 'phui-comment-panel-css' => 'ec4e31c0', ··· 2066 2066 'javelin-uri', 2067 2067 'phabricator-notification', 2068 2068 ), 2069 + 'ddb9dd1f' => array( 2070 + 'phui-chart-css', 2071 + 'd3', 2072 + 'javelin-chart-curtain-view', 2073 + 'javelin-chart-function-label', 2074 + ), 2069 2075 'dfa1d313' => array( 2070 2076 'javelin-behavior', 2071 2077 'javelin-dom', ··· 2126 2132 'javelin-behavior', 2127 2133 'phabricator-keyboard-shortcut', 2128 2134 'javelin-stratcom', 2129 - ), 2130 - 'eec96de0' => array( 2131 - 'phui-chart-css', 2132 - 'd3', 2133 - 'javelin-chart-curtain-view', 2134 - 'javelin-chart-function-label', 2135 2135 ), 2136 2136 'ef836bf2' => array( 2137 2137 'javelin-behavior',
+58 -3
src/applications/fact/chart/PhabricatorChartStackedAreaDataset.php
··· 17 17 18 18 $datapoints = $function->newDatapoints($data_query); 19 19 foreach ($datapoints as $point) { 20 - $x = $point['x']; 21 - $function_points[$function_idx][$x] = $point; 20 + $x_value = $point['x']; 21 + $function_points[$function_idx][$x_value] = $point; 22 22 } 23 23 } 24 24 ··· 140 140 141 141 $series = array_reverse($series); 142 142 143 + // We're going to group multiple events into a single point if they have 144 + // X values that are very close to one another. 145 + // 146 + // If the Y values are also close to one another (these points are near 147 + // one another in a horizontal line), it can be hard to select any 148 + // individual point with the mouse. 149 + // 150 + // Even if the Y values are not close together (the points are on a 151 + // fairly steep slope up or down), it's usually better to be able to 152 + // mouse over a single point at the top or bottom of the slope and get 153 + // a summary of what's going on. 154 + 155 + $domain_max = $data_query->getMaximumValue(); 156 + $domain_min = $data_query->getMinimumValue(); 157 + $resolution = ($domain_max - $domain_min) / 100; 158 + 143 159 $events = array(); 144 160 foreach ($raw_points as $function_idx => $points) { 145 161 $event_list = array(); 162 + 163 + $event_group = array(); 164 + $head_event = null; 146 165 foreach ($points as $point) { 147 - $event_list[] = $point; 166 + $x = $point['x']; 167 + 168 + if ($head_event === null) { 169 + // We don't have any points yet, so start a new group. 170 + $head_event = $x; 171 + $event_group[] = $point; 172 + } else if (($x - $head_event) <= $resolution) { 173 + // This point is close to the first point in this group, so 174 + // add it to the existing group. 175 + $event_group[] = $point; 176 + } else { 177 + // This point is not close to the first point in the group, 178 + // so create a new group. 179 + $event_list[] = $event_group; 180 + $head_event = $x; 181 + $event_group = array($point); 182 + } 148 183 } 184 + 185 + if ($event_group) { 186 + $event_list[] = $event_group; 187 + } 188 + 189 + $event_spec = array(); 190 + foreach ($event_list as $key => $event_points) { 191 + // NOTE: We're using the last point as the representative point so 192 + // that you can learn about a section of a chart by hovering over 193 + // the point to right of the section, which is more intuitive than 194 + // other points. 195 + $event = last($event_points); 196 + 197 + $event = $event + array( 198 + 'n' => count($event_points), 199 + ); 200 + 201 + $event_list[$key] = $event; 202 + } 203 + 149 204 $events[] = $event_list; 150 205 } 151 206
+4 -3
webroot/rsrc/css/phui/phui-chart.css
··· 36 36 } 37 37 38 38 .chart .point { 39 - fill: {$lightblue}; 39 + fill: #ffffff; 40 40 stroke: {$blue}; 41 - stroke-width: 1px; 41 + stroke-width: 2px; 42 + position: relative; 43 + cursor: pointer; 42 44 } 43 45 44 46 .chart-tooltip { 45 47 position: absolute; 46 48 text-align: center; 47 49 width: 120px; 48 - height: 16px; 49 50 overflow: hidden; 50 51 padding: 2px; 51 52 background: {$lightbluebackground};
+18 -4
webroot/rsrc/js/application/fact/Chart.js
··· 133 133 }, 134 134 135 135 _newStackedArea: function(g, dataset, x, y, div, curtain) { 136 + var ii; 137 + 136 138 var to_date = JX.bind(this, this._newDate); 137 139 138 140 var area = d3.area() ··· 144 146 .x(function(d) { return x(to_date(d.x)); }) 145 147 .y(function(d) { return y(d.y1); }); 146 148 147 - for (var ii = 0; ii < dataset.data.length; ii++) { 149 + for (ii = 0; ii < dataset.data.length; ii++) { 148 150 var label = new JX.ChartFunctionLabel(dataset.labels[ii]); 149 151 150 152 var fill_color = label.getFillColor() || label.getColor(); ··· 160 162 .style('stroke', stroke_color) 161 163 .attr('d', line(dataset.data[ii])); 162 164 165 + curtain.addFunctionLabel(label); 166 + } 167 + 168 + // Now that we've drawn all the areas and lines, draw the dots. 169 + for (ii = 0; ii < dataset.data.length; ii++) { 163 170 g.selectAll('dot') 164 171 .data(dataset.events[ii]) 165 172 .enter() ··· 178 185 179 186 var d_d = dd.getDate(); 180 187 188 + var y = parseInt(d.y1); 189 + 190 + var label = d.n + ' Points'; 191 + 192 + var view = 193 + d_y + '-' + d_m + '-' + d_d + ': ' + y + '<br />' + 194 + label; 195 + 181 196 div 182 - .html(d_y + '-' + d_m + '-' + d_d + ': ' + d.y1) 197 + .html(view) 183 198 .style('opacity', 0.9) 184 199 .style('left', (d3.event.pageX - 60) + 'px') 185 200 .style('top', (d3.event.pageY - 38) + 'px'); ··· 187 202 .on('mouseout', function() { 188 203 div.style('opacity', 0); 189 204 }); 205 + } 190 206 191 - curtain.addFunctionLabel(label); 192 - } 193 207 }, 194 208 195 209 _newDate: function(epoch) {