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-big-info-view.css' => '362ad37b',
|
||||||
'rsrc/css/phui/phui-box.css' => '5ed3b8cb',
|
'rsrc/css/phui/phui-box.css' => '5ed3b8cb',
|
||||||
'rsrc/css/phui/phui-bulk-editor.css' => '374d5e30',
|
'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-cms.css' => '8c05c41e',
|
||||||
'rsrc/css/phui/phui-comment-form.css' => '68a2d99a',
|
'rsrc/css/phui/phui-comment-form.css' => '68a2d99a',
|
||||||
'rsrc/css/phui/phui-comment-panel.css' => 'ec4e31c0',
|
'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/diffusion/behavior-pull-lastmodified.js' => 'c715c123',
|
||||||
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '6a85bc5a',
|
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '6a85bc5a',
|
||||||
'rsrc/js/application/drydock/drydock-live-operation-status.js' => '47a0728b',
|
'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-document-engine.js' => '243d6c22',
|
||||||
'rsrc/js/application/files/behavior-icon-composer.js' => '38a6cedb',
|
'rsrc/js/application/files/behavior-icon-composer.js' => '38a6cedb',
|
||||||
'rsrc/js/application/files/behavior-launch-icon-composer.js' => 'a17b84f1',
|
'rsrc/js/application/files/behavior-launch-icon-composer.js' => 'a17b84f1',
|
||||||
@@ -696,7 +697,8 @@ return array(
|
|||||||
'javelin-behavior-user-menu' => '60cd9241',
|
'javelin-behavior-user-menu' => '60cd9241',
|
||||||
'javelin-behavior-view-placeholder' => 'a9942052',
|
'javelin-behavior-view-placeholder' => 'a9942052',
|
||||||
'javelin-behavior-workflow' => '9623adc1',
|
'javelin-behavior-workflow' => '9623adc1',
|
||||||
'javelin-chart' => 'a3516cea',
|
'javelin-chart' => 'b88a227d',
|
||||||
|
'javelin-chart-curtain-view' => 'd10a3c25',
|
||||||
'javelin-color' => '78f811c9',
|
'javelin-color' => '78f811c9',
|
||||||
'javelin-cookie' => '05d290ef',
|
'javelin-cookie' => '05d290ef',
|
||||||
'javelin-diffusion-locate-file-source' => '94243d89',
|
'javelin-diffusion-locate-file-source' => '94243d89',
|
||||||
@@ -823,7 +825,7 @@ return array(
|
|||||||
'phui-calendar-day-css' => '9597d706',
|
'phui-calendar-day-css' => '9597d706',
|
||||||
'phui-calendar-list-css' => 'ccd7e4e2',
|
'phui-calendar-list-css' => 'ccd7e4e2',
|
||||||
'phui-calendar-month-css' => 'cb758c42',
|
'phui-calendar-month-css' => 'cb758c42',
|
||||||
'phui-chart-css' => '7853a69b',
|
'phui-chart-css' => '10135a9d',
|
||||||
'phui-cms-css' => '8c05c41e',
|
'phui-cms-css' => '8c05c41e',
|
||||||
'phui-comment-form-css' => '68a2d99a',
|
'phui-comment-form-css' => '68a2d99a',
|
||||||
'phui-comment-panel-css' => 'ec4e31c0',
|
'phui-comment-panel-css' => 'ec4e31c0',
|
||||||
@@ -1767,10 +1769,6 @@ return array(
|
|||||||
'javelin-workflow',
|
'javelin-workflow',
|
||||||
'phabricator-draggable-list',
|
'phabricator-draggable-list',
|
||||||
),
|
),
|
||||||
'a3516cea' => array(
|
|
||||||
'phui-chart-css',
|
|
||||||
'd3',
|
|
||||||
),
|
|
||||||
'a4356cde' => array(
|
'a4356cde' => array(
|
||||||
'javelin-install',
|
'javelin-install',
|
||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
@@ -1935,6 +1933,11 @@ return array(
|
|||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
'phabricator-draggable-list',
|
'phabricator-draggable-list',
|
||||||
),
|
),
|
||||||
|
'b88a227d' => array(
|
||||||
|
'phui-chart-css',
|
||||||
|
'd3',
|
||||||
|
'javelin-chart-curtain-view',
|
||||||
|
),
|
||||||
'b9109f8f' => array(
|
'b9109f8f' => array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-uri',
|
'javelin-uri',
|
||||||
|
|||||||
@@ -94,10 +94,8 @@ final class PhabricatorChartRenderingEngine
|
|||||||
'div',
|
'div',
|
||||||
array(
|
array(
|
||||||
'id' => $chart_node_id,
|
'id' => $chart_node_id,
|
||||||
'style' => 'background: #ffffff; '.
|
'class' => 'chart-hardpoint',
|
||||||
'height: 480px; ',
|
));
|
||||||
),
|
|
||||||
'');
|
|
||||||
|
|
||||||
$data_uri = urisprintf('/fact/chart/%s/draw/', $chart_key);
|
$data_uri = urisprintf('/fact/chart/%s/draw/', $chart_key);
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chart .axis text {
|
.chart .axis text {
|
||||||
|
font: {$basefont};
|
||||||
fill: {$darkgreytext};
|
fill: {$darkgreytext};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,3 +53,43 @@
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
pointer-events: none;
|
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
|
* @provides javelin-chart
|
||||||
* @requires phui-chart-css
|
* @requires phui-chart-css
|
||||||
* d3
|
* d3
|
||||||
|
* javelin-chart-curtain-view
|
||||||
*/
|
*/
|
||||||
JX.install('Chart', {
|
JX.install('Chart', {
|
||||||
|
|
||||||
@@ -14,6 +15,8 @@ JX.install('Chart', {
|
|||||||
members: {
|
members: {
|
||||||
_rootNode: null,
|
_rootNode: null,
|
||||||
_data: null,
|
_data: null,
|
||||||
|
_chartContainerNode: null,
|
||||||
|
_curtain: null,
|
||||||
|
|
||||||
setData: function(blob) {
|
setData: function(blob) {
|
||||||
this._data = blob;
|
this._data = blob;
|
||||||
@@ -26,23 +29,42 @@ JX.install('Chart', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var hardpoint = this._rootNode;
|
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.
|
// 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;
|
var config = this._data;
|
||||||
|
|
||||||
function css_function(n) {
|
function css_function(n) {
|
||||||
return n + '(' + JX.$A(arguments).slice(1).join(', ') + ')';
|
return n + '(' + JX.$A(arguments).slice(1).join(', ') + ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
var padding = {
|
var padding = {};
|
||||||
top: 24,
|
if (JX.Device.isDesktop()) {
|
||||||
left: 48,
|
padding = {
|
||||||
bottom: 48,
|
top: 24,
|
||||||
right: 32
|
left: 48,
|
||||||
};
|
bottom: 48,
|
||||||
|
right: 12
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
padding = {
|
||||||
|
top: 12,
|
||||||
|
left: 36,
|
||||||
|
bottom: 24,
|
||||||
|
right: 4
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
var size = {
|
var size = {
|
||||||
frameWidth: viewport.x,
|
frameWidth: viewport.x,
|
||||||
@@ -61,20 +83,20 @@ JX.install('Chart', {
|
|||||||
var xAxis = d3.axisBottom(x);
|
var xAxis = d3.axisBottom(x);
|
||||||
var yAxis = d3.axisLeft(y);
|
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('width', size.frameWidth)
|
||||||
.attr('height', size.frameHeight)
|
.attr('height', size.frameHeight)
|
||||||
.attr('class', 'chart');
|
.attr('class', 'chart');
|
||||||
|
|
||||||
var g = svg.append('g')
|
var g = svg.append('g')
|
||||||
.attr(
|
.attr(
|
||||||
'transform',
|
'transform',
|
||||||
css_function('translate', padding.left, padding.top));
|
css_function('translate', padding.left, padding.top));
|
||||||
|
|
||||||
g.append('rect')
|
g.append('rect')
|
||||||
.attr('class', 'inner')
|
.attr('class', 'inner')
|
||||||
.attr('width', size.width)
|
.attr('width', size.width)
|
||||||
.attr('height', size.height);
|
.attr('height', size.height);
|
||||||
|
|
||||||
x.domain([this._newDate(config.xMin), this._newDate(config.xMax)]);
|
x.domain([this._newDate(config.xMin), this._newDate(config.xMax)]);
|
||||||
y.domain([config.yMin, config.yMax]);
|
y.domain([config.yMin, config.yMax]);
|
||||||
@@ -84,16 +106,20 @@ JX.install('Chart', {
|
|||||||
.attr('class', 'chart-tooltip')
|
.attr('class', 'chart-tooltip')
|
||||||
.style('opacity', 0);
|
.style('opacity', 0);
|
||||||
|
|
||||||
|
curtain.reset();
|
||||||
|
|
||||||
for (var idx = 0; idx < config.datasets.length; idx++) {
|
for (var idx = 0; idx < config.datasets.length; idx++) {
|
||||||
var dataset = config.datasets[idx];
|
var dataset = config.datasets[idx];
|
||||||
|
|
||||||
switch (dataset.type) {
|
switch (dataset.type) {
|
||||||
case 'stacked-area':
|
case 'stacked-area':
|
||||||
this._newStackedArea(g, dataset, x, y, div);
|
this._newStackedArea(g, dataset, x, y, div, curtain);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
curtain.redraw();
|
||||||
|
|
||||||
g.append('g')
|
g.append('g')
|
||||||
.attr('class', 'x axis')
|
.attr('class', 'x axis')
|
||||||
.attr('transform', css_function('translate', 0, size.height))
|
.attr('transform', css_function('translate', 0, size.height))
|
||||||
@@ -105,7 +131,7 @@ JX.install('Chart', {
|
|||||||
.call(yAxis);
|
.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 to_date = JX.bind(this, this._newDate);
|
||||||
|
|
||||||
var area = d3.area()
|
var area = d3.area()
|
||||||
@@ -155,11 +181,30 @@ JX.install('Chart', {
|
|||||||
div.style('opacity', 0);
|
div.style('opacity', 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
curtain.addFunctionLabel('Important Data');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_newDate: function(epoch) {
|
_newDate: function(epoch) {
|
||||||
return new Date(epoch * 1000);
|
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