Skip to content

Commit

Permalink
Stop Focus Circles from Eating Entries (#5145)
Browse files Browse the repository at this point in the history
* stop focus circles from eating entries

* fix forecastCircles remove

* resolve possible key collision

* Fixes a major bug where plugins were ran against a sandbox twice, causing issues with predictions. Fixes the look ahead timings.
  • Loading branch information
jpcunningh authored and sulkaharo committed Oct 25, 2019
1 parent 8d33c6e commit c1244ea
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 84 deletions.
133 changes: 73 additions & 60 deletions lib/client/chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);

Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -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))
];
}
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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 {
Expand All @@ -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')
Expand All @@ -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();
Expand All @@ -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);
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
19 changes: 9 additions & 10 deletions lib/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
};
Expand Down
39 changes: 25 additions & 14 deletions lib/client/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];
Expand Down Expand Up @@ -175,15 +161,40 @@ function init (client, d3) {
.style('top', (d3.event.pageY + 15) + 'px');
}

var focusData = client.entries;
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
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);

// 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);

forecastCircles.exit().remove();
};

renderer.addTreatmentCircles = function addTreatmentCircles (nowDate) {
Expand Down

0 comments on commit c1244ea

Please sign in to comment.