-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
504 lines (398 loc) · 17.5 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
<html>
<head>
<script src="work.json"></script>
<script>
//A visualization of how work hours were spent, based on project or work type
//
//Data stored in JSON format:
// Date
// Time Start - Start time in 12HR clock
// Time End - End time in 12HR clock
// Time Taken - Half hour blocks
// Project - Project name or descriptor
// Type of Work - The type of work by task
var workyear = "2017"; //What is the year we display?
var maxWidth = 1024; //Max canvas width
var maxHeight = 600; //Max canvas height
//Data that we will store for use in legend
var canvasLegendWidth = 270; //Width of the legend/options on the right?
var minpadding = 12; //Used to place text along edges
var titleFont = "bold 24px Arial"; //The font for the title
var titleColor = "white"; //Fill color of title
var legendFont = "bold 18px Arial"; //The font for the legend box title
var legendColor = "white"; //Fill color of all legend box text
var displayProjects = false; //Default to project view first
var switchBox = {"X":16, "Y":565, "W":24, "H":24, "Checked":false}; //A checkbox object for switching between display projects and display worktypes
var legendX = 32; //How far from the edge of the legend to start the list of toggles?
var legendY = 34; //How far down from top to start the list of toggles?
var legendItemColor = "white"; //Text of ignored projects/work types
var legendItemFont = "bold 14px Arial"; //Text font for projects/work types in legend
var toggleWidth = 16; //Togle button width
var toggleHeight = 16; //Toggle button height
var toggleSpacingX = 12; //X distance between checkbox edge and text
var toggleSpacingY = 8; //Y distance between toggleSpacing
var projectlegend = []; //Not wiped, this stores the projects as strings
var worktypelegend = []; //Not wiped, this stores the work types as strings
//We filter the data by both projects and worktypes when preparing it
var ignoreprojects = []; //Projects to ignore?
var ignoreworktypes = []; //Work types to ignore?
//We use dynamic arrays to store the sorted and processed data
var projects = []; //Store the projects worked on
var projectcolors = []; //Hue from HSL color for each project
var worktypes = []; //Store the types of work for later sorting
var worktypecolors = []; //Hue from HSL color for each work type
var hoursperproject = []; //Store the hours per project
var hoursperworktype = []; //Store the hours spent on each work type
var projectpercentages = []; //Store the hours in percentage (0 - 100) for each project
var worktypepercentages = []; //Store the hours in percentage (0 - 100) for each worktype
var totalhours = 0; //How many hours total?
//Store the size of the pie chart
var pieRadius = 280; //How big is the radius of the pie?
function setup() {
canvas = document.getElementById("mycanvas");
ctx = canvas.getContext("2d");
//Mouse variables
mouseX = 0;
mouseY = 0;
wasMouseDown = false;
mouseDown = false;
//Set up mouse reactions
canvas.onmousedown = respondToMouseDown;
canvas.onmousemove = respondToMouseMove;
canvas.onmouseup = respondToMouseUp;
//Build legend for later use to turn on/off features
prepareLegend();
//Pre-prepare the data for display, based on the JSON data
prepareData();
//Set up refresh screen timer to display the data and allow interaction to appear
myInterval = setInterval(function() { refreshScreen(); }, 500); //Every 500ms run refreshScreen
}
function prepareLegend() {
//Sort the data once at start, to make it easier to work with later (at roughly 2fps!)
//data[0] through to data[data.length - 1] (in this case, 653)
//Date: in dd/mm/yyyy format (unused)
//Time start in h:mm:ss AM/PM format (unused)
//Time end in h:mm:ss AM/PM format (unused)
//Time taken in 0.5 increments (rounded)
//Project string
//Type of work string
// Example Types of work:
// --------------
// Bug Testing
// Documentation
// Meetings
// Presentation
// Programming
// Research
// Server
// Skype Call
// Video Editing
// Website
for (var i = 0; i < data.length; i++) {
var obj = data[i];
if (projectlegend.indexOf(obj["Project"]) == -1) {
//We don't have this project yet
projectlegend.push(obj["Project"]); //Add it to list
}
if (worktypelegend.indexOf(obj["Type of Work"]) == -1) {
//We don't have this work type yet
if (obj["Type of Work"] == undefined) console.log(i);
worktypelegend.push(obj["Type of Work"]);
}
}
}
function prepareData() {
totalhours = 0; //Reset total hours count
//We actually do clear the ignores depending on the view the user is in, to make the process less mystical, and take up less legend space
if (displayProjects) {
ignoreworktypes = [];
} else {
ignoreprojects = [];
}
//Clear the arrays, to be re-filled if this is called again
projects = []; //Store the projects worked on
projectcolors = []; //Hue from HSL color for each project
worktypes = []; //Store the types of work for later sorting
worktypecolors = []; //Hue from HSL color for each work type
hoursperproject = []; //Store the hours per project
hoursperworktype = []; //Store the hours spent on each work type
projectpercentages = []; //Store the hours in percentage (0 - 100) for each project
worktypepercentages = []; //Store the hours in percentage (0 - 100) for each worktype
for (var i = 0; i < data.length; i++) {
var obj = data[i];
//Using the square bracket method of object instead of . due to spaces in the properties "Type of Work" and "Time Taken"
var canSaveProject = true; //Can we save this project?
var canSaveWorktype = true; //Can we save this work type?
if (ignoreprojects.indexOf(obj["Project"]) != -1) {
canSaveProject = false; //Actually we ignore this project type
}
if (ignoreworktypes.indexOf(obj["Type of Work"]) != -1) {
canSaveWorktype = false; //Actually we ignore this work type
}
if(canSaveProject && canSaveWorktype) {
//If we can actually count this data piece
if (projects.indexOf(obj["Project"]) == -1) {
//We don't have this project yet
projects.push(obj["Project"]); //Add it to list
hoursperproject.push(0); //Default to 0 hours
}
if (worktypes.indexOf(obj["Type of Work"]) == -1) {
//We don't have this work type yet
worktypes.push(obj["Type of Work"]);
hoursperworktype.push(0); //Default to 0 hours
}
//Store the index of these existing types of project and work in memory
var projIndex = projects.indexOf(obj["Project"]);
var workIndex = worktypes.indexOf(obj["Type of Work"]);
//Add the hours to the totals
var time = obj["Time Taken"];
//If this is a valid type of work, we can count it
hoursperproject[projIndex] = hoursperproject[projIndex] + time;
hoursperworktype[workIndex] = hoursperworktype[workIndex] + time;
totalhours = totalhours + time; //Add the time
}
}
//Now that we've loaded our arrays, we can calculate percentages per pie slice for both charts, and give them colors
for (var i = 0; i < projects.length; i++) {
var percent = (hoursperproject[i] / totalhours) * 100.0;
projectpercentages.push(percent);
var hue = i * (360.0 / projects.length);
projectcolors.push(hue);
}
for (var i = 0; i < worktypes.length; i++) {
var percent = (hoursperworktype[i] / totalhours) * 100.0;
worktypepercentages.push(percent);
var hue = i * (360.0 / worktypes.length);
worktypecolors.push(hue);
}
}
function respondToMouseDown(ev) {
mouseDown = true;
updateMouse(ev);
checkInput(); //Check for user input affecting the app
}
function respondToMouseMove(ev) {
updateMouse(ev);
}
function respondToMouseUp(ev) {
mouseDown = false;
updateMouse(ev);
}
function updateMouse(ev) {
//Update mouse position variables
mouseX = ev.clientX - canvas.offsetLeft;
mouseY = ev.clientY - canvas.offsetTop;
if (!mouseDown) {
wasMouseDown = false;
}
}
function checkInput() {
//Respond to user input
if (mouseX > maxWidth - canvasLegendWidth && mouseDown && !wasMouseDown) {
//We're clicking within the options / legend space, check if we click on our switch view button
var switchX = maxWidth - canvasLegendWidth + switchBox["X"]; //Fancy extra calculations so legend can be adjusted
var optionSelected = false; //Something clicked?
if (mouseX > switchX && mouseX < switchX + switchBox["W"]) {
//Within the X bounds
if (mouseY > switchBox["Y"] && mouseY < switchBox["Y"] + switchBox["H"]) {
//Within the Y bounds, that's a click!
displayProjects = !displayProjects; //Flip our bool
optionSelected = true;
}
}
if (!optionSelected) {
//Still here? Check if the user has changed what we ignore!
var edgeX = maxWidth - canvasLegendWidth + legendX;
if (mouseX > edgeX && mouseX < edgeX + toggleWidth) {
//Within the X bounds of the buttons!
if (displayProjects) {
//We are enabling / disabling a project name
for (var i = 0; i < projectlegend.length; i++) {
var isIgnored = false;
var ignoreIndex = ignoreprojects.indexOf(projectlegend[i]);
if (ignoreIndex != -1) isIgnored = true; //This is currently ignored, we will need to pop it off the stack later
var posY = legendY + (toggleHeight * i) + (toggleSpacingY * i); //Get the Y of this box and check if we're within it
if (mouseY > posY && mouseY < posY + toggleHeight) {
//Within Y bounds! This is a click!
if (isIgnored) {
//We pop something out of the ignore list
ignoreprojects.splice(ignoreIndex, 1); //Remove the ignored part
} else {
//We add something into the ignore list
ignoreprojects.push(projectlegend[i]);
}
optionSelected = true; //Time to update the data!
}
}
}
else {
//We are enabling / disabling a work type
for (var i = 0; i < worktypelegend.length; i++) {
var isIgnored = false;
var ignoreIndex = ignoreworktypes.indexOf(worktypelegend[i]);
if (ignoreIndex != -1) isIgnored = true; //This is currently ignored, we will need to pop it off the stack later
var posY = legendY + (toggleHeight * i) + (toggleSpacingY * i); //Get the Y of this box and check if we're within it
if (mouseY > posY && mouseY < posY + toggleHeight) {
//Within Y bounds! This is a click!
if (isIgnored) {
//We pop something out of the ignore list
ignoreworktypes.splice(ignoreIndex, 1); //Remove the ignored part
} else {
//We add something into the ignore list
ignoreworktypes.push(worktypelegend[i]);
}
optionSelected = true; //Time to update the data!
}
}
}
}
}
if (optionSelected) {
//We selected something here! Better re-prep data just in case!
prepareData();
}
wasMouseDown = true;
}
}
function checkBox(x, y, w, h, isChecked) {
//Helper function to draw a checkbox
ctx.fillStyle = "white";
ctx.fillRect(x, y, w, h);
ctx.lineWidth = 2;
ctx.strokeStyle = "black";
ctx.strokeRect(x, y, w, h);
if (isChecked) {
ctx.fillStyle = "hsla(220, 100%, 57%, 1)";
ctx.fillRect(x + 4, y + 4, w - 8, h - 8);
ctx.lineWidth = 2;
ctx.strokeStyle = "black";
ctx.strokeRect(x + 5, y + 5, w - 10, h - 10);
}
}
function pieSlice(x, y, start, end, r) {
//draw a pie slice around x and y with start % of a circle, end % of a circle, and radius
var startAng = (start / 100.0) * (Math.PI * 2); //We expect our start as a ratio between 0% and 100%
var endAng = (end / 100.0) * (Math.PI * 2) //We expect our end as a ratio between 0% and 100%
ctx.beginPath();
ctx.moveTo(x,y);
ctx.arc(x, y, r, startAng, endAng);
ctx.closePath();
ctx.fill();
ctx.lineWidth = 1;
ctx.strokeStyle = "black";
ctx.stroke();
}
function drawLegend() {
//Draws the legend box as well as the title
var title = "Hours Worked By Project " + workyear;
if (!displayProjects) {
title = "Hours Worked By Type " + workyear;
}
//Draw title
ctx.fillStyle = titleColor;
ctx.font = titleFont;
ctx.fillText(title, minpadding, minpadding * 2);
//Now legend box
ctx.fillStyle = "hsla(220, 100%, 8%, 1)";
ctx.fillRect(maxWidth - canvasLegendWidth, 0, canvasLegendWidth, maxHeight);
//Draw legend title
ctx.fillStyle = legendColor;
ctx.font = legendFont;
ctx.fillText("Legend", maxWidth - canvasLegendWidth/1.6, minpadding * 2);
//Draw the checkboxes and titles for each of the type
var edgeX = maxWidth - canvasLegendWidth + legendX;
if (displayProjects) {
//Displaying by project name
for (var i = 0; i < projectlegend.length; i++) {
var isNotIgnored = true;
if (ignoreprojects.indexOf(projectlegend[i]) != -1) isNotIgnored = false;
//Draw the checkbox
var posY = legendY + (toggleHeight * i) + (toggleSpacingY * i);
checkBox(edgeX, posY, toggleWidth, toggleHeight, isNotIgnored);
//Draw the text
ctx.font = legendItemFont;
ctx.fillStyle = legendItemColor;
hourstext = "";
if (isNotIgnored)
{
var indexofproj = projects.indexOf(projectlegend[i]);
ctx.fillStyle = "hsla(" + projectcolors[indexofproj] + ",100%,50%,0.8)"; //We colorize the text to show it's not ignored
hourstext = " (H:" + hoursperproject[indexofproj] + ")";
}
ctx.fillText(projectlegend[i] + hourstext, edgeX + toggleWidth + toggleSpacingX, posY + toggleHeight/1.5);
}
}
else {
//Displaying by work type
for (var i = 0; i < worktypelegend.length; i++) {
var isNotIgnored = true;
if (ignoreworktypes.indexOf(worktypelegend[i]) != -1) isNotIgnored = false;
//Draw the checkbox
var posY = legendY + (toggleHeight * i) + (toggleSpacingY * i);
checkBox(edgeX, posY, toggleWidth, toggleHeight, isNotIgnored);
//Draw the text
ctx.font = legendItemFont;
ctx.fillStyle = legendItemColor;
hourstext = "";
if (isNotIgnored)
{
var indexofwt = worktypes.indexOf(worktypelegend[i]);
ctx.fillStyle = "hsla(" + worktypecolors[indexofwt] + ",100%,50%,0.8)"; //We colorize the text to show it's not ignored
hourstext = " (H:" + hoursperworktype[indexofwt] + ")";
}
ctx.fillText(worktypelegend[i] + hourstext, edgeX + toggleWidth + toggleSpacingX, posY + toggleHeight/1.5);
}
}
//Draw the checkbox for changing display type
var switchX = maxWidth - canvasLegendWidth + switchBox["X"];
checkBox(switchX, switchBox["Y"], switchBox["W"], switchBox["H"], switchBox["Checked"]); //Draw the switch box
var switchTextY = switchBox["Y"] + switchBox["H"]/1.25;
ctx.fillStyle = legendColor;
ctx.font = legendFont;
ctx.fillText("Display By Projects", switchX + switchBox["W"] + toggleSpacingX, switchTextY);
//Draw total hours
if (ignoreprojects.length + ignoreworktypes.length == 0) {
//Nothing ignored, showing total hours
ctx.fillText("Total Hours: " + totalhours, maxWidth - canvasLegendWidth*1.7, maxHeight - minpadding);
} else {
ctx.fillText("Filtered Hours: " + totalhours, maxWidth - canvasLegendWidth*1.75, maxHeight - minpadding);
}
}
function refreshScreen() {
//The "main program loop"
ctx.clearRect(0,0,maxWidth,maxHeight);
switchBox["Checked"] = displayProjects; //Store the value to the checkbox
drawLegend(); //Draw our legend
displayData(); //Draw our pie chart
}
function displayData()
{
if (displayProjects) {
//We're displaying the pie chart of our projects
var nextStart = 0; //Start angle for next?
for (var i = 0; i < projects.length; i++) {
var end = nextStart + projectpercentages[i];
//Grab the color and draw its pie slice
ctx.fillStyle = "hsla(" + projectcolors[i] + ",100%,50%,0.8)";
pieSlice(maxWidth/2.65, maxHeight/1.95, nextStart, end, pieRadius);
nextStart = end; //The next one continues from here
}
}
else {
//We're displaying the pie chart of our work types
var nextStart = 0; //Start angle for next?
for (var i = 0; i < worktypes.length; i++) {
var end = nextStart + worktypepercentages[i];
//Draw the color and draw its pie slice
ctx.fillStyle = "hsla(" + worktypecolors[i] + ",100%,50%,0.8)";
pieSlice(maxWidth/2.65, maxHeight/1.95, nextStart, end, pieRadius);
nextStart = end; //The next one continues from here
}
}
}
</script>
</head>
<body onload="setup();">
<canvas width="1024" height="600" style="background-color:hsla(220, 100%, 26%, 1);" id="mycanvas"></canvas>
</body>
</html>