Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,9 @@
<span class="currentDirection">-</span>
</div>
<ul class="dropdown-menu" id="silenceBtn">
<li><a href="#" data-snooze-time="1200000">Silence for 20 minutes</a></li>
<li><a href="#" data-snooze-time="1800000">Silence for 30 minutes</a></li>
<li><a href="#" data-snooze-time="2400000">Silence for 40 minutes</a></li>
<li><a href="#" data-snooze-time="3000000">Silence for 50 minutes</a></li>
<li><a href="#" data-snooze-time="3600000">Silence for 60 minutes</a></li>
<li><a href="#" data-snooze-time="5400000">Silence for 90 minutes</a></li>
</ul>
</div>
</div>
Expand Down
110 changes: 101 additions & 9 deletions js/client.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
(function() {
"use strict";

var treatments,
var retrospectivePredictor = true,
latestSGV,
treatments,
padding = {top: 20, right: 10, bottom: 80, left: 10},
opacity = {current: 1, DAY: 1, NIGHT: 0.8},
now = Date.now(),
Expand All @@ -19,6 +21,9 @@
brushTimer,
brushInProgress = false,
clip,
TWENTY_FIVE_MINS_IN_MS = 1500000,
THIRTY_MINS_IN_MS = 1800000,
FORTY_TWO_MINS_IN_MS = 2520000,
FOCUS_DATA_RANGE_MS = 12600000, // 3.5 hours of actual data
audio = document.getElementById('audio'),
alarmInProgress = false,
Expand Down Expand Up @@ -158,22 +163,67 @@

// ensure that brush updating is with the time range
if (brushExtent[0].getTime() + FOCUS_DATA_RANGE_MS > d3.extent(data, dateFn)[1].getTime()) {
brushExtent[0] = new Date(brushExtent[1].getTime() - FOCUS_DATA_RANGE_MS);
d3.select('.brush')
.call(brush.extent([new Date(brushExtent[1].getTime() - FOCUS_DATA_RANGE_MS), brushExtent[1]]));
.call(brush.extent([brushExtent[0], brushExtent[1]]));
} else {
brushExtent[1] = new Date(brushExtent[0].getTime() + FOCUS_DATA_RANGE_MS);
d3.select('.brush')
.call(brush.extent([brushExtent[0], new Date(brushExtent[0].getTime() + FOCUS_DATA_RANGE_MS)]));
.call(brush.extent([brushExtent[0], brushExtent[1]]));
}
}

// get slice of data so that concatenation of predictions do not interfere with subsequent updates
var focusData = data.slice();

var element = document.getElementById('bgButton').hidden == '';

// predict for retrospective data
if (retrospectivePredictor && brushExtent[1].getTime() - THIRTY_MINS_IN_MS < now && element != true) {
// filter data for -12 and +5 minutes from reference time for retrospective focus data prediction
var nowData = data.filter(function(d) {
return d.date.getTime() >= brushExtent[1].getTime() - FORTY_TWO_MINS_IN_MS &&
d.date.getTime() <= brushExtent[1].getTime() - TWENTY_FIVE_MINS_IN_MS
});
if (nowData.length > 1) {
var prediction = predictAR(nowData);
focusData = focusData.concat(prediction);
var focusPoint = nowData[nowData.length - 1];
$('.container .currentBG')
.text((focusPoint.sgv))
.css('text-decoration','line-through');
$('.container .currentDirection')
.html(focusPoint.direction)
} else {
$('.container .currentBG')
.text("---")
.css('text-decoration','none');
}
$('#currentTime')
.text(d3.time.format('%I:%M%p')(brushExtent[1]))
.css('text-decoration','line-through');
} else if (retrospectivePredictor) {
// if the brush comes back into the current time range then it should reset to the current time and sg
var dateTime = new Date(now);
$('#currentTime')
.text(d3.time.format('%I:%M%p')(dateTime))
.css('text-decoration','none');
$('.container .currentBG')
.text(latestSGV.y)
.css('text-decoration','none');
$('.container .currentDirection')
.html(latestSGV.direction);
}

xScale.domain(brush.extent());

// bind up the focus chart data to an array of circles
// selects all our data into data and uses date function to get current max date
var focusCircles = focus.selectAll('circle').data(data, dateFn);
var focusCircles = focus.selectAll('circle').data(focusData, dateFn);

// if already existing then transition each circle to its new position
focusCircles.transition()
focusCircles
.transition()
.duration(UPDATE_TRANS_MS)
.attr('cx', function (d) { return xScale(d.date); })
.attr('cy', function (d) { return yScale(d.sgv); })
Expand Down Expand Up @@ -222,9 +272,9 @@
focus.select('.now-line')
.transition()
.duration(UPDATE_TRANS_MS)
.attr('x1', xScale(new Date(now)))
.attr('x1', xScale(new Date(brushExtent[1].getTime() - THIRTY_MINS_IN_MS)))
.attr('y1', yScale(36))
.attr('x2', xScale(new Date(now)))
.attr('x2', xScale(new Date(brushExtent[1].getTime() - THIRTY_MINS_IN_MS)))
.attr('y2', yScale(420));

// update x axis
Expand Down Expand Up @@ -581,6 +631,7 @@
// change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr)
if (d[0].length) {
var current = d[0][d[0].length - 1];
latestSGV = current;
var secsSinceLast = (Date.now() - new Date(current.x).getTime()) / 1000;
var currentBG = current.y;

Expand All @@ -605,7 +656,7 @@
$('.container .currentDirection').html(current.direction);
$('.container .current').toggleClass('high', current.y > 180).toggleClass('low', current.y < 70)
}
data = d[0].map(function (obj) { return { date: new Date(obj.x), sgv: obj.y, color: 'grey'} });
data = d[0].map(function (obj) { return { date: new Date(obj.x), sgv: obj.y, direction: obj.direction, color: 'grey'} });
data = data.concat(d[1].map(function (obj) { return { date: new Date(obj.x), sgv: obj.y, color: 'blue'} }));
data = data.concat(d[2].map(function (obj) { return { date: new Date(obj.x), sgv: obj.y, color: 'red'} }));
treatments = d[3];
Expand All @@ -630,18 +681,23 @@
console.log("Alarm raised!");
currentAlarmType = 'alarm';
generateAlarm(alarmSound);
brushInProgress = false;
updateChart(false);
});
socket.on('urgent_alarm', function() {
console.log("Urgent alarm raised!");
currentAlarmType = 'urgent_alarm';
generateAlarm(urgentAlarmSound);
brushInProgress = false;
updateChart(false);
});
socket.on('clear_alarm', function() {
if (alarmInProgress) {
console.log('clearing alarm');
stopAlarm();
}
});
// TODO: this is dead code, maybe delete and remove from server
socket.on('clients', function(watchers) {
console.log('number of clients has changed to ' + watchers);
$('#watchers').text(watchers);
Expand Down Expand Up @@ -680,6 +736,7 @@
// only emit ack if client invoke by button press
if (isClient) {
socket.emit('ack', currentAlarmType || 'alarm', silenceTime);
brushed(false);
}
}

Expand Down Expand Up @@ -707,7 +764,7 @@

}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//draw a compact visualization of a treatment (carbs, insulin)
Expand Down Expand Up @@ -773,4 +830,39 @@
.text(function (d) { return d.element; })
}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// function to predict
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function predictAR(actual) {
var ONE_MINUTE = 60 * 1000;
var FIVE_MINUTES = 5 * ONE_MINUTE;
var predicted = [];
var BG_REF = 140;
var BG_MIN = 36;
var BG_MAX = 400;
if (actual.length < 2) {
var y = [Math.log(actual[0].sgv / BG_REF), Math.log(actual[0].sgv / BG_REF)];
} else {
var elapsedMins = (actual[1].date - actual[0].date) / ONE_MINUTE;
if (elapsedMins < 5.1) {
y = [Math.log(actual[0].sgv / BG_REF), Math.log(actual[1].sgv / BG_REF)];
} else {
y = [Math.log(actual[0].sgv / BG_REF), Math.log(actual[0].sgv / BG_REF)];
}
}
var n = 20;
var AR = [-0.723, 1.716];
var dt = actual[1].date.getTime();
for (var i = 0; i <= n; i++) {
y = [y[1], AR[0] * y[0] + AR[1] * y[1]];
dt = dt + FIVE_MINUTES;
predicted[i] = {
date: new Date(dt+3000),
sgv: Math.max(BG_MIN, Math.min(BG_MAX, Math.round(BG_REF * Math.exp(y[1])))),
color: 'blue'
};
}
return predicted;
}
})();