Public
Edited
Apr 17, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof order = Inputs.select(
new Map([
["Funding", (a, b) => d3.descending(a.funding_2022, b.funding_2022)],
["Burden", (a, b) => d3.descending(a.daly, b.daly)]
]),
{ label: "Order" }
)
Insert cell
chart = BarChart(data_female_male, {
x: (d) => d.funding_2022,
y: (d) => d.disease,
xFormat: ",",
xLabel: "Funding →",
width,
color: colorScale,
marginLeft: 250
})
Insert cell
Insert cell
update = chart.update(d3.sort(data_female_male, order))
Insert cell
data_female_male = combined_data.filter(
(d) => d.gender === "Female" || d.gender === "Male"
)
Insert cell
d3.groupSort(
data_female_male,
([d]) => -d.funding_2022,
(d) => d.disease
)
Insert cell
// Copyright 2021 Observable, Inc.
// Released under the ISC license.
// https://observablehq.com/@d3/horizontal-bar-chart
function BarChart(
data,
{
x = (d) => d, // given d in data, returns the (quantitative) x-value
y = (d, i) => i, // given d in data, returns the (ordinal) y-value
title, // given d in data, returns the title text
marginTop = 30, // the top margin, in pixels
marginRight = 0, // the right margin, in pixels
marginBottom = 10, // the bottom margin, in pixels
marginLeft = 30, // the left margin, in pixels
width = 640, // the outer width of the chart, in pixels
height, // outer height, in pixels
xType = d3.scaleLinear, // type of x-scale
xDomain, // [xmin, xmax]
xRange = [marginLeft, width - marginRight], // [left, right]
xFormat, // a format specifier string for the x-axis
xLabel, // a label for the x-axis
yPadding = 0.1, // amount of y-range to reserve to separate bars
yDomain, // an array of (ordinal) y-values
yRange, // [top, bottom]
color = "currentColor", // bar fill color
titleColor = "white", // title fill color when atop bar
titleAltColor = "currentColor", // title fill color when atop background
duration: initialDuration = 500, // transition duration, in milliseconds
delay: initialDelay = (_, i) => i * 20 // per-element transition delay, in milliseconds
} = {}
) {
// Compute values.
const X = d3.map(data, x);
const Y = d3.map(data, y);

// Compute default domains, and unique the y-domain.
if (xDomain === undefined) xDomain = [0, d3.max(X)];
if (yDomain === undefined) yDomain = Y;
yDomain = new d3.InternSet(yDomain);

// Omit any data not present in the y-domain.
const I = d3.range(X.length).filter((i) => yDomain.has(Y[i]));

// Compute the default height.
if (height === undefined)
height =
Math.ceil((yDomain.size + yPadding) * 25) + marginTop + marginBottom;
if (yRange === undefined) yRange = [marginTop, height - marginBottom];

// Construct scales and axes.
const xScale = xType(xDomain, xRange);
const yScale = d3.scaleBand(yDomain, yRange).padding(yPadding);
const xAxis = d3.axisTop(xScale).ticks(width / 80, xFormat);
const yAxis = d3.axisLeft(yScale).tickSizeOuter(0);

// Compute titles.
// if (title === undefined) {
// const formatValue = xScale.tickFormat(100, xFormat);
// title = (i) => `${formatValue(X[i])}`;
// } else {
// const O = d3.map(data, (d) => d);
// const T = title;
// title = (i) => T(O[i], i, data);
// }

const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;");

let xGroup = svg
.append("g")
.attr("transform", `translate(0,${marginTop})`)
.call(xAxis)
.call((g) => g.select(".domain").remove())
.call((g) =>
g
.selectAll(".tick line")
.clone()
.attr("y2", height - marginTop - marginBottom)
.attr("stroke-opacity", 0.1)
)
.call((g) =>
g
.append("text")
.attr("x", width - marginRight)
.attr("y", -22)
.attr("fill", "currentColor")
.attr("text-anchor", "end")
.text(xLabel)
);

let rect = svg
.append("g")
.selectAll("rect")
.data(I)
.join("rect")
.property("key", (i) => Y[i]) // for future transitions
.call(
position,
(i) => xScale(X[i]),
(i) => yScale(Y[i])
)
.attr("fill", (d, i) => color(data[i].gender));

// A helper method for updating the position of bars.
function position(rect, x, y) {
return rect
.attr("x", xScale(0))
.attr("y", y)
.attr("width", (i) => x(i) - xScale(0))
.attr("height", yScale.bandwidth());
}

// svg
// .append("g")
// .attr("fill", titleColor)
// .attr("text-anchor", "end")
// .attr("font-family", "sans-serif")
// .attr("font-size", 10)
// .selectAll("text")
// .data(I)
// .join("text")
// .attr("x", (i) => xScale(X[i]))
// .attr("y", (i) => yScale(Y[i]) + yScale.bandwidth() / 2)
// .attr("dy", "0.35em")
// .attr("dx", -4)
// .text(title)
// .call((text) =>
// text
// .filter((i) => xScale(X[i]) - xScale(0) < 20) // short bars
// .attr("dx", +4)
// .attr("fill", titleAltColor)
// .attr("text-anchor", "start")
// );

let yGroup = svg
.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(yAxis);

// return svg.node();

// Call chart.update(data, options) to transition to new data.
return Object.assign(svg.node(), {
update(
data,
{
xDomain, // an array of (ordinal) x-values
yDomain, // [ymin, ymax]
duration = initialDuration, // transition duration, in milliseconds
delay = initialDelay // per-element transition delay, in milliseconds
} = {}
) {
// Compute values.
const X = d3.map(data, x);
const Y = d3.map(data, y);

// Compute default domains, and unique the y-domain.
if (xDomain === undefined) xDomain = [0, d3.max(X)];
if (yDomain === undefined) yDomain = Y;
yDomain = new d3.InternSet(yDomain);

// Omit any data not present in the x-domain.
const I = d3.range(X.length).filter((i) => yDomain.has(Y[i]));

// Compute the default height.
if (height === undefined)
height =
Math.ceil((yDomain.size + yPadding) * 25) + marginTop + marginBottom;
if (yRange === undefined) yRange = [marginTop, height - marginBottom];

// Update scale domains.
xScale.domain(xDomain);
yScale.domain(yDomain);

// Start a transition.
const t = svg.transition().duration(duration);

// Join the data, applying enter and exit.
rect = rect
.data(I, function (i) {
return this.tagName === "rect" ? this.key : Y[i];
})
.join(
(enter) =>
enter
.append("rect")
.property("key", (i) => Y[i]) // for future transitions
.call(
position,
(i) => xScale(X[i]),
(i) => yScale(Y[i])
)
.style("mix-blend-mode", "multiply")
.call((enter) => enter.append("title")),
(update) => update,
(exit) =>
exit
.transition(t)
.delay(delay)
.attr("y", yScale(0))
.attr("height", 0)
.remove()
);

// Update the title text on all entering and updating bars.
// rect.select("title").text((i) => [X[i], format(Y[i])].join("\n"));

// Transition entering and updating bars to their new position. Note
// that this assumes that the input data and the x-domain are in the
// same order, or else the ticks and bars may have different delays.
rect
.transition(t)
.delay(delay)
.call(
position,
(i) => xScale(X[i]),
(i) => yScale(Y[i])
);

// Transition the x-axis (using a possibly staggered delay per tick).
xGroup
.transition(t)
.call(xAxis)
.call((g) => g.selectAll(".tick").delay(delay));

// Transition the y-axis, then post process for grid lines etc.
yGroup
.transition(t)
.call(yAxis)
.selection()
.call((g) => g.select(".domain").remove());
// .call((g) =>
// g.selectAll(".tick").selectAll(".grid").data([,]).join(grid)
// );
}
});
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
xScale_stage_5 = (d) => {
// [rearrange to show Chronic Fatigue Syndrome (ME/CFS) in the center]

let position = 1.5;

if (d.gender === "Female") {
position = 0.5;
}

return position;
}
Insert cell
xScale_stage_7 = (d) => {
// [rearrange to show Chronic Fatigue Syndrome (ME/CFS) in the center]

let position = 1.5;

if (d.gender === "Male") {
position = 0.5;
} else if (d.gender === "Female") {
position = -1.5;
}

return position;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
data = Array.from(combined_table).filter((d) => d.gender !== undefined)
Insert cell
combined_data = Array.from(combined_table).filter((d) => d.gender !== undefined)
Insert cell
testRollupForDuplicates = d3.rollup(
combined_data,
(v) => {
let array_length = v.length;
let daly_set = new Set(v.map((d) => d.daly));
let funding_set = new Set(v.map((d) => d.funding_2022));
let daly_duplicates = array_length !== daly_set.size;
let funding_duplicates = array_length !== funding_set.size;
return { daly_duplicates, funding_duplicates };
},
(d) => d.gender
)
Insert cell
combined_data_ranked = [
...combined_data
.filter((d) => d.gender === "Male" || d.gender === "Female")
.map((d, i, array) => ({
...d,
funding_rank: d3.rank(
array.map((d) => d.funding_2022),
d3.descending
)[i],
daly_rank: d3.rank(
array.map((d) => d.daly),
d3.descending
)[i]
})),
...combined_data
.filter((d) => d.gender !== "Male" && d.gender !== "Female")
.map((d, i, array) => ({
...d,
funding_rank: undefined,
daly_rank: undefined
}))
]
Insert cell
genderGroups = [...new Set(data.map((d) => d.gender))]
Insert cell
colors = ["#E74C3C", "#F1C40F", "#3498DB", "#8E44AD", "#95A5A6"]
Insert cell
import { swatches as Swatches } from "@d3/color-legend"
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