In the second lab of the Metis data visualization course my group (group 1) was to visualize the 1st example of Anscombe's Quartet. Creating a scatter plot is all well and good, but why not make things a little bit more interesting.
What's more interesting than a scatter plot, you might ask. Well, how about random size circles appearing and disappearing from your screen? No, this does not conform to data visualization best practices, but that's not what I was going for in this example.
You must know the rules, before you can break them.
xxxxxxxxxx
<head>
<meta charset="utf-8">
<style type="text/css">
body {
font-family: avenir, sans;
}
.axis text {
font: 10px avenir;
fill: #777;
}
.axis path {
display: none;
}
.axis line {
stroke-width:1px;
stroke: #ccc;
stroke-dasharray: 2px 2px;
}
circle {
opacity: 1;
}
.anscombe-group text {
opacity: 0;
pointer-events: none;
}
.anscombe-group:hover text {
opacity: 1;
font: 15px avenir;
}
circle:hover {
opacity: 0.6;
}
</style>
</head>
<body>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.js" charset="utf-8"></script>
<script>
// D3 margin convention bl.ock https://bl.ocks.org/mbostock/3019563
// define the margin object, clockwise starting from the top
var margin = {top: 20, right: 10, bottom: 20, left: 30};
// define width and height as inner dimensions of the chart
var width = 520 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// define svg as a G element that translates the origin to the top-left corner of the chart area
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("class", "anscombe-canvas")
.attr("transform", "translate(" + margin.left + "," + margin.top +")");
d3.tsv("quartet.tsv", function(error, data) {
// To perform calculations on our data we need to convert x/y to numbers
data.forEach(function(d) {
d.x = +d.x;
d.y = +d.y;
});
// In this is example we only need to work with group "I"
var g1 = data.filter(function(d) {
return d.group == "I";
});
var xDomain = [0, + d3.extent(g1, function(d) {
return d.x;
})[1]];
// Since we want a square we'll use x extent values to set both domains
var yDomain = [0, d3.extent(g1, function(d) {
return d.x;
})[1]];
// build our utilities for this scatter plot
var xScale = d3.scale.linear()
.domain(xDomain)
.range([0, width]);
var yScale = d3.scale.linear()
.domain(yDomain)
.range([height, 0]);
// The tickSize property is negative because we want it to extend from the
// x-axis to the top of the chart, whiche is 0.
var xAxis = d3.svg.axis()
.scale(xScale)
.tickSize([-height])
.ticks(5)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.tickSize(-width)
.ticks(5)
.orient("left");
// finaly give the scatter plot axises
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(0,0)" )
.call(yAxis);
// To get cirles to show up on top of the dashed lines they need to render
// after the axis. In order to select a different get than the one used
// by the axis I need to select the class of the g.
var groups = svg.selectAll(".anscombe-canvas")
.data(g1)
.enter()
.append("g")
.attr("class", "anscombe-group")
.attr("transform", function(d) {
return "translate(" + xScale(d.x) + "," + yScale(d.y) + ")";
});
var circleAttributes = groups
.append("circle")
.attr("class", "anscombe-circle")
.attr("r", "5")
.attr("fill", d3.rgb(0, 114, 200));
var circleLabels = groups
.append("text");
var labelAttributes = circleLabels
.text(function(d) {
return "(X:" + d.x.toFixed() + ", Y:" + d.y.toFixed(2) + ")";
})
.attr("fill", "black");
setInterval(function () {
circleAttributes
.attr("fill", "#fff")
.transition(500)
.delay(function(d, i) {
return i * 150;
})
.duration(300)
.attr("r", (Math.random() * 50 + 15).toString())
.attr("fill", d3.rgb(0, 114, 200));
}, 2500);
});
</script>
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.js