Published
Edited
Dec 19, 2021
Importers
1 star
Insert cell
# Rough Violin Plot with Classical Iris Dataset
Insert cell
Insert cell
drawViolinPlot()
Insert cell
dimensions = {
const width = 400;
const height = 400;
const dimensions = {
violin: {
width: width,
height: height,
margin: {
top: 30,
right: 0,
bottom: 60,
left: 30
}
}
};
dimensions.violin.boundedWidth =
dimensions.violin.width -
dimensions.violin.margin.left -
dimensions.violin.margin.right;
dimensions.violin.boundedHeight =
dimensions.violin.height -
dimensions.violin.margin.top -
dimensions.violin.margin.bottom;
return dimensions;
}
Insert cell
wrapper = d3
.create("svg")
.attr("width", dimensions.violin.width)
.attr("height", dimensions.violin.height);
Insert cell
function drawViolinPlot() {
const colors = ["264653", "2a9d8f", "843B62"].map((d) => "#" + d);

const pathToCSV = "./iris.csv";
const target = "Sepal_Length";
const targetAccessor = (d) => +d[target];

const styleAccessor = (d) => d.Species;
const species = ["setosa", "versicolor", "virginica"];
const speciesDisplay = {
setosa: "setosa",
versicolor: "versicolor",
virginica: "virginica"
};

let data = [];
rawData.forEach((d) => {
if (species.indexOf(d.Species) < 0) {
return;
} else {
data.push(d);
}
});
const bounds = wrapper.append("g").style(
"transform",
`translate(${dimensions.violin.margin.left}px,
${dimensions.violin.margin.top}px)`
);
const rc = rough.svg(bounds);

bounds
.append("text")
.attr("class", "title")
.text(target)
.style(
"transform",
`translate(${dimensions.violin.boundedWidth / 2 - 10}px, -15px)`
)
.style("font-size", "20px")
.style("text-anchor", "middle")
.style("font-weight", "bold");

var y = d3
.scaleLinear()
.domain(d3.extent(data, targetAccessor))
.range([dimensions.violin.boundedHeight, 0]);

bounds
.append("g")
.attr("id", "axisLeft")
.style("font-size", "10px")
.call(d3.axisLeft(y).ticks(20))
.attr("stroke-opacity", 0);

const leftAxisPath = d3.select("#axisLeft > path").attr("d");

bounds.append("g").each(function (datum, idx) {
const aPath = d3.select(this);
const container = d3.select(this.parentNode);
const child = rc.path(leftAxisPath);
container.node().appendChild(child);
aPath.remove();
});

var x = d3
.scaleBand()
.range([0, dimensions.violin.boundedWidth])
.domain(species)
.padding(0.05);

bounds
.append("g")
.attr("id", "axisBottom")
.attr("transform", "translate(0," + dimensions.violin.boundedHeight + ")")
.style("font-size", "16px")
.call(d3.axisBottom(x).tickFormat((d) => speciesDisplay[d]))
.attr("stroke-opacity", 0)
.selectAll("text");

const bottomAxisPath = d3.select("#axisBottom > path").attr("d");

bounds.append("g").each(function (datum, idx) {
const aPath = d3.select(this);
const container = d3.select(this.parentNode);
const child = rc.path(bottomAxisPath);
child.setAttribute(
"transform",
"translate(0," + dimensions.violin.boundedHeight + ")"
);
container.node().appendChild(child);

aPath.remove();
});

var histogram = d3
.histogram()
.domain(y.domain())
.thresholds(y.ticks(20))
.value((d) => d);

var sumstat = d3
.nest()
.key(styleAccessor)
.rollup(function (d) {
const input = d.map(targetAccessor);
const bins = histogram(input);
return bins;
})
.entries(data);

var maxNum = 0;
for (let i in sumstat) {
const allBins = sumstat[i].value;
const lengths = allBins.map(function (a) {
return a.length;
});
const longuest = d3.max(lengths);
if (longuest > maxNum) {
maxNum = longuest;
}
}

var xNum = d3
.scaleLinear()
.range([0, x.bandwidth()])
.domain([-maxNum, maxNum]);

const eachViolin = bounds
.selectAll("violin")
.data(sumstat)
.enter()
.append("g")
.attr("transform", function (d) {
return "translate(" + x(d.key) + " ,0)";
});

eachViolin
.append("path")
.datum(function (d) {
return d.value;
})
.each(function (datum, idx) {
const aPath = d3.select(this);
const container = d3.select(this.parentNode);
const violinRoughAttr = {
stroke: "gray",
strokeWidth: "0.8",
fill: colors[idx],
roughness: 1,
hachureGap: 3,
fillWeight: 1
};
const path = d3
.area()
.x0(function (d) {
return xNum(-d.length);
})
.x1(function (d) {
return xNum(d.length);
})
.y(function (d) {
return y(d.x0);
})
.curve(d3.curveCatmullRom)(datum);

const child = rc.path(path, violinRoughAttr);
container.node().appendChild(child);
aPath.remove();
});
return wrapper;
}
Insert cell
Insert cell
d3 = require("d3@5")
Insert cell
rough = require("https://unpkg.com/roughjs@latest/bundled/rough.js").catch(() => window.rough)
Insert cell
style = html`
<link href="https://fonts.googleapis.com/css2?family=Fuzzy+Bubbles&display=swap" rel="stylesheet"></link>
<style>
* {
font-family: "Fuzzy Bubbles", cursive;
}
</style>
`
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