Public
Edited
Mar 3, 2023
Insert cell
Insert cell
Insert cell
splitBar(data, {
bind: d3.select(".vis-1"),
numberFormat: d3.format(",.1f"),
dataColumn: "cat1",
dataRow: "cat2",
value: "value"
})
Insert cell
function splitBar(
_data,
{
bind = null, // d3 selection where to place the svg
numberFormat = d3.format(",.0f"), // format of numbers in the bar labels
margin = { top: 10, right: 20, bottom: 10, left: 50 },
height = 300,
width = 500,
dataColumn, // define which column from data file represents the columns
dataRow, // define which column from data file represents the rows
value, // define which column from data file represents the calues
colour = "#00AEEF" // colour of the bars
} = {}
) {
// remove the vis before renderging, can take out if exporting the code locally to JS.
bind.selectAll("svg").remove();

// bring the data into a format that can be used with this template
const data = Array.from(
d3.group(_data, (d) => d[dataColumn]),
([key, value]) => ({ category: key, value: value })
);

// define the formatter for the label
const formatLabel = (d) => d3.format(numberFormat)(d);

// replace the value with -- if it returns null
const xText = (d) => (d[value] === null ? "--" : formatLabel(d[value]));

// setup to create columns
const column = (d) => d.category;

const columnScale = d3
.scaleBand()
.range([0, width - margin.right])
.domain(data.map(column))
.paddingInner(0.075);

const columnValue = (d) => columnScale(column(d));

// setup for the x value for bars and labels
const x = (d) => +d[value];

const xScale = d3
.scaleLinear()
.range([0, columnScale.bandwidth()])
.domain([0, d3.max(_data, (d) => d.value)]);

// setup for y values of bars
const y = (d) => d[dataRow];

const yScale = d3
.scaleBand()
.range([height - margin.bottom, 0])
.domain(_data.map(y).reverse())
.padding(0.2);

// make the svg
const svg = bind
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);

const g = svg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);

// create the columns
const gColumn = g
.append("g")
.attr("class", "columns")
.selectAll(".column")
.data(data)
.join("g")
.attr("class", "column")
.attr("transform", (d) => `translate(${columnValue(d)}, ${margin.top})`);

gColumn
.append("text")
.attr("x", 0)
.attr("y", -10)
.attr("dy", "0.32em")
.attr("fill", "#454545")
.text(column)
.each((d) => d3.select(this).call(wrap, xScale.range()[1]));

// create the bars
const bars = gColumn.append("g").attr("class", "bars");

// underlying bars that go from 0 - max as they serve as a background
bars
.selectAll(".bar-underlying")
.data((d) => d.value)
.join("rect")
.attr("class", "bar bar-underlying")
.attr("x", 0)
.attr("y", (d) => yScale(y(d)))
.attr("width", xScale.range()[1])
.attr("height", yScale.bandwidth())
.attr("fill", (d) => {
if (d[dataColumn] === "Total" || d[dataRow] === "Total") {
return "none";
} else {
return "#e5e5e3";
}
});

// overlying bars that represent the value of each category
bars
.selectAll(".bar-overlying")
.data((d) => d.value)
.join("rect")
.attr("class", "bar bar-overlying")
.attr("x", 0)
.attr("y", (d) => yScale(y(d)))
.attr("width", (d) => (d[value] !== null ? xScale(x(d)) : 0))
.attr("height", yScale.bandwidth())
.attr("fill", (d) => {
if (d[dataColumn] === "Total" || d[dataRow] === "Total") {
return "none";
} else {
return colour;
}
});

// make the data labels and position them accordingly
gColumn
.append("g")
.attr("class", "labels")
.selectAll(".label")
.data((d) => d.value)
.enter()
.append("text")
.attr("class", "label")
.attr("fill", "#454545")
.text((d) => xText(d))
.each(positionLabel);

// make the y axis - serves as row labels
g.append("g")
.attr("class", "axis axis-y")
.attr("transform", `translate(0, ${margin.top})`)
.call(d3.axisLeft(yScale))
.call((g) => g.select(".domain").remove())
.call((g) => g.selectAll("line").remove())
.selectAll(".tick text")
.each(function (d) {
d3.select(this).call(wrap, margin.left);
});

// select the labels and colour them
d3.selectAll(".label-white").attr("fill", "#fafafa");
d3.selectAll(".label-na").attr("fill", "#454545");

// helper function that positiones the labels inside/outside of the overlying bars (depending on value)
function positionLabel(d) {
const xValue = xScale(x(d));
const xMax = xScale.range()[1];

if (xValue < 0.5 * xMax) {
d3.select(this)
.classed("label-white", false)
.attr("x", xValue)
.attr("dx", 2);
} else {
d3.select(this).classed("label-white", true).attr("x", 0).attr("dx", 4);
}

if (d[dataColumn] === "Total" || d[dataRow] === "Total") {
d3.select(this)
.classed("label-white", false)
.classed("label-total", true);
}

if (d["value"] === null) {
d3.select(this).classed("label-white", false).classed("label-na", true);
}

d3.select(this)
.attr("y", yScale(y(d)) + yScale.bandwidth() / 2)
.attr("dy", "0.33em");
}

// helper function to break long labels on y axis into multiple rows
function wrap(text, width) {
text.each(function () {
const text = d3.select(this);
const words = text.text().split(/\s+/).reverse();
let word = "";
let line = [];
const lineHeight = 1.1; // ems
const x = text.attr("x");
let tspan = text.text(null).append("tspan").attr("x", x);
while ((word = words.pop())) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text
.append("tspan")
.attr("x", x)
.attr("dy", lineHeight + "em")
.text(word);
}
}
});

const breaks = text.selectAll("tspan").size();
text.attr("y", function () {
return +text.attr("y") + -6 * (breaks - 1);
});
}

return svg.node();
}
Insert cell
Insert cell
data.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more