1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-22 21:40:55 +01:00

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
This commit is contained in:
epriestley 2019-05-08 06:50:23 -07:00
parent 81456db559
commit c6052b41a6
5 changed files with 201 additions and 29 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' => '7853a69b',
'rsrc/css/phui/phui-chart.css' => '10135a9d',
'rsrc/css/phui/phui-cms.css' => '8c05c41e',
'rsrc/css/phui/phui-comment-form.css' => '68a2d99a',
'rsrc/css/phui/phui-comment-panel.css' => 'ec4e31c0',
@ -389,7 +389,8 @@ 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' => 'a3516cea',
'rsrc/js/application/fact/Chart.js' => 'b88a227d',
'rsrc/js/application/fact/ChartCurtainView.js' => 'd10a3c25',
'rsrc/js/application/files/behavior-document-engine.js' => '243d6c22',
'rsrc/js/application/files/behavior-icon-composer.js' => '38a6cedb',
'rsrc/js/application/files/behavior-launch-icon-composer.js' => 'a17b84f1',
@ -696,7 +697,8 @@ return array(
'javelin-behavior-user-menu' => '60cd9241',
'javelin-behavior-view-placeholder' => 'a9942052',
'javelin-behavior-workflow' => '9623adc1',
'javelin-chart' => 'a3516cea',
'javelin-chart' => 'b88a227d',
'javelin-chart-curtain-view' => 'd10a3c25',
'javelin-color' => '78f811c9',
'javelin-cookie' => '05d290ef',
'javelin-diffusion-locate-file-source' => '94243d89',
@ -823,7 +825,7 @@ return array(
'phui-calendar-day-css' => '9597d706',
'phui-calendar-list-css' => 'ccd7e4e2',
'phui-calendar-month-css' => 'cb758c42',
'phui-chart-css' => '7853a69b',
'phui-chart-css' => '10135a9d',
'phui-cms-css' => '8c05c41e',
'phui-comment-form-css' => '68a2d99a',
'phui-comment-panel-css' => 'ec4e31c0',
@ -1767,10 +1769,6 @@ return array(
'javelin-workflow',
'phabricator-draggable-list',
),
'a3516cea' => array(
'phui-chart-css',
'd3',
),
'a4356cde' => array(
'javelin-install',
'javelin-dom',
@ -1935,6 +1933,11 @@ return array(
'javelin-dom',
'phabricator-draggable-list',
),
'b88a227d' => array(
'phui-chart-css',
'd3',
'javelin-chart-curtain-view',
),
'b9109f8f' => array(
'javelin-behavior',
'javelin-uri',

View file

@ -94,10 +94,8 @@ final class PhabricatorChartRenderingEngine
'div',
array(
'id' => $chart_node_id,
'style' => 'background: #ffffff; '.
'height: 480px; ',
),
'');
'class' => 'chart-hardpoint',
));
$data_uri = urisprintf('/fact/chart/%s/draw/', $chart_key);

View file

@ -10,6 +10,7 @@
}
.chart .axis text {
font: {$basefont};
fill: {$darkgreytext};
}
@ -52,3 +53,43 @@
border-radius: 8px;
pointer-events: none;
}
.chart-hardpoint {
min-height: 480px;
overflow: hidden;
position: relative;
}
.device-desktop .chart-container {
position: absolute;
bottom: 0;
top: 0;
left: 0;
right: 300px;
}
.device .chart-container {
min-height: 480px;
}
.device-desktop .chart-curtain {
width: 300px;
position: absolute;
bottom: 0;
top: 0;
right: 0;
}
.chart-function-label-list {
background: {$lightbluebackground};
border: 1px solid {$lightblueborder};
padding: 8px 12px;
}
.device-desktop .chart-function-label-list {
margin-top: 23px;
}
.chart-function-label-list-item .phui-icon-view {
margin-right: 8px;
}

View file

