Scatterplot using axes that I saw in one of Edward Tufte's books. I can't remember which (maybe this one).
The axes are pretty pared down but provide basic distributional information of the data. The 1st and 4th quartiles are indicated by the outer lines and the 2nd and 3rd by the inner lines. The gap is the median.
Hovering allows you to see the actual value of a given point. Uses an invisible Voronoi tessellation to make point selection nicer (inspired by this block).
xxxxxxxxxx
<html>
<head>
<meta charset="utf-8">
<title>Tufte Scatter</title>
<style>
html {
font-size: 12px;
font-family: monospace;
}
.refresh {
font-size: 12px;
font-family: monospace;
position: absolute;
top: 20px;
right: 160px;
}
.show-voronoi {
position: absolute;
top: 20px;
right: 50px;
}
.axis path {
fill: none;
stroke: #000;
stroke-width: 1px;
}
.voronoi {
fill: white;
stroke: #000;
stroke-width: .5px;
opacity: 0;
}
.voronoi.show {
opacity: .5;
}
.crosshair.line {
stroke: #000;
stroke-width: .5px;
stroke-dasharray: 2, 6;
}
</style>
</head>
<body>
<div class="container">
<button class="refresh">Change Data</button>
<label class="show-voronoi"><input type="checkbox">Show Voronoi</label>
</div>
<script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="tufte-axis.js"></script>
<script>
var margin = { top: 10, left: 50, bottom: 30, right: 10 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var scale = {
x: d3.scale.linear().range([0, width]).nice(),
y: d3.scale.linear().range([height, 0]).nice()
};
var access = {
x: function(d) { return d.x; },
y: function(d) { return d.y; }
};
var value = {
x: function(d) { return scale.x(access.x(d)); },
y: function(d) { return scale.y(access.y(d)); }
};
var axis = {
x: tufteAxis().scale(scale.x).orient("bottom"),
y: tufteAxis().scale(scale.y).orient("left")
};
var svg = d3.select(".container").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 + ")");
svg.append("g").attr("class", "x axis");
svg.append("g").attr("class", "y axis");
var data = createData(100);
svg.call(renderPlot, data);
var refreshButton = d3.select("button.refresh")
.on("click", function() {
data = data.slice(0, 50).concat(createData(50));
svg.call(renderPlot, data);
});
var showVoronoiCheckbox = d3.select(".show-voronoi input")
.on("change", function() {
d3.selectAll(".voronoi")
.classed("show", this.checked);
});
function renderPlot(selection, data) {
updateScales(data);
axis.x.data(data.map(access.x));
axis.y.data(data.map(access.y));
selection.select(".x.axis").call(axis.x)
.attr("transform", "translate(0," + height + ")");
selection.select(".y.axis").call(axis.y);
selection
.call(renderVoronoi, data)
.call(renderPoints, data);
}
function renderVoronoi(selection, data) {
var voronoi = d3.geom.voronoi()
.x(value.x)
.y(value.y)
.clipExtent([[0, 0], [width, height]]);
var polygons = selection.selectAll(".voronoi")
.data(voronoi(data));
polygons.enter().append("path")
.attr("class", "voronoi")
.on("mouseenter", function(d, i) {
var datum = selection.selectAll(".point").data()[i];
selection.call(renderCrosshair, datum);
})
.on("mouseleave", function(d, i) {
selection.selectAll(".crosshair").remove();
});
polygons
.attr("d", d3.svg.line());
polygons.exit()
.remove();
}
function renderCrosshair(selection, datum) {
var lineData = [
// vertical line
[[value.x(datum), height],[value.x(datum), 0]],
// horizontal line
[[0, value.y(datum)],[width, value.y(datum)]]
];
var crosshairs = selection.selectAll(".crosshair.line").data(lineData);
crosshairs.enter().append("path")
.attr("class", "crosshair line");
crosshairs
.attr("d", d3.svg.line());
crosshairs.exit()
.remove();
var labelData = [
{
x: -6,
y: value.y(datum) + 4,
text: Math.round(access.y(datum)),
orient: "left"
},
{
x: value.x(datum),
y: height + 16,
text: Math.round(access.x(datum)),
orient: "bottom"
}
];
var labels = selection.selectAll(".crosshair.label").data(labelData);
labels.enter().append("text")
.attr("class", "crosshair label");
labels
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.style("text-anchor", function(d) {
return d.orient === "left" ? "end" : "middle";
})
.text(function(d) { return d.text; });
labels.exit().remove();
}
function renderPoints(selection, data) {
var points = selection.selectAll(".point").data(data);
points.enter().append("circle")
.attr("class", "point")
.attr("cx", value.x)
.attr("cy", value.y)
.attr("r", 0)
.style("opacity", 0);
points
.transition().duration(1000)
.attr("cx", value.x)
.attr("cy", value.y)
.attr("r", 2)
.style("opacity", 1);
points.exit()
.transition().duration(1000)
.attr("r", 0)
.style("opacity", 0)
.remove();
}
function updateScales(data) {
var extent = {
x: d3.extent(data, access.x),
y: d3.extent(data, access.y)
};
scale.x.domain([extent.x[0] - 5, extent.x[1] + 5]);
scale.y.domain([extent.y[0] - 5, extent.y[1] + 5]);
}
function createData(n) {
return d3.range(0, n).map(function(i) {
var x = d3.random.normal(50, 10)(),
y = .25*Math.pow(x, 2) + .5*x + d3.random.normal(0, 250)();
return { x: x, y: y };
});
}
</script>
</body>
</html>
https://d3js.org/d3.v3.min.js