stub out time in range tool

This commit is contained in:
Ben West
2015-11-16 23:28:17 -08:00
parent a5ed6c2bcb
commit 8c333babb4
7 changed files with 635 additions and 3 deletions
+1
View File
@@ -3,6 +3,7 @@
window._ = require('lodash');
window.$ = window.jQuery = require('jquery');
window.moment = require('moment-timezone');
window.sugar = require('sugar');
window.Nightscout = window.Nightscout || {};
window.Nightscout = {
+3 -1
View File
@@ -64,19 +64,21 @@
"jquery": "^2.1.4",
"lodash": "^3.9.1",
"long": "~2.2.3",
"mfb": "^0.12.0",
"minimed-connect-to-nightscout": "git://github.com/mddub/minimed-connect-to-nightscout#v0.2.4",
"moment": "2.10.6",
"moment-timezone": "^0.4.0",
"parse-duration": "^0.1.1",
"mongodb": "^2.0.42",
"mqtt": "~0.3.11",
"node-cache": "^3.0.0",
"parse-duration": "^0.1.1",
"pushover-notifications": "0.2.0",
"request": "^2.58.0",
"sgvdata": "git://github.com/ktind/sgvdata.git#wip/protobuf",
"share2nightscout-bridge": "git://github.com/bewest/share2nightscout-bridge.git#wip/generalize",
"simple-statistics": "~0.7.0",
"socket.io": "^1.3.5",
"sugar": "^1.4.1",
"traverse": "^0.6.6"
},
"devDependencies": {
+147
View File
@@ -0,0 +1,147 @@
<!DOCTYPE html>
<html>
<head>
<title>Nightscout performance comparison</title>
<link rel="apple-touch-icon" sizes="57x57" href="/images/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/images/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/images/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/images/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/images/apple-touch-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/images/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/images/apple-touch-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/images/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-180x180.png">
<link rel="icon" type="image/png" href="/images/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="/images/android-chrome-192x192.png" sizes="192x192">
<link rel="icon" type="image/png" href="/images/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="/images/favicon-16x16.png" sizes="16x16">
<link rel="manifest" href="/manifest.json">
<link rel="shortcut icon" href="/images/favicon.ico">
<meta name="msapplication-TileColor" content="#00a300">
<meta name="msapplication-TileImage" content="/images/mstile-144x144.png">
<meta name="msapplication-config" content="/browserconfig.xml">
<meta name="theme-color" content="#333333">
<link rel="stylesheet" href="http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
<link rel="stylesheet" type="text/css" href="/css/report.css" />
<link rel="stylesheet" type="text/css" href="css/compare.css" />
<link rel="stylesheet" type="text/css" href="/bower_components/jquery-ui/themes/ui-lightness/jquery-ui.min.css">
</head>
<body>
<h1><img src="/images/logo1.png"><span class="translate">Nightscout performance comparison</span></h1>
<!--
<div id="info"></div>
<hr>
<div id="pluginchartplaceholders"></div>
<div id="chartContainer" ></div>
-->
<div id="toolbar">
<div id="menu_holder">
<ul id="menu" class="mfb-component--tr mfb-zoomin" data-mfb-toggle="hover">
<li class="mfb-component__wrap">
<a href="#" class="mfb-component__button--main">
<i class="mfb-component__main-icon--resting ion-plus-round"></i>
<i class="mfb-component__main-icon--active ion-close-round"></i>
</a>
<ul class="mfb-component__list">
<li>
<a href="#Details" data-mfb-label="Details" class="mfb-component__button--child">
<i class="mfb-component__child-icon ion-more"></i>
</a>
</li>
<li>
<a href="#Add" data-mfb-label="Add" class="mfb-component__button--child">
<i class="mfb-component__child-icon ion-plus"></i>
</a>
</li>
</ul>
</li>
</ul>
</div>
<!--
<div class="reticle">
<div class="controls">
<span class="begin"><input class="begin-input" value="two weeks ago"/></span>
<span class="interstice">from</span>
<span class="end"><input class="end-input" size=30 value="now"/></span>
</div>
<div class="timeline">
</div>
</div>
</div>
-->
<div id="view">
<div class="ranges">
</div>
<div class="observations">
</div>
</div>
<div id="templates">
<div class="reticle">
<div class="controls">
<span class="begin"><input class="begin-input" value="two weeks ago"/></span>
<span class="interstice">from</span>
<span class="end"><input class="end-input" size=30 value="now"/></span>
</div>
<div class="timeline">
</div>
</div>
<div class="pool">
</div>
</div>
</div>
<hr>
<b><span class="translate">Authentication status</span>: </b><span id="authentication_placeholder"></span>
<script src="/api/v1/status.js"></script>
<script src="/public/js/bundle.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script src="/bower_components/jQuery-Storage-API/jquery.storageapi.min.js"></script>
<script src="/bower_components/tipsy-jmalonzo/src/javascripts/jquery.tipsy.js"></script>
<script src="/bower_components/jquery-flot/jquery.flot.js"></script>
<script src="/bower_components/jquery-flot/jquery.flot.pie.js"></script>
<script src="/bower_components/jquery-flot/jquery.flot.time.js"></script>
<script src="/bower_components/jquery-flot/jquery.flot.fillbetween.js"></script>
<script src="/bower_components/jquery-ui/jquery-ui.min.js"></script>
<script src="/report/js/flotcandle.js"></script>
<script src="js/report.js"></script>
<script src="js/compare.js"></script>
<script src="../mfb/mfb.js"></script>
<script type="text/javascript">
$(document).ready(function ( ) {
var toolbar = $('#toolbar');
var view = $('#view');
var timeline = $('#toolbar .timeline');
var templates = $('#templates');
var manage_opts = {
templates: templates
}
var pools = manager(view, [ ], manage_opts);
var control = slippy(timeline,
{ controls: toolbar.find('.controls')
} );
var compare = { toolbar: toolbar, timeline: timeline, control: control};
Nightscout.compare = compare;
});
</script>
</body>
</html>
+76
View File
@@ -0,0 +1,76 @@
@import url("../../mfb/mfb.min.css");
#chartContainer, #toolbar, #view, #templates {
margin: 0 5%;
width: 80%;
position: relative;
min-height: 49%;
}
#chartContainer svg {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.reticle .controls>span,
.reticle .controls .begin,
.reticle .controls .end {
display: block;
float: left;
margin: 0;
}
.reticle .controls .interstice {
width: 100%;
text-align: center;
line-height: 120%;
font-size: 200%;
}
.reticle .controls .begin {
position: absolute;
left: 0;
}
.reticle .controls .end {
position: absolute;
right: 0;
}
.reticle .controls INPUT {
line-height: 120%;
font-size: 200%;
}
#menu_holder {
}
#menu {
}
.reticle .timeline {
min-height: 8em;
border: 1px solid pink;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.pool {
min-height: 500px;
border: 1px solid blue;
}
#templates {
display: none;
}
.debug #templates {
display: block;
}
+1
View File
File diff suppressed because one or more lines are too long
+405
View File
@@ -0,0 +1,405 @@
function slippy (dom, opt) {
console.log(dom, opt);
var svg = { };
var chart;
var container;
var scales = { x: { }, y: { } };
var dom_width, dom_height, width, height;
var scaleExtent = [ 0, 200 ];
var zoomer;
var margin = {top: 20, right: 50, bottom: 20, left: 50};
get_dimensions( );
function frame ( ) {
get_dimensions( );
var range = frame.getRange( );
// var width = dom.width( );
// var height = dom.height( );
svg.attr('width', dom_width)
// .attr('height', height)
;
var begin = scales.x.invert(0);
var end = scales.x.invert(width);
range = frame.setRange(begin, end);
var delta = moment(begin).from(moment(end));
// .replace(' ago', '');
opt.controls.find('.begin-input').val(delta);
opt.controls.find('.end-input').val(end.format(Date.ISO8601_DATETIME));
opt.controls.data('range', {begin: begin, end: end});
// console.log(width, height, delta)
// console.log(opt);
chart.select(".x.axis").call(frame.xAxis);
dom.trigger('refocus', [range, frame]);
}
frame.getRange = function _getRange ( ) {
return [frame.begin, frame.end];
}
frame.setRange = function _setRange (begin, end) {
frame.begin = Date.create(begin);
frame.end = Date.create(end);
return frame.getRange( );
}
function get_dimensions( ) {
dom_width = dom.width( );
dom_height = dom.height( );
margin = {top: 20, right: 50, bottom: 20, left: 50},
width = dom_width - margin.left - margin.right,
height = dom_height - margin.top - margin.bottom;
}
function init ( ) {
var begin = opt.begin || opt.controls.find('INPUT.begin-input').val( );
var end = opt.end || opt.controls.find('INPUT.end-input').val( );
var range = frame.setRange(begin, end);
var width = dom.width( );
var height = dom.height( );
svg = d3.select(dom.get(0)).append('svg')
.attr('class', 'slippy-chart')
;
;
chart = svg.append('g')
.attr('class', 'widget')
.attr("tranform", "translate(" + 0 + ", " + margin.top + ")")
;
scales.x = d3.time.scale( )
.domain(range)
.nice(d3.time.week)
.rangeRound([0, dom.width( )])
;
scales.y = d3.scale.log( )
.domain([ 40, 400 ])
.rangeRound([1, dom.height( )])
;
frame.xAxis = d3.svg.axis( )
.scale(scales.x)
.ticks(7)
.tickSize(12, 1, 1)
;
chart.append("g")
.attr("transform", "translate(" + 0 + ", " + (height / 2) + ")")
.attr("class", "x axis")
.call(frame.xAxis)
;
zoomer = d3.behavior.zoom( )
.x(scales.x)
.scaleExtent(scaleExtent)
.on('zoom', frame);
;
svg.call(zoomer);
// frame.setRange(begin, end);
opt.controls.on('change', 'INPUT', refocus);
frame( );
return frame;
}
function refocus (ev) {
var target = $(ev.target);
if (target.is('INPUT')) {
var begin = opt.controls.find('INPUT.begin-input').val( );
var end = opt.controls.find('INPUT.end-input').val( );
var range = frame.setRange(begin, end);
}
scales.x.domain(range)
.nice(d3.time.week)
;
frame( );
}
return init( );
// return frame;
}
function manager (view, data, opts) {
var templates = opts.templates;
var item_opts = opts.item_opts || { };
var pools = [ ];
function manage ( ) {
}
function init ( ) {
rows = view.find('.reticle');
if (rows.length > 0) {
} else {
var item = make({ });
pools.push(item);
}
view.on('refocus', '.reticle', on_refocus);
return manage;
}
function make (data) {
var item = templates.find('.reticle').clone(true);
var pool = templates.find('.pool').clone(true);
var end = data.end || Date.create('now').format(Date.ISO8601_DATETIME);
var begin = data.begin || Date.create(end).rewind({weeks: 6}).format(Date.ISO8601_DATETIME);
view.find('.ranges').append(item);
view.find('.observations').append(pool);
var control = slippy(item.find('.timeline'),
{ controls: item.find('.controls')
, begin: begin
, end: end
} );
var reticle = {dom: item, control: control};
var lense = ranger(pool, { });
var display = { dom: pool, control: lense };
return { reticle: reticle, display: display };
}
function on_refocus (ev, range) {
var target = $(ev.target);
var pool = pools.filter(function (pool) {
var timeline = pool.reticle.dom.find('.timeline');
return timeline.is(target);
}).pop( );
if (pool) {
pool.display.control.setRange(range[0], range[1]);
}
}
return init( );
}
function pager (opts) {
var query = { };
var url = opts.url || '/api/v1/entries.json?find[type]=sgv&find[sgv][$gt]=39&count=500000&';
query.begin = opts.begin;
query.end = opts.end;
var payload;
function page ( ) {
}
function refresh (start, end) {
// console.log("PAYLOAD?", payload);
if (payload) {
// TODO: soft update, only get deltas against the edges of the
// cursor.
// start.isBefore
// start.isBetween
// start.isAfter
// end.isBefore
// end.isBetween
// end.isAfter
do_query(start, end, first_page);
} else {
do_query(start, end, first_page);
}
query.begin = start;
query.end = end;
}
function param_string (begin, end) {
return [
"find[dateString][$gte]=" + Date.create(begin).format(Date.ISO8601_DATETIME)
, "find[dateString][$lte]=" + Date.create(end).format(Date.ISO8601_DATETIME)
].join('&')
}
function do_query (begin, end, cb) {
var fetch = url + param_string(begin, end);
$.getJSON(fetch, cb);
}
function first_page (resp) {
// console.log('resp', resp);
payload = resp;
do_payload( );
}
function do_payload ( ) {
if (opts.callback && opts.callback.call) {
opts.callback(payload);
} else {
console.log(payload);
}
}
function init ( ) {
if (query.begin && query.end) {
do_query(query.begin, query.end, first_page);
}
page( );
return page;
}
page.refresh = refresh;
return init( );
}
function time_in_range (data, opts) {
var range = {
high: 180
, low: 80
};
function sgv (d) { return d.sgv; }
function is_clean (d) { return sgv(d) > 39; }
function is_in_range (d) { return sgv(d) >= range.low && sgv(d) <= range.high; }
function is_high (d) { return sgv(d) > range.high; }
function is_low (d) { return sgv(d) < range.low; }
var results = [ ];
var days = d3.nest( )
.key(function (d) {
return Date.create(d.dateString).format('{yyyy}-{MM}-{dd}')
} )
.rollup(function (leaves) {
var clean = leaves.filter(is_clean);
var nominal = clean.filter(is_in_range);
var highs = clean.filter(is_high);
var lows = clean.filter(is_low);
return {
length: clean.length
, color: opts.color || '#eee'
, target: nominal.length / clean.length * 100
, highs: highs.length / clean.length * 100
, lows: lows.length / clean.length * 100
, mean: d3.mean(clean, sgv)
, median: d3.median(clean, sgv)
};
} )
.entries(data);
;
return days;
}
function ranger (dom, opts) {
var scales = { x: { }, y: { } };
var dimensions = {height: null, width: null };
var margin = {top: 20, right: 50, bottom: 20, left: 50};
var xAxis, yAxis;
var dom_width, dom_height;
var width, height;
var root = { };
var dots = { };
var chart = { };
function my ( ) {
console.log('dots', dots);
// dots = chart.selectAll('circle')
// dots.enter( )
// .append("circle")
}
function render_circles (dots) {
console.log('selection', dots);
dots.append('circle')
.attr("class", "dot")
.attr("r", 5)
.attr("title", function (d) { return d.key; })
.attr("color", function (d) { return d.values.color; })
.attr("cx", function (d) {console.log(d); return scales.x(d.values.target); })
.attr("cy", function (d) { return scales.y(d.values.mean); })
}
function render (selection) {
console.log('selection');
}
my.setRange = function _setRange (begin, end) {
adjuster(begin, end);
}
function adjust_range (begin, end) {
my.page.refresh(begin, end);
}
var adjuster = _.debounce(adjust_range, 1000);
function on_data (payload) {
var days = time_in_range(payload, opts);
dots.data(days).enter( ).call(render_circles).remove( );
my( );
console.log('got days', days);
}
function init ( ) {
my.page = pager({begin: opts.begin, end: opts.end, callback: on_data});
get_dimensions( );
scales.x = d3.scale.linear( )
.domain([0, 100])
.range([0, width])
;
scales.y = d3.scale.log( )
.domain( [40, 400] )
.rangeRound( [height - (margin.top + margin.bottom), 1] )
.base(12)
;
// Axis
xAxis = d3.svg.axis( )
.ticks(10)
.scale(scales.x)
.tickFormat(function(d) { return parseInt(d, 10) + "%"; })
.orient('top')
// d3.svg.axis()
// .scale(x)
// .ticks(my.range.length)
// .tickSize(12, 1, 1)
;
;
// .tickFormat(d3.time.format('%m/%d/%y'))
//.tickSize(25, 18);
yAxis = d3.svg.axis()
.scale(scales.y)
.tickValues([40, 60, 70, 80, 120, 160, 180, 200, 220, 260, 300, 350, 400])
.tickFormat(d3.format("d"))
.tickSize(6, 3, 1)
.orient('left')
;
root = d3.select(dom.get(0)).append('svg')
.attr('class', 'ranger')
.attr('height', dom_height)
.attr('width', dom_width)
;
chart = root.append('g')
.attr("transform", "translate(" + 0 + ", " + (margin.top) + ")")
.attr('class', 'ranger-chart')
;
chart.append("g")
.attr("transform", "translate(" + margin.left + ", " + (margin.top) + ")")
.attr("class", "x axis")
.call(xAxis)
;
chart.append("g")
.attr("transform", "translate(" + margin.left + ", " + (margin.top) + ")")
// .attr("transform", "translate(" + dom_width + ", " + (0) + ")")
.attr("class", "y axis")
.call(yAxis)
;
dots = chart.selectAll("circle");
return my;
}
function get_dimensions( ) {
dom_width = dom.width( );
dom_height = dom.height( );
margin = {top: 20, right: 50, bottom: 20, left: 50},
width = dom_width - margin.left - margin.right,
height = dom_height - margin.top - margin.bottom;
}
return init( );
}
+2 -2
View File
@@ -77,7 +77,7 @@
filter.subcategory = '';
$('#rp_subcategory').empty().append('<option value="">' + translate('(none)') + '</option>');
if (filter.category !== '') {
Object.keys(food_categories[filter.category]).forEach(function eachSubCategory(s) {
Object.keys(food_categories[filter.category] || {}).forEach(function eachSubCategory(s) {
$('#rp_subcategory').append('<option value="' + s + '">' + s + '</option>');
});
}
@@ -665,4 +665,4 @@
}
return false;
}
})();
})();