Skip to content

Instantly share code, notes, and snippets.

@velickym
Last active November 11, 2015 10:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save velickym/d19f76572243c774557f to your computer and use it in GitHub Desktop.
Save velickym/d19f76572243c774557f to your computer and use it in GitHub Desktop.
HeartBeat using D3.js
<!DOCTYPE html>
<html>
<head>
<title>HeartBeat</title>
<script src="//d3js.org/d3.v3.min.js"></script>
<style>
#svg-wrapper {
width: 500px;
height: 160px;
margin: 2em auto;
}
svg path {
fill: none;
stroke: #000;
stroke-width: 1.5px;
}
svg .axis {
font-size: 12px;
}
svg .axis path {
display: none;
}
</style>
<script type="text/javascript">
window.onload = function() {
var svg = null;
var circle = null;
var circleTransition = null;
var latestBeat = null;
var insideBeat = false;
var data = [];
var SECONDS_SAMPLE = 5;
var BEAT_TIME = 400;
var TICK_FREQUENCY = SECONDS_SAMPLE * 1000 / BEAT_TIME;
var BEAT_VALUES = [0, 0, 3, -4, 10, -7, 3, 0, 0];
var CIRCLE_FULL_RADIUS = 40;
var MAX_LATENCY = 5000;
var colorScale = d3.scale.linear()
.domain([BEAT_TIME, (MAX_LATENCY - BEAT_TIME) / 2, MAX_LATENCY])
.range(["#6D9521", "#D77900", "#CD3333"]);
var radiusScale = d3.scale.linear()
.range([5, CIRCLE_FULL_RADIUS])
.domain([MAX_LATENCY, BEAT_TIME]);
function beat() {
if (insideBeat) return;
insideBeat = true;
var now = new Date();
var nowTime = now.getTime();
if (data.length > 0 && data[data.length - 1].date > now) {
data.splice(data.length - 1, 1);
}
data.push({
date: now,
value: 0
});
var step = BEAT_TIME / BEAT_VALUES.length - 2;
for (var i = 1; i < BEAT_VALUES.length; i++) {
data.push({
date: new Date(nowTime + i * step),
value: BEAT_VALUES[i]
});
}
latestBeat = now;
circleTransition = circle.transition()
.duration(BEAT_TIME)
.attr("r", CIRCLE_FULL_RADIUS)
.attr("fill", "#6D9521");
setTimeout(function() {
insideBeat = false;
}, BEAT_TIME);
}
var svgWrapper = document.getElementById("svg-wrapper");
var margin = {left: 10, top: 10, right: CIRCLE_FULL_RADIUS * 3, bottom: 10},
width = svgWrapper.offsetWidth - margin.left - margin.right,
height = svgWrapper.offsetHeight - margin.top - margin.bottom;
// create SVG
svg = d3.select('#svg-wrapper').append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.bottom + margin.top)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
circle = svg
.append("circle")
.attr("fill", "#6D9521")
.attr("cx", width + margin.right / 2)
.attr("cy", height / 2)
.attr("r", CIRCLE_FULL_RADIUS);
// init scales
var now = new Date(),
fromDate = new Date(now.getTime() - SECONDS_SAMPLE * 1000);
// create initial set of data
data.push({
date: now,
value: 0
});
var x = d3.time.scale()
.domain([fromDate, new Date(now.getTime())])
.range([0, width]),
y = d3.scale.linear()
.domain([-10, 10])
.range([height, 0]);
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.value);
});
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.seconds, 1)
.tickFormat(function(d) {
var seconds = d.getSeconds() === 0 ? "00" : d.getSeconds();
return seconds % 10 === 0 ? d.getMinutes() + ":" + seconds : ":" + seconds;
});
// add clipPath
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var axis = d3.select("svg").append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
var path = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.attr("class", "line");
svg.select(".line")
.attr("d", line(data));
var transition = d3.select("path").transition()
.duration(100)
.ease("linear");
(function tick() {
transition = transition.each(function() {
// update the domains
now = new Date();
fromDate = new Date(now.getTime() - SECONDS_SAMPLE * 1000);
x.domain([fromDate, new Date(now.getTime() - 100)]);
var translateTo = x(new Date(fromDate.getTime()) - 100);
// redraw the line
svg.select(".line")
.attr("d", line(data))
.attr("transform", null)
.transition()
.attr("transform", "translate(" + translateTo + ")");
// slide the x-axis left
axis.call(xAxis);
}).transition().each("start", tick);
})();
setInterval(function() {
now = new Date();
fromDate = new Date(now.getTime() - SECONDS_SAMPLE * 1000);
for (var i = 0; i < data.length; i++) {
if (data[i].date < fromDate) {
data.shift();
} else {
break;
}
}
if (insideBeat) return;
data.push({
date: now,
value: 0
});
if (circleTransition != null) {
var diff = now.getTime() - latestBeat.getTime();
if (diff < MAX_LATENCY) {
circleTransition = circle.transition()
.duration(TICK_FREQUENCY)
.attr("r", radiusScale(diff))
.attr("fill", colorScale(diff));
}
}
}, TICK_FREQUENCY);
setInterval(function() {
beat();
}, 2000);
beat();
};
</script>
<body>
<div id="svg-wrapper"></div>
</body>
</html>
@velickym
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment