What do you call a joyplot that represents data as half circles rather than as an area path? How about a wartplot?!
This wartplot uses D3’s general update pattern to animate transitions with regularly changing data.
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0;
font-family: "Helvetica Neue", sans-serif;
}
.circle {
stroke: #000;
opacity: .8;
}
.circle:hover {
opacity: 1;
stroke-width: 1.5px;
}
.baseline {
stroke: #000;
shape-rendering: crispEdges;
}
.label {
text-anchor: end;
font-size: .8em;
}
.axis .domain {
display: none;
}
</style>
</head>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/jeezy@1.11.0/lib/jeezy.min.js"></script>
<script>
var alpha = "Annie,Bob,Christina,Dylan,Elizabeth,Fred,Gertrude,Isaiah,Jennifer".split(",");
var w = window.innerWidth, h = window.innerHeight;
var margin = {top: h / 6, left: 75, bottom: 10, right: w / 20}, width = w - margin.right - margin.left, height = h - margin.top - margin.bottom;
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + ", " + margin.top + ")");
var y_scale = d3.scaleBand()
.rangeRound([0, height])
.domain(alpha)
var circle_scale = d3.scaleLinear()
.range([0, y_scale.bandwidth()])
.domain([0, 100])
var x_scale = d3.scaleBand()
.rangeRound([0, width])
.domain(Array.apply(null, {length: 21}).map(Number.call, Number));
// lines and labels
var line = svg.selectAll(".baseline")
.data(alpha)
.enter().append("line")
.attr("class", "baseline")
.attr("x1", 0)
.attr("x2", width)
.attr("y1", function(d){ return y_scale(d); })
.attr("y2", function(d){ return y_scale(d); })
var label = svg.selectAll(".label")
.data(alpha)
.enter().append("text")
.attr("class", "label")
.attr("x", -10)
.attr("y", function(d){ return y_scale(d); })
.text(function(d){ return d; });
var axes = svg.selectAll(".x")
.data(alpha)
.enter().append("g")
.attr("class", "x axis")
.attr("transform", function(d) { return "translate(0, " + y_scale(d) + ")"; })
.call(d3.axisBottom(x_scale))
drawChart(makeData());
d3.interval(function(){ drawChart(makeData()); }, 750);
function drawChart(data){
// JOIN
var circle = svg.selectAll(".circle")
.data(data, function(d){ return d.id + d.day; });
// EXIT
circle.exit()
.style("fill", "tomato")
.transition().delay(325)
.attr("d", function(d){ return half_circle(x_scale(d.day) + (x_scale.bandwidth() / 2), y_scale(d.id), 0)})
.remove();
// UPDATE
circle
.transition()
.style("fill", "steelblue")
.attr("d", function(d){ return half_circle(x_scale(d.day) + (x_scale.bandwidth() / 2), y_scale(d.id), circle_scale(d.value)); });
// ENTER
circle.enter().append("path")
.attr("class", "circle")
.attr("d", function(d){ return half_circle(x_scale(d.day) + (x_scale.bandwidth() / 2), y_scale(d.id), 0)})
.style("fill", "#47ff63")
.transition()
.attr("d", function(d){ return half_circle(x_scale(d.day) + (x_scale.bandwidth() / 2), y_scale(d.id), circle_scale(d.value)); });
}
function makeData(){
var arr = [];
alpha.map(function(letter){
for (var i = 0; i <= 20; i++){
arr.push({id: letter, day: i, value: jz.num.randBetween(10, 90)})
}
});
// don't return the whole thing, so we have exiting and entering
return jz.arr.shuffle(arr).map(function(d, i){ if (i < jz.num.randBetween(70, 90)) return d; }).filter(function(d){ return d != undefined; });
}
function half_circle(cx, cy, r){
return "M" + (cx + r) + "," + cy + " a" + r + "," + r + " 1 1,0 -" + (r * 2) + ",0";
}
</script>
</body>
</html>