Skip to content

Commit cf66be8

Browse files
committed
Initial commit of the ActivityMeter
1 parent dd2862e commit cf66be8

File tree

1 file changed

+392
-0
lines changed

1 file changed

+392
-0
lines changed
Lines changed: 392 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,392 @@
1+
if(!javaxt) var javaxt={};
2+
if(!javaxt.express) javaxt.express={};
3+
4+
//******************************************************************************
5+
//** Activity Meter
6+
//******************************************************************************
7+
/**
8+
* Panel used to render user activity using real-time charts. Requires D3.
9+
*
10+
******************************************************************************/
11+
12+
javaxt.express.ActivityMeter = function(parent, config) {
13+
14+
var me = this;
15+
var defaultConfig = {
16+
maxIdleTime: 5*60*1000, //5 minutes
17+
lineGraph: {
18+
refeshRate: 500
19+
},
20+
userChart: {
21+
22+
}
23+
};
24+
25+
var lineChart, pieChart; //d3 svg
26+
var requestsPerMinute = {};
27+
var activeUsers = {};
28+
29+
30+
//**************************************************************************
31+
//** Constructor
32+
//**************************************************************************
33+
var init = function(){
34+
35+
//Parse config
36+
config = merge(config, defaultConfig);
37+
if (!config.style) config.style = javaxt.dhtml.style.default;
38+
39+
var div = createElement("div", parent, "user-stats");
40+
41+
42+
//Create main table
43+
var table = createTable(div);
44+
var tr = table.addRow();
45+
46+
47+
//Create line chart
48+
createLineChart(tr.addColumn({
49+
width: "100%",
50+
height: "100%",
51+
padding: "20px"
52+
}));
53+
54+
55+
//Create donut chart
56+
createDonutChart(tr.addColumn({
57+
padding: "20px",
58+
height: "100%"
59+
}));
60+
61+
62+
me.el = div;
63+
addShowHide(me);
64+
};
65+
66+
67+
//**************************************************************************
68+
//** notify
69+
//**************************************************************************
70+
this.notify = function(op, model, id, userID){
71+
var currTime = getCurrentTime();
72+
var numRequests = requestsPerMinute[currTime];
73+
requestsPerMinute[currTime] = !numRequests ? 1 : numRequests+1;
74+
};
75+
76+
77+
//**************************************************************************
78+
//** clear
79+
//**************************************************************************
80+
this.clear = function(){
81+
if (lineChart){
82+
lineChart.stop();
83+
lineChart.clear();
84+
}
85+
if (pieChart) {
86+
pieChart.stop();
87+
pieChart.clear();
88+
}
89+
};
90+
91+
92+
//**************************************************************************
93+
//** update
94+
//**************************************************************************
95+
this.update = function(_activeUsers){
96+
activeUsers = _activeUsers;
97+
if (lineChart) lineChart.start();
98+
if (pieChart) pieChart.start();
99+
};
100+
101+
102+
//**************************************************************************
103+
//** createLineChart
104+
//**************************************************************************
105+
var createLineChart = function(parent){
106+
107+
var div = createElement("div", parent, {
108+
width: "100%",
109+
height: "100%"
110+
});
111+
112+
113+
114+
115+
onRender(div, function(){
116+
117+
var _chart = d3.select(div);
118+
const bb = _chart.node().getBoundingClientRect();
119+
120+
121+
const xScale = d3.scaleLinear()
122+
.domain([0, 100])
123+
.range([0, bb.width]);
124+
125+
const yScale = d3.scaleLinear()
126+
.domain([0, 10])
127+
.range([bb.height, 0]);
128+
129+
130+
var data = new Array(100).fill(0);
131+
132+
lineChart = _chart
133+
.append('svg')
134+
.attr('width', '100%')
135+
.attr('height', '100%');
136+
137+
138+
const line = d3.line()
139+
.curve(d3.curveBasis)
140+
.x((_, i) => xScale(i))
141+
.y((d) => yScale(d));
142+
143+
144+
lineChart.append('path')
145+
.attr('d', line(data))
146+
.style("stroke", "#777")
147+
.style("stroke-width", 1)
148+
.style("fill", "none");
149+
150+
151+
152+
lineChart.start = function(){
153+
lineChart.stop();
154+
lineChart.interval = setInterval(function(){
155+
156+
var currTime = getCurrentTime();
157+
var numRequests = requestsPerMinute[currTime];
158+
if (!numRequests) numRequests = 0;
159+
delete requestsPerMinute[currTime];
160+
161+
data.pop();
162+
data.unshift(numRequests);
163+
164+
var lineColor = "#777";
165+
for (var i in data){
166+
if (data[i]>0){
167+
lineColor = "#6699CC";
168+
break;
169+
}
170+
}
171+
172+
lineChart.selectAll('path')
173+
.data([data])
174+
.attr('d', line)
175+
.style("stroke", lineColor);
176+
177+
}, config.lineGraph.refeshRate);
178+
179+
};
180+
181+
lineChart.stop = function(){
182+
clearInterval(lineChart.interval);
183+
};
184+
185+
lineChart.clear = function(){
186+
data = new Array(100).fill(0);
187+
lineChart.selectAll('path')
188+
.data([data])
189+
.attr('d', line);
190+
};
191+
192+
lineChart.start();
193+
});
194+
};
195+
196+
197+
//**************************************************************************
198+
//** createDonutChart
199+
//**************************************************************************
200+
var createDonutChart = function(parent){
201+
202+
var div = createElement("div", parent,{
203+
width: "350px",
204+
height: "100%"
205+
});
206+
207+
//title: "Active Users",
208+
209+
210+
onRender(div, function(){
211+
212+
213+
var width = div.offsetWidth;
214+
var height = div.offsetHeight;
215+
var radius = Math.min(width, height) / 2;
216+
var cutout = 0.65; //percent
217+
var innerRadius = radius*cutout;
218+
var arc = d3.arc()
219+
.innerRadius(innerRadius)
220+
.outerRadius(radius);
221+
222+
223+
//Create pie chart
224+
pieChart = d3.select(div).append("svg")
225+
.attr("width", "100%")
226+
.attr("height", "100%")
227+
.append("g")
228+
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
229+
230+
231+
//Creat pie function to parse data
232+
var pie = d3.pie().value(function(d) {return d.value; });
233+
234+
235+
//Create label in the center of the pie chart
236+
var label = pieChart.append("text")
237+
.attr("text-anchor", "middle")
238+
.attr('font-size', '4em')
239+
.attr('y', 20);
240+
241+
//Create function to animate changes in the pie chart
242+
var arcTween = function(a) {
243+
var i = d3.interpolate(this._current, a);
244+
this._current = i(0);
245+
return function(t) {
246+
return arc(i(t));
247+
};
248+
};
249+
250+
251+
//Create function to generate data for the pie chart
252+
var getData = function(){
253+
var data = [];
254+
for (var key in activeUsers) {
255+
var lastUpdate = new Date(activeUsers[key]).getTime();
256+
var currTime = new Date().getTime();
257+
var elapsedTime = currTime-lastUpdate;
258+
if (true) { //if (elapsedTime<config.maxIdleTime){
259+
data.push({
260+
key: key,
261+
value: config.maxIdleTime-elapsedTime
262+
});
263+
}
264+
}
265+
return data;
266+
};
267+
268+
269+
//Create function to colorize categorical data
270+
var getColor = d3.scaleOrdinal(d3.schemeCategory10);
271+
272+
273+
//Create render function used to update the chart
274+
var render = function(data){
275+
276+
var numActiveUsers = Object.keys(data).length;
277+
label.text(numActiveUsers+"");
278+
279+
if (numActiveUsers===0){
280+
data.push({
281+
key: "-1",
282+
value: 1
283+
});
284+
}
285+
286+
287+
288+
289+
var pieData = pie(data);
290+
291+
292+
// add transition to new path
293+
pieChart.selectAll("path").data(pieData).transition().duration(100).attrTween("d", arcTween);
294+
295+
296+
297+
// add any new paths
298+
pieChart.selectAll("path")
299+
.data(pieData)
300+
.enter().append("path")
301+
.attr("d", arc)
302+
.attr("fill", function(d,i){
303+
if (d.data.key === "-1") return "#dcdcdc";
304+
return getColor(d.data.key);
305+
})
306+
.style("opacity", 0.8)
307+
.each(function(d){ this._current = d; });
308+
309+
// remove data not being used
310+
pieChart.selectAll("path")
311+
.data(pieData).exit().remove();
312+
313+
};
314+
315+
316+
317+
318+
319+
pieChart.start = function(){
320+
pieChart.stop();
321+
pieChart.interval = setInterval(function(){
322+
323+
var inactiveUsers = [];
324+
for (var key in activeUsers) {
325+
var lastUpdate = activeUsers[key];
326+
var currTime = new Date().getTime();
327+
var elapsedTime = currTime-lastUpdate;
328+
if (elapsedTime>config.maxIdleTime){
329+
inactiveUsers.push(key);
330+
}
331+
}
332+
333+
334+
//Prune inactive users
335+
for (var i in inactiveUsers){
336+
var userID = inactiveUsers[i];
337+
delete activeUsers[userID];
338+
}
339+
340+
render(getData());
341+
342+
}, 1000);
343+
};
344+
345+
pieChart.stop = function(){
346+
clearInterval(pieChart.interval);
347+
};
348+
349+
pieChart.clear = function(){
350+
render([]);
351+
//pieChart.selectAll("*").remove();
352+
};
353+
354+
pieChart.start();
355+
356+
});
357+
};
358+
359+
360+
//**************************************************************************
361+
//** getCurrentTime
362+
//**************************************************************************
363+
var getCurrentTime = function(){
364+
var d = new Date();
365+
return d.getUTCFullYear() +
366+
pad(d.getUTCMonth()+1) +
367+
pad(d.getUTCDate()) +
368+
pad(d.getUTCHours()) +
369+
pad(d.getUTCMinutes());
370+
};
371+
372+
373+
//**************************************************************************
374+
//** pad
375+
//**************************************************************************
376+
var pad = function(i){
377+
return (i<9 ? "0"+i : i+"");
378+
};
379+
380+
381+
//**************************************************************************
382+
//** Utils
383+
//**************************************************************************
384+
var createElement = javaxt.dhtml.utils.createElement;
385+
var createTable = javaxt.dhtml.utils.createTable;
386+
var addShowHide = javaxt.dhtml.utils.addShowHide;
387+
var onRender = javaxt.dhtml.utils.onRender;
388+
var merge = javaxt.dhtml.utils.merge;
389+
390+
391+
init();
392+
};

0 commit comments

Comments
 (0)