Public
Edited
Apr 14, 2023
1 fork
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Swatches({ color: colorScale })
Insert cell
canvasChart = {
const context = DOM.context2d(width, height);
let text = "Stage 1";
const centerX = width / 2;
const centerY = height / 2;
const spaceBetweenBubbles = 3;

let radiusScale = (d) => {
return fundingScale(d.funding_2022);
};

let xScale = (d) => {
return xScale_stage_1(d);
};

const newData = [...combined_data_ranked];

const simulation = d3
.forceSimulation(newData)
.alphaTarget(0.5)
.velocityDecay(0.8)
.force("charge", d3.forceManyBody().strength(-width / 100))
.force("x", d3.forceX().strength(0.1))
.force("y", d3.forceY().strength(0.1))
.force(
"collide",
d3
.forceCollide()
.radius((d) => radiusScale(d) + spaceBetweenBubbles)
.iterations(10)
)
.on("tick", ticked);

switch (stage) {
case "one":
text = "Stage 1";

simulation
.force(
"x",
d3
.forceX()
.strength(0.1)
.x((d) => xScale(d) * width)
)
.force(
"y",
d3
.forceY()
.strength(0.1)
.y(height / 2)
);
break;

case "two":
text = "Stage 2";
xScale = (d) => {
return xScale_stage_2(d);
};

simulation
.force(
"x",
d3
.forceX()
.strength(0.2)
.x((d) => xScale(d) * width)
)
.force(
"y",
d3
.forceY()
.strength(0.15)
.y(height / 2)
);
break;

case "three":
text = "Stage 3";
xScale = (d) => {
return xScale_stage_3(d);
};

simulation
.force(
"x",
d3
.forceX()
.strength(0.2)
.x((d) => xScale(d) * width)
)
.force(
"y",
d3
.forceY()
.strength(0.15)
.y(height / 2)
);
break;

case "four":
text = "Stage 4";
xScale = (d) => {
return xScale_stage_4(d);
};

simulation
.force(
"x",
d3
.forceX()
.strength(0.2)
.x((d) => xScale(d) * width)
)
.force(
"y",
d3
.forceY()
.strength(0.15)
.y(height / 2)
);
break;

default:
break;
}

invalidation.then(() => simulation.stop()); // a promise to stop the simulation when the cell is re-run

function ticked() {
context.clearRect(0, 0, width, height);
context.textAlign = "center";
context.textBaseline = "middle";
for (const d of newData) {
let radius = radiusScale(d);
context.beginPath();
context.moveTo(d.x + radius, d.y);
context.arc(d.x, d.y, radius, 0, 2 * Math.PI);
context.fillStyle = colorScale(d.gender);
context.fill();
if (showDiseaseNames) {
context.font = "10px Arial";
context.fillStyle = "#000000";
context.fillText(d.disease, d.x, d.y);
}
}
context.font = "30px Arial";
context.fillStyle = "#000000";
context.fillText(text, centerX, centerY);
}

return context.canvas;
}
Insert cell
height = 400
Insert cell
colorScale = d3.scaleOrdinal().domain(genderGroups).range(colors)
Insert cell
fundingScale = d3
.scaleSqrt()
.domain(d3.extent(data, (d) => d.funding_2022))
.range([3, 50])
Insert cell
dalyScale = d3
.scaleSqrt()
.domain(d3.extent(data, (d) => d.daly))
.range([3, 50])
Insert cell
xScale_stage_1 = (d) => {
// All circles in the center
return 0.5;
}
Insert cell
xScale_stage_2 = (d) => {
// [grey bubbles disappear; leaving m and f diseases (stage2)]
const groupPositions = new Map();

groupPositions.set("Female", 1 / 3);
groupPositions.set("Female semi-dominant", 1.5);
groupPositions.set("Male", (2 / 3));
groupPositions.set("Male semi-dominant", 1.5 );
groupPositions.set("Neutral", 1.5 );

return groupPositions.get(d.gender);
}
Insert cell
xScale_stage_3 = (d) => {
// [bubbles rearrange to show 3-5 big hitters in one cluster, others in a separate cluster?]
// 1. we need four possible solutions Female small, female big, male big, male small
// 2. This positions are 1/5, 2/5, 3/5, 4/5
let position = 1.5;

switch (d.gender) {
case "Female":
if (d.funding_rank < 5) {
position = 2 / 5;
} else {
position = 1 / 5;
}

break;

case "Male":
if (d.funding_rank < 5) {
position = 3 / 5;
} else {
position = 4 / 5;
}

break;

default:
break;
}
return position;
}
Insert cell
xScale_stage_4 = (d) => {
// [rearrange to show 3-5 diseases with biggest burden – hopefully a pattern emerges]
// 1. we need four possible solutions Female small, female big, male big, male small
// 2. This positions are 1/5, 2/5, 3/5, 4/5
let position = 1.5;

switch (d.gender) {
case "Female":
if (d.daly_rank < 5) {
position = 2 / 5;
} else {
position = 1 / 5;
}

break;

case "Male":
if (d.daly_rank < 5) {
position = 3 / 5;
} else {
position = 4 / 5;
}

break;

default:
break;
}
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 = d3
.groups(combined_data, (d) => d.gender)
.map(([key, array]) =>
array.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]
}))
)
.flat()
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