From 1e744f1aee50ccd9e5e8bb613a458ebbf5d6d224 Mon Sep 17 00:00:00 2001 From: Jeremy Cunningham Date: Wed, 23 Oct 2019 21:38:57 -0500 Subject: [PATCH 1/4] stop focus circles from eating entries --- lib/client/chart.js | 2 +- lib/client/renderer.js | 44 ++++++++++++++++++++++++++++-------------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/lib/client/chart.js b/lib/client/chart.js index b3395613c34..cb512da5a4d 100644 --- a/lib/client/chart.js +++ b/lib/client/chart.js @@ -695,7 +695,7 @@ function init (client, d3, $) { maxForecastMills = _.max(_.map(shownForecastPoints, function(point) { return point.mills })); } - if (!client.settings.showForecast || client.settings.showForecast == "") { + if (!client.settings.showForecast || client.settings.showForecast == "") { maxForecastMills = to + times.mins(30).msecs; } diff --git a/lib/client/renderer.js b/lib/client/renderer.js index 19e8e3ae557..0473b7513de 100644 --- a/lib/client/renderer.js +++ b/lib/client/renderer.js @@ -75,20 +75,6 @@ function init (client, d3) { }; renderer.addFocusCircles = function addFocusCircles () { - // get slice of data so that concatenation of predictions do not interfere with subsequent updates - var focusData = client.entries.slice(); - - if (client.sbx.pluginBase.forecastPoints) { - var shownForecastPoints = _.filter(client.sbx.pluginBase.forecastPoints, function isShown (point) { - return client.settings.showForecast.indexOf(point.info.type) > -1; - }); - - focusData = focusData.concat(shownForecastPoints); - } - - // bind up the focus chart data to an array of circles - // selects all our data into data and uses date function to get current max date - var focusCircles = chart().focus.selectAll('circle').data(focusData, client.entryToDate); function updateFocusCircles (sel) { var badData = []; @@ -175,11 +161,41 @@ function init (client, d3) { .style('top', (d3.event.pageY + 15) + 'px'); } + // get slice of data so that concatenation of predictions do not interfere with subsequent updates + var focusData = client.entries.slice(); + var shownForecastPoints = [ ]; + + if (client.sbx.pluginBase.forecastPoints) { + shownForecastPoints = _.filter(client.sbx.pluginBase.forecastPoints, function isShown (point) { + return client.settings.showForecast.indexOf(point.info.type) > -1; + }); + } + + // bind up the focus chart data to an array of circles + // selects all our data into data and uses date function to get current max date + var focusCircles = chart().focus.selectAll('circle.entry-dot').data(focusData, client.entryToDate); + // if already existing then transition each circle to its new position updateFocusCircles(focusCircles); // if new circle then just display prepareFocusCircles(focusCircles.enter().append('circle')) + .attr('class', 'entry-dot') + .on('mouseover', focusCircleTooltip) + .on('mouseout', hideTooltip); + + focusCircles.exit().remove(); + + // bind up the focus chart data to an array of circles + // selects all our data into data and uses date function to get current max date + var forecastCircles = chart().focus.selectAll('circle.forecast-dot').data(shownForecastPoints, client.entryToDate); + + // if already existing then transition each circle to its new position + updateFocusCircles(forecastCircles); + + // if new circle then just display + prepareFocusCircles(forecastCircles.enter().append('circle')) + .attr('class', 'forecast-dot') .on('mouseover', focusCircleTooltip) .on('mouseout', hideTooltip); From c00a998ab6ad23e064114e24aa0757f0f9ddfcc2 Mon Sep 17 00:00:00 2001 From: Jeremy Cunningham Date: Wed, 23 Oct 2019 22:15:40 -0500 Subject: [PATCH 2/4] fix forecastCircles remove --- lib/client/renderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/client/renderer.js b/lib/client/renderer.js index 0473b7513de..06efb4c4c87 100644 --- a/lib/client/renderer.js +++ b/lib/client/renderer.js @@ -199,7 +199,7 @@ function init (client, d3) { .on('mouseover', focusCircleTooltip) .on('mouseout', hideTooltip); - focusCircles.exit().remove(); + forecastCircles.exit().remove(); }; renderer.addTreatmentCircles = function addTreatmentCircles (nowDate) { From 944770c54732442c7c5e05c31357281267580bd0 Mon Sep 17 00:00:00 2001 From: Jeremy Cunningham Date: Thu, 24 Oct 2019 07:47:49 -0500 Subject: [PATCH 3/4] resolve possible key collision --- lib/client/renderer.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/client/renderer.js b/lib/client/renderer.js index 06efb4c4c87..a34e890b2a1 100644 --- a/lib/client/renderer.js +++ b/lib/client/renderer.js @@ -161,8 +161,7 @@ function init (client, d3) { .style('top', (d3.event.pageY + 15) + 'px'); } - // get slice of data so that concatenation of predictions do not interfere with subsequent updates - var focusData = client.entries.slice(); + var focusData = client.entries; var shownForecastPoints = [ ]; if (client.sbx.pluginBase.forecastPoints) { @@ -173,7 +172,9 @@ function init (client, d3) { // bind up the focus chart data to an array of circles // selects all our data into data and uses date function to get current max date - var focusCircles = chart().focus.selectAll('circle.entry-dot').data(focusData, client.entryToDate); + var focusCircles = chart().focus.selectAll('circle.entry-dot').data(focusData, function genKey (d) { + return d.forecastType + d.mills; + }); // if already existing then transition each circle to its new position updateFocusCircles(focusCircles); From 97d8f92789bd830f3fb3ae24667ae7ac224267fb Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Fri, 25 Oct 2019 16:55:57 +0300 Subject: [PATCH 4/4] Fixes a major bug where plugins were ran against a sandbox twice, causing issues with predictions. Fixes the look ahead timings. --- lib/client/chart.js | 133 ++++++++++++++++++++++------------------- lib/client/index.js | 19 +++--- lib/client/renderer.js | 8 +-- 3 files changed, 83 insertions(+), 77 deletions(-) diff --git a/lib/client/chart.js b/lib/client/chart.js index cb512da5a4d..8d0aa754e09 100644 --- a/lib/client/chart.js +++ b/lib/client/chart.js @@ -6,20 +6,18 @@ var d3locales = require('./d3locales'); var scrolling = false , scrollNow = 0 , scrollBrushExtent = null - , scrollRange = null -; + , scrollRange = null; var PADDING_BOTTOM = 30 , CONTEXT_MAX = 420 , CONTEXT_MIN = 36 , FOCUS_MAX = 510 - , FOCUS_MIN = 30 -; + , FOCUS_MIN = 30; var loadTime = Date.now(); function init (client, d3, $) { - var chart = { }; + var chart = {}; var utils = client.utils; var renderer = client.renderer; @@ -36,9 +34,9 @@ function init (client, d3, $) { .attr('x', 0) .attr('y', 0) .append('g') - .style('fill', 'none') - .style('stroke', '#0099ff') - .style('stroke-width', 2) + .style('fill', 'none') + .style('stroke', '#0099ff') + .style('stroke-width', 2) .append('path').attr('d', 'M0,0 l' + dashWidth + ',' + dashWidth) .append('path').attr('d', 'M' + dashWidth + ',0 l-' + dashWidth + ',' + dashWidth); @@ -52,12 +50,12 @@ function init (client, d3, $) { .attr('markerHeight', 8) .attr('orient', 'auto') .append('path') - .attr('d', 'M0,-5L10,0L0,5') - .attr('class', 'arrowHead'); + .attr('d', 'M0,-5L10,0L0,5') + .attr('class', 'arrowHead'); var localeFormatter = d3.timeFormatLocale(d3locales.locale(client.settings.language)); - function beforeBrushStarted ( ) { + function beforeBrushStarted () { // go ahead and move the brush because // a single click will not execute the brush event var now = new Date(); @@ -76,18 +74,18 @@ function init (client, d3, $) { chart.theBrush.call(chart.brush.move, brush); } - function brushStarted ( ) { + function brushStarted () { // update the opacity of the context data points to brush extent chart.context.selectAll('circle') .data(client.entries) .style('opacity', 1); } - function brushEnded ( ) { + function brushEnded () { // update the opacity of the context data points to brush extent chart.context.selectAll('circle') .data(client.entries) - .style('opacity', function (d) { return renderer.highlightBrushPoints(d) }); + .style('opacity', function(d) { return renderer.highlightBrushPoints(d) }); } var extent = client.dataExtent(); @@ -108,13 +106,16 @@ function init (client, d3, $) { , targetTop = client.settings.thresholds.bgTargetTop // filter to only use actual SGV's (not rawbg's) to set the view window. // can switch to Logarithmic (non-dynamic) to see anything that doesn't fit in the dynamicDomain - , mgdlMax = d3.max(client.entries, function (d) { if ( d.type === 'sgv') { return d.mgdl; } }); - // use the 99th percentile instead of max to avoid rescaling for 1 flukey data point - // need to sort client.entries by mgdl first - //, mgdlMax = d3.quantile(client.entries, 0.99, function (d) { return d.mgdl; }); + , mgdlMax = d3.max(client.entries, function(d) { if (d.type === 'sgv') { return d.mgdl; } }); + // use the 99th percentile instead of max to avoid rescaling for 1 flukey data point + // need to sort client.entries by mgdl first + //, mgdlMax = d3.quantile(client.entries, 0.99, function (d) { return d.mgdl; }); return [ utils.scaleMgdl(FOCUS_MIN) + + + , Math.max(utils.scaleMgdl(mgdlMax * mult), utils.scaleMgdl(targetTop * mult)) ]; } @@ -136,7 +137,7 @@ function init (client, d3, $) { var xScale2 = chart.xScale2 = d3.scaleTime().domain(extent); - contextYDomain = dynamicDomainOrElse(contextYDomain); + contextYDomain = dynamicDomainOrElse(contextYDomain); var yScale2 = chart.yScale2 = yScaleType() .domain(contextYDomain); @@ -146,25 +147,25 @@ function init (client, d3, $) { chart.yScaleBasals = d3.scaleLinear() .domain([0, 5]); - 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 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); @@ -203,12 +204,12 @@ function init (client, d3, $) { chart.theBrush = null; - chart.futureOpacity = (function () { - var scale = d3.scaleLinear( ) + chart.futureOpacity = (function() { + var scale = d3.scaleLinear() .domain([times.mins(25).msecs, times.mins(60).msecs]) .range([0.8, 0.1]); - return function (delta) { + return function(delta) { if (delta < 0) { return null; } else { @@ -231,7 +232,7 @@ function init (client, d3, $) { chart.focus.append('g') .attr('class', 'x axis') .style("font-size", "16px"); - + // create the y axis container chart.focus.append('g') .attr('class', 'y axis') @@ -250,7 +251,7 @@ function init (client, d3, $) { .attr('class', 'y axis') .style("font-size", "16px"); - chart.createBrushedRange = function () { + 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(); @@ -271,7 +272,7 @@ function init (client, d3, $) { return range; } - chart.createAdjustedRange = function () { + chart.createAdjustedRange = function() { var adjustedRange = chart.createBrushedRange(); adjustedRange[1] = new Date(adjustedRange[1].getTime() + client.forecastTime); @@ -371,7 +372,7 @@ function init (client, d3, $) { .attr('class', 'x brush') .call(chart.brush) .call(g => g.select(".overlay") - .datum({type: 'selection'}) + .datum({ type: 'selection' }) .on('mousedown touchstart', beforeBrushStarted)); chart.theBrush.selectAll('rect') @@ -584,7 +585,7 @@ function init (client, d3, $) { chart.theBrush.call(chart.brush.move, currentBrushExtent.map(chart.xScale2)); }; - chart.updateContext = function (dataRange_) { + chart.updateContext = function(dataRange_) { if (client.documentHidden) { console.info('Document Hidden, not updating - ' + (new Date())); return; @@ -677,32 +678,44 @@ function init (client, d3, $) { scrolling = true; }; - - chart.setForecastTime = function setForecastTime () { + + chart.getMaxForecastMills = function getMaxForecastMills () { + // limit lookahead to the same as lookback + var selectedRange = chart.createBrushedRange(); + var to = selectedRange[1].getTime(); + return to + client.focusRangeMS; + }; + + chart.getForecastData = function getForecastData () { + + var maxForecastAge = chart.getMaxForecastMills(); if (client.sbx.pluginBase.forecastPoints) { - var shownForecastPoints = _.filter(client.sbx.pluginBase.forecastPoints, function isShown (point) { - return client.settings.showForecast.indexOf(point.info.type) > -1; + return _.filter(client.sbx.pluginBase.forecastPoints, function isShown (point) { + return point.mills < maxForecastAge && client.settings.showForecast.indexOf(point.info.type) > -1; }); - // limit lookahead to the same as lookback - var selectedRange = chart.createBrushedRange(); - var to = selectedRange[1].getTime(); + } else return []; + }; + + chart.setForecastTime = function setForecastTime () { - var focusHoursAheadMills = to + client.focusRangeMS; - var maxForecastMills = focusHoursAheadMills; + if (client.sbx.pluginBase.forecastPoints) { + var shownForecastPoints = chart.getForecastData(); + + var focusHoursAheadMills = chart.getMaxForecastMills(); + var selectedRange = chart.createBrushedRange(); + var to = selectedRange[1].getTime(); + var maxForecastMills = to + times.mins(30).msecs; + if (shownForecastPoints.length > 0) { maxForecastMills = _.max(_.map(shownForecastPoints, function(point) { return point.mills })); } - - if (!client.settings.showForecast || client.settings.showForecast == "") { - maxForecastMills = to + times.mins(30).msecs; - } maxForecastMills = Math.min(focusHoursAheadMills, maxForecastMills); client.forecastTime = maxForecastMills > 0 ? maxForecastMills - client.sbx.lastSGVMills() : 0; } - } + }; return chart; } diff --git a/lib/client/index.js b/lib/client/index.js index 30a72ae9106..01b1dce60ea 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -1200,20 +1200,19 @@ client.load = function load (serverSettings, callback) { // Don't invoke D3 in headless mode + if (headless) return; + if (!isInitialData) { isInitialData = true; - if (!headless) { - chart = client.chart = require('./chart')(client, d3, $); - chart.update(true); - brushed(); - chart.update(false); - } + chart = client.chart = require('./chart')(client, d3, $); + chart.update(true); + brushed(); + chart.update(false); } else if (!inRetroMode()) { - if (!headless) chart.update(false); - client.plugins.updateVisualisations(client.nowSBX); - if (!headless) brushed(); + brushed(); + chart.update(false); } else { - if (!headless) chart.updateContext(); + chart.updateContext(); } } }; diff --git a/lib/client/renderer.js b/lib/client/renderer.js index a34e890b2a1..4da2119731d 100644 --- a/lib/client/renderer.js +++ b/lib/client/renderer.js @@ -162,13 +162,7 @@ function init (client, d3) { } var focusData = client.entries; - var shownForecastPoints = [ ]; - - if (client.sbx.pluginBase.forecastPoints) { - shownForecastPoints = _.filter(client.sbx.pluginBase.forecastPoints, function isShown (point) { - return client.settings.showForecast.indexOf(point.info.type) > -1; - }); - } + var shownForecastPoints = client.chart.getForecastData(); // bind up the focus chart data to an array of circles // selects all our data into data and uses date function to get current max date