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:
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
79
webroot/rsrc/js/application/fact/Chart.js
vendored
79
webroot/rsrc/js/application/fact/Chart.js
vendored
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
85
webroot/rsrc/js/application/fact/ChartCurtainView.js
Normal file
85
webroot/rsrc/js/application/fact/ChartCurtainView.js
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user