Published
Edited
Mar 5, 2021
Importers
2 stars
Insert cell
Insert cell
Insert cell
violin_data = user_data
Insert cell
Insert cell
Insert cell
chart = {
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);

// Male bars
svg
.append("g")
.attr("fill", "steelblue")
.selectAll("rect")
.data(m_users)
.join("rect")
.attr("x", x_m(0))
.attr("y", d => y(d.age))
.attr("width", d => x_m(d.average_playcount) - x_m(0))
.attr("height", y.bandwidth());

// Male labels
svg
.append("g")
.attr("fill", "white")
.attr("text-anchor", "end")
.attr("font-family", "sans-serif")
.attr("font-size", 12)
.selectAll("text")
.data(m_users)
.join("text")
.attr("x", d => x_m(d.average_playcount))
.attr("y", d => y(d.age) + y.bandwidth() / 2)
.attr("dy", "0.35em")
.attr("dx", -4)
.text(d => d.average_playcount)
.call(text =>
text
.filter(d => x_m(d.average_playcount) - x_m(0) < 20) // short bars
.attr("dx", +4)
.attr("fill", "black")
.attr("text-anchor", "start")
);

// Female bars
svg
.append("g")
.attr("fill", "firebrick")
.selectAll("rect")
.data(f_users)
.join("rect")
.attr("x", d => x_f(0) - (x_m(d.average_playcount) - x_m(0)))
.attr("y", d => y(d.age))
.attr("width", d => x_m(d.average_playcount) - x_m(0))
.attr("height", y.bandwidth());

// Female labels
svg
.append("g")
.attr("fill", "white")
.attr("text-anchor", "end")
.attr("font-family", "sans-serif")
.attr("font-size", 12)
.selectAll("text")
.data(f_users)
.join("text")
.attr("x", d => x_f(0) - x_m(d.average_playcount) + x_m(0))
.attr("y", d => y(d.age) + y.bandwidth() / 2)
.attr("dy", "0.35em")
.attr("dx", 4)
.attr("text-anchor", "start")
.text(d => d.average_playcount)
.call(text =>
text
.filter(d => x_m(d.average_playcount) - x_m(0) < 20) // short bars
.attr("dx", 4)
.attr("fill", "white")
.attr("text-anchor", "end")
);

svg.append("g").call(xmAxis);
svg.append("g").call(xfAxis);

svg.append("g").call(yAxis);
svg.append("g").call(y2Axis);

svg
.append("text")
.attr("transform", `translate(${middle}, ${margin.top - 10})`)
.style("text-anchor", "middle")
.style("font-size", "18px")
.text("Age");

svg
.append("text")
.attr("transform", `translate(${middle}, ${height - margin.bottom + 40})`)
.style("text-anchor", "middle")
.style("font-size", "18px")
.text("Average # of Listens Per Day");

const tooltip = d3
.select("body")
.append("div")
.attr("class", "svg-tooltip")
.style("position", "absolute")
.style("visibility", "hidden");

svg
.selectAll("rect")
.on("mouseover", d => {
tooltip.text(
"Age: " +
d.age +
" years old\nAverage Playcount: " +
d.average_playcount +
" plays/day\nNumber of Records: " +
d.n
);
return tooltip.style("visibility", "visible");
})
.on("mousemove", function() {
return tooltip
.style("top", d3.event.pageY - 10 + "px")
.style("left", d3.event.pageX + 10 + "px");
})
.on("mouseout", function() {
return tooltip.style("visibility", "hidden");
});

return svg.node();
}
Insert cell
/*tooltip = {
chart;

const tooltip = d3
.select("body")
.append("div")
.attr("class", "svg-tooltip")
.style("position", "absolute")
.style("visibility", "hidden");

d3.selectAll("rect")
.on("mouseover", d => {
tooltip.text(
"Age: " +
d.age +
" years old\nAverage Playcount: " +
d.average_playcount +
" plays/day\nNumber of Records: " +
d.n
);
return tooltip.style("visibility", "visible");
})
.on("mousemove", function() {
return tooltip
.style("top", d3.event.pageY - 10 + "px")
.style("left", d3.event.pageX + 10 + "px");
})
.on("mouseout", function() {
return tooltip.style("visibility", "hidden");
});
}*/
Insert cell
together = f_users.concat(m_users)
Insert cell
middle = width / 2
Insert cell
adjust = 20
Insert cell
x_m = d3
.scaleLinear()
.domain([0, d3.max(together, d => d.average_playcount)])
.range([middle + adjust, width - margin.right])
Insert cell
x_f = d3
.scaleLinear()
.domain([d3.max(together, d => d.average_playcount), 0])
.range([margin.left, middle - adjust])
Insert cell
y = d3
.scaleBand()
.domain(
d3
.range(d3.min(together, d => d.age), d3.max(together, d => d.age) + 1)
.reverse()
)
.range([margin.top, height - margin.bottom])
.padding(0.1)
Insert cell
yAxis = g =>
g.attr("transform", `translate(${middle + adjust },0)`).call(
d3
.axisLeft(y)
.tickFormat(i => "")
.tickSizeOuter(0)
)
Insert cell
y2Axis = g =>
g
.attr("transform", `translate(${middle - adjust},0)`)
.call(
d3
.axisRight(y)
.tickFormat(i => (i % 5 == 0 ? i : ""))
.tickSizeOuter(0)
)
.selectAll("text")
.attr("font-size", 14)
.attr("text-anchor", "middle")
.attr("dx", adjust / 2)
Insert cell
xfAxis = g =>
g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x_f).ticks(width / 120, together.format))
.call(g => g.select(".domain").remove())
Insert cell
xmAxis = g =>
g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x_m).ticks(width / 120, together.format))
.call(g => g.select(".domain").remove())
Insert cell
f_users = {
let f_users = violin_data.filter(d => d.gender == "f");

f_users = d3
.nest()
.key(d => d.age)
.rollup(v => {
return {
avg: Math.round(d3.mean(v, d => +d.playcount) * 100) / 100,
n: v.length
};
})
.entries(f_users);

f_users = f_users
.map(d => {
return {
age: parseInt(d.key),
average_playcount: d.value.avg,
n: d.value.n
};
})
// .filter(a => a.age > 12 && a.age < 80)
.sort((a, b) => a.age - b.age);
return f_users;
}
Insert cell
m_users = {
let m_users = violin_data.filter(d => d.gender == "m");

m_users = d3
.nest()
.key(d => d.age)
.rollup(v => {
return {
avg: Math.round(d3.mean(v, d => +d.playcount) * 100) / 100,
n: v.length
};
})
.entries(m_users);

m_users = m_users
.map(d => {
return {
age: parseInt(d.key),
average_playcount: d.value.avg,
n: d.value.n
};
})
// .filter(a => a.age > 12 && a.age < 80)
.sort((a, b) => a.age - b.age);
return m_users;
}
Insert cell
Insert cell
// Tooltip inspired by: https://observablehq.com/@jianan-li/basic-tooltip
Insert cell
Insert cell
d3 = require("d3@v5")
Insert cell
_ = require("lodash")
Insert cell
height = 1000
Insert cell
margin = ({ top: 30, bottom: 50, left: 20, right: 20 })
Insert cell
Insert cell
Insert cell
Insert cell
user_data
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