@ -2,6 +2,7 @@
* @provides javelin-chart
* @requires phui-chart-css
* d3
* javelin-chart-curtain-view
*/
JX.install('Chart', {
@ -14,6 +15,8 @@ JX.install('Chart', {
members: {
_rootNode: null,
_data: null,
_chartContainerNode: null,
_curtain: null,
setData: function(blob) {
this._data = blob;
@ -26,23 +29,42 @@ JX.install('Chart', {
}
var hardpoint = this._rootNode;
var curtain = this._getCurtain();
var container_node = this._getChartContainerNode();
var content = [
container_node,
curtain.getNode(),
];
JX.DOM.setContent(hardpoint, content);
// Remove the old chart (if one exists) before drawing the new chart.
JX.DOM.setContent(hardpoint, []);
JX.DOM.setContent(container_node, []);
var viewport = JX.Vector.getDim(hardpoint);
var viewport = JX.Vector.getDim(container_node);
var config = this._data;
function css_function(n) {
return n + '(' + JX.$A(arguments).slice(1).join(', ') + ')';
}
var padding = {
top: 24,
left: 48,
bottom: 48,
right: 32
};
var padding = {};
if (JX.Device.isDesktop()) {
padding = {
top: 24,
left: 48,
bottom: 48,
right: 12
};
} else {
padding = {
top: 12,
left: 36,
bottom: 24,
right: 4
};
}
var size = {
frameWidth: viewport.x,
@ -61,20 +83,20 @@ JX.install('Chart', {
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
var svg = d3.select('#' + hardpoint.id).append('svg')
var svg = d3.select(container_node).append('svg')
.attr('width', size.frameWidth)
.attr('height', size.frameHeight)
.attr('class', 'chart');
var g = svg.append('g')
.attr(
'transform',
css_function('translate', padding.left, padding.top));
.attr(
'transform',
css_function('translate', padding.left, padding.top));
g.append('rect')
.attr('class', 'inner')
.attr('width', size.width)
.attr('height', size.height);
.attr('class', 'inner')
.attr('width', size.width)
.attr('height', size.height);
x.domain([this._newDate(config.xMin), this._newDate(config.xMax)]);
y.domain([config.yMin, config.yMax]);
@ -84,16 +106,20 @@ JX.install('Chart', {
.attr('class', 'chart-tooltip')
.style('opacity', 0);
curtain.reset();
for (var idx = 0; idx < config.datasets.length; idx++) {
var dataset = config.datasets[idx];
switch (dataset.type) {
case 'stacked-area':
this._newStackedArea(g, dataset, x, y, div);
this._newStackedArea(g, dataset, x, y, div, curtain);
break;
}
}
curtain.redraw();
g.append('g')
.attr('class', 'x axis')
.attr('transform', css_function('translate', 0, size.height))
@ -105,7 +131,7 @@ JX.install('Chart', {
.call(yAxis);
},
_newStackedArea: function(g, dataset, x, y, div) {
_newStackedArea: function(g, dataset, x, y, div, curtain) {
var to_date = JX.bind(this, this._newDate);
var area = d3.area()
@ -155,11 +181,30 @@ JX.install('Chart', {
div.style('opacity', 0);
});
curtain.addFunctionLabel('Important Data');
}
},
_newDate: function(epoch) {
return new Date(epoch * 1000);
},
_getCurtain: function() {
if (!this._curtain) {
this._curtain = new JX.ChartCurtainView();
}
return this._curtain;
},
_getChartContainerNode: function() {
if (!this._chartContainerNode) {
var attrs = {
className: 'chart-container'
};
this._chartContainerNode = JX.$N('div', attrs);
}
return this._chartContainerNode;
}
}

View file

@ -0,0 +1,85 @@
/**
* @provides javelin-chart-curtain-view
*/
JX.install('ChartCurtainView', {
construct: function() {
this._labels = [];
},
members: {
_node: null,
_labels: null,
_labelsNode: null,
getNode: function() {
if (!this._node) {
var attr = {
className: 'chart-curtain'
};
this._node = JX.$N('div', attr);
}
return this._node;
},
reset: function() {
this._labels = [];
},
addFunctionLabel: function(label) {
this._labels.push(label);
return this;
},
redraw: function() {
var content = [this._getFunctionLabelsNode()];
JX.DOM.setContent(this.getNode(), content);
return this;
},
_getFunctionLabelsNode: function() {
if (!this._labels.length) {
return null;
}
if (!this._labelsNode) {
var list_attrs = {
className: 'chart-function-label-list'
};
var labels = JX.$N('ul', list_attrs);
var items = [];
for (var ii = 0; ii < this._labels.length; ii++) {
items.push(this._newFunctionLabelItem(this._labels[ii]));
}
JX.DOM.setContent(labels, items);
this._labelsNode = labels;
}
return this._labelsNode;
},
_newFunctionLabelItem: function(item) {
var item_attrs = {
className: 'chart-function-label-list-item'
};
var icon = new JX.PHUIXIconView()
.setIcon('fa-circle');
var content = [
icon.getNode(),
item
];
return JX.$N('li', item_attrs, content);
}
}
});