Published
Edited
May 13, 2020
1 star
Insert cell
md`# Violin Plot without the Violin`
Insert cell
d3 = require("d3@5")
Insert cell
data = d3.csv(
"https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/iris.csv",
function(d) {
// cast string values to numeric
d.Sepal_Length = +d.Sepal_Length;
d.Sepal_Width = +d.Sepal_Width;
d.Petal_Length = +d.Petal_Length;
d.Petal_Width = +d.Petal_Width;
return d;
}
)
Insert cell
// make sure our data looks right
data[0]
Insert cell
container = DOM.element('div')
Insert cell
chart = {
// set the dimensions and margins of the graph
var margin = { top: 10, right: 30, bottom: 30, left: 80 },
jitterWidth = 40,
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;

// clear existing data whenver we re-render
d3.select(container)
.selectAll('svg')
.remove();

// append the svg object to the body of the page
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 + ")");

// Build and Show the Y scale
var x = d3
.scaleLinear()
.domain([3.5, 8]) // Note that here the Y scale is set manually
.range([height, 0]);
svg
.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));

// Build and Show the X scale. It is a band scale like for a boxplot: each group has an dedicated RANGE on the axis. This range has a length of x.bandwidth
var y = d3
.scaleBand()
.range([0, width])
.domain(["setosa", "versicolor", "virginica"])
.padding(0.05); // This is important: it is the space between 2 groups. 0 means no padding. 1 is the maximum.
svg.append("g").call(d3.axisLeft(y));

// Features of the histogram
var histogram = d3
.histogram()
.domain(x.domain())
.thresholds(x.ticks(20)) // Important: how many bins approx are going to be made? It is the 'resolution' of the violin plot
.value(d => d);

// Compute the binning for each group of the dataset
var sumstat = d3
.nest() // nest function allows to group the calculation per level of a factor
.key(function(d) {
return d.Species;
})
.rollup(function(d) {
var inp = d.map(function(g) {
return g.Sepal_Length;
}); // Keep the variable called Sepal_Length
var bins = histogram(inp); // And compute the binning on it.
return bins;
})
.entries(data);

// What is the biggest number of value in a bin? We need it cause this value will have a width of 100% of the bandwidth.
var maxNum = 0;
for (var i in sumstat) {
var allBins = sumstat[i].value;
var lengths = allBins.map(function(a) {
return a.length;
});
var longuest = d3.max(lengths);
if (longuest > maxNum) {
maxNum = longuest;
}
}

// The maximum width of a violin must be x.bandwidth = the width dedicated to a group
var yNum = d3
.scaleLinear()
.range([0, y.bandwidth()])
.domain([-maxNum, maxNum]);

// Color scale for dots
var myColor = d3
.scaleSequential()
.interpolator(d3.interpolateInferno)
.domain([3, 9]);

// Add individual points with jitter
svg
.selectAll("indPoints")
.data(data)
.enter()
.append("circle")
.attr("cy", function(d) {
return y(d.Species) + y.bandwidth() / 2 - Math.random() * jitterWidth;
})
.attr("cx", function(d) {
return x(d.Sepal_Length);
})
.attr("r", 5)
.style("fill", function(d) {
return myColor(d.Sepal_Length);
})
.attr("stroke", "white");
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more