Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/webroot/rsrc/js/application/fact/Chart.js
12242 views
1
/**
2
* @provides javelin-chart
3
* @requires phui-chart-css
4
* d3
5
* javelin-chart-curtain-view
6
* javelin-chart-function-label
7
*/
8
JX.install('Chart', {
9
10
construct: function(root_node) {
11
this._rootNode = root_node;
12
13
JX.Stratcom.listen('resize', null, JX.bind(this, this._redraw));
14
},
15
16
members: {
17
_rootNode: null,
18
_data: null,
19
_chartContainerNode: null,
20
_curtain: null,
21
22
setData: function(blob) {
23
this._data = blob;
24
this._redraw();
25
},
26
27
_redraw: function() {
28
if (!this._data) {
29
return;
30
}
31
32
var hardpoint = this._rootNode;
33
var curtain = this._getCurtain();
34
var container_node = this._getChartContainerNode();
35
36
var content = [
37
container_node,
38
curtain.getNode(),
39
];
40
41
JX.DOM.setContent(hardpoint, content);
42
43
// Remove the old chart (if one exists) before drawing the new chart.
44
JX.DOM.setContent(container_node, []);
45
46
var viewport = JX.Vector.getDim(container_node);
47
var config = this._data;
48
49
function css_function(n) {
50
return n + '(' + JX.$A(arguments).slice(1).join(', ') + ')';
51
}
52
53
var padding = {};
54
if (JX.Device.isDesktop()) {
55
padding = {
56
top: 24,
57
left: 48,
58
bottom: 48,
59
right: 12
60
};
61
} else {
62
padding = {
63
top: 12,
64
left: 36,
65
bottom: 24,
66
right: 4
67
};
68
}
69
70
var size = {
71
frameWidth: viewport.x,
72
frameHeight: viewport.y,
73
};
74
75
size.width = size.frameWidth - padding.left - padding.right;
76
size.height = size.frameHeight - padding.top - padding.bottom;
77
78
var x = d3.scaleTime()
79
.range([0, size.width]);
80
81
var y = d3.scaleLinear()
82
.range([size.height, 0]);
83
84
var xAxis = d3.axisBottom(x);
85
var yAxis = d3.axisLeft(y);
86
87
var svg = d3.select(container_node).append('svg')
88
.attr('width', size.frameWidth)
89
.attr('height', size.frameHeight)
90
.attr('class', 'chart');
91
92
var g = svg.append('g')
93
.attr(
94
'transform',
95
css_function('translate', padding.left, padding.top));
96
97
g.append('rect')
98
.attr('class', 'inner')
99
.attr('width', size.width)
100
.attr('height', size.height);
101
102
x.domain([this._newDate(config.xMin), this._newDate(config.xMax)]);
103
y.domain([config.yMin, config.yMax]);
104
105
var div = d3.select('body')
106
.append('div')
107
.attr('class', 'chart-tooltip')
108
.style('opacity', 0);
109
110
curtain.reset();
111
112
for (var idx = 0; idx < config.datasets.length; idx++) {
113
var dataset = config.datasets[idx];
114
115
switch (dataset.type) {
116
case 'stacked-area':
117
this._newStackedArea(g, dataset, x, y, div, curtain);
118
break;
119
}
120
}
121
122
curtain.redraw();
123
124
g.append('g')
125
.attr('class', 'x axis')
126
.attr('transform', css_function('translate', 0, size.height))
127
.call(xAxis);
128
129
g.append('g')
130
.attr('class', 'y axis')
131
.attr('transform', css_function('translate', 0, 0))
132
.call(yAxis);
133
},
134
135
_newStackedArea: function(g, dataset, x, y, div, curtain) {
136
var ii;
137
138
var to_date = JX.bind(this, this._newDate);
139
140
var area = d3.area()
141
.x(function(d) { return x(to_date(d.x)); })
142
.y0(function(d) {
143
// When the area is positive, draw it above the X axis. When the area
144
// is negative, draw it below the X axis. We currently avoid having
145
// functions which cross the X axis by clever construction.
146
if (d.y0 >= 0 && d.y1 >= 0) {
147
return y(d.y0);
148
}
149
150
if (d.y0 <= 0 && d.y1 <= 0) {
151
return y(d.y0);
152
}
153
154
return y(0);
155
})
156
.y1(function(d) { return y(d.y1); });
157
158
var line = d3.line()
159
.x(function(d) { return x(to_date(d.x)); })
160
.y(function(d) { return y(d.y1); });
161
162
for (ii = 0; ii < dataset.data.length; ii++) {
163
var label = new JX.ChartFunctionLabel(dataset.labels[ii]);
164
165
var fill_color = label.getFillColor() || label.getColor();
166
167
g.append('path')
168
.style('fill', fill_color)
169
.attr('d', area(dataset.data[ii]));
170
171
var stroke_color = label.getColor();
172
173
g.append('path')
174
.attr('class', 'line')
175
.style('stroke', stroke_color)
176
.attr('d', line(dataset.data[ii]));
177
178
curtain.addFunctionLabel(label);
179
}
180
181
// Now that we've drawn all the areas and lines, draw the dots.
182
for (ii = 0; ii < dataset.data.length; ii++) {
183
g.selectAll('dot')
184
.data(dataset.events[ii])
185
.enter()
186
.append('circle')
187
.attr('class', 'point')
188
.attr('r', 3)
189
.attr('cx', function(d) { return x(to_date(d.x)); })
190
.attr('cy', function(d) { return y(d.y1); })
191
.on('mouseover', function(d) {
192
var dd = to_date(d.x);
193
194
var d_y = dd.getFullYear();
195
196
// NOTE: Javascript months are zero-based. See PHI1017.
197
var d_m = dd.getMonth() + 1;
198
199
var d_d = dd.getDate();
200
201
var y = parseInt(d.y1);
202
203
var label = d.n + ' Points';
204
205
var view =
206
d_y + '-' + d_m + '-' + d_d + ': ' + y + '<br />' +
207
label;
208
209
div
210
.html(view)
211
.style('opacity', 0.9)
212
.style('left', (d3.event.pageX - 60) + 'px')
213
.style('top', (d3.event.pageY - 38) + 'px');
214
})
215
.on('mouseout', function() {
216
div.style('opacity', 0);
217
});
218
}
219
220
},
221
222
_newDate: function(epoch) {
223
return new Date(epoch * 1000);
224
},
225
226
_getCurtain: function() {
227
if (!this._curtain) {
228
this._curtain = new JX.ChartCurtainView();
229
}
230
return this._curtain;
231
},
232
233
_getChartContainerNode: function() {
234
if (!this._chartContainerNode) {
235
var attrs = {
236
className: 'chart-container'
237
};
238
239
this._chartContainerNode = JX.$N('div', attrs);
240
}
241
return this._chartContainerNode;
242
}
243
244
}
245
246
});
247
248