diff --git a/.travis.yml b/.travis.yml index 90331521284..78b056b8dbc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,5 +26,5 @@ matrix: include: - node_js: "10" <<: *node_js-steps - - node_js: "node" # Latest Node is not supported, and recommend, but we'll test it to know incompatibility issues + - node_js: "12" # Latest Node is not supported, and recommend, but we'll test it to know incompatibility issues <<: *node_js-steps diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index de4a539a48d..c77a0df1c6a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -257,7 +257,7 @@ Languages with less than 90% coverage will be removed in a future Nightscout ver | Suomi (`fi`)|[@sulkaharo] |OK| | Français (`fr`)|Please volunteer|OK| | עברית (`he`)|Please volunteer|OK| -| Hrvatski (`hr`)|[@OpossumGit]|Needs attention: 47.8% - committed 100% to dev| +| Hrvatski (`hr`)|[@OpossumGit]|OK| | Italiano (`it`)|Please volunteer|OK| | 日本語 (`ja`)|[@LuminaryXion]|Working on this| | 한국어 (`ko`)|Please volunteer|Needs attention: 80.6%| diff --git a/Makefile b/Makefile index bf87aaed1c1..1ca626ab88c 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ report: test_onebyone: python -c 'import os,sys,fcntl; flags = fcntl.fcntl(sys.stdout, fcntl.F_GETFL); fcntl.fcntl(sys.stdout, fcntl.F_SETFL, flags&~os.O_NONBLOCK);' - $(foreach var,$(wildcard tests/*.js),${MONGO_SETTINGS} ${MOCHA} --timeout 30000 --exit --bail -R tap $(var);) + for var in tests/*.js; do ${MONGO_SETTINGS} ${MOCHA} --timeout 30000 --exit --bail -R tap $$var; done | tap-set-exit test: ${MONGO_SETTINGS} ${MOCHA} --timeout 30000 --exit --bail -R tap ${TESTS} @@ -52,7 +52,7 @@ travis: python -c 'import os,sys,fcntl; flags = fcntl.fcntl(sys.stdout, fcntl.F_GETFL); fcntl.fcntl(sys.stdout, fcntl.F_SETFL, flags&~os.O_NONBLOCK);' # NODE_ENV=test ${MONGO_SETTINGS} \ # ${ISTANBUL} cover ${MOCHA} --report lcovonly -- --timeout 5000 -R tap ${TESTS} - $(foreach var,$(wildcard tests/*.js),${MONGO_SETTINGS} ${MOCHA} --timeout 30000 --exit --bail -R tap $(var);) + for var in tests/*.js; do ${MONGO_SETTINGS} ${MOCHA} --timeout 30000 --exit --bail -R tap $$var; done docker_release: # Get the version from the package.json file diff --git a/README.md b/README.md index d418480a311..8ad79a39d72 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Nightscout Web Monitor (a.k.a. cgm-remote-monitor) [![Dependency Status][dependency-img]][dependency-url] [![Coverage Status][coverage-img]][coverage-url] [![Codacy Badge][codacy-img]][codacy-url] -[![Gitter chat][gitter-img]][gitter-url] +[![Discord chat][discord-img]][discord-url] [![Deploy to Azure](http://azuredeploy.net/deploybutton.png)](https://azuredeploy.net/) [![Deploy to Heroku][heroku-img]][heroku-url] [![Update your site][update-img]][update-fork] @@ -35,8 +35,8 @@ Community maintained fork of the [coverage-url]: https://coveralls.io/github/nightscout/cgm-remote-monitor?branch=master [codacy-img]: https://www.codacy.com/project/badge/f79327216860472dad9afda07de39d3b [codacy-url]: https://www.codacy.com/app/Nightscout/cgm-remote-monitor -[gitter-img]: https://img.shields.io/badge/Gitter-Join%20Chat%20%E2%86%92-1dce73.svg -[gitter-url]: https://gitter.im/nightscout/public +[discord-img]: https://img.shields.io/discord/629952586895851530?label=discord%20chat +[discord-url]: https://discordapp.com/channels/629952586895851530/629952669967974410 [heroku-img]: https://www.herokucdn.com/deploy/button.png [heroku-url]: https://heroku.com/deploy [update-img]: update.png @@ -311,7 +311,6 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs/ or * `Clock` - Shows current BG, trend arrow, and time of day. Grey text on a black background. * `Color` - Shows current BG and trend arrow. White text on a background that changes color to indicate current BG threshold (green = in range; blue = below range; yellow = above range; red = urgent below/above). * `Simple` - Shows current BG. Grey text on a black background. - * Optional configuration: set `SHOW_CLOCK_CLOSEBUTTON` to `false` to never show the small X button in clock views. For bookmarking a clock view without the close box but have it appear when navigating to a clock from the Nightscout menu, don't change the settng, but remove the `showClockClosebutton=true` parameter from the clock view URL. ### Plugins diff --git a/lib/client/chart.js b/lib/client/chart.js index bb9ef950977..ba35ed52f6a 100644 --- a/lib/client/chart.js +++ b/lib/client/chart.js @@ -3,7 +3,20 @@ // var _ = require('lodash'); var times = require('../times'); var d3locales = require('./d3locales'); -var padding = { bottom: 30 }; +var scrolling = false + , scrollNow = 0 + , scrollBrushExtent = null + , scrollRange = null +; + +var PADDING_BOTTOM = 30 + , CONTEXT_MAX = 420 + , CONTEXT_MIN = 36 + , FOCUS_MAX = 510 + , FOCUS_MIN = 30 +; + +var loadTime = Date.now(); function init (client, d3, $) { var chart = { }; @@ -31,20 +44,37 @@ function init (client, d3, $) { // arrow head defs.append('marker') - .attr({ - 'id': 'arrow', - 'viewBox': '0 -5 10 10', - 'refX': 5, - 'refY': 0, - 'markerWidth': 8, - 'markerHeight': 8, - 'orient': 'auto' - }) + .attr('id', 'arrow') + .attr('viewBox', '0 -5 10 10') + .attr('refX', 5) + .attr('refY', 0) + .attr('markerWidth', 8) + .attr('markerHeight', 8) + .attr('orient', 'auto') .append('path') .attr('d', 'M0,-5L10,0L0,5') .attr('class', 'arrowHead'); - var localeFormatter = d3.locale(d3locales.locale(client.settings.language)); + var localeFormatter = d3.timeFormatLocale(d3locales.locale(client.settings.language)); + + function beforeBrushStarted ( ) { + // go ahead and move the brush because + // a single click will not execute the brush event + var now = new Date(); + var dx = chart.xScale2(now) - chart.xScale2(new Date(now.getTime() - client.focusRangeMS)); + + var cx = d3.mouse(this)[0]; + var x0 = cx - dx / 2; + var x1 = cx + dx / 2; + + var range = chart.xScale2.range(); + var X0 = range[0]; + var X1 = range[1]; + + var brush = x0 < X0 ? [X0, X0 + dx] : x1 > X1 ? [X1 - dx, X1] : [x0, x1]; + + chart.theBrush.call(chart.brush.move, brush); + } function brushStarted ( ) { // update the opacity of the context data points to brush extent @@ -64,15 +94,15 @@ function init (client, d3, $) { var yScaleType; if (client.settings.scaleY === 'linear') { - yScaleType = d3.scale.linear; + yScaleType = d3.scaleLinear; } else { - yScaleType = d3.scale.log; + yScaleType = d3.scaleLog; } - var focusYDomain = [utils.scaleMgdl(30), utils.scaleMgdl(510)]; - var contextYDomain = [utils.scaleMgdl(36), utils.scaleMgdl(420)]; + var focusYDomain = [utils.scaleMgdl(FOCUS_MIN), utils.scaleMgdl(FOCUS_MAX)]; + var contextYDomain = [utils.scaleMgdl(CONTEXT_MIN), utils.scaleMgdl(CONTEXT_MAX)]; - function dynamicDomain() { + function dynamicDomain () { // allow y-axis to extend all the way to the top of the basal area, but leave room to display highest value var mult = 1.15 , targetTop = client.settings.thresholds.bgTargetTop @@ -84,12 +114,12 @@ function init (client, d3, $) { //, mgdlMax = d3.quantile(client.entries, 0.99, function (d) { return d.mgdl; }); return [ - utils.scaleMgdl(30) + utils.scaleMgdl(FOCUS_MIN) , Math.max(utils.scaleMgdl(mgdlMax * mult), utils.scaleMgdl(targetTop * mult)) ]; } - function dynamicDomainOrElse(defaultDomain) { + function dynamicDomainOrElse (defaultDomain) { if (client.settings.scaleY === 'linear' || client.settings.scaleY === 'log-dynamic') { return dynamicDomain(); } else { @@ -98,71 +128,94 @@ function init (client, d3, $) { } // define the parts of the axis that aren't dependent on width or height - var xScale = chart.xScale = d3.time.scale().domain(extent); + var xScale = chart.xScale = d3.scaleTime().domain(extent); + focusYDomain = dynamicDomainOrElse(focusYDomain); var yScale = chart.yScale = yScaleType() - .domain(dynamicDomainOrElse(focusYDomain)); + .domain(focusYDomain); - var xScale2 = chart.xScale2 = d3.time.scale().domain(extent); + var xScale2 = chart.xScale2 = d3.scaleTime().domain(extent); + + contextYDomain = dynamicDomainOrElse(contextYDomain); var yScale2 = chart.yScale2 = yScaleType() - .domain(dynamicDomainOrElse(contextYDomain)); + .domain(contextYDomain); - chart.xScaleBasals = d3.time.scale().domain(extent); + chart.xScaleBasals = d3.scaleTime().domain(extent); - chart.yScaleBasals = d3.scale.linear() + chart.yScaleBasals = d3.scaleLinear() .domain([0, 5]); - var tickFormat = localeFormatter.timeFormat.multi( [ - ['.%L', function(d) { return d.getMilliseconds(); }], - [':%S', function(d) { return d.getSeconds(); }], - [client.settings.timeFormat === 24 ? '%H:%M' : '%I:%M', function(d) { return d.getMinutes(); }], - [client.settings.timeFormat === 24 ? '%H:%M' : '%-I %p', function(d) { return d.getHours(); }], - ['%a %d', function(d) { return d.getDay() && d.getDate() !== 1; }], - ['%b %d', function(d) { return d.getDate() !== 1; }], - ['%B', function(d) { return d.getMonth(); }], - ['%Y', function() { return true; }] - ]); + var formatMillisecond = localeFormatter.format('.%L'), + formatSecond = localeFormatter.format(':%S'), + formatMinute = client.settings.timeFormat === 24 ? localeFormatter.format('%H:%M') : + localeFormatter.format('%I:%M'), + formatHour = client.settings.timeFormat === 24 ? localeFormatter.format('%H:%M') : + localeFormatter.format('%-I %p'), + formatDay = localeFormatter.format('%a %d'), + formatWeek = localeFormatter.format('%b %d'), + formatMonth = localeFormatter.format('%B'), + formatYear = localeFormatter.format('%Y'); + + var tickFormat = function (date) { + return (d3.timeSecond(date) < date ? formatMillisecond + : d3.timeMinute(date) < date ? formatSecond + : d3.timeHour(date) < date ? formatMinute + : d3.timeDay(date) < date ? formatHour + : d3.timeMonth(date) < date ? (d3.timeWeek(date) < date ? formatDay : formatWeek) + : d3.timeYear(date) < date ? formatMonth + : formatYear)(date); + }; var tickValues = client.ticks(client); - chart.xAxis = d3.svg.axis() - .scale(xScale) + chart.xAxis = d3.axisBottom(xScale) + + chart.xAxis = d3.axisBottom(xScale) .tickFormat(tickFormat) - .ticks(4) - .orient('bottom'); + .ticks(6); - chart.yAxis = d3.svg.axis() - .scale(yScale) + chart.yAxis = d3.axisLeft(yScale) .tickFormat(d3.format('d')) - .tickValues(tickValues) - .orient('left'); + .tickValues(tickValues); - chart.xAxis2 = d3.svg.axis() - .scale(xScale2) + chart.xAxis2 = d3.axisBottom(xScale2) .tickFormat(tickFormat) - .ticks(6) - .orient('bottom'); + .ticks(6); - chart.yAxis2 = d3.svg.axis() - .scale(yScale2) + chart.yAxis2 = d3.axisRight(yScale2) .tickFormat(d3.format('d')) - .tickValues(tickValues) - .orient('right'); + .tickValues(tickValues); + + d3.select('tick') + .style('z-index', '10000'); // setup a brush - chart.brush = d3.svg.brush() - .x(xScale2) - .on('brushstart', brushStarted) + chart.brush = d3.brushX() + .on('start', brushStarted) .on('brush', function brush (time) { - client.loadRetroIfNeeded(); + // layouting the graph causes a brushed event + // ignore retro data load the first two seconds + if (Date.now() - loadTime > 2000) client.loadRetroIfNeeded(); client.brushed(time); }) - .on('brushend', brushEnded); + .on('end', brushEnded); + + chart.theBrush = null; - chart.futureOpacity = d3.scale.linear( ) - .domain([times.mins(25).msecs, times.mins(60).msecs]) - .range([0.8, 0.1]); + chart.futureOpacity = (function () { + var scale = d3.scaleLinear( ) + .domain([times.mins(25).msecs, times.mins(60).msecs]) + .range([0.8, 0.1]); + + return function (delta) { + if (delta < 0) { + return null; + } else { + return scale(delta); + } + }; + })(); // create svg and g to contain the chart contents chart.charts = d3.select('#chartContainer').append('svg') @@ -176,48 +229,72 @@ function init (client, d3, $) { // create the x axis container chart.focus.append('g') - .attr('class', 'x axis'); - + .attr('class', 'x axis') + .style("font-size", "16px"); + // create the y axis container chart.focus.append('g') - .attr('class', 'y axis'); + .attr('class', 'y axis') + .style("font-size", "16px"); - chart.context = chart.charts.append('g').attr('class', 'chart-context'); + chart.context = chart.charts.append('g') + .attr('class', 'chart-context'); // create the x axis container chart.context.append('g') - .attr('class', 'x axis'); + .attr('class', 'x axis') + .style("font-size", "16px"); // create the y axis container chart.context.append('g') - .attr('class', 'y axis'); + .attr('class', 'y axis') + .style("font-size", "16px"); - function createAdjustedRange() { - var range = chart.brush.extent().slice(); + chart.createBrushedRange = function () { + var brushedRange = chart.theBrush && d3.brushSelection(chart.theBrush.node()) || null; + var range = brushedRange && brushedRange.map(chart.xScale2.invert); + var dataExtent = client.dataExtent(); - var end = range[1].getTime() + client.forecastTime; + if (!brushedRange) { + // console.log('No current brushed range. Setting range to last focusRangeMS amount of available data'); + range = dataExtent; + + range[0] = new Date(range[1].getTime() - client.focusRangeMS); + } + + var end = range[1].getTime() if (!chart.inRetroMode()) { - var lastSGVMills = client.latestSGV ? client.latestSGV.mills : client.now; - end += (client.now - lastSGVMills); + end = client.now > dataExtent[1].getTime() ? client.now : dataExtent[1].getTime(); } range[1] = new Date(end); + range[0] = new Date(end - client.focusRangeMS); return range; } - chart.inRetroMode = function inRetroMode() { - if (!chart.brush || !chart.xScale2) { + chart.createAdjustedRange = function () { + var adjustedRange = chart.createBrushedRange(); + + adjustedRange[1] = new Date(adjustedRange[1].getTime() + client.forecastTime); + + return adjustedRange; + } + + chart.inRetroMode = function inRetroMode () { + var brushedRange = chart.theBrush && d3.brushSelection(chart.theBrush.node()) || null; + + if (!brushedRange || !chart.xScale2) { return false; } - var brushTime = chart.brush.extent()[1].getTime(); var maxTime = chart.xScale2.domain()[1].getTime(); + var brushTime = chart.xScale2.invert(brushedRange[1]).getTime(); return brushTime < maxTime; }; // called for initial update and updates for resize - chart.update = function update(init) { + chart.update = function update (init) { if (client.documentHidden && !init) { console.info('Document Hidden, not updating - ' + (new Date())); @@ -235,15 +312,16 @@ function init (client, d3, $) { var dataRange = client.dataExtent(); var chartContainerRect = chartContainer[0].getBoundingClientRect(); var chartWidth = chartContainerRect.width; - var chartHeight = chartContainerRect.height - padding.bottom; + var chartHeight = chartContainerRect.height - PADDING_BOTTOM; // get the height of each chart based on its container size ratio var focusHeight = chart.focusHeight = chartHeight * .7; - var contextHeight = chart.contextHeight = chartHeight * .2; + var contextHeight = chart.contextHeight = chartHeight * .3; chart.basalsHeight = focusHeight / 4; // get current brush extent - var currentBrushExtent = createAdjustedRange(); + var currentRange = chart.createAdjustedRange(); + var currentBrushExtent = chart.createBrushedRange(); // only redraw chart if chart size has changed var widthChanged = (chart.prevChartWidth !== chartWidth); @@ -259,14 +337,14 @@ function init (client, d3, $) { //set the width and height of the SVG element chart.charts.attr('width', chartWidth) - .attr('height', chartHeight + padding.bottom); + .attr('height', chartHeight + PADDING_BOTTOM); // ranges are based on the width and height available so reset chart.xScale.range([0, chartWidth]); chart.xScale2.range([0, chartWidth]); chart.xScaleBasals.range([0, chartWidth]); chart.yScale.range([focusHeight, 0]); - chart.yScale2.range([chartHeight, chartHeight - contextHeight]); + chart.yScale2.range([contextHeight, 0]); chart.yScaleBasals.range([0, focusHeight / 4]); if (init) { @@ -281,42 +359,48 @@ function init (client, d3, $) { .call(chart.yAxis); // if first run then just display axis with no transition + chart.context + .attr('transform', 'translate(0,' + focusHeight + ')') + chart.context.select('.x') - .attr('transform', 'translate(0,' + chartHeight + ')') + .attr('transform', 'translate(0,' + contextHeight + ')') .call(chart.xAxis2); -// chart.basals.select('.y') -// .attr('transform', 'translate(0,' + 0 + ')') -// .call(chart.yAxisBasals); - - chart.context.append('g') + chart.theBrush = chart.context.append('g') .attr('class', 'x brush') - .call(d3.svg.brush().x(chart.xScale2).on('brush', client.brushed)) - .selectAll('rect') - .attr('y', focusHeight) - .attr('height', chartHeight - focusHeight); + .call(chart.brush) + .call(g => g.select(".overlay") + .datum({type: 'selection'}) + .on('mousedown touchstart', beforeBrushStarted)); + + chart.theBrush.selectAll('rect') + .attr('y', 0) + .attr('height', contextHeight); // disable resizing of brush - d3.select('.x.brush').select('.background').style('cursor', 'move'); - d3.select('.x.brush').select('.resize.e').style('cursor', 'move'); - d3.select('.x.brush').select('.resize.w').style('cursor', 'move'); + chart.context.select('.x.brush').select('.overlay').style('cursor', 'move'); + chart.context.select('.x.brush').selectAll('.handle') + .style('cursor', 'move'); + + chart.context.select('.x.brush').select('.selection') + .style('visibility', 'hidden'); // add a line that marks the current time chart.focus.append('line') .attr('class', 'now-line') .attr('x1', chart.xScale(new Date(client.now))) - .attr('y1', chart.yScale(utils.scaleMgdl(30))) + .attr('y1', chart.yScale(focusYDomain[0])) .attr('x2', chart.xScale(new Date(client.now))) - .attr('y2', chart.yScale(utils.scaleMgdl(420))) + .attr('y2', chart.yScale(focusYDomain[1])) .style('stroke-dasharray', ('3, 3')) .attr('stroke', 'grey'); // add a y-axis line that shows the high bg threshold chart.focus.append('line') .attr('class', 'high-line') - .attr('x1', chart.xScale(dataRange[0])) + .attr('x1', chart.xScale.range()[0]) .attr('y1', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgHigh))) - .attr('x2', chart.xScale(dataRange[1])) + .attr('x2', chart.xScale.range()[1]) .attr('y2', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgHigh))) .style('stroke-dasharray', ('1, 6')) .attr('stroke', '#777'); @@ -324,9 +408,9 @@ function init (client, d3, $) { // add a y-axis line that shows the high bg threshold chart.focus.append('line') .attr('class', 'target-top-line') - .attr('x1', chart.xScale(dataRange[0])) + .attr('x1', chart.xScale.range()[0]) .attr('y1', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgTargetTop))) - .attr('x2', chart.xScale(dataRange[1])) + .attr('x2', chart.xScale.range()[1]) .attr('y2', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgTargetTop))) .style('stroke-dasharray', ('3, 3')) .attr('stroke', 'grey'); @@ -334,9 +418,9 @@ function init (client, d3, $) { // add a y-axis line that shows the low bg threshold chart.focus.append('line') .attr('class', 'target-bottom-line') - .attr('x1', chart.xScale(dataRange[0])) + .attr('x1', chart.xScale.range()[0]) .attr('y1', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgTargetBottom))) - .attr('x2', chart.xScale(dataRange[1])) + .attr('x2', chart.xScale.range()[1]) .attr('y2', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgTargetBottom))) .style('stroke-dasharray', ('3, 3')) .attr('stroke', 'grey'); @@ -344,9 +428,9 @@ function init (client, d3, $) { // add a y-axis line that shows the low bg threshold chart.focus.append('line') .attr('class', 'low-line') - .attr('x1', chart.xScale(dataRange[0])) + .attr('x1', chart.xScale.range()[0]) .attr('y1', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgLow))) - .attr('x2', chart.xScale(dataRange[1])) + .attr('x2', chart.xScale.range()[1]) .attr('y2', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgLow))) .style('stroke-dasharray', ('1, 6')) .attr('stroke', '#777'); @@ -371,9 +455,9 @@ function init (client, d3, $) { chart.context.append('line') .attr('class', 'now-line') .attr('x1', chart.xScale(new Date(client.now))) - .attr('y1', chart.yScale2(utils.scaleMgdl(36))) + .attr('y1', chart.yScale2(contextYDomain[0])) .attr('x2', chart.xScale(new Date(client.now))) - .attr('y2', chart.yScale2(utils.scaleMgdl(420))) + .attr('y2', chart.yScale2(contextYDomain[1])) .style('stroke-dasharray', ('3, 3')) .attr('stroke', 'grey'); @@ -400,7 +484,7 @@ function init (client, d3, $) { } else { // for subsequent updates use a transition to animate the axis to the new position - var focusTransition = chart.focus.transition(); + var focusTransition = chart.focus; focusTransition.select('.x') .attr('transform', 'translate(0,' + focusHeight + ')') @@ -410,86 +494,75 @@ function init (client, d3, $) { .attr('transform', 'translate(' + chartWidth + ', 0)') .call(chart.yAxis); - var contextTransition = chart.context.transition(); + var contextTransition = chart.context; + + chart.context + .attr('transform', 'translate(0,' + focusHeight + ')') contextTransition.select('.x') - .attr('transform', 'translate(0,' + chartHeight + ')') + .attr('transform', 'translate(0,' + contextHeight + ')') .call(chart.xAxis2); - chart.basals.transition(); - -// basalsTransition.select('.y') -// .attr('transform', 'translate(0,' + 0 + ')') -// .call(chart.yAxisBasals); + chart.basals; // reset brush location - chart.context.select('.x.brush') - .selectAll('rect') - .attr('y', focusHeight) - .attr('height', chartHeight - focusHeight); + chart.theBrush.selectAll('rect') + .attr('y', 0) + .attr('height', contextHeight); - // clear current brushs - d3.select('.brush').call(chart.brush.clear()); + // console.log('Redrawing old brush with new dimensions: ', currentBrushExtent); // redraw old brush with new dimensions - d3.select('.brush').transition().call(chart.brush.extent(currentBrushExtent)); + chart.theBrush.call(chart.brush.move, currentBrushExtent.map(chart.xScale2)); // transition lines to correct location chart.focus.select('.high-line') - .transition() - .attr('x1', chart.xScale(currentBrushExtent[0])) + .attr('x1', chart.xScale.range()[0]) .attr('y1', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgHigh))) - .attr('x2', chart.xScale(currentBrushExtent[1])) + .attr('x2', chart.xScale.range()[1]) .attr('y2', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgHigh))); chart.focus.select('.target-top-line') - .transition() - .attr('x1', chart.xScale(currentBrushExtent[0])) + .attr('x1', chart.xScale.range()[0]) .attr('y1', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgTargetTop))) - .attr('x2', chart.xScale(currentBrushExtent[1])) + .attr('x2', chart.xScale.range()[1]) .attr('y2', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgTargetTop))); chart.focus.select('.target-bottom-line') - .transition() - .attr('x1', chart.xScale(currentBrushExtent[0])) + .attr('x1', chart.xScale.range()[0]) .attr('y1', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgTargetBottom))) - .attr('x2', chart.xScale(currentBrushExtent[1])) + .attr('x2', chart.xScale.range()[1]) .attr('y2', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgTargetBottom))); chart.focus.select('.low-line') - .transition() - .attr('x1', chart.xScale(currentBrushExtent[0])) + .attr('x1', chart.xScale.range()[0]) .attr('y1', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgLow))) - .attr('x2', chart.xScale(currentBrushExtent[1])) + .attr('x2', chart.xScale.range()[1]) .attr('y2', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgLow))); // transition open-top line to correct location chart.context.select('.open-top') - .transition() - .attr('x1', chart.xScale2(currentBrushExtent[0])) - .attr('y1', chart.yScale(utils.scaleMgdl(30))) - .attr('x2', chart.xScale2(currentBrushExtent[1])) - .attr('y2', chart.yScale(utils.scaleMgdl(30))); + .attr('x1', chart.xScale2(currentRange[0])) + .attr('y1', chart.yScale2(utils.scaleMgdl(CONTEXT_MAX))) + .attr('x2', chart.xScale2(currentRange[1])) + .attr('y2', chart.yScale2(utils.scaleMgdl(CONTEXT_MAX))); // transition open-left line to correct location chart.context.select('.open-left') - .transition() - .attr('x1', chart.xScale2(currentBrushExtent[0])) - .attr('y1', focusHeight) - .attr('x2', chart.xScale2(currentBrushExtent[0])) - .attr('y2', chartHeight); + .attr('x1', chart.xScale2(currentRange[0])) + .attr('y1', chart.yScale2(contextYDomain[0])) + .attr('x2', chart.xScale2(currentRange[0])) + .attr('y2', chart.yScale2(contextYDomain[1])); // transition open-right line to correct location chart.context.select('.open-right') - .transition() - .attr('x1', chart.xScale2(currentBrushExtent[1])) - .attr('y1', focusHeight) - .attr('x2', chart.xScale2(currentBrushExtent[1])) - .attr('y2', chartHeight); + .attr('x1', chart.xScale2(currentRange[1])) + .attr('y1', chart.yScale2(contextYDomain[0])) + .attr('x2', chart.xScale2(currentRange[1])) + .attr('y2', chart.yScale2(contextYDomain[1])); // transition high line to correct location chart.context.select('.high-line') - .transition() .attr('x1', chart.xScale2(dataRange[0])) .attr('y1', chart.yScale2(utils.scaleMgdl(client.settings.thresholds.bgTargetTop))) .attr('x2', chart.xScale2(dataRange[1])) @@ -497,7 +570,6 @@ function init (client, d3, $) { // transition low line to correct location chart.context.select('.low-line') - .transition() .attr('x1', chart.xScale2(dataRange[0])) .attr('y1', chart.yScale2(utils.scaleMgdl(client.settings.thresholds.bgTargetBottom))) .attr('x2', chart.xScale2(dataRange[1])) @@ -505,64 +577,83 @@ function init (client, d3, $) { } } - // update domain - chart.xScale2.domain(dataRange); + chart.updateContext(dataRange); + chart.xScaleBasals.domain(dataRange); - var updateBrush = d3.select('.brush').transition(); - updateBrush - .call(chart.brush.extent([new Date(dataRange[1].getTime() - client.focusRangeMS), dataRange[1]])); - client.brushed(true); + // console.log('Redrawing brush due to update: ', currentBrushExtent); + + chart.theBrush.call(chart.brush.move, currentBrushExtent.map(chart.xScale2)); + }; + + chart.updateContext = function (dataRange_) { + if (client.documentHidden) { + console.info('Document Hidden, not updating - ' + (new Date())); + return; + } + + // get current data range + var dataRange = dataRange_ || client.dataExtent(); + + // update domain + chart.xScale2.domain(dataRange); renderer.addContextCircles(); // update x axis domain chart.context.select('.x').call(chart.xAxis2); - }; - chart.scroll = function scroll (nowDate) { - chart.xScale.domain(createAdjustedRange()); - chart.yScale.domain(dynamicDomainOrElse(focusYDomain)); - chart.xScaleBasals.domain(createAdjustedRange()); + function scrollUpdate () { + scrolling = false; + + var nowDate = scrollNow; + + var currentBrushExtent = scrollBrushExtent; + var currentRange = scrollRange; + + chart.xScale.domain(currentRange); + + focusYDomain = dynamicDomainOrElse(focusYDomain); + + chart.yScale.domain(focusYDomain); + chart.xScaleBasals.domain(currentRange); // remove all insulin/carb treatment bubbles so that they can be redrawn to correct location d3.selectAll('.path').remove(); // transition open-top line to correct location chart.context.select('.open-top') - .attr('x1', chart.xScale2(chart.brush.extent()[0])) - .attr('y1', chart.yScale(utils.scaleMgdl(30))) - .attr('x2', chart.xScale2(new Date(chart.brush.extent()[1].getTime() + client.forecastTime))) - .attr('y2', chart.yScale(utils.scaleMgdl(30))); + .attr('x1', chart.xScale2(currentRange[0])) + .attr('y1', chart.yScale2(contextYDomain[1])) + .attr('x2', chart.xScale2(currentRange[1])) + .attr('y2', chart.yScale2(contextYDomain[1])); // transition open-left line to correct location chart.context.select('.open-left') - .attr('x1', chart.xScale2(chart.brush.extent()[0])) - .attr('y1', chart.focusHeight) - .attr('x2', chart.xScale2(chart.brush.extent()[0])) - .attr('y2', chart.prevChartHeight); + .attr('x1', chart.xScale2(currentRange[0])) + .attr('y1', chart.yScale2(contextYDomain[0])) + .attr('x2', chart.xScale2(currentRange[0])) + .attr('y2', chart.yScale2(contextYDomain[1])); // transition open-right line to correct location chart.context.select('.open-right') - .attr('x1', chart.xScale2(new Date(chart.brush.extent()[1].getTime() + client.forecastTime))) - .attr('y1', chart.focusHeight) - .attr('x2', chart.xScale2(new Date(chart.brush.extent()[1].getTime() + client.forecastTime))) - .attr('y2', chart.prevChartHeight); + .attr('x1', chart.xScale2(currentRange[1])) + .attr('y1', chart.yScale2(contextYDomain[0])) + .attr('x2', chart.xScale2(currentRange[1])) + .attr('y2', chart.yScale2(contextYDomain[1])); chart.focus.select('.now-line') - .transition() .attr('x1', chart.xScale(nowDate)) - .attr('y1', chart.yScale(utils.scaleMgdl(36))) + .attr('y1', chart.yScale(focusYDomain[0])) .attr('x2', chart.xScale(nowDate)) - .attr('y2', chart.yScale(utils.scaleMgdl(420))); + .attr('y2', chart.yScale(focusYDomain[1])); chart.context.select('.now-line') - .transition() - .attr('x1', chart.xScale2(chart.brush.extent()[1])) - .attr('y1', chart.yScale2(utils.scaleMgdl(36))) - .attr('x2', chart.xScale2(chart.brush.extent()[1])) - .attr('y2', chart.yScale2(utils.scaleMgdl(420))); + .attr('x1', chart.xScale2(currentBrushExtent[1])) + .attr('y1', chart.yScale2(contextYDomain[0])) + .attr('x2', chart.xScale2(currentBrushExtent[1])) + .attr('y2', chart.yScale2(contextYDomain[1])); // update x,y axis chart.focus.select('.x.axis').call(chart.xAxis); @@ -575,6 +666,18 @@ function init (client, d3, $) { renderer.addTreatmentProfiles(client); renderer.drawTreatments(client); + } + + chart.scroll = function scroll (nowDate) { + scrollNow = nowDate; + scrollBrushExtent = chart.createBrushedRange(); + scrollRange = chart.createAdjustedRange(); + + if (!scrolling) { + requestAnimationFrame(scrollUpdate); + } + + scrolling = true; }; return chart; diff --git a/lib/client/clock-client.js b/lib/client/clock-client.js index 18b5116f94d..5ff3a787e28 100644 --- a/lib/client/clock-client.js +++ b/lib/client/clock-client.js @@ -98,13 +98,6 @@ client.render = function render (xhr) { if (m < 10) m = "0" + m; $('#clock').text(h + ":" + m); - var queryDict = {}; - location.search.substr(1).split("&").forEach(function(item) { queryDict[item.split("=")[0]] = item.split("=")[1] }); - - if (!window.serverSettings.settings.showClockClosebutton || !queryDict['showClockClosebutton']) { - $('#close').css('display', 'none'); - } - // defined in the template this is loaded into // eslint-disable-next-line no-undef if (clockFace === 'clock-color') { @@ -122,11 +115,6 @@ client.render = function render (xhr) { var green = 'rgba(134,207,70,1)'; var blue = 'rgba(78,143,207,1)'; - var darkRed = 'rgba(183,9,21,1)'; - var darkYellow = 'rgba(214,168,0,1)'; - var darkGreen = 'rgba(110,192,70,1)'; - var darkBlue = 'rgba(78,143,187,1)'; - var elapsedMins = Math.round(((now - last) / 1000) / 60); // Insert the BG stale time text. @@ -135,28 +123,18 @@ client.render = function render (xhr) { // Threshold background coloring. if (bgNum < bgLow) { $('body').css('background-color', red); - $('#close').css('border-color', darkRed); - $('#close').css('color', darkRed); } if ((bgLow <= bgNum) && (bgNum < bgTargetBottom)) { $('body').css('background-color', blue); - $('#close').css('border-color', darkBlue); - $('#close').css('color', darkBlue); } if ((bgTargetBottom <= bgNum) && (bgNum < bgTargetTop)) { $('body').css('background-color', green); - $('#close').css('border-color', darkGreen); - $('#close').css('color', darkGreen); } if ((bgTargetTop <= bgNum) && (bgNum < bgHigh)) { $('body').css('background-color', yellow); - $('#close').css('border-color', darkYellow); - $('#close').css('color', darkYellow); } if (bgNum >= bgHigh) { $('body').css('background-color', red); - $('#close').css('border-color', darkRed); - $('#close').css('color', darkRed); } // Restyle body bg, and make the "x minutes ago" visible too. diff --git a/lib/client/index.js b/lib/client/index.js index 979348c0cb5..5e9874f2ea6 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -102,8 +102,7 @@ client.init = function init (callback) { client.load = function load (serverSettings, callback) { - var UPDATE_TRANS_MS = 750 // milliseconds - , FORMAT_TIME_12 = '%-I:%M %p' + var FORMAT_TIME_12 = '%-I:%M %p' , FORMAT_TIME_12_COMPACT = '%-I:%M' , FORMAT_TIME_24 = '%H:%M%' , FORMAT_TIME_12_SCALE = '%-I %p' @@ -124,7 +123,11 @@ client.load = function load (serverSettings, callback) { , urgentAlarmSound = 'alarm2.mp3' , previousNotifyTimestamp; - client.entryToDate = function entryToDate (entry) { return new Date(entry.mills); }; + client.entryToDate = function entryToDate (entry) { + if (entry.date) return entry.date; + entry.date = new Date(entry.mills); + return entry.date; + }; client.now = Date.now(); client.ddata = require('../data/ddata')(); @@ -261,10 +264,12 @@ client.load = function load (serverSettings, callback) { //client.ctx.bus.uptime( ); client.dataExtent = function dataExtent () { - return client.entries.length > 0 ? - d3.extent(client.entries, client.entryToDate) : - d3.extent([new Date(client.now - times.hours(history).msecs), new Date(client.now)]); - }; + if (client.entries.length > 0) { + return[client.entryToDate(client.entries[0]), client.entryToDate(client.entries[client.entries.length-1])]; + } else { + return [new Date(client.now - times.hours(history).msecs), new Date(client.now)]; + } + }; client.bottomOfPills = function bottomOfPills () { //the offset's might not exist for some tests @@ -276,7 +281,7 @@ client.load = function load (serverSettings, callback) { function formatTime (time, compact) { var timeFormat = getTimeFormat(false, compact); - time = d3.time.format(timeFormat)(time); + time = d3.timeFormat(timeFormat)(time); if (client.settings.timeFormat !== 24) { time = time.toLowerCase(); } @@ -375,14 +380,14 @@ client.load = function load (serverSettings, callback) { // clears the current user brush and resets to the current real time data function updateBrushToNow (skipBrushing) { - // get current time range - var dataRange = client.dataExtent(); - // update brush and focus chart with recent data - d3.select('.brush') - .transition() - .duration(UPDATE_TRANS_MS) - .call(chart.brush.extent([new Date(dataRange[1].getTime() - client.focusRangeMS), dataRange[1]])); + var brushExtent = client.dataExtent(); + + brushExtent[0] = new Date(brushExtent[1].getTime() - client.focusRangeMS); + + // console.log('Resetting brush in updateBrushToNow: ', brushExtent); + + chart.theBrush && chart.theBrush.call(chart.brush.move, brushExtent.map(chart.xScale2)); if (!skipBrushing) { brushed(); @@ -398,21 +403,35 @@ client.load = function load (serverSettings, callback) { } function brushed () { + // Brush not initialized + console.log("brushed"); + if (!chart.theBrush) { + return; + } + + // default to most recent focus period + var brushExtent = client.dataExtent(); + brushExtent[0] = new Date(brushExtent[1].getTime() - client.focusRangeMS); + + var brushedRange = d3.brushSelection(chart.theBrush.node()); + + if (brushedRange) { + brushExtent = brushedRange.map(chart.xScale2.invert); + } - var brushExtent = chart.brush.extent(); + // console.log('Brushed to: ', brushExtent); - // ensure that brush extent is fixed at 3.5 hours - if (brushExtent[1].getTime() - brushExtent[0].getTime() !== client.focusRangeMS) { + if (!brushedRange || (brushExtent[1].getTime() - brushExtent[0].getTime() !== client.focusRangeMS)) { // ensure that brush updating is with the time range if (brushExtent[0].getTime() + client.focusRangeMS > client.dataExtent()[1].getTime()) { brushExtent[0] = new Date(brushExtent[1].getTime() - client.focusRangeMS); - d3.select('.brush') - .call(chart.brush.extent([brushExtent[0], brushExtent[1]])); } else { brushExtent[1] = new Date(brushExtent[0].getTime() + client.focusRangeMS); - d3.select('.brush') - .call(chart.brush.extent([brushExtent[0], brushExtent[1]])); } + + // console.log('Updating brushed to: ', brushExtent); + + chart.theBrush.call(chart.brush.move, brushExtent.map(chart.xScale2)); } function adjustCurrentSGVClasses (value, isCurrent) { @@ -428,7 +447,6 @@ client.load = function load (serverSettings, callback) { currentBG.toggleClass('icon-hourglass', value === 9); currentBG.toggleClass('error-code', value < 39); currentBG.toggleClass('bg-limit', value === 39 || value > 400); - container.removeClass('loading'); } function updateCurrentSGV (entry) { @@ -458,10 +476,22 @@ client.load = function load (serverSettings, callback) { client.ddata.inRetroMode = inRetroMode(); client.ddata.profile = profile; + // retro data only ever contains device statuses + // Cleate a clone of the data for the sandbox given to plugins + + var mergedStatuses = client.ddata.devicestatus; + + if (client.retro.data) { + mergedStatuses = _.merge({}, client.retro.data.devicestatus, client.ddata.devicestatus); + } + + var clonedData = _.clone(client.ddata); + clonedData.devicestatus = mergedStatuses; + client.sbx = sandbox.clientInit( client.ctx , new Date(time).getTime() //make sure we send a timestamp - , _.merge({}, client.retro.data || {}, client.ddata) + , clonedData ); //all enabled plugins get a chance to set properties, even if they aren't shown @@ -546,6 +576,7 @@ client.load = function load (serverSettings, callback) { var top = (client.bottomOfPills() + 5); $('#chartContainer').css({ top: top + 'px', height: $(window).height() - top - 10 }); + container.removeClass('loading'); } function sgvToColor (sgv) { @@ -1127,9 +1158,14 @@ client.load = function load (serverSettings, callback) { point.color = 'transparent'; } }); + + client.entries.sort(function sorter(a,b) { + return a.mills - b.mills; + }); } - function dataUpdate (received) { + function dataUpdate (received, headless) { + console.info('got dataUpdate', new Date(client.now)); var lastUpdated = Date.now(); receiveDData(received, client.ddata, client.settings); @@ -1162,15 +1198,23 @@ client.load = function load (serverSettings, callback) { prepareEntries(); updateTitle(); + // Don't invoke D3 in headless mode + if (!isInitialData) { isInitialData = true; - chart = client.chart = require('./chart')(client, d3, $); - brushed(); - chart.update(true); + if (!headless) { + chart = client.chart = require('./chart')(client, d3, $); + chart.update(true); + brushed(); + chart.update(false); +// brushed(); + } } else if (!inRetroMode()) { - chart.update(false); + if (!headless) chart.update(false); client.plugins.updateVisualisations(client.nowSBX); - brushed(); + if (!headless) brushed(); + } else { + if (!headless) chart.updateContext(); } } }; diff --git a/lib/client/renderer.js b/lib/client/renderer.js index 84f8574ed47..c526798e76c 100644 --- a/lib/client/renderer.js +++ b/lib/client/renderer.js @@ -6,10 +6,11 @@ var times = require('../times'); var DEFAULT_FOCUS = times.hours(3).msecs , WIDTH_SMALL_DOTS = 420 , WIDTH_BIG_DOTS = 800 - , TOOLTIP_TRANS_MS = 100 // milliseconds , TOOLTIP_WIDTH = 150 //min-width + padding ; +const zeroDate = new Date(0); + function init (client, d3) { var renderer = {}; @@ -17,6 +18,12 @@ function init (client, d3) { var utils = client.utils; var translate = client.translate; + function getOrAddDate(entry) { + if (entry.date) return entry.date; + entry.date = new Date(entry.mills); + return entry.date; + } + //chart isn't created till the client gets data, so can grab the var at init function chart () { return client.chart; @@ -40,20 +47,22 @@ function init (client, d3) { }; function tooltipLeft () { - var windowWidth = $(client.tooltip).parent().parent().width(); + var windowWidth = $(client.tooltip.node()).parent().parent().width(); var left = d3.event.pageX + TOOLTIP_WIDTH < windowWidth ? d3.event.pageX : windowWidth - TOOLTIP_WIDTH - 10; return left + 'px'; } function hideTooltip () { - client.tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); + client.tooltip.style('opacity', 0); } // get the desired opacity for context chart based on the brush extent renderer.highlightBrushPoints = function highlightBrushPoints (data) { - if (client.latestSGV && data.mills >= chart().brush.extent()[0].getTime() && data.mills <= chart().brush.extent()[1].getTime()) { + var selectedRange = chart().createAdjustedRange(); + var from = selectedRange[0].getTime(); + var to = selectedRange[1].getTime(); + + if (client.latestSGV && data.mills >= from && data.mills <= to) { return chart().futureOpacity(data.mills - client.latestSGV.mills); } else { return 0.5; @@ -73,9 +82,17 @@ function init (client, d3) { var shownForecastPoints = _.filter(client.sbx.pluginBase.forecastPoints, function isShown (point) { return client.settings.showForecast.indexOf(point.info.type) > -1; }); - var maxForecastMills = _.max(_.map(shownForecastPoints, function(point) { return point.mills })); // limit lookahead to the same as lookback - var focusHoursAheadMills = chart().brush.extent()[1].getTime() + client.focusRangeMS; + var selectedRange = chart().createBrushedRange(); + var to = selectedRange[1].getTime(); + + var focusHoursAheadMills = to + client.focusRangeMS; + var maxForecastMills = focusHoursAheadMills; + + if (shownForecastPoints.length > 0) { + maxForecastMills = _.max(_.map(shownForecastPoints, function(point) { return point.mills })); + } + maxForecastMills = Math.min(focusHoursAheadMills, maxForecastMills); client.forecastTime = maxForecastMills > 0 ? maxForecastMills - client.sbx.lastSGVMills() : 0; focusData = focusData.concat(shownForecastPoints); @@ -85,17 +102,17 @@ function init (client, d3) { // selects all our data into data and uses date function to get current max date var focusCircles = chart().focus.selectAll('circle').data(focusData, client.entryToDate); - function prepareFocusCircles (sel) { + function updateFocusCircles (sel) { var badData = []; sel.attr('cx', function(d) { if (!d) { console.error('Bad data', d); - return chart().xScale(new Date(0)); + return chart().xScale(zeroDate); } else if (!d.mills) { console.error('Bad data, no mills', d); - return chart().xScale(new Date(0)); + return chart().xScale(zeroDate); } else { - return chart().xScale(new Date(d.mills)); + return chart().xScale(getOrAddDate(d)); } }) .attr('cy', function(d) { @@ -107,17 +124,12 @@ function init (client, d3) { return chart().yScale(scaled); } }) - .attr('fill', function(d) { - return d.type === 'forecast' ? 'none' : d.color; - }) .attr('opacity', function(d) { - return d.noFade || !client.latestSGV ? 100 : chart().futureOpacity(d.mills - client.latestSGV.mills); - }) - .attr('stroke-width', function(d) { - return d.type === 'mbg' ? 2 : d.type === 'forecast' ? 2 : 0; - }) - .attr('stroke', function(d) { - return (d.type === 'mbg' ? 'white' : d.color); + if (d.noFade) { + return null; + } else { + return !client.latestSGV ? 1 : chart().futureOpacity(d.mills - client.latestSGV.mills); + } }) .attr('r', function(d) { return dotRadius(d.type); @@ -130,6 +142,21 @@ function init (client, d3) { return sel; } + function prepareFocusCircles (sel) { + updateFocusCircles(sel) + .attr('fill', function(d) { + return d.type === 'forecast' ? 'none' : d.color; + }) + .attr('stroke-width', function(d) { + return d.type === 'mbg' ? 2 : d.type === 'forecast' ? 2 : 0; + }) + .attr('stroke', function(d) { + return (d.type === 'mbg' ? 'white' : d.color); + }); + + return sel; + } + function focusCircleTooltip (d) { if (d.type !== 'sgv' && d.type !== 'mbg' && d.type !== 'forecast') { return; @@ -149,19 +176,19 @@ function init (client, d3) { var rawbgInfo = getRawbgInfo(); - client.tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + client.tooltip.style('opacity', .9); client.tooltip.html('' + translate('BG') + ': ' + client.sbx.scaleEntry(d) + (d.type === 'mbg' ? '
' + translate('Device') + ': ' + d.device : '') + (d.type === 'forecast' && d.forecastType ? '
' + translate('Forecast Type') + ': ' + d.forecastType : '') + (rawbgInfo.value ? '
' + translate('Raw BG') + ': ' + rawbgInfo.value : '') + (rawbgInfo.noise ? '
' + translate('Noise') + ': ' + rawbgInfo.noise : '') + - '
' + translate('Time') + ': ' + client.formatTime(new Date(d.mills))) + '
' + translate('Time') + ': ' + client.formatTime(getOrAddDate(d))) .style('left', tooltipLeft()) .style('top', (d3.event.pageY + 15) + 'px'); } // if already existing then transition each circle to its new position - prepareFocusCircles(focusCircles.transition()); + updateFocusCircles(focusCircles); // if new circle then just display prepareFocusCircles(focusCircles.enter().append('circle')) @@ -215,7 +242,7 @@ function init (client, d3) { } } - return '' + translate('Time') + ': ' + client.formatTime(new Date(d.mills)) + '
' + + return '' + translate('Time') + ': ' + client.formatTime(getOrAddDate(d)) + '
' + (d.eventType ? '' + translate('Treatment type') + ': ' + translate(client.careportal.resolveEventName(d.eventType)) + '
' : '') + (d.reason ? '' + translate('Reason') + ': ' + translate(d.reason) + '
' : '') + (d.glucose ? '' + translate('BG') + ': ' + d.glucose + (d.glucoseType ? ' (' + translate(d.glucoseType) + ')' : '') + '
' : '') + @@ -229,7 +256,7 @@ function init (client, d3) { } function announcementTooltip (d) { - return '' + translate('Time') + ': ' + client.formatTime(new Date(d.mills)) + '
' + + return '' + translate('Time') + ': ' + client.formatTime(getOrAddDate(d)) + '
' + (d.eventType ? '' + translate('Announcement') + '
' : '') + (d.notes && d.notes.length > 1 ? '' + translate('Message') + ': ' + d.notes + '
' : '') + (d.enteredBy ? '' + translate('Entered By') + ': ' + d.enteredBy + '
' : ''); @@ -240,7 +267,7 @@ function init (client, d3) { //NOTE: treatments with insulin or carbs are drawn by drawTreatment() // bind up the focus chart data to an array of circles - var treatCircles = chart().focus.selectAll('treatment-dot').data(client.ddata.treatments.filter(function(treatment) { + var treatCircles = chart().focus.selectAll('.treatment-dot').data(client.ddata.treatments.filter(function(treatment) { var notCarbsOrInsulin = !treatment.carbs && !treatment.insulin; var notTempOrProfile = !_.includes(['Temp Basal', 'Profile Switch', 'Combo Bolus', 'Temporary Target'], treatment.eventType); @@ -253,7 +280,22 @@ function init (client, d3) { })); return notCarbsOrInsulin && !treatment.duration && treatment.durationType !== 'indefinite' && notTempOrProfile && notOpenAPSSpam; - })); + }), function (d) { return d._id; }); + + function updateTreatCircles (sel) { + + sel.attr('cx', function(d) { + return chart().xScale(getOrAddDate(d)); + }) + .attr('cy', function(d) { + return chart().yScale(client.sbx.scaleEntry(d)); + }) + .attr('r', function() { + return dotRadius('mbg'); + }); + + return sel; + } function prepareTreatCircles (sel) { function strokeColor (d) { @@ -276,15 +318,7 @@ function init (client, d3) { return color; } - sel.attr('cx', function(d) { - return chart().xScale(new Date(d.mills)); - }) - .attr('cy', function(d) { - return chart().yScale(client.sbx.scaleEntry(d)); - }) - .attr('r', function() { - return dotRadius('mbg'); - }) + updateTreatCircles(sel) .attr('stroke-width', 2) .attr('stroke', strokeColor) .attr('fill', fillColor); @@ -293,18 +327,21 @@ function init (client, d3) { } // if already existing then transition each circle to its new position - prepareTreatCircles(treatCircles.transition()); + updateTreatCircles(treatCircles); // if new circle then just display prepareTreatCircles(treatCircles.enter().append('circle')) + .attr('class', 'treatment-dot') .on('mouseover', function(d) { - client.tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + client.tooltip.style('opacity', .9); client.tooltip.html(d.isAnnouncement ? announcementTooltip(d) : treatmentTooltip(d)) .style('left', tooltipLeft()) .style('top', (d3.event.pageY + 15) + 'px'); }) .on('mouseout', hideTooltip); + treatCircles.exit().remove(); + var durationTreatments = client.ddata.treatments.filter(function(treatment) { return !treatment.carbs && !treatment.insulin && (treatment.duration || treatment.durationType !== undefined) && !_.includes(['Temp Basal', 'Profile Switch', 'Combo Bolus', 'Temporary Target'], treatment.eventType); @@ -342,26 +379,26 @@ function init (client, d3) { if (d.eventType === 'Temporary Target') { top = d.targetTop === d.targetBottom ? d.targetTop + rectHeight(d) : d.targetTop; } - return 'translate(' + chart().xScale(new Date(d.mills)) + ',' + chart().yScale(utils.scaleMgdl(top)) + ')'; + return 'translate(' + chart().xScale(getOrAddDate(d)) + ',' + chart().yScale(utils.scaleMgdl(top)) + ')'; } function treatmentRectWidth (d) { if (d.durationType === "indefinite") { - return chart().xScale(chart().xScale.domain()[1].getTime()) - chart().xScale(new Date(d.mills)); + return chart().xScale(chart().xScale.domain()[1].getTime()) - chart().xScale(getOrAddDate(d)); } else { - return chart().xScale(new Date(d.mills + times.mins(d.duration).msecs)) - chart().xScale(new Date(d.mills)); + return chart().xScale(new Date(d.mills + times.mins(d.duration).msecs)) - chart().xScale(getOrAddDate(d)); } } function treatmentTextTransform (d) { if (d.durationType === "indefinite") { var offset = 0; - if (chart().xScale(new Date(d.mills)) < chart().xScale(chart().xScale.domain()[0].getTime())) { - offset = chart().xScale(nowDate) - chart().xScale(new Date(d.mills)); + if (chart().xScale(getOrAddDate(d)) < chart().xScale(chart().xScale.domain()[0].getTime())) { + offset = chart().xScale(nowDate) - chart().xScale(getOrAddDate(d)); } return 'translate(' + offset + ',' + 10 + ')'; } else { - return 'translate(' + (chart().xScale(new Date(d.mills + times.mins(d.duration).msecs)) - chart().xScale(new Date(d.mills))) / 2 + ',' + 10 + ')'; + return 'translate(' + (chart().xScale(new Date(d.mills + times.mins(d.duration).msecs)) - chart().xScale(getOrAddDate(d))) / 2 + ',' + 10 + ')'; } } @@ -377,8 +414,8 @@ function init (client, d3) { } // if transitioning, update rect text, position, and width - var rectUpdates = treatRects.transition() - rectUpdates.attr('transform', rectTranslate) + var rectUpdates = treatRects; + rectUpdates.attr('transform', rectTranslate); rectUpdates.select('text') .text(treatmentText) @@ -393,7 +430,7 @@ function init (client, d3) { .attr('class', 'g-duration') .attr('transform', rectTranslate) .on('mouseover', function(d) { - client.tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + client.tooltip.style('opacity', .9); client.tooltip.html(d.isAnnouncement ? announcementTooltip(d) : treatmentTooltip(d)) .style('left', tooltipLeft()) .style('top', (d3.event.pageY + 15) + 'px'); @@ -430,7 +467,7 @@ function init (client, d3) { function prepareContextCircles (sel) { var badData = []; - sel.attr('cx', function(d) { return chart().xScale2(new Date(d.mills)); }) + sel.attr('cx', function(d) { return chart().xScale2(getOrAddDate(d)); }) .attr('cy', function(d) { var scaled = client.sbx.scaleEntry(d); if (isNaN(scaled)) { @@ -441,7 +478,7 @@ function init (client, d3) { } }) .attr('fill', function(d) { return d.color; }) - .style('opacity', function(d) { return renderer.highlightBrushPoints(d) }) + //.style('opacity', function(d) { return renderer.highlightBrushPoints(d) }) .attr('stroke-width', function(d) { return d.type === 'mbg' ? 2 : 0; }) .attr('stroke', function() { return 'white'; }) .attr('r', function(d) { return d.type === 'mbg' ? 4 : 2; }); @@ -454,7 +491,7 @@ function init (client, d3) { } // if already existing then transition each circle to its new position - prepareContextCircles(contextCircles.transition()); + prepareContextCircles(contextCircles); // if new circle then just display prepareContextCircles(contextCircles.enter().append('circle')); @@ -538,7 +575,7 @@ function init (client, d3) { arc_data[4].element = translate(treatment.status); } - var arc = d3.svg.arc() + var arc = d3.arc() .innerRadius(function(d) { return 5 * d.inner; }) @@ -595,8 +632,8 @@ function init (client, d3) { glucose = Math.round(glucose * decimals) / decimals; } - client.tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - client.tooltip.html('' + translate('Time') + ': ' + client.formatTime(new Date(treatment.mills)) + '
' + '' + translate('Treatment type') + ': ' + translate(client.careportal.resolveEventName(treatment.eventType)) + '
' + + client.tooltip.style('opacity', .9); + client.tooltip.html('' + translate('Time') + ': ' + client.formatTime(getOrAddDate(treatment)) + '
' + '' + translate('Treatment type') + ': ' + translate(client.careportal.resolveEventName(treatment.eventType)) + '
' + (treatment.carbs ? '' + translate('Carbs') + ': ' + treatment.carbs + '
' : '') + (treatment.protein ? '' + translate('Protein') + ': ' + treatment.protein + '
' : '') + (treatment.fat ? '' + translate('Fat') + ': ' + treatment.fat + '
' : '') + @@ -617,12 +654,12 @@ function init (client, d3) { var insulinRect = { x: 0, y: 0, width: 0, height: 0 }; var carbsRect = { x: 0, y: 0, width: 0, height: 0 }; var operation; - renderer.drag = d3.behavior.drag() - .on('dragstart', function() { + renderer.drag = d3.drag() + .on('start', function() { //console.log(treatment); - var windowWidth = $(client.tooltip).parent().parent().width(); + var windowWidth = $(client.tooltip.node()).parent().parent().width(); var left = d3.event.x + TOOLTIP_WIDTH < windowWidth ? d3.event.x : windowWidth - TOOLTIP_WIDTH - 10; - client.tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9) + client.tooltip.style('opacity', .9) .style('left', left + 'px') .style('top', (d3.event.pageY ? d3.event.pageY + 15 : 40) + 'px'); @@ -633,29 +670,25 @@ function init (client, d3) { , height: chart().yScale(chart().yScale.domain()[0]) }; chart().drag.append('rect') - .attr({ - class: 'drag-droparea' - , x: deleteRect.x - , y: deleteRect.y - , width: deleteRect.width - , height: deleteRect.height - , fill: 'red' - , opacity: 0.4 - , rx: 10 - , ry: 10 - }); + .attr('class', 'drag-droparea') + .attr('x', deleteRect.x) + .attr('y', deleteRect.y) + .attr('width', deleteRect.width) + .attr('height', deleteRect.height) + .attr('fill', 'red') + .attr('opacity', 0.4) + .attr('rx', 10) + .attr('ry', 10); chart().drag.append('text') - .attr({ - class: 'drag-droparea' - , x: deleteRect.x + deleteRect.width / 2 - , y: deleteRect.y + deleteRect.height / 2 - , 'font-size': 15 - , 'font-weight': 'bold' - , fill: 'red' - , 'text-anchor': 'middle' - , dy: '.35em' - , transform: 'rotate(-90 ' + (deleteRect.x + deleteRect.width / 2) + ',' + (deleteRect.y + deleteRect.height / 2) + ')' - }) + .attr('class', 'drag-droparea') + .attr('x', deleteRect.x + deleteRect.width / 2) + .attr('y', deleteRect.y + deleteRect.height / 2) + .attr('font-size', 15) + .attr('font-weight', 'bold') + .attr('fill', 'red') + .attr('text-anchor', 'middle') + .attr('dy', '.35em') + .attr('transform', 'rotate(-90 ' + (deleteRect.x + deleteRect.width / 2) + ',' + (deleteRect.y + deleteRect.height / 2) + ')') .text(translate('Remove')); if (treatment.insulin && treatment.carbs) { @@ -672,52 +705,44 @@ function init (client, d3) { , height: 50 }; chart().drag.append('rect') - .attr({ - class: 'drag-droparea' - , x: carbsRect.x - , y: carbsRect.y - , width: carbsRect.width - , height: carbsRect.height - , fill: 'white' - , opacity: 0.4 - , rx: 10 - , ry: 10 - }); + .attr('class', 'drag-droparea') + .attr('x', carbsRect.x) + .attr('y', carbsRect.y) + .attr('width', carbsRect.width) + .attr('height', carbsRect.height) + .attr('fill', 'white') + .attr('opacitys', 0.4) + .attr('rx', 10) + .attr('ry', 10); chart().drag.append('text') - .attr({ - class: 'drag-droparea' - , x: carbsRect.x + carbsRect.width / 2 - , y: carbsRect.y + carbsRect.height / 2 - , 'font-size': 15 - , 'font-weight': 'bold' - , fill: 'white' - , 'text-anchor': 'middle' - , dy: '.35em' - }) + .attr('class', 'drag-droparea') + .attr('x', carbsRect.x + carbsRect.width / 2) + .attr('y', carbsRect.y + carbsRect.height / 2) + .attr('font-size', 15) + .attr('font-weight', 'bold') + .attr('fill', 'white') + .attr('text-anchor', 'middle') + .attr('dy', '.35em') .text(translate('Move carbs')); chart().drag.append('rect') - .attr({ - class: 'drag-droparea' - , x: insulinRect.x - , y: insulinRect.y - , width: insulinRect.width - , height: insulinRect.height - , fill: '#0099ff' - , opacity: 0.4 - , rx: 10 - , ry: 10 - }); + .attr('class', 'drag-droparea') + .attr('x', insulinRect.x) + .attr('y', insulinRect.y) + .attr('width', insulinRect.width) + .attr('height', insulinRect.height) + .attr('fill', '#0099ff') + .attr('opacity', 0.4) + .attr('rx', 10) + .attr('ry', 10); chart().drag.append('text') - .attr({ - class: 'drag-droparea' - , x: insulinRect.x + insulinRect.width / 2 - , y: insulinRect.y + insulinRect.height / 2 - , 'font-size': 15 - , 'font-weight': 'bold' - , fill: '#0099ff' - , 'text-anchor': 'middle' - , dy: '.35em' - }) + .attr('class', 'drag-droparea') + .attr('x', insulinRect.x + insulinRect.width / 2) + .attr('y', insulinRect.y + insulinRect.height / 2) + .attr('font-size', 15) + .attr('font-weight', 'bold') + .attr('fill', '#0099ff') + .attr('text-anchor', 'middle') + .attr('dy', '.35em') .text(translate('Move insulin')); } @@ -727,7 +752,7 @@ function init (client, d3) { }) .on('drag', function() { //console.log(d3.event); - client.tooltip.transition().style('opacity', .9); + client.tooltip.style('opacity', .9); var x = Math.min(Math.max(0, d3.event.x), chart().charts.attr('width')); var y = Math.min(Math.max(0, d3.event.y), chart().focusHeight); @@ -754,19 +779,16 @@ function init (client, d3) { chart().drag.selectAll('.arrow').remove(); chart().drag.append('line') - .attr({ - 'class': 'arrow' - , 'marker-end': 'url(#arrow)' - , 'x1': chart().xScale(new Date(treatment.mills)) - , 'y1': chart().yScale(client.sbx.scaleEntry(treatment)) - , 'x2': x - , 'y2': y - , 'stroke-width': 2 - , 'stroke': 'white' - }); - + .attr('class', 'arrow') + .attr('marker-end', 'url(#arrow)') + .attr('x1', chart().xScale(getOrAddDate(treatment))) + .attr('y1', chart().yScale(client.sbx.scaleEntry(treatment))) + .attr('x2', x) + .attr('y2', y) + .attr('stroke-width', 2) + .attr('stroke', 'white'); }) - .on('dragend', function() { + .on('end', function() { var newTreatment; chart().drag.selectAll('.drag-droparea').remove(); hideTooltip(); @@ -781,7 +803,7 @@ function init (client, d3) { } , function callback (result) { console.log(result); - chart().drag.selectAll('.arrow').transition().duration(5000).style('opacity', 0).remove(); + chart().drag.selectAll('.arrow').style('opacity', 0).remove(); } ); } else { @@ -798,7 +820,7 @@ function init (client, d3) { } , function callback (result) { console.log(result); - chart().drag.selectAll('.arrow').transition().duration(5000).style('opacity', 0).remove(); + chart().drag.selectAll('.arrow').style('opacity', 0).remove(); } ); } else { @@ -815,7 +837,7 @@ function init (client, d3) { } , function callback (result) { console.log(result); - chart().drag.selectAll('.arrow').transition().duration(5000).style('opacity', 0).remove(); + chart().drag.selectAll('.arrow').style('opacity', 0).remove(); } ); } else { @@ -831,7 +853,7 @@ function init (client, d3) { } , function callback (result) { console.log(result); - chart().drag.selectAll('.arrow').transition().duration(5000).style('opacity', 0).remove(); + chart().drag.selectAll('.arrow').style('opacity', 0).remove(); } ); } else { @@ -859,7 +881,7 @@ function init (client, d3) { } , function callback (result) { console.log(result); - chart().drag.selectAll('.arrow').transition().duration(5000).style('opacity', 0).remove(); + chart().drag.selectAll('.arrow').style('opacity', 0).remove(); } ); } else { @@ -887,7 +909,7 @@ function init (client, d3) { } , function callback (result) { console.log(result); - chart().drag.selectAll('.arrow').transition().duration(5000).style('opacity', 0).remove(); + chart().drag.selectAll('.arrow').style('opacity', 0).remove(); } ); } else { @@ -903,7 +925,7 @@ function init (client, d3) { .enter() .append('g') .attr('class', 'draggable-treatment') - .attr('transform', 'translate(' + chart().xScale(new Date(treatment.mills)) + ', ' + chart().yScale(client.sbx.scaleEntry(treatment)) + ')') + .attr('transform', 'translate(' + chart().xScale(getOrAddDate(treatment)) + ', ' + chart().yScale(client.sbx.scaleEntry(treatment)) + ')') .on('mouseover', treatmentTooltip) .on('mouseout', hideTooltip); if (client.editMode) { @@ -985,7 +1007,7 @@ function init (client, d3) { //when the tests are run window isn't available var innerWidth = window && window.innerWidth || -1; // don't render the treatment if it's not visible - if (Math.abs(chart().xScale(new Date(treatment.mills))) > innerWidth) { + if (Math.abs(chart().xScale(getOrAddDate(treatment))) > innerWidth) { return; } @@ -1009,8 +1031,9 @@ function init (client, d3) { var basalareadata = []; var tempbasalareadata = []; var comboareadata = []; - var from = chart().brush.extent()[0].getTime(); - var to = Math.max(chart().brush.extent()[1].getTime(), client.sbx.time) + client.forecastTime; + var selectedRange = chart().createAdjustedRange(); + var from = selectedRange[0].getTime(); + var to = selectedRange[1].getTime(); var date = from; var lastbasal = 0; @@ -1069,16 +1092,16 @@ function init (client, d3) { chart().basals.selectAll('.tempbasalarea').remove().data(tempbasalareadata); chart().basals.selectAll('.comboarea').remove().data(comboareadata); - var valueline = d3.svg.line() - .interpolate('step-after') + var valueline = d3.line() .x(function(d) { return chart().xScaleBasals(d.d); }) - .y(function(d) { return chart().yScaleBasals(d.b); }); + .y(function(d) { return chart().yScaleBasals(d.b); }) + .curve(d3.curveStepAfter); - var area = d3.svg.area() - .interpolate('step-after') + var area = d3.area() .x(function(d) { return chart().xScaleBasals(d.d); }) .y0(chart().yScaleBasals(0)) - .y1(function(d) { return chart().yScaleBasals(d.b); }); + .y1(function(d) { return chart().yScaleBasals(d.b); }) + .curve(d3.curveStepAfter); var g = chart().basals.append('g'); @@ -1149,7 +1172,7 @@ function init (client, d3) { } function profileTooltip (d) { - return '' + translate('Time') + ': ' + client.formatTime(new Date(d.mills)) + '
' + + return '' + translate('Time') + ': ' + client.formatTime(getOrAddDate(d)) + '
' + (d.eventType ? '' + translate('Treatment type') + ': ' + translate(client.careportal.resolveEventName(d.eventType)) + '
' : '') + (d.endprofile ? '' + translate('End of profile') + ': ' + d.endprofile + '
' : '') + (d.profile ? '' + translate('Profile') + ': ' + d.profile + '
' : '') + @@ -1159,8 +1182,9 @@ function init (client, d3) { } // calculate position of profile on left side - var from = chart().brush.extent()[0].getTime(); - var to = chart().brush.extent()[1].getTime(); + var selectedRange = chart().createAdjustedRange(); + var from = selectedRange[0].getTime(); + var to = selectedRange[1].getTime(); var mult = (to - from) / times.hours(24).msecs; from += times.mins(20 * mult).msecs; @@ -1199,8 +1223,7 @@ function init (client, d3) { return ret; }; - treatProfiles.transition().duration(0) - .attr('transform', function(t) { + treatProfiles.attr('transform', function(t) { // change text of record on left side return 'rotate(-90,' + chart().xScale(t.mills) + ',' + chart().yScaleBasals(topOfText) + ') ' + 'translate(' + chart().xScale(t.mills) + ',' + chart().yScaleBasals(topOfText) + ')'; @@ -1220,7 +1243,7 @@ function init (client, d3) { }) .text(generateText) .on('mouseover', function(d) { - client.tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + client.tooltip.style('opacity', .9); client.tooltip.html(profileTooltip(d)) .style('left', (d3.event.pageX) + 'px') .style('top', (d3.event.pageY + 15) + 'px'); diff --git a/lib/data/ddata.js b/lib/data/ddata.js index 1912ca4a6ae..0251ec17d99 100644 --- a/lib/data/ddata.js +++ b/lib/data/ddata.js @@ -32,39 +32,11 @@ function init () { }); }; - ddata.splitRecent = function splitRecent (time, cutoff, max, treatmentsToo) { - var result = { - first: {} - , rest: {} - }; - - function recent (item) { - return item.mills >= time - cutoff; - } - - function filterMax (item) { - return item.mills >= time - max; - } - - function partition (field, filter) { - var data; - if (filter) { - data = ddata[field].filter(filterMax); - } else { - data = ddata[field]; - } - - var parts = _.partition(data, recent); - result.first[field] = parts[0]; - result.rest[field] = parts[1]; - } - - partition('treatments', treatmentsToo ? filterMax : false); - - result.first.devicestatus = ddata.recentDeviceStatus(time); - - result.first.sgvs = ddata.sgvs.filter(filterMax); - result.first.cals = ddata.cals; + ddata.dataWithRecentStatuses = function dataWithRecentStatuses() { + var results = {}; + results.devicestatus = ddata.recentDeviceStatus(Date.now()); + results.sgvs = ddata.sgvs; + results.cals = ddata.cals; var profiles = _.cloneDeep(ddata.profiles); if (profiles && profiles[0]) { @@ -74,17 +46,14 @@ function init () { } }) } - result.first.profiles = profiles; - - result.rest.mbgs = ddata.mbgs.filter(filterMax); - result.rest.food = ddata.food; - result.rest.activity = ddata.activity; + results.profiles = profiles; + results.mbgs = ddata.mbgs; + results.food = ddata.food; + results.treatments = ddata.treatments; - console.log('results.first size', JSON.stringify(result.first).length, 'bytes'); - console.log('results.rest size', JSON.stringify(result.rest).length, 'bytes'); + return results; - return result; - }; + } ddata.recentDeviceStatus = function recentDeviceStatus (time) { diff --git a/lib/language.js b/lib/language.js index bd84df3acd0..cb56d3f500f 100644 --- a/lib/language.js +++ b/lib/language.js @@ -14048,31 +14048,37 @@ function init() { 'Protein': { fi: 'Proteiini' , de: 'Protein' + ,hr: 'Proteini' }, 'Fat': { fi: 'Rasva' , de: 'Fett' + ,hr: 'Masti' }, 'Protein average': { fi: 'Proteiini keskiarvo' , de: 'Proteine Durchschnitt' + ,hr: 'Prosjek proteina' }, 'Fat average': { fi: 'Rasva keskiarvo' , de: 'Fett Durchschnitt' - + ,hr: 'Prosjek masti' }, 'Total carbs': { fi: 'Hiilihydraatit yhteensä' , de: 'Kohlenhydrate gesamt' + ,hr: 'Ukupno ugh' }, 'Total protein': { fi: 'Proteiini yhteensä' , de: 'Protein gesamt' + ,hr: 'Ukupno proteini' }, 'Total fat': { fi: 'Rasva yhteensä' , de: 'Fett gesamt' + ,hr: 'Ukupno masti' } }; diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index bc769197c2b..5020f2e6458 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -41,14 +41,16 @@ function init (ctx) { } var devicestatusCOB = cob.lastCOBDeviceStatus(devicestatus, time); + var result = devicestatusCOB; - var treatmentCOB = (treatments !== undefined && treatments.length) ? cob.fromTreatments(treatments, devicestatus, profile, time, spec_profile) : {}; + const TEN_MINUTES = 10 * 60 * 1000; + + if (_.isEmpty(result) || _.isNil(result.cob) || (Date.now() - result.mills) > TEN_MINUTES) { + + var treatmentCOB = (treatments !== undefined && treatments.length) ? cob.fromTreatments(treatments, devicestatus, profile, time, spec_profile) : {}; - var result = devicestatusCOB; - if (_.isEmpty(result)) { result = treatmentCOB; result.source = 'Care Portal'; - } else if (treatmentCOB) { result.treatmentCOB = treatmentCOB; } diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index a6f0e77e455..87102d1831d 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -84,9 +84,9 @@ function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { }).join('
\n'); pill.mouseover(function pillMouseover (event) { - tooltip.transition().duration(200).style('opacity', .9); + tooltip.style('opacity', .9); - var windowWidth = $(tooltip).parent().parent().width(); + var windowWidth = $(tooltip.node()).parent().parent().width(); var left = event.pageX + TOOLTIP_WIDTH < windowWidth ? event.pageX : windowWidth - TOOLTIP_WIDTH - 10; tooltip.html(html) .style('left', left + 'px') @@ -94,9 +94,7 @@ function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { }); pill.mouseout(function pillMouseout ( ) { - tooltip.transition() - .duration(200) - .style('opacity', 0); + tooltip.style('opacity', 0); }); } else { pill.off('mouseover'); diff --git a/lib/profilefunctions.js b/lib/profilefunctions.js index 3c7d1337282..5826e6108b4 100644 --- a/lib/profilefunctions.js +++ b/lib/profilefunctions.js @@ -4,16 +4,20 @@ var _ = require('lodash'); var moment = require('moment-timezone'); var c = require('memory-cache'); var times = require('./times'); -var crypto = require('crypto'); - -var cacheTTL = 600; +var cacheTTL = 5000; var prevBasalTreatment = null; +var cache = new c.Cache(); function init (profileData) { var profile = {}; - var cache = new c.Cache(); + + profile.clear = function clear() { + cache.clear(); + profile.data = null; + prevBasalTreatment = null; + } profile.loadData = function loadData (profileData) { if (profileData && profileData.length) { @@ -71,6 +75,15 @@ function init (profileData) { profile.getValueByTime = function getValueByTime (time, valueType, spec_profile) { if (!time) { time = Date.now(); } + //round to the minute for better caching + var minuteTime = Math.round(time / 60000) * 60000; + var cacheKey = (minuteTime + valueType + spec_profile); + var returnValue = cache.get(cacheKey); + + if (returnValue) { + return returnValue; + } + // CircadianPercentageProfile support var timeshift = 0; var percentage = 100; @@ -83,16 +96,6 @@ function init (profileData) { var offset = timeshift % 24; time = time + offset * times.hours(offset).msecs; - //round to the minute for better caching - var minuteTime = Math.round(time / 60000) * 60000; - - var cacheKey = (minuteTime + valueType + spec_profile + profile.profiletreatments_hash); - var returnValue = cache.get(cacheKey); - - if (returnValue) { - return returnValue; - } - var valueContainer = profile.getCurrentProfile(time, spec_profile)[valueType]; // Assumes the timestamps are in UTC @@ -139,10 +142,22 @@ function init (profileData) { }; profile.getCurrentProfile = function getCurrentProfile (time, spec_profile) { - time = time || new Date().getTime(); + + time = time || Date.now(); + var minuteTime = Math.round(time / 60000) * 60000; + var cacheKey = ("profile" + minuteTime + spec_profile); + var returnValue = cache.get(cacheKey); + + if (returnValue) { + return returnValue; + } + var data = profile.hasData() ? profile.data[0] : null; var timeprofile = spec_profile || profile.activeProfileToTime(time); - return data && data.store[timeprofile] ? data.store[timeprofile] : {}; + returnValue = data && data.store[timeprofile] ? data.store[timeprofile] : {}; + + cache.put(cacheKey, returnValue, cacheTTL); + return returnValue; }; profile.getUnits = function getUnits (spec_profile) { @@ -204,9 +219,8 @@ function init (profileData) { }); profile.combobolustreatments = combobolustreatments || []; - profile.profiletreatments_hash = crypto.createHash('sha1').update(JSON.stringify(profile.profiletreatments)).digest('hex'); - profile.tempbasaltreatments_hash = crypto.createHash('sha1').update(JSON.stringify(profile.tempbasaltreatments)).digest('hex'); - profile.combobolustreatments_hash = crypto.createHash('sha1').update(JSON.stringify(profile.combobolustreatments)).digest('hex'); + + cache.clear(); }; profile.activeProfileToTime = function activeProfileToTime (time) { @@ -223,9 +237,10 @@ function init (profileData) { }; profile.activeProfileTreatmentToTime = function activeProfileTreatmentToTime (time) { - var cacheKey = 'profile' + time + profile.profiletreatments_hash; - //var returnValue = profile.timeValueCache[cacheKey]; - var returnValue; + + var minuteTime = Math.round(time / 60000) * 60000; + var cacheKey = 'profileCache' + minuteTime; + var returnValue = cache.get(cacheKey); if (returnValue) { return returnValue; @@ -312,7 +327,8 @@ function init (profileData) { profile.getTempBasal = function getTempBasal (time, spec_profile) { - var cacheKey = 'basal' + time + profile.tempbasaltreatments_hash + profile.combobolustreatments_hash + profile.profiletreatments_hash + spec_profile; + var minuteTime = Math.round(time / 60000) * 60000; + var cacheKey = 'basalCache' + minuteTime + spec_profile; var returnValue = cache.get(cacheKey); if (returnValue) { diff --git a/lib/report_plugins/calibrations.js b/lib/report_plugins/calibrations.js index 958dd06c906..baf16a47a27 100644 --- a/lib/report_plugins/calibrations.js +++ b/lib/report_plugins/calibrations.js @@ -146,20 +146,16 @@ calibrations.report = function report_calibrations (datastorage, sorteddaystosho calibration_context = charts.append('g'); // define the parts of the axis that aren't dependent on width or height - xScale2 = d3.scale.linear() + xScale2 = d3.scaleLinear() .domain([0, maxBG]); - yScale2 = d3.scale.linear() + yScale2 = d3.scaleLinear() .domain([0, 400000]); - var xAxis2 = d3.svg.axis() - .scale(xScale2) - .ticks(10) - .orient('bottom'); + var xAxis2 = d3.axisBottom(xScale2) + .ticks(10); - var yAxis2 = d3.svg.axis() - .scale(yScale2) - .orient('left'); + var yAxis2 = d3.axisLeft(yScale2); // get current data range var dataRange = [0, maxBG]; diff --git a/lib/report_plugins/daytoday.js b/lib/report_plugins/daytoday.js index b4b1d65167b..4fde5658b19 100644 --- a/lib/report_plugins/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -82,8 +82,6 @@ daytoday.report = function report_daytoday (datastorage, sorteddaystoshow, optio var report_plugins = Nightscout.report_plugins; var scaledTreatmentBG = report_plugins.utils.scaledTreatmentBG; - var TOOLTIP_TRANS_MS = 300; - var padding = { top: 15, right: 22, bottom: 30, left: 35 }; var tddSum = 0; @@ -170,37 +168,33 @@ daytoday.report = function report_daytoday (datastorage, sorteddaystoshow, optio context = charts.append('g'); // define the parts of the axis that aren't dependent on width or height - xScale2 = d3.time.scale() + xScale2 = d3.scaleTime() .domain(d3.extent(data.sgv, dateFn)); if (options.scale === report_plugins.consts.SCALE_LOG) { - yScale2 = d3.scale.log() + yScale2 = d3.scaleLog() .domain([client.utils.scaleMgdl(options.basal ? 30 : 36), client.utils.scaleMgdl(420)]); } else { - yScale2 = d3.scale.linear() + yScale2 = d3.scaleLinear() .domain([client.utils.scaleMgdl(options.basal ? -40 : 36), client.utils.scaleMgdl(420)]); } // allow insulin to be negative (when plotting negative IOB) - yInsulinScale = d3.scale.linear() + yInsulinScale = d3.scaleLinear() .domain([-2 * options.maxInsulinValue, 2 * options.maxInsulinValue]); - yCarbsScale = d3.scale.linear() + yCarbsScale = d3.scaleLinear() .domain([0, options.maxCarbsValue * 1.25]); - yScaleBasals = d3.scale.linear(); + yScaleBasals = d3.scaleLinear(); - xAxis2 = d3.svg.axis() - .scale(xScale2) + xAxis2 = d3.axisBottom(xScale2) .tickFormat(timeTicks) - .ticks(24) - .orient('bottom'); + .ticks(24); - yAxis2 = d3.svg.axis() - .scale(yScale2) + yAxis2 = d3.axisLeft(yScale2) .tickFormat(d3.format('d')) - .tickValues(tickValues) - .orient('left'); + .tickValues(tickValues); // get current data range var dataRange = d3.extent(data.sgv, dateFn); @@ -294,7 +288,7 @@ daytoday.report = function report_daytoday (datastorage, sorteddaystoshow, optio }) .on('mouseover', function(d) { if (options.openAps && d.openaps) { - client.tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + client.tooltip.style('opacity', .9); var text = 'BG: ' + d.openaps.suggested.bg + ', ' + d.openaps.suggested.reason + (d.openaps.suggested.mealAssist ? ' Meal Assist: ' + d.openaps.suggested.mealAssist : ''); @@ -602,13 +596,13 @@ daytoday.report = function report_daytoday (datastorage, sorteddaystoshow, optio yScaleBasals.domain([basalMax, 0]); - var valueline = d3.svg.line() - .interpolate('step-after') + var valueline = d3.line() + .curve(d3.curveStepAfter) .x(function(d) { return xScale2(d.d) + padding.left; }) .y(function(d) { return yScaleBasals(d.b) + padding.top; }); - var area = d3.svg.area() - .interpolate('step-after') + var area = d3.area() + .curve(d3.curveStepAfter) .x(function(d) { return xScale2(d.d) + padding.left; }) .y0(yScaleBasals(0) + padding.top) .y1(function(d) { return yScaleBasals(d.b) + padding.top; }); @@ -931,9 +925,9 @@ daytoday.report = function report_daytoday (datastorage, sorteddaystoshow, optio var height = 120; var radius = Math.min(width, height) / 2; - var color = d3.scale.ordinal().range([basalcolor, boluscolor]); + var color = d3.scaleOrdinal().range([basalcolor, boluscolor]); - var labelArc = d3.svg.arc() + var labelArc = d3.arc() .outerRadius(radius / 2) .innerRadius(radius / 2); @@ -945,10 +939,11 @@ daytoday.report = function report_daytoday (datastorage, sorteddaystoshow, optio .attr('transform', 'translate(' + (width / 2) + ',' + (height / 2) + ')'); - var arc = d3.svg.arc() + var arc = d3.arc() + .innerRadius(0) .outerRadius(radius); - var pie = d3.layout.pie() + var pie = d3.pie() .value(function(d) { return d.count; }) @@ -980,7 +975,7 @@ daytoday.report = function report_daytoday (datastorage, sorteddaystoshow, optio // Carbs pie chart - var carbscolor = d3.scale.ordinal().range(['red']); + var carbscolor = d3.scaleOrdinal().range(['red']); var carbsData = [ { label: translate('Carbs'), count: data.dailyCarbs } @@ -994,10 +989,10 @@ daytoday.report = function report_daytoday (datastorage, sorteddaystoshow, optio .attr('transform', 'translate(' + (width / 2) + ',' + (height / 2) + ')'); - var carbsarc = d3.svg.arc() + var carbsarc = d3.arc() .outerRadius(radius * data.dailyCarbs / options.maxDailyCarbsValue); - var carbspie = d3.layout.pie() + var carbspie = d3.pie() .value(function(d) { return d.count; }) @@ -1066,8 +1061,6 @@ daytoday.report = function report_daytoday (datastorage, sorteddaystoshow, optio } function hideTooltip () { - client.tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); + client.tooltip.style('opacity', 0); } }; diff --git a/lib/report_plugins/weektoweek.js b/lib/report_plugins/weektoweek.js index 3ff84a78639..8719af81542 100644 --- a/lib/report_plugins/weektoweek.js +++ b/lib/report_plugins/weektoweek.js @@ -79,8 +79,6 @@ weektoweek.report = function report_weektoweek(datastorage, sorteddaystoshow, op var client = Nightscout.client; var report_plugins = Nightscout.report_plugins; - var TOOLTIP_TRANS_MS = 300; - var padding = { top: 15, right: 22, bottom: 30, left: 35 }; var weekstoshow = [ ]; @@ -196,28 +194,24 @@ weektoweek.report = function report_weektoweek(datastorage, sorteddaystoshow, op context = charts.append('g'); // define the parts of the axis that aren't dependent on width or height - xScale2 = d3.time.scale() + xScale2 = d3.scaleTime() .domain(d3.extent(sgvData, dateFn)); if (options.weekscale === report_plugins.consts.SCALE_LOG) { - yScale2 = d3.scale.log() + yScale2 = d3.scaleLog() .domain([client.utils.scaleMgdl(36), client.utils.scaleMgdl(420)]); } else { - yScale2 = d3.scale.linear() + yScale2 = d3.scaleLinear() .domain([client.utils.scaleMgdl(36), client.utils.scaleMgdl(420)]); } - xAxis2 = d3.svg.axis() - .scale(xScale2) + xAxis2 = d3.axisBottom(xScale2) .tickFormat(timeTicks) - .ticks(24) - .orient('bottom'); + .ticks(24); - yAxis2 = d3.svg.axis() - .scale(yScale2) + yAxis2 = d3.axisLeft(yScale2) .tickFormat(d3.format('d')) - .tickValues(tickValues) - .orient('left'); + .tickValues(tickValues); // get current data range var dataRange = d3.extent(sgvData, dateFn); @@ -326,8 +320,6 @@ weektoweek.report = function report_weektoweek(datastorage, sorteddaystoshow, op } function hideTooltip ( ) { - client.tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); + client.tooltip.style('opacity', 0); } }; diff --git a/lib/server/websocket.js b/lib/server/websocket.js index 3946c129a5d..76276edffb2 100644 --- a/lib/server/websocket.js +++ b/lib/server/websocket.js @@ -447,20 +447,15 @@ function init (env, ctx, server) { filterTreatments = true; msecHistory = Math.min(new Date().getTime() - from, msecHistory); } - // send all data upon new connection - if (lastData && lastData.splitRecent) { - var split = lastData.splitRecent(Date.now(), times.hours(3).msecs, msecHistory, filterTreatments); + + if (lastData && lastData.dataWithRecentStatuses) { + let data = lastData.dataWithRecentStatuses(); + if (message.status) { - split.first.status = status(split.first.profiles); + data.status = status(data.profiles); } - //send out first chunk - socket.emit('dataUpdate', split.first); - - //then send out the rest - setTimeout(function sendTheRest() { - split.rest.delta = true; - socket.emit('dataUpdate', split.rest); - }, 500); + + socket.emit('dataUpdate', data); } } console.log(LOG_WS + 'Authetication ID: ', socket.client.id, ' client: ', clientType, ' history: ' + history); diff --git a/lib/settings.js b/lib/settings.js index 2d16abd0f2d..cd2aa3dc928 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -47,7 +47,6 @@ function init () { , secureHstsHeaderIncludeSubdomains: false , secureHstsHeaderPreload: false , secureCsp: false - , showClockClosebutton: true , deNormalizeDates: false }; @@ -69,7 +68,6 @@ function init () { , insecureUseHttp: mapTruthy , secureHstsHeader: mapTruthy , secureCsp: mapTruthy - , showClockClosebutton: mapTruthy , deNormalizeDates: mapTruthy }; diff --git a/lib/utils.js b/lib/utils.js index fe1778f8120..083c4284846 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -39,7 +39,8 @@ function init(ctx) { return '0'; } var mult = Math.pow(10,digits); - var fixed = Math.sign(value) * Math.round(Math.abs(value)*mult) / mult + var fixed = Math.sign(value) * Math.round(Math.abs(value)*mult) / mult; + if (isNaN(fixed)) return '0'; return String(fixed); }; diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 46108450a9c..554719ebdd2 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -3185,9 +3185,268 @@ "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=" }, "d3": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/d3/-/d3-3.5.17.tgz", - "integrity": "sha1-vEZ0gAQ3iyGjYMn8fPUjF5B2L7g=" + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-5.12.0.tgz", + "integrity": "sha512-flYVMoVuhPFHd9zVCe2BxIszUWqBcd5fvQGMNRmSiBrgdnh6Vlruh60RJQTouAK9xPbOB0plxMvBm4MoyODXNg==", + "requires": { + "d3-array": "1", + "d3-axis": "1", + "d3-brush": "1", + "d3-chord": "1", + "d3-collection": "1", + "d3-color": "1", + "d3-contour": "1", + "d3-dispatch": "1", + "d3-drag": "1", + "d3-dsv": "1", + "d3-ease": "1", + "d3-fetch": "1", + "d3-force": "1", + "d3-format": "1", + "d3-geo": "1", + "d3-hierarchy": "1", + "d3-interpolate": "1", + "d3-path": "1", + "d3-polygon": "1", + "d3-quadtree": "1", + "d3-random": "1", + "d3-scale": "2", + "d3-scale-chromatic": "1", + "d3-selection": "1", + "d3-shape": "1", + "d3-time": "1", + "d3-time-format": "2", + "d3-timer": "1", + "d3-transition": "1", + "d3-voronoi": "1", + "d3-zoom": "1" + } + }, + "d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + }, + "d3-axis": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz", + "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==" + }, + "d3-brush": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.3.tgz", + "integrity": "sha512-v8bbYyCFKjyCzFk/tdWqXwDykY8YWqhXYjcYxfILIit085VZOpj4XJKOMccTsvWxgzSLMJQg5SiqHjslsipEDg==", + "requires": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, + "d3-chord": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz", + "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==", + "requires": { + "d3-array": "1", + "d3-path": "1" + } + }, + "d3-collection": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", + "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" + }, + "d3-color": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.0.tgz", + "integrity": "sha512-TzNPeJy2+iEepfiL92LAAB7fvnp/dV2YwANPVHdDWmYMm23qIJBYww3qT8I8C1wXrmrg4UWs7BKc2tKIgyjzHg==" + }, + "d3-contour": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz", + "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==", + "requires": { + "d3-array": "^1.1.1" + } + }, + "d3-dispatch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.5.tgz", + "integrity": "sha512-vwKx+lAqB1UuCeklr6Jh1bvC4SZgbSqbkGBLClItFBIYH4vqDJCA7qfoy14lXmJdnBOdxndAMxjCbImJYW7e6g==" + }, + "d3-drag": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.4.tgz", + "integrity": "sha512-ICPurDETFAelF1CTHdIyiUM4PsyZLaM+7oIBhmyP+cuVjze5vDZ8V//LdOFjg0jGnFIZD/Sfmk0r95PSiu78rw==", + "requires": { + "d3-dispatch": "1", + "d3-selection": "1" + } + }, + "d3-dsv": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.1.1.tgz", + "integrity": "sha512-1EH1oRGSkeDUlDRbhsFytAXU6cAmXFzc52YUe6MRlPClmWb85MP1J5x+YJRzya4ynZWnbELdSAvATFW/MbxaXw==", + "requires": { + "commander": "2", + "iconv-lite": "0.4", + "rw": "1" + } + }, + "d3-ease": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.5.tgz", + "integrity": "sha512-Ct1O//ly5y5lFM9YTdu+ygq7LleSgSE4oj7vUt9tPLHUi8VCV7QoizGpdWRWAwCO9LdYzIrQDg97+hGVdsSGPQ==" + }, + "d3-fetch": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.1.2.tgz", + "integrity": "sha512-S2loaQCV/ZeyTyIF2oP8D1K9Z4QizUzW7cWeAOAS4U88qOt3Ucf6GsmgthuYSdyB2HyEm4CeGvkQxWsmInsIVA==", + "requires": { + "d3-dsv": "1" + } + }, + "d3-force": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", + "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", + "requires": { + "d3-collection": "1", + "d3-dispatch": "1", + "d3-quadtree": "1", + "d3-timer": "1" + } + }, + "d3-format": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.1.tgz", + "integrity": "sha512-TUswGe6hfguUX1CtKxyG2nymO+1lyThbkS1ifLX0Sr+dOQtAD5gkrffpHnx+yHNKUZ0Bmg5T4AjUQwugPDrm0g==" + }, + "d3-geo": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.6.tgz", + "integrity": "sha512-z0J8InXR9e9wcgNtmVnPTj0TU8nhYT6lD/ak9may2PdKqXIeHUr8UbFLoCtrPYNsjv6YaLvSDQVl578k6nm7GA==", + "requires": { + "d3-array": "1" + } + }, + "d3-hierarchy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.8.tgz", + "integrity": "sha512-L+GHMSZNwTpiq4rt9GEsNcpLa4M96lXMR8M/nMG9p5hBE0jy6C+3hWtyZMenPQdwla249iJy7Nx0uKt3n+u9+w==" + }, + "d3-interpolate": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.3.2.tgz", + "integrity": "sha512-NlNKGopqaz9qM1PXh9gBF1KSCVh+jSFErrSlD/4hybwoNX/gt1d8CDbDW+3i+5UOHhjC6s6nMvRxcuoMVNgL2w==", + "requires": { + "d3-color": "1" + } + }, + "d3-path": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.8.tgz", + "integrity": "sha512-J6EfUNwcMQ+aM5YPOB8ZbgAZu6wc82f/0WFxrxwV6Ll8wBwLaHLKCqQ5Imub02JriCVVdPjgI+6P3a4EWJCxAg==" + }, + "d3-polygon": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.5.tgz", + "integrity": "sha512-RHhh1ZUJZfhgoqzWWuRhzQJvO7LavchhitSTHGu9oj6uuLFzYZVeBzaWTQ2qSO6bz2w55RMoOCf0MsLCDB6e0w==" + }, + "d3-quadtree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.6.tgz", + "integrity": "sha512-NUgeo9G+ENQCQ1LsRr2qJg3MQ4DJvxcDNCiohdJGHt5gRhBW6orIB5m5FJ9kK3HNL8g9F4ERVoBzcEwQBfXWVA==" + }, + "d3-random": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz", + "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==" + }, + "d3-scale": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", + "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", + "requires": { + "d3-array": "^1.2.0", + "d3-collection": "1", + "d3-format": "1", + "d3-interpolate": "1", + "d3-time": "1", + "d3-time-format": "2" + } + }, + "d3-scale-chromatic": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz", + "integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==", + "requires": { + "d3-color": "1", + "d3-interpolate": "1" + } + }, + "d3-selection": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.0.tgz", + "integrity": "sha512-EYVwBxQGEjLCKF2pJ4+yrErskDnz5v403qvAid96cNdCMr8rmCYfY5RGzWz24mdIbxmDf6/4EAH+K9xperD5jg==" + }, + "d3-shape": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.5.tgz", + "integrity": "sha512-VKazVR3phgD+MUCldapHD7P9kcrvPcexeX/PkMJmkUov4JM8IxsSg1DvbYoYich9AtdTsa5nNk2++ImPiDiSxg==", + "requires": { + "d3-path": "1" + } + }, + "d3-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", + "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" + }, + "d3-time-format": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.3.tgz", + "integrity": "sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA==", + "requires": { + "d3-time": "1" + } + }, + "d3-timer": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.9.tgz", + "integrity": "sha512-rT34J5HnQUHhcLvhSB9GjCkN0Ddd5Y8nCwDBG2u6wQEeYxT/Lf51fTFFkldeib/sE/J0clIe0pnCfs6g/lRbyg==" + }, + "d3-transition": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.2.0.tgz", + "integrity": "sha512-VJ7cmX/FPIPJYuaL2r1o1EMHLttvoIuZhhuAlRoOxDzogV8iQS6jYulDm3xEU3TqL80IZIhI551/ebmCMrkvhw==", + "requires": { + "d3-color": "1", + "d3-dispatch": "1", + "d3-ease": "1", + "d3-interpolate": "1", + "d3-selection": "^1.1.0", + "d3-timer": "1" + } + }, + "d3-voronoi": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz", + "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==" + }, + "d3-zoom": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz", + "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==", + "requires": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } }, "dashdash": { "version": "1.14.1", @@ -9509,6 +9768,11 @@ "aproba": "^1.1.1" } }, + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" + }, "rxjs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", diff --git a/package.json b/package.json index 8b4fdb16723..bf3c4c7f802 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "compression": "^1.7.4", "css-loader": "^1.0.1", "cssmin": "^0.4.3", - "d3": "^3.5.17", + "d3": "^5.12.0", "ejs": "^2.6.2", "errorhandler": "^1.5.1", "event-stream": "3.3.4", diff --git a/tests/admintools.test.js b/tests/admintools.test.js index bbede54156a..9f867a543c3 100644 --- a/tests/admintools.test.js +++ b/tests/admintools.test.js @@ -153,7 +153,9 @@ describe('admintools', function ( ) { var d3 = require('d3'); //disable all d3 transitions so most of the other code can run with jsdom - d3.timer = function mockTimer() { }; + //d3.timer = function mockTimer() { }; + let timer = d3.timer(function mockTimer() { }); + timer.stop(); var cookieStorageType = self.localStorage._type diff --git a/tests/careportal.test.js b/tests/careportal.test.js index 782bc4fa566..36f48d3a5a4 100644 --- a/tests/careportal.test.js +++ b/tests/careportal.test.js @@ -49,7 +49,7 @@ describe('client', function ( ) { client.init(); - client.dataUpdate(nowData); + client.dataUpdate(nowData, true); client.careportal.prepareEvents(); diff --git a/tests/client.renderer.test.js b/tests/client.renderer.test.js index 569691cd717..ca81e7d99e8 100644 --- a/tests/client.renderer.test.js +++ b/tests/client.renderer.test.js @@ -54,6 +54,10 @@ describe('renderer', () => { } } , futureOpacity: (millsDifference) => { return 1; } + , createAdjustedRange: () => { return [ + { getTime: () => { return extent.times[0]}}, + { getTime: () => { return extent.times[1]}} + ] } } , latestSGV: { mills: 120 } }; diff --git a/tests/ddata.test.js b/tests/ddata.test.js index f3757348c53..ceb163b7c4f 100644 --- a/tests/ddata.test.js +++ b/tests/ddata.test.js @@ -41,19 +41,6 @@ describe('ddata', function ( ) { done( ); }); - it('has #split( )', function (done) { - var date = new Date( ); - var time = date.getTime( ); - var cutoff = 1000 * 60 * 5; - var max = 1000 * 60 * 60 * 24 * 2; - var pieces = ctx.ddata.splitRecent(time, cutoff, max); - should.exist(pieces); - should.exist(pieces.first); - should.exist(pieces.rest); - - done( ); - }); - // TODO: ensure partition function gets called via: // Properties // * ddata.devicestatus diff --git a/tests/profile.test.js b/tests/profile.test.js index 8171f459e3d..373f0479d9d 100644 --- a/tests/profile.test.js +++ b/tests/profile.test.js @@ -5,6 +5,10 @@ describe('Profile', function ( ) { var profile_empty = require('../lib/profilefunctions')(); + beforeEach(function() { + profile_empty.clear(); + }); + it('should say it does not have data before it has data', function() { var hasData = profile_empty.hasData(); hasData.should.equal(false); @@ -30,8 +34,6 @@ describe('Profile', function ( ) { }; var profile = require('../lib/profilefunctions')([profileData]); -// console.log(profile); - var now = Date.now(); it('should know what the DIA is with old style profiles', function() { diff --git a/tests/reports.test.js b/tests/reports.test.js index 3c79e3b096a..7d5a0eb7009 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -261,10 +261,12 @@ describe('reports', function ( ) { var result = $('body').html(); //var filesys = require('fs'); //var logfile = filesys.createWriteStream('out.txt', { flags: 'a'} ) - //logfile.write($('body').html()); - + //logfile.write(result); + //console.log('RESULT', result); + result.indexOf('Milk now').should.be.greaterThan(-1); // daytoday - result.indexOf('50 g (1.67U)').should.be.greaterThan(-1); // daytoday + result.indexOf('50 g').should.be.greaterThan(-1); // daytoday + result.indexOf('TDD average: 2.9U').should.be.greaterThan(-1); // daytoday result.indexOf('0%100%0%2').should.be.greaterThan(-1); //dailystats //TODO FIXME result.indexOf('td class="tdborder" style="background-color:#8f8">Normal: 64.7%6').should.be.greaterThan(-1); // distribution result.indexOf('16 (100%)').should.be.greaterThan(-1); // hourlystats diff --git a/views/clockviews/bgclock.css b/views/clockviews/bgclock.css index 0a80dae7db0..3e2cbef246f 100644 --- a/views/clockviews/bgclock.css +++ b/views/clockviews/bgclock.css @@ -1,42 +1,7 @@ -body { - text-align: center; - margin: 0 0; - padding: 0; - overflow: hidden; - font-family: 'Open Sans'; - color: grey; - background-color: black; -} - -main { - display: -webkit-box; - display: -ms-flexbox; - display: -webkit-flex; - display: flex; - -webkit-box-align: center; - -ms-flex-align: center; - -webkit-align-items: center; - align-items: center; - height: 100vh; -} - .inner { - width: 100%; -webkit-transform: translateY(-2%); } -#trend { - display: -ms-flexbox; - display: -webkit-flex; - display: flex; - -ms-flex-align: center; - -webkit-align-items: center; - align-items: center; - justify-content: center; - -webkit-flex-direction: row; - flex-direction: row; -} - #bgnow, #arrowDiv { display: flex; flex-grow: 0; @@ -55,25 +20,9 @@ img#arrow { #clock { font-weight: 700; font-size: 25vmin; + display: inline; } .stale { text-decoration: line-through; -} - -.close { - color: white; - font: 4em 'Open Sans'; - position: absolute; - right: 20px; - text-decoration: none; -} - -.close:after { - content: '\00D7'; -} - -.hidden { - opacity: 0; - transition: opacity 0.5s linear; } \ No newline at end of file diff --git a/views/clockviews/clock-color.css b/views/clockviews/clock-color.css index 36002c6b9ac..5a8c41d2451 100644 --- a/views/clockviews/clock-color.css +++ b/views/clockviews/clock-color.css @@ -1,44 +1,9 @@ body { - text-align: center; - margin: 0 0; - padding: 0; - overflow: hidden; - font-family: 'Open Sans'; color: white; - background-color: white; -} - -main { - display: -webkit-box; - display: -ms-flexbox; - display: -webkit-flex; - display: flex; - -webkit-box-align: center; - -ms-flex-align: center; - -webkit-align-items: center; - align-items: center; - height: 100vh; -} - -.inner { - width: 100%; - -webkit-transform: translateY(-5%); -} - -#bgnow { - font-weight: 700; - font-size: 40vmin; } #trend { - display: -ms-flexbox; - display: -webkit-flex; - display: flex; - -ms-flex-align: center; - -webkit-align-items: center; -webkit-transform: translateX(1%); - align-items: center; - justify-content: center; -webkit-flex-direction: column; flex-direction: column; } @@ -50,31 +15,4 @@ main { img#arrow { height: 30vmin; -} - -#staleTime { - flex-grow: 1; - font-size: 6vmin; - display: none; -} - -#clock { - display: none; -} - -.close { - color: white; - font: 4em 'Open Sans'; - position: absolute; - right: 20px; - text-decoration: none; -} - -.close:after { - content: '\00D7'; -} - -.hidden { - opacity: 0; - transition: opacity 0.5s linear; } \ No newline at end of file diff --git a/views/clockviews/clock-shared.css b/views/clockviews/clock-shared.css new file mode 100644 index 00000000000..9919a1a11c1 --- /dev/null +++ b/views/clockviews/clock-shared.css @@ -0,0 +1,72 @@ +body { + text-align: center; + margin: 0 0; + padding: 0; + overflow: hidden; + font-family: 'Open Sans'; + color: grey; + background-color: black; +} + +main { + display: -webkit-box; + display: -ms-flexbox; + display: -webkit-flex; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + -webkit-align-items: center; + align-items: center; + height: 100vh; +} + +.inner { + width: 100%; + -webkit-transform: translateY(-5%); +} + +#bgnow { + font-weight: 700; + font-size: 40vmin; +} + +#trend { + display: -ms-flexbox; + display: -webkit-flex; + display: flex; + -ms-flex-align: center; + -webkit-align-items: center; + align-items: center; + justify-content: center; + -webkit-flex-direction: row; + flex-direction: row; +} + +#staleTime { + flex-grow: 1; + font-size: 6vmin; + display: none; +} + +#clock { + display: none; +} + +.close { + color: white; + font: 4em 'Open Sans'; + position: absolute; + top: 0; + right: 20px; + text-decoration: none; + z-index: 10; +} + +.close:after { + content: '\00D7'; +} + +.hidden { + opacity: 0; + transition: opacity 0.5s linear; +} \ No newline at end of file diff --git a/views/clockviews/clock.css b/views/clockviews/clock.css index e73f715061f..96ffe68b84a 100644 --- a/views/clockviews/clock.css +++ b/views/clockviews/clock.css @@ -1,71 +1,5 @@ -body { - text-align: center; - margin: 0 0; - padding: 0; - overflow: hidden; - font-family: 'Open Sans'; - color: grey; - background-color: black; -} - -main { - display: -webkit-box; - display: -ms-flexbox; - display: -webkit-flex; - display: flex; - -webkit-box-align: center; - -ms-flex-align: center; - -webkit-align-items: center; - align-items: center; - height: 100vh; -} - -.inner { - width: 100%; - -webkit-transform: translateY(-5%); -} - -#bgnow { - font-weight: 700; - font-size: 40vmin; -} - #trend { - display: -ms-flexbox; - display: -webkit-flex; - display: flex; - -ms-flex-align: center; - -webkit-align-items: center; -webkit-transform: translateX(1%); - align-items: center; - justify-content: center; -webkit-flex-direction: column; flex-direction: column; -} - -#staleTime { - flex-grow: 1; - font-size: 6vmin; - display: none; -} - -#clock { - display: none; -} - -.close { - color: white; - font: 4em 'Open Sans'; - position: absolute; - right: 20px; - text-decoration: none; -} - -.close:after { - content: '\00D7'; -} - -.hidden { - opacity: 0; - transition: opacity 0.5s linear; } \ No newline at end of file diff --git a/views/clockviews/shared.html b/views/clockviews/shared.html index 401b5bb3c25..1be330dee66 100644 --- a/views/clockviews/shared.html +++ b/views/clockviews/shared.html @@ -20,14 +20,14 @@ -
+
diff --git a/views/index.html b/views/index.html index 2531fbba743..7690dcc848c 100644 --- a/views/index.html +++ b/views/index.html @@ -174,9 +174,9 @@
  • Admin Tools