@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.

Label important data on charts

Summary:
Ref T13279. Adds client-side support for rendering function labels on charts, then labels every function as important data.

Works okay on mobile, although I'm not planning to target mobile terribly heavily for v0.

Test Plan: {F6438860}

Reviewers: amckinley

Reviewed By: amckinley

Subscribers: yelirekim

Maniphest Tasks: T13279

Differential Revision: https://secure.phabricator.com/D20500

+201 -29
+11 -8
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' => '7853a69b', 144 + 'rsrc/css/phui/phui-chart.css' => '10135a9d', 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', ··· 389 389 'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'c715c123', 390 390 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '6a85bc5a', 391 391 'rsrc/js/application/drydock/drydock-live-operation-status.js' => '47a0728b', 392 - 'rsrc/js/application/fact/Chart.js' => 'a3516cea', 392 + 'rsrc/js/application/fact/Chart.js' => 'b88a227d', 393 + 'rsrc/js/application/fact/ChartCurtainView.js' => 'd10a3c25', 393 394 'rsrc/js/application/files/behavior-document-engine.js' => '243d6c22', 394 395 'rsrc/js/application/files/behavior-icon-composer.js' => '38a6cedb', 395 396 'rsrc/js/application/files/behavior-launch-icon-composer.js' => 'a17b84f1', ··· 696 697 'javelin-behavior-user-menu' => '60cd9241', 697 698 'javelin-behavior-view-placeholder' => 'a9942052', 698 699 'javelin-behavior-workflow' => '9623adc1', 699 - 'javelin-chart' => 'a3516cea', 700 + 'javelin-chart' => 'b88a227d', 701 + 'javelin-chart-curtain-view' => 'd10a3c25', 700 702 'javelin-color' => '78f811c9', 701 703 'javelin-cookie' => '05d290ef', 702 704 'javelin-diffusion-locate-file-source' => '94243d89', ··· 823 825 'phui-calendar-day-css' => '9597d706', 824 826 'phui-calendar-list-css' => 'ccd7e4e2', 825 827 'phui-calendar-month-css' => 'cb758c42', 826 - 'phui-chart-css' => '7853a69b', 828 + 'phui-chart-css' => '10135a9d', 827 829 'phui-cms-css' => '8c05c41e', 828 830 'phui-comment-form-css' => '68a2d99a', 829 831 'phui-comment-panel-css' => 'ec4e31c0', ··· 1767 1769 'javelin-workflow', 1768 1770 'phabricator-draggable-list', 1769 1771 ), 1770 - 'a3516cea' => array( 1771 - 'phui-chart-css', 1772 - 'd3', 1773 - ), 1774 1772 'a4356cde' => array( 1775 1773 'javelin-install', 1776 1774 'javelin-dom', ··· 1934 1932 'javelin-workflow', 1935 1933 'javelin-dom', 1936 1934 'phabricator-draggable-list', 1935 + ), 1936 + 'b88a227d' => array( 1937 + 'phui-chart-css', 1938 + 'd3', 1939 + 'javelin-chart-curtain-view', 1937 1940 ), 1938 1941 'b9109f8f' => array( 1939 1942 'javelin-behavior',
+2 -4
src/applications/fact/engine/PhabricatorChartRenderingEngine.php
··· 94 94 'div', 95 95 array( 96 96 'id' => $chart_node_id, 97 - 'style' => 'background: #ffffff; '. 98 - 'height: 480px; ', 99 - ), 100 - ''); 97 + 'class' => 'chart-hardpoint', 98 + )); 101 99 102 100 $data_uri = urisprintf('/fact/chart/%s/draw/', $chart_key); 103 101
+41
webroot/rsrc/css/phui/phui-chart.css
··· 10 10 } 11 11 12 12 .chart .axis text { 13 + font: {$basefont}; 13 14 fill: {$darkgreytext}; 14 15 } 15 16 ··· 52 53 border-radius: 8px; 53 54 pointer-events: none; 54 55 } 56 + 57 + .chart-hardpoint { 58 + min-height: 480px; 59 + overflow: hidden; 60 + position: relative; 61 + } 62 + 63 + .device-desktop .chart-container { 64 + position: absolute; 65 + bottom: 0; 66 + top: 0; 67 + left: 0; 68 + right: 300px; 69 + } 70 + 71 + .device .chart-container { 72 + min-height: 480px; 73 + } 74 + 75 + .device-desktop .chart-curtain { 76 + width: 300px; 77 + position: absolute; 78 + bottom: 0; 79 + top: 0; 80 + right: 0; 81 + } 82 + 83 + .chart-function-label-list { 84 + background: {$lightbluebackground}; 85 + border: 1px solid {$lightblueborder}; 86 + padding: 8px 12px; 87 + } 88 + 89 + .device-desktop .chart-function-label-list { 90 + margin-top: 23px; 91 + } 92 + 93 + .chart-function-label-list-item .phui-icon-view { 94 + margin-right: 8px; 95 + }
+62 -17
webroot/rsrc/js/application/fact/Chart.js
··· 2 2 * @provides javelin-chart 3 3 * @requires phui-chart-css 4 4 * d3 5 + * javelin-chart-curtain-view 5 6 */ 6 7 JX.install('Chart', { 7 8 ··· 14 15 members: { 15 16 _rootNode: null, 16 17 _data: null, 18 + _chartContainerNode: null, 19 + _curtain: null, 17 20 18 21 setData: function(blob) { 19 22 this._data = blob; ··· 26 29 } 27 30 28 31 var hardpoint = this._rootNode; 32 + var curtain = this._getCurtain(); 33 + var container_node = this._getChartContainerNode(); 34 + 35 + var content = [ 36 + container_node, 37 + curtain.getNode(), 38 + ]; 39 + 40 + JX.DOM.setContent(hardpoint, content); 29 41 30 42 // Remove the old chart (if one exists) before drawing the new chart. 31 - JX.DOM.setContent(hardpoint, []); 43 + JX.DOM.setContent(container_node, []); 32 44 33 - var viewport = JX.Vector.getDim(hardpoint); 45 + var viewport = JX.Vector.getDim(container_node); 34 46 var config = this._data; 35 47 36 48 function css_function(n) { 37 49 return n + '(' + JX.$A(arguments).slice(1).join(', ') + ')'; 38 50 } 39 51 40 - var padding = { 41 - top: 24, 42 - left: 48, 43 - bottom: 48, 44 - right: 32 45 - }; 52 + var padding = {}; 53 + if (JX.Device.isDesktop()) { 54 + padding = { 55 + top: 24, 56 + left: 48, 57 + bottom: 48, 58 + right: 12 59 + }; 60 + } else { 61 + padding = { 62 + top: 12, 63 + left: 36, 64 + bottom: 24, 65 + right: 4 66 + }; 67 + } 46 68 47 69 var size = { 48 70 frameWidth: viewport.x, ··· 61 83 var xAxis = d3.axisBottom(x); 62 84 var yAxis = d3.axisLeft(y); 63 85 64 - var svg = d3.select('#' + hardpoint.id).append('svg') 86 + var svg = d3.select(container_node).append('svg') 65 87 .attr('width', size.frameWidth) 66 88 .attr('height', size.frameHeight) 67 89 .attr('class', 'chart'); 68 90 69 91 var g = svg.append('g') 70 - .attr( 71 - 'transform', 72 - css_function('translate', padding.left, padding.top)); 92 + .attr( 93 + 'transform', 94 + css_function('translate', padding.left, padding.top)); 73 95 74 96 g.append('rect') 75 - .attr('class', 'inner') 76 - .attr('width', size.width) 77 - .attr('height', size.height); 97 + .attr('class', 'inner') 98 + .attr('width', size.width) 99 + .attr('height', size.height); 78 100 79 101 x.domain([this._newDate(config.xMin), this._newDate(config.xMax)]); 80 102 y.domain([config.yMin, config.yMax]); ··· 84 106 .attr('class', 'chart-tooltip') 85 107 .style('opacity', 0); 86 108 109 + curtain.reset(); 110 + 87 111 for (var idx = 0; idx < config.datasets.length; idx++) { 88 112 var dataset = config.datasets[idx]; 89 113 90 114 switch (dataset.type) { 91 115 case 'stacked-area': 92 - this._newStackedArea(g, dataset, x, y, div); 116 + this._newStackedArea(g, dataset, x, y, div, curtain); 93 117 break; 94 118 } 95 119 } 120 + 121 + curtain.redraw(); 96 122 97 123 g.append('g') 98 124 .attr('class', 'x axis') ··· 105 131 .call(yAxis); 106 132 }, 107 133 108 - _newStackedArea: function(g, dataset, x, y, div) { 134 + _newStackedArea: function(g, dataset, x, y, div, curtain) { 109 135 var to_date = JX.bind(this, this._newDate); 110 136 111 137 var area = d3.area() ··· 155 181 div.style('opacity', 0); 156 182 }); 157 183 184 + curtain.addFunctionLabel('Important Data'); 158 185 } 159 186 }, 160 187 161 188 _newDate: function(epoch) { 162 189 return new Date(epoch * 1000); 190 + }, 191 + 192 + _getCurtain: function() { 193 + if (!this._curtain) { 194 + this._curtain = new JX.ChartCurtainView(); 195 + } 196 + return this._curtain; 197 + }, 198 + 199 + _getChartContainerNode: function() { 200 + if (!this._chartContainerNode) { 201 + var attrs = { 202 + className: 'chart-container' 203 + }; 204 + 205 + this._chartContainerNode = JX.$N('div', attrs); 206 + } 207 + return this._chartContainerNode; 163 208 } 164 209 165 210 }
+85
webroot/rsrc/js/application/fact/ChartCurtainView.js
··· 1 + /** 2 + * @provides javelin-chart-curtain-view 3 + */ 4 + JX.install('ChartCurtainView', { 5 + 6 + construct: function() { 7 + this._labels = []; 8 + }, 9 + 10 + members: { 11 + _node: null, 12 + _labels: null, 13 + _labelsNode: null, 14 + 15 + getNode: function() { 16 + if (!this._node) { 17 + var attr = { 18 + className: 'chart-curtain' 19 + }; 20 + 21 + this._node = JX.$N('div', attr); 22 + } 23 + return this._node; 24 + }, 25 + 26 + reset: function() { 27 + this._labels = []; 28 + }, 29 + 30 + addFunctionLabel: function(label) { 31 + this._labels.push(label); 32 + return this; 33 + }, 34 + 35 + redraw: function() { 36 + var content = [this._getFunctionLabelsNode()]; 37 + 38 + JX.DOM.setContent(this.getNode(), content); 39 + return this; 40 + }, 41 + 42 + _getFunctionLabelsNode: function() { 43 + if (!this._labels.length) { 44 + return null; 45 + } 46 + 47 + if (!this._labelsNode) { 48 + var list_attrs = { 49 + className: 'chart-function-label-list' 50 + }; 51 + 52 + var labels = JX.$N('ul', list_attrs); 53 + 54 + var items = []; 55 + for (var ii = 0; ii < this._labels.length; ii++) { 56 + items.push(this._newFunctionLabelItem(this._labels[ii])); 57 + } 58 + 59 + JX.DOM.setContent(labels, items); 60 + 61 + this._labelsNode = labels; 62 + } 63 + 64 + return this._labelsNode; 65 + }, 66 + 67 + _newFunctionLabelItem: function(item) { 68 + var item_attrs = { 69 + className: 'chart-function-label-list-item' 70 + }; 71 + 72 + var icon = new JX.PHUIXIconView() 73 + .setIcon('fa-circle'); 74 + 75 + var content = [ 76 + icon.getNode(), 77 + item 78 + ]; 79 + 80 + return JX.$N('li', item_attrs, content); 81 + } 82 + 83 + } 84 + 85 + });