|
/* |
|
|
|
MIT License 2016 - Dealga McArdle. |
|
================================= |
|
|
|
|
|
For now this is moderately light usage of d3.js - maybe to the chagrin of more weathered |
|
d3.js masters - simply to illustrate a problem i'm facing. |
|
|
|
typical json to parse: |
|
|
|
.... |
|
"01/04/2016": { |
|
"sleeptimes": "00:00->07:00,15:20->18:30,23:00->24:00", |
|
"comments": "AMC visit, Cystoscopy - all clear", |
|
"glucose": [ |
|
{"time": "06:35", "value": 6.94}, |
|
{"time": "15:18", "value": 14.93}, |
|
{"time": "20:28", "value": 11.77}, |
|
{"time": "22:13", "value": 12.71} |
|
] |
|
}, |
|
.... |
|
|
|
- could color grade using 0-5 5-10 10-15 15-20 20-25 25-up |
|
|
|
|
|
intentionally left blank. |
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
|
|
function get_ratio_from_time(time_str){ |
|
// this function converts a time_str into how far into the day it is |
|
// f.ex 12:00 => 0.5 06:00 => 0.25 |
|
var time_parts = time_str.split(':'); |
|
var a = +time_parts[0]; |
|
var b = +time_parts[1]; |
|
return (1/1440*((a*60) + b)) |
|
} |
|
|
|
|
|
function get_color(tval){ |
|
if (tval < 5.0){return {bg:"#18dff5", tx: "#111"}} |
|
else if (tval >= 5.0 && tval < 10.0){return {bg:"#27f518", tx: "#111"}} |
|
else if (tval >= 10.0 && tval < 15.0){return {bg:"#c8f97f", tx: "#111"}} |
|
else if (tval >= 15.0 && tval < 20.0){return {bg:"#ffb76b", tx: "#111"}} |
|
else if (tval >= 20.0){return {bg:"#fe70bc", tx: "#ffffff"}} |
|
} |
|
|
|
var svg = d3.select("svg") |
|
var format_day = d3.time.format("%d/%m/%Y"); |
|
var format_hours = d3.time.format("%H:%M"); |
|
var formatTime = d3.time.format("%m / %d"); // formatTime(new Date); // "June 30, 2015" |
|
|
|
d3.json("times.json", function(error, times) { |
|
if (error) throw error; |
|
times = json_preprocessor(times); |
|
draw_graph(times); |
|
}); |
|
|
|
|
|
function times_preprocessor(t){ |
|
var ts = t.split(','); |
|
var emb = []; |
|
for (var k of ts){ |
|
var abl = k.split('->'); |
|
if (abl.length === 2){ emb.push(abl); } |
|
} |
|
return emb; |
|
} |
|
|
|
function json_preprocessor(p){ |
|
var new_object_array = []; |
|
for (var key in p) { |
|
if (p.hasOwnProperty(key)) { |
|
var day_datum = format_day.parse(key); |
|
var time_object = p[key]; |
|
var processed_times = times_preprocessor(time_object.sleeptimes); |
|
new_object_array.push({ |
|
day: day_datum, |
|
times: processed_times, |
|
comments: time_object.comments, |
|
glucose: time_object.glucose |
|
}); |
|
} |
|
} |
|
return new_object_array; |
|
} |
|
|
|
function draw_graph(times){ |
|
|
|
var margin = {top: 20, right: 80, bottom: 30, left: 50}, |
|
width = 960 - margin.left - margin.right, |
|
height = 500 - margin.top - margin.bottom; |
|
|
|
svg |
|
.attr("width", width + margin.left + margin.right) |
|
.attr("height", height + margin.top + margin.bottom); |
|
|
|
var main_group = svg.append("g") |
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); |
|
|
|
var tracks = main_group.append('g').classed('tracks', true) |
|
|
|
var yindex = 0; |
|
var begin_time, end_time; |
|
var tab_height = 12; |
|
var bar_height = 15; |
|
var tab_ear = 4; |
|
var vertical_skip = 17; |
|
var time_offset_down = 6; |
|
|
|
for (var item of times){ |
|
|
|
var mg = tracks.append('g'); |
|
for (var time_slot of item.times){ |
|
|
|
begin_time = get_ratio_from_time(time_slot[0]); |
|
end_time = get_ratio_from_time(time_slot[1]); |
|
|
|
var rec = mg.append('rect'); |
|
rec.attr("width", (end_time - begin_time) * width) |
|
.attr("height", bar_height) |
|
.style({fill: "#badcfc"}) |
|
.attr("transform", function(d){ |
|
return "translate(" + [ |
|
begin_time * width, |
|
yindex * vertical_skip |
|
] + ")" |
|
}) |
|
} |
|
|
|
var gg = tracks.append('g'); |
|
for (var reading of item.glucose){ |
|
var gtime = get_ratio_from_time(reading.time); |
|
var gval = reading.value; |
|
var ggroup = gg.append('g'); |
|
ggroup.attr({ |
|
transform: "translate(" + [0, (yindex * vertical_skip + 8)] + ")"}) |
|
|
|
var cl = ggroup.append('rect'); |
|
cl.attr({transform: "translate(" + [(gtime * width) + tab_ear , -6] +")"}) |
|
.attr({'height': tab_height}) |
|
.style({fill: get_color(gval).bg}) |
|
|
|
var cl2 = ggroup.append('path'); |
|
cl2.attr({transform: "translate(" + [(gtime * width) + tab_ear , -6] +")"}) |
|
.attr({'d': 'M' + [0,0,-tab_ear,tab_height/2,0,tab_height] + 'z'}) |
|
.style({fill: get_color(gval).bg}) |
|
|
|
var cltext = ggroup.append('text'); |
|
cltext.text(gval) |
|
.attr({transform: "translate(" + [(gtime * width) + tab_ear , 4] +")"}) |
|
.attr({ |
|
'text-anchor': "start", |
|
"font-size": 11, |
|
"font-family": "sans-serif" |
|
}) |
|
cltext.style({'fill': get_color(gval).tx }) |
|
|
|
var textwidth = cltext.node().getComputedTextLength(); |
|
cl.attr({'width': textwidth}) |
|
} |
|
|
|
// draw comments |
|
if (item.comments.length > 0){ |
|
var comment_group = mg.append('g'); |
|
comment_group.attr({ |
|
transform: "translate(" + [width + 20, yindex * vertical_skip] + ")" |
|
}) |
|
var newrec = comment_group.append('rect') |
|
newrec.attr('width', 20).attr('height', 13) |
|
newrec.style({fill: "#fce7ba"}) |
|
} |
|
|
|
|
|
yindex += 1; |
|
mg.append('text') |
|
.text(formatTime(item.day)) |
|
.attr("transform", "translate(-21," + (yindex * vertical_skip - 7) + ")") |
|
.attr({'text-anchor': "middle", "font-size": 10, "font-family": "sans-serif"}) |
|
} |
|
|
|
var indicat = main_group.append('g').classed('indications', true); |
|
var ditimes = "00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24".split(" "); |
|
var circle_push_vertical = 1.593856; // <---- use to push hourly circles up / down |
|
var time_index = 0; |
|
for (var tick of ditimes){ |
|
tick = tick + ":00"; |
|
var tgl = indicat.append('g'); |
|
var tl = tgl.append('line'); |
|
var xpostime = Math.floor(get_ratio_from_time(tick) * width); |
|
tl.attr('x1', xpostime) |
|
.attr('x2', xpostime) |
|
.attr('y1', 0) |
|
.attr('y2', height) |
|
.style({"stroke-width": 1, stroke: "#cfdbe7"}) |
|
|
|
if (time_index % 3 === 0){ |
|
tl.style({"stroke-width": 1, stroke: "#aabfd4"}) |
|
var tcl = tgl.append('circle'); |
|
tcl.attr('cx', Math.floor(get_ratio_from_time(tick) * width)) |
|
.attr('cy', height/circle_push_vertical) |
|
.attr('r', 16) |
|
.style({fill: "#e0eeff"}) |
|
|
|
var txl = tgl.append('text'); |
|
txl.attr({ |
|
'text-anchor': "middle", |
|
"font-size": 17, |
|
"font-family": "sans-serif" |
|
}) |
|
.attr( |
|
'transform', |
|
'translate(' + [ |
|
Math.floor(get_ratio_from_time(tick) * width), |
|
(height/circle_push_vertical)+time_offset_down ] + |
|
')') |
|
.style({'fill': "#7c7c7c"}) |
|
.text(tick.slice(0,2)) |
|
|
|
} |
|
time_index += 1; |
|
} |
|
} |