block by kenpenn dd72bb9365543c52ce70

brush with scatterplots & sparklines

Full Screen

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

index.html

<!doctype html>
<head>
<meta charset='utf-8'>
<title>brush with scatterplots &amp; sparklines</title>
  <!-- forked from //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>

bp-brush.js

/* 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;
  }
}());

bp.json

[
  {
    "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
  }
]