Skip to content

Instantly share code, notes, and snippets.

@kenpenn
Last active July 27, 2016 02:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kenpenn/dd72bb9365543c52ce70 to your computer and use it in GitHub Desktop.
Save kenpenn/dd72bb9365543c52ce70 to your computer and use it in GitHub Desktop.
brush with scatterplots & sparklines

This example demonstrates how to use D3's brush component to implement focus + context zooming, with sparklines and scatterplots.

Click and drag in the small chart below to pan or zoom.

Sample data is blood pressure readings over time.

Averages for blood-pressure samples in a range are shown at the top.

Mousing over a systolic/diastolic pair highlights the pair.

Clicking on a highlighted systolic/diastolic pair opens a popup.

forked from Mike Bostock's block Focus+Context via Brushing

Thanks to Zan Armstrong for the d3 time formatting example

/* ranges from http://www.heart.org/HEARTORG/Conditions/HighBloodPressure/AboutHighBloodPressure/Understanding-Blood-Pressure-Readings_UCM_301764_Article.jsp */
/* adapted from http://bl.ocks.org/mbostock/1667367 Focus+Context via Brushing */
(function () {
'use strict';
var svg = d3.select('svg'),
svgDims = svg.node().getBoundingClientRect(),
margin = { top: 30, right: 10, bottom: 20, left: 40 },
margin2 = { top: 0, right: 10, bottom: 20, left: 40 },
width = svgDims.width - margin.left - margin.right,
chartHeights = svgDims.height - margin.top,
height = ( 0.7 * chartHeights ) - margin.bottom,
height2 = chartHeights - height - margin.bottom - margin2.bottom - 10,
timeFormat = d3.time.format("%-I:​%M %p %a, %b %-d '%y");
margin2.top = height + margin.bottom + margin2.bottom + 10;
var x = d3.time.scale().range([width, 0]),
x2 = d3.time.scale().range([width, 0]),
y = d3.scale.linear().range([height, 0]),
y2 = d3.scale.linear().range([height2, 0]);
var xAxis = d3.svg.axis().scale(x).orient('bottom'),
xAxis2 = d3.svg.axis().scale(x2).orient('bottom'),
yAxis = d3.svg.axis().scale(y).orient('left');
var brush = d3.svg.brush()
.x(x2)
.on('brush', brushed)
.on('brushend', function () {
showFocusInfo(avgExtent);
addUX();
});
svg.select('defs #focus-clip rect')
.attr('width', width)
.attr('height', height);
var focusInfo = svg.append('g')
.attr('class', 'focus-info')
.attr('transform', 'translate(' + margin.left + ',' + '0)');
var focus = svg.append('g')
.attr('class', 'focus')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var context = svg.append('g')
.attr('class', 'context')
.attr('transform', 'translate(' + margin2.left + ',' + margin2.top + ')');
var bpData = [];
var timeExtent = [];
var avgExtent = [];
d3.json('bp.json', function(err, data) {
bpData = data;
var sysExtent = d3.extent(data, function(d) {
return d.systolic;
});
var diaExtent = d3.extent(data, function(d) {
return d.diastolic;
});
var bpExtent = d3.extent(sysExtent.concat(diaExtent));
bpExtent[0] -=10;
bpExtent[1] +=10;
// -10 & +10 added so dots, lines don't occupy full height of group.
y.domain(bpExtent);
timeExtent = d3.extent(data, function(d) { return d.ts })
avgExtent[0] = timeExtent[0];
avgExtent[1] = timeExtent[1];
x.domain(timeExtent);
x2.domain(x.domain());
y2.domain(y.domain());
focus.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
focus.append('g')
.attr('class', 'y axis')
.call(yAxis);
genBpChart ( focus, x, y, bpData, 4 );
showFocusInfo([ timeExtent[0], timeExtent[1] ]);
context.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height2 + ')')
.call(xAxis2);
genBpChart ( context, x2, y2, bpData, 3 );
context.append('g')
.attr('class', 'x brush')
.call(brush)
.selectAll('rect')
.attr('y', 2)
.attr('height', height2 );
});
function getRange (key, val) {
var rx = 0,
ranges = [
{ systolic : 120, diastolic : 80, fill : '#1ebc16', color: '#fff', title : 'Normal' },
{ systolic : 140, diastolic : 90, fill : 'gold', color: '#fff', title : 'Prehypertension' },
{ systolic : 160, diastolic : 100, fill : '#ff9315', color: '#000', title : 'Stage 1 Hypertension' },
{ systolic : 180, diastolic : 110, fill : 'tomato', color: '#fff', title : 'Stage 2 Hypertension' },
{ systolic : 999, diastolic : 999, fill : 'crimson', color: '#fff', title : 'Hypertensive Crisis' }
],
rlen = ranges.length;
for ( rx; rx < rlen; rx += 1 ) {
if ( val < ranges[rx][key] ) {
return ranges[rx];
}
}
}
function genPath ( grp, val, xScale, yScale, data ) {
var line = d3.svg.line()
.x(function(d) { return xScale( d.ts ) })
.y(function(d) { return yScale( d[val] ) });
grp.append('path')
.attr('class', 'bp-line ' + val)
.attr('stroke', '#ddd')
.attr('fill', 'none')
.attr('stroke-width', '.5')
.attr('d', line(data));
}
function genDots ( grp, val, xScale, yScale, data, dotRadius) {
grp.append('circle')
.attr('class', val)
.attr('r', dotRadius)
.attr('stroke', 'none')
.attr('fill', function (d) { return getRange( val, d[val] ).fill })
.attr('cx', function(d) { return xScale( d.ts ) })
.attr('cy', function(d) { return yScale( d[val] ) });
}
function genBpChart ( grp, xScale, yScale, data, dotRadius ) {
var sys = 'systolic',
dia = 'diastolic';
genPath( grp, sys, xScale, yScale, data );
genPath( grp, dia, xScale, yScale, data );
var bpGrp = grp.selectAll('bp-grp')
.data(data)
.enter()
.append('g')
.attr('class', 'bp-grp');
genDots( bpGrp, sys, xScale, yScale, data, dotRadius );
genDots( bpGrp, dia, xScale, yScale, data, dotRadius );
addUX();
}
function addUX() {
var sys = 'systolic',
dia = 'diastolic',
rad = 4;
focus.selectAll('.bp-grp')
.each(function () {
var dis = d3.select(this);
var sysDot = dis.select('.systolic');
var sysX = sysDot.attr('cx');
var sysY = sysDot.attr('cy');
dis.append('rect') // add first rect so that mouseover works for bp group
.attr('x', function(d) { return x( d.ts ) - rad } )
.attr('y', function(d) { return y( d.systolic ) })
.attr('width', 8)
.attr('height', function(d) { return y( d.diastolic ) - y( d.systolic ) })
.attr('fill', 'transparent')
.attr('stroke', 'none');
dis.on('mouseenter', function(d) {
dis.append('rect') // add second rect on mouseenter to highlight bp group
.attr('x', function(d) { return ( x( d.ts ) - rad ) - 1 })
.attr('rx', rad + 1 )
.attr('y', function(d) { return y( d[sys] ) - ( rad + 1 ) })
.attr('ry', rad + 1 )
.attr('width', ( rad * 2 ) + 2 )
.attr('height', function(d) { return ( y( d[dia] ) - y( d[sys]) ) + ( rad * 2 ) + 2 })
.attr('stroke', '#ddd')
.attr('stroke-width', '.5')
.attr('fill', 'rgba(220,220,220,0.2)')
.attr('class', 'bp-outline');
})
.on('mouseleave', function(d) {
d3.select(this).select('.bp-outline').remove();
})
.on('mousedown', function(d) {
popupBp(d, sysX, sysY)
});
});
}
function getFocusInfo(extent) {
var daysLen = 0;
var evesLen = 0;
var daySumSys = 0;
var daySumDia = 0;
var eveSumSys = 0;
var eveSumDia = 0;
var daysAvg = '';
var evesAvg = '';
bpData.forEach(function(d) {
var hr;
if ( d.ts >= extent[0] && d.ts <= extent[1] ) {
hr = d.time.substring(11,13);
if ( hr > '06' && hr < '18' ) {
daysLen += 1;
daySumSys += d.systolic;
daySumDia += d.diastolic;
} else {
evesLen += 1;
eveSumSys += d.systolic;
eveSumDia += d.diastolic;
}
}
});
daysAvg = daysLen ? Math.round( daySumSys / daysLen ) + '/' + Math.round( daySumDia / daysLen ) : 'none'
evesAvg = evesLen ? Math.round( eveSumSys / evesLen ) + '/' + Math.round( eveSumDia / evesLen ) : 'none'
return {
range : {
from: showDate(extent[0]),
to: showDate(extent[1])
},
days : {
avg: daysAvg,
samples: daysLen
},
eves : {
avg: evesAvg,
samples: evesLen
}
}
}
function showFocusInfo(extent) {
var periodText, bpText,
info = getFocusInfo(extent),
// ugghh! no padding or margins for svg text
periodSpans = [
{ txt : 'Period', dx : 0, hasCss : 'bold' },
{ txt : 'To: ' + info.range.to, dx : 30 },
{ txt : 'From: ' + info.range.from, dx : 12 },
],
bpSpans = [
{ txt : 'Blood Pressure Averages ', dx : 0, hasCss : 'bold' },
{ txt : 'Days: ' + info.days.avg, dx : 12 },
{ txt : 'Samples: ' + info.days.samples, dx : 12 },
{ txt : 'Eves: ' + info.eves.avg, dx : 20 },
{ txt : 'Samples: ' + info.eves.samples, dx : 12 }
];
focusInfo.selectAll('text').remove();
focusInfo.selectAll('.line-arrow').remove();
periodText = focusInfo.append('text')
.attr('x', (width * .5) - 12.5 ) // offset for width of one arrow
.attr('y', 15)
.attr('text-anchor', 'middle');
appendSpans(periodText, periodSpans);
bpText = focusInfo.append('text')
.attr('x', width * .5)
.attr('y', 35)
.attr('text-anchor', 'middle');
appendSpans(bpText, bpSpans);
appendArrow(focusInfo.select('tspan'), '#line-arrow-left')
appendArrow(focusInfo.select('text'), '#line-arrow-right')
function appendSpans(el, spans) {
spans.forEach(function (span) {
var tspan = el.append('tspan')
.text(span.txt)
.attr('dx', span.dx);
if ( span.hasCss ) { tspan.attr('class', span.hasCss ) }
});
}
function appendArrow(d3el, arrowId) {
var txtEl = d3el.node(),
transX = (txtEl.getBoundingClientRect().left + txtEl.offsetWidth) - getTrans(focusInfo).tx,
arrowGrp = focusInfo.append('g')
.attr('class', 'line-arrow')
.attr('transform', 'translate(' + transX + ',5.5)');
arrowGrp.append('use')
.attr('xlink:href', arrowId);
};
}
function brushed() {
var xBefore, xAfter, xLast;
var domain = brush.empty() ? x2.domain() : brush.extent();
var minTs = domain[0].getTime();
var maxTs = domain[1].getTime();
x.domain(domain);
avgExtent[0] = minTs;
avgExtent[1] = maxTs;
focus.selectAll('.bp-line').remove();
focus.selectAll('.bp-grp').remove();
genBpChart ( focus, x, y, bpData, 4 );
focus.select('.x.axis').call(xAxis);
}
function popupBp( d, bpx, bpy ) {
var popupWidth = 0;
var transX = 0;
var popup = svg.append('g') // appended to svg so it floats over everything else
.datum(d)
.attr('class', 'popup');
var popupBg = popup.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('rx', 2)
.attr('ry', 2)
.attr('width', 100)
.attr('height', 80)
.attr('fill', '#efefef')
.attr('stroke', '#ddd')
.attr('stroke-width', '.5')
var sysDot = popup.append('circle')
.attr('class', 'systolic')
.attr('cx', 17)
.attr('cy', 17)
.attr('r', 7)
.attr('fill', getRange('systolic', d.systolic).fill)
var sysText = popup.append('text')
.attr('x', 30)
.attr('y', 22)
.text('Systolic:')
.append('tspan')
.text(d.systolic)
.attr('dx', 10)
var diaDot = popup.append('circle')
.attr('class', 'diastolic')
.attr('cx', 17)
.attr('cy', 43)
.attr('r', 7)
.attr('fill', getRange('diastolic', d.diastolic).fill)
var diaText = popup.append('text')
.attr('x', 30)
.attr('y', 48)
.text('Diastolic:')
.append('tspan')
.text(d.diastolic)
.attr('dx', 5)
var timeText = popup.append('text')
.attr('x', 10)
.attr('y', 70)
.text(showDate(d.ts));
popupWidth = timeText.node().getBoundingClientRect().width + 20;
popupBg.attr('width', popupWidth);
var closeButton = popup.append('g')
.attr('class', 'close-button')
.attr('transform', 'translate(' + (popupWidth - 28) + ',8)')
.on('click', function(evt) {
popup.remove();
});
closeButton.append('use')
.attr('xlink:href', '#close-button');
bpx = parseFloat(bpx, 10);
bpy = parseFloat(bpy, 10);
if ( bpx + popupWidth + margin.left + 25 > width ) {
transX = bpx - popupWidth - margin.left - 25
} else {
transX = bpx + 25 + margin.left;
}
popup.attr('transform', 'translate(' + transX + ',' + (bpy + margin.top) + ')');
popupDrag(popup);
}
function popupDrag(dragger) {
dragger.call(d3.behavior.drag()
.on('dragstart', dragStr)
.on('drag', dragging)
.on('dragend', dragEnd)
);
function dragStr () {
dragger.transition()
.duration(200)
.attr('opacity', .4);
}
function dragging () {
d3.event.sourceEvent.cancelBubble = true;
var trans = getTrans(dragger)
var transX = d3.event.dx + trans.tx;
var transY = d3.event.dy + trans.ty;
dragger.attr('transform', 'translate(' + transX + ',' + transY + ')');
}
function dragEnd () {
d3.event.sourceEvent.cancelBubble = true;
dragger.transition()
.duration(200)
.attr('opacity', 1);
}
}
function showDate(ts) {
var tsloc = ts + (new Date(ts).getTimezoneOffset() * 60 * 1000); // shows date/time in time local to browser
return timeFormat(new Date(tsloc)).replace(/AM/g, 'am').replace(/PM/g, 'pm');
}
function getTrans(el) {
var trans = el.attr('transform').match(/translate\(([\d\.,]+\))/g)
var txy = [0,0];
if (trans) {
txy = trans[0].replace(/translate\(/, '') .replace(/\)/, '').split(',');
}
var tx = txy[1] ? parseFloat(txy[0]) : 0;
var ty = txy[1] ? parseFloat(txy[1]) : 0;
return { tx: tx, ty: ty };
}
function padZ(num, z) {
var padded = num + '',
len = z || 2;
if (padded.length < len) {
while (padded.length < len) {
padded = '0' + padded;
}
}
return padded;
}
}());
[
{
"time": "2015-10-26T10:28:00",
"ts": 1445855280000,
"key": "bp",
"systolic": 137,
"diastolic": 101,
"pulse": 60
},
{
"time": "2015-10-25T19:57:00",
"ts": 1445803020000,
"key": "bp",
"systolic": 119,
"diastolic": 73,
"pulse": 65
},
{
"time": "2015-10-24T19:05:00",
"key": "bp",
"systolic": 125,
"diastolic": 87,
"pulse": 66,
"ts": 1445713500000
},
{
"time": "2015-10-24T12:07:00",
"key": "bp",
"systolic": 108,
"diastolic": 79,
"pulse": 65,
"ts": 1445688420000
},
{
"time": "2015-10-22T13:48:00",
"key": "bp",
"systolic": 143,
"diastolic": 95,
"pulse": 62,
"ts": 1445521680000
},
{
"time": "2015-10-21T09:15:00",
"key": "bp",
"systolic": 140,
"diastolic": 104,
"pulse": 61,
"ts": 1445418900000
},
{
"time": "2015-10-20T10:50:00",
"key": "bp",
"systolic": 138,
"diastolic": 88,
"pulse": 69,
"ts": 1445338200000
},
{
"time": "2015-10-19T15:51:00",
"key": "bp",
"systolic": 124,
"diastolic": 93,
"pulse": 67,
"ts": 1445269860000
},
{
"time": "2015-10-18T11:40:00",
"key": "bp",
"systolic": 125,
"diastolic": 91,
"pulse": 67,
"ts": 1445168400000
},
{
"time": "2015-10-17T10:20:00",
"key": "bp",
"systolic": 146,
"diastolic": 111,
"pulse": 63,
"ts": 1445077200000
},
{
"time": "2015-10-16T16:53:00",
"key": "bp",
"systolic": 122,
"diastolic": 79,
"pulse": 64,
"ts": 1445014380000
},
{
"time": "2015-10-16T10:35:00",
"key": "bp",
"systolic": 138,
"diastolic": 94,
"pulse": 68,
"ts": 1444991700000
},
{
"time": "2015-10-14T13:47:00",
"key": "bp",
"systolic": 138,
"diastolic": 94,
"pulse": 68,
"ts": 1444830420000
},
{
"time": "2015-10-13T15:02:00",
"key": "bp",
"systolic": 100,
"diastolic": 60,
"pulse": 66,
"ts": 1444748520000
},
{
"time": "2015-10-13T11:48:00",
"key": "bp",
"systolic": 114,
"diastolic": 76,
"pulse": 66,
"ts": 1444736880000
},
{
"time": "2015-10-13T10:38:00",
"key": "bp",
"systolic": 148,
"diastolic": 112,
"pulse": 72,
"ts": 1444732680000
},
{
"time": "2015-10-11T08:54:00",
"key": "bp",
"systolic": 132,
"diastolic": 103,
"pulse": 66,
"ts": 1444553640000
},
{
"time": "2015-10-10T17:54:00",
"key": "bp",
"systolic": 135,
"diastolic": 98,
"pulse": 67,
"ts": 1444499640000
},
{
"time": "2015-10-10T10:03:00",
"key": "bp",
"systolic": 161,
"diastolic": 113,
"pulse": 61,
"ts": 1444471380000
},
{
"time": "2015-10-09T23:50:00",
"key": "bp",
"systolic": 110,
"diastolic": 77,
"pulse": 68,
"ts": 1444434600000
},
{
"time": "2015-10-09T10:11:00",
"key": "bp",
"systolic": 158,
"diastolic": 109,
"pulse": 60,
"ts": 1444385460000
},
{
"time": "2015-10-08T17:35:00",
"key": "bp",
"systolic": 140,
"diastolic": 99,
"pulse": 66,
"ts": 1444325700000
},
{
"time": "2015-10-08T10:04:00",
"key": "bp",
"systolic": 135,
"diastolic": 94,
"pulse": 75,
"ts": 1444298640000
},
{
"time": "2015-10-07T12:01:00",
"key": "bp",
"systolic": 149,
"diastolic": 101,
"pulse": 62,
"ts": 1444219260000
},
{
"time": "2015-10-06T22:06:00",
"key": "bp",
"systolic": 112,
"diastolic": 70,
"pulse": 69,
"ts": 1444169160000
},
{
"time": "2015-10-06T15:39:00",
"key": "bp",
"systolic": 146,
"diastolic": 110,
"pulse": 66,
"ts": 1444145940000
},
{
"time": "2015-10-06T11:12:00",
"key": "bp",
"systolic": 147,
"diastolic": 101,
"pulse": 75,
"ts": 1444129920000
},
{
"time": "2015-10-05T23:50:00",
"key": "bp",
"systolic": 87,
"diastolic": 58,
"pulse": 74,
"ts": 1444089000000
},
{
"time": "2015-10-05T16:40:00",
"key": "bp",
"systolic": 138,
"diastolic": 79,
"pulse": 70,
"ts": 1444063200000
},
{
"time": "2015-10-05T13:59:00",
"key": "bp",
"systolic": 162,
"diastolic": 110,
"pulse": 68,
"ts": 1444053540000
},
{
"time": "2015-10-04T22:19:00",
"key": "bp",
"systolic": 92,
"diastolic": 60,
"pulse": 80,
"ts": 1443997140000
},
{
"time": "2015-10-04T11:06:00",
"key": "bp",
"systolic": 138,
"diastolic": 101,
"pulse": 60,
"ts": 1443956760000
},
{
"time": "2015-10-04T01:56:00",
"key": "bp",
"systolic": 116,
"diastolic": 83,
"pulse": 55,
"ts": 1443923760000
},
{
"time": "2015-10-03T23:06:00",
"key": "bp",
"systolic": 112,
"diastolic": 77,
"pulse": 62,
"ts": 1443913560000
},
{
"time": "2015-10-03T15:12:00",
"key": "bp",
"systolic": 123,
"diastolic": 84,
"pulse": 72,
"ts": 1443885120000
},
{
"time": "2015-10-03T13:11:00",
"key": "bp",
"systolic": 156,
"diastolic": 109,
"pulse": 63,
"ts": 1443877860000
},
{
"time": "2015-10-03T10:24:00",
"key": "bp",
"systolic": 153,
"diastolic": 110,
"pulse": 63,
"ts": 1443867840000
},
{
"time": "2015-10-02T16:45:00",
"key": "bp",
"systolic": 118,
"diastolic": 78,
"pulse": 81,
"ts": 1443804300000
},
{
"time": "2015-10-02T14:30:00",
"key": "bp",
"systolic": 159,
"diastolic": 99,
"pulse": 68,
"ts": 1443796200000
},
{
"time": "2015-10-02T11:34:00",
"key": "bp",
"systolic": 160,
"diastolic": 111,
"pulse": 68,
"ts": 1443785640000
},
{
"time": "2015-09-30T13:12:00",
"key": "bp",
"systolic": 153,
"diastolic": 103,
"pulse": 64,
"ts": 1443618720000
},
{
"time": "2015-09-30T09:46:00",
"key": "bp",
"systolic": 151,
"diastolic": 108,
"pulse": 61,
"ts": 1443606360000
},
{
"time": "2015-09-29T22:27:00",
"key": "bp",
"systolic": 101,
"diastolic": 70,
"pulse": 76,
"ts": 1443565620000
},
{
"time": "2015-09-29T14:24:00",
"key": "bp",
"systolic": 120,
"diastolic": 82,
"pulse": 70,
"ts": 1443536640000
},
{
"time": "2015-09-29T13:19:00",
"key": "bp",
"systolic": 149,
"diastolic": 105,
"pulse": 72,
"ts": 1443532740000
},
{
"time": "2015-09-29T11:48:00",
"key": "bp",
"systolic": 141,
"diastolic": 107,
"pulse": 66,
"ts": 1443527280000
},
{
"time": "2015-09-29T10:15:00",
"key": "bp",
"systolic": 175,
"diastolic": 119,
"pulse": 64,
"ts": 1443521700000
},
{
"time": "2015-09-28T08:01:00",
"key": "bp",
"systolic": 151,
"diastolic": 112,
"pulse": 66,
"note": "for two days, taking incorrect smaller dosage of carvedilol",
"ts": 1443427260000
},
{
"time": "2015-09-27T21:05:00",
"key": "bp",
"systolic": 121,
"diastolic": 84,
"pulse": 59,
"ts": 1443387900000
},
{
"time": "2015-09-27T08:19:00",
"key": "bp",
"systolic": 143,
"diastolic": 109,
"pulse": 59,
"ts": 1443341940000
},
{
"time": "2015-09-26T20:45:00",
"key": "bp",
"systolic": 121,
"diastolic": 83,
"pulse": 85,
"ts": 1443300300000
},
{
"time": "2015-09-26T08:17:00",
"key": "bp",
"systolic": 149,
"diastolic": 109,
"pulse": 67,
"ts": 1443255420000
},
{
"time": "2015-09-25T19:45:00",
"key": "bp",
"systolic": 112,
"diastolic": 90,
"pulse": 67,
"ts": 1443210300000
},
{
"time": "2015-09-25T08:58:00",
"key": "bp",
"systolic": 133,
"diastolic": 99,
"pulse": 67,
"ts": 1443171480000
},
{
"time": "2015-09-24T18:55:00",
"key": "bp",
"systolic": 82,
"diastolic": 54,
"pulse": 65,
"ts": 1443120900000
},
{
"time": "2015-09-24T10:53:00",
"key": "bp",
"systolic": 101,
"diastolic": 75,
"pulse": 67,
"ts": 1443091980000
},
{
"time": "2015-09-23T10:10:00",
"key": "bp",
"systolic": 148,
"diastolic": 103,
"pulse": 60,
"ts": 1443003000000
},
{
"time": "2015-09-22T21:18:00",
"key": "bp",
"systolic": 87,
"diastolic": 56,
"pulse": 68,
"ts": 1442956680000
},
{
"time": "2015-09-22T18:11:00",
"key": "bp",
"systolic": 111,
"diastolic": 80,
"pulse": 65,
"ts": 1442945460000
},
{
"time": "2015-09-22T12:27:00",
"key": "bp",
"systolic": 112,
"diastolic": 85,
"pulse": 72,
"ts": 1442924820000
},
{
"time": "2015-09-22T01:01:00",
"key": "bp",
"systolic": 80,
"diastolic": 48,
"pulse": 69,
"ts": 1442883660000
},
{
"time": "2015-09-21T18:40:00",
"key": "bp",
"systolic": 92,
"diastolic": 60,
"pulse": 72,
"ts": 1442860800000
},
{
"time": "2015-09-21T10:32:00",
"key": "bp",
"systolic": 118,
"diastolic": 86,
"pulse": 66,
"ts": 1442831520000
},
{
"time": "2015-09-21T09:34:00",
"key": "bp",
"systolic": 139,
"diastolic": 105,
"pulse": 66,
"ts": 1442828040000
},
{
"time": "2015-09-21T09:15:00",
"key": "bp",
"systolic": 158,
"diastolic": 115,
"pulse": 68,
"ts": 1442826900000
},
{
"time": "2015-09-20T22:43:00",
"key": "bp",
"systolic": 60,
"diastolic": 45,
"pulse": 76,
"ts": 1442788980000
},
{
"time": "2015-09-20T16:47:00",
"key": "bp",
"systolic": 87,
"diastolic": 64,
"pulse": 78,
"ts": 1442767620000
},
{
"time": "2015-09-20T09:05:00",
"key": "bp",
"systolic": 119,
"diastolic": 92,
"pulse": 65,
"ts": 1442739900000
},
{
"time": "2015-09-19T21:13:00",
"key": "bp",
"systolic": 92,
"diastolic": 67,
"pulse": 73,
"ts": 1442697180000
},
{
"time": "2015-09-17T09:15:00",
"key": "bp",
"systolic": 127,
"diastolic": 92,
"pulse": 76,
"ts": 1442481300000
},
{
"time": "2015-09-16T21:15:00",
"key": "bp",
"systolic": 99,
"diastolic": 70,
"pulse": 69,
"ts": 1442438100000
},
{
"time": "2015-09-16T08:08:00",
"key": "bp",
"systolic": 129,
"diastolic": 93,
"pulse": 66,
"ts": 1442390880000
},
{
"time": "2015-09-15T22:10:00",
"key": "bp",
"systolic": 127,
"diastolic": 91,
"pulse": 65,
"ts": 1442355000000
},
{
"time": "2015-09-15T11:40:00",
"key": "bp",
"systolic": 173,
"diastolic": 115,
"pulse": 65,
"ts": 1442317200000
},
{
"time": "2015-09-14T14:15:00",
"key": "bp",
"systolic": 158,
"diastolic": 112,
"pulse": 69,
"ts": 1442240100000
},
{
"time": "2015-09-14T10:25:00",
"key": "bp",
"systolic": 149,
"diastolic": 106,
"pulse": 67,
"ts": 1442226300000
},
{
"time": "2015-09-14T09:32:00",
"key": "bp",
"systolic": 157,
"diastolic": 112,
"pulse": 72,
"ts": 1442223120000
},
{
"time": "2015-09-13T19:32:00",
"key": "bp",
"systolic": 144,
"diastolic": 95,
"pulse": 79,
"note": "this and all previous timestamps from memory",
"ts": 1442172720000
},
{
"time": "2015-09-12T16:32:00",
"key": "bp",
"systolic": 139,
"diastolic": 93,
"pulse": 59,
"ts": 1442075520000
},
{
"time": "2015-09-11T20:32:00",
"key": "bp",
"systolic": 116,
"diastolic": 90,
"pulse": 74,
"ts": 1442003520000
},
{
"time": "2015-09-11T07:32:00",
"key": "bp",
"systolic": 148,
"diastolic": 109,
"pulse": 74,
"ts": 1441956720000
},
{
"time": "2015-09-10T19:32:00",
"key": "bp",
"systolic": 115,
"diastolic": 94,
"pulse": 73,
"ts": 1441913520000
},
{
"time": "2015-09-10T07:32:00",
"key": "bp",
"systolic": 133,
"diastolic": 89,
"pulse": 63,
"ts": 1441870320000
},
{
"time": "2015-09-09T18:32:00",
"key": "bp",
"systolic": 123,
"diastolic": 88,
"pulse": 77,
"ts": 1441823520000
},
{
"time": "2015-09-09T08:12:00",
"key": "bp",
"systolic": 159,
"diastolic": 111,
"pulse": 71,
"ts": 1441786320000
},
{
"time": "2015-09-08T08:01:00",
"key": "bp",
"systolic": 167,
"diastolic": 121,
"pulse": 68,
"ts": 1441699260000
},
{
"time": "2015-09-07T18:32:00",
"key": "bp",
"systolic": 134,
"diastolic": 89,
"pulse": 75,
"ts": 1441650720000
},
{
"time": "2015-09-07T07:32:00",
"key": "bp",
"systolic": 138,
"diastolic": 103,
"pulse": 70,
"ts": 1441611120000
},
{
"time": "2015-09-06T18:32:00",
"key": "bp",
"systolic": 134,
"diastolic": 94,
"pulse": 87,
"ts": 1441564320000
},
{
"time": "2015-09-06T06:55:00",
"key": "bp",
"systolic": 149,
"diastolic": 102,
"pulse": 74,
"ts": 1441522500000
}
]
<!doctype html>
<head>
<meta charset='utf-8'>
<title>brush with scatterplots &amp; sparklines</title>
<!-- forked from http://bl.ocks.org/mbostock/1667367 Focus+Context via Brushing -->
<style>
body {
background: #fff;
color: #444;
font-family: sans-serif;
font-size: 16px;
margin: 0;
height: 100vh;
width: 100vw;
}
svg {
margin: 0.625em;
width: calc(100vw - 20px);
min-width: 940px;
height: calc(100vh - 20px);
min-height: 480px;
}
.focus-info {
font-size: 0.9em;
}
.focus .bp-line, .focus .bp-grp {
clip-path: url(#focus-clip);
}
.axis {
font-size: 0.625em;
}
.axis path, .axis line {
fill: none;
stroke: #444;
shape-rendering: crispEdges;
}
.brush .extent {
stroke: #fff;
fill-opacity: .125;
shape-rendering: crispEdges;
}
.focus .bp-grp, .close-button {
cursor: pointer;
}
.focus-info .bold {
font-weight: bold;
}
.line-arrow {
stroke: #444;
stroke-width: 2.5;
marker-end: url(#line-marker);
}
.line-marker {
fill: #444;
stroke: none;
}
.popup {
cursor: move;
}
.popup text {
font-size: .9em;
}
</style>
</head>
<body>
<svg>
<defs>
<g id="close-button" transform="scale(0.15625)">
<circle fill="darkgray" stroke="none" cx="64" cy="64" r="64"></circle>
<rect x="16" width="96" rx="6" y="58" height="12" ry="6" transform="rotate(45 64 64)" fill="white" stroke="none"></rect>
<rect x="16" width="96" rx="6" y="58" height="12" ry="6" transform="rotate(-45 64 64)" fill="white" stroke="none"></rect>
</g>
<clipPath id="focus-clip">
<rect></rect>
</clipPath>
<marker id="line-marker" markerWidth="12" markerHeight="12" refX="6" refY="4" orient="auto">
<path d="M 1 1 7 4 1 7 Z" class="line-marker"></path>
</marker>
<g id="line-arrow-left" transform="scale(0.5)">
<line x1="25" x2="0" y1="10" y2="10" class="line-arrow"></line>
</g>
<g id="line-arrow-right" transform="scale(0.5)">
<line x1="0" x2="25" y1="10" y2="10" class="line-arrow"></line>
</g>
</defs>
</svg>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js'></script>
<script src='bp-brush.js'></script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment