Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stop Focus Circles from Eating Entries #5145

Merged
merged 4 commits into from
Oct 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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