Public
Edited
Jul 26, 2023
1 fork
32 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function make_map(opts = {}) {
let { max_size = 900, link_chart = null } = opts;
let map_width = width < max_size ? width : max_size;
let height = 0.575 * map_width;

let div = d3
.create("div")
.style("width", `${map_width}px`)
.style("height", `${height}px`)
.style("overflow", "hidden")
.style("position", "relative");

let svg = div
.append("svg")
.style("overflow", "hidden")
.attr("viewBox", [0, 0, map_width, height]);

let proj = d3
.geoIdentity()
.reflectY(true)
.fitSize([map_width, height], stateTiles);
let path = d3.geoPath().projection(proj);

let map = svg.append("g");
let density_map = new Map();
map
.selectAll("path.tile")
.data(stateTiles.features)
.join("path")
.attr("class", "tile")
.attr("data-fips", (o) => o.properties.fips)
.attr("data-name", (o) => o.properties.name)
.attr("data-abbr", (o) => o.properties.abbr)
.attr("data-rrs", function (d) {
let fd = data.filter((o) => o.abbr == d.properties.abbr);
if (fd.length > 0) {
return fd[0].rr_score;
} else {
return "NA";
}
})
.attr("data-covariate", function (d) {
if (covariate != "---") {
let fd = filtered_data.filter((o) => o.abbr == d.properties.abbr);
if (fd.length > 0) {
return fd[0][covariate];
} else {
return "NA";
}
}
})
.attr("d", path)
.attr("fill", (o) =>
covariate == "---"
? rr_shade(o.properties.abbr)
: state_color(o.properties.abbr)
)
.attr("stroke-width", 0.8)
.attr("stroke", "white")
.attr("stroke-linejoin", "round")
// .on("touchmove", (e) => e.preventDefault())
.on("pointerenter mouseenter", function (evt, o) {
evt.preventDefault();
if (covariate != "---") {
let class_selector = ".color" + state_color(o.properties.abbr).slice(1);
legend
.select(class_selector)
.attr("stroke", "black")
.attr("stroke-width", 2)
.raise();
if (link_chart) {
let selected_circles = d3
.select(link_chart)
.select(`#${o.properties.abbr}`)
.attr("fill", "yellow")
.attr("stroke", "black")
.attr("r", 0.005 * width)
.raise();
}
} else {
if (link_chart) {
d3.select(link_chart)
.select(`.${o.properties.abbr}`)
.attr("stroke", "black")
.attr("stroke-width", 0.002 * width)
.raise();
d3.select(link_chart).select();
}
}
})
.on("mouseout", function (evt, o) {
if (covariate != "---") {
let class_selector = ".color" + state_color(o.properties.abbr).slice(1);
legend
.select(class_selector)
.attr("stroke", "white")
.attr("stroke-width", 0.5);
if (link_chart) {
let selected_circles = d3
.select(link_chart)
.select(`#${o.properties.abbr}`)
.attr("fill", "black")
.attr("r", 0.002 * width);
}
} else {
if (link_chart) {
d3.select(link_chart)
.select(`.${o.properties.abbr}`)
.attr("stroke", null);
}
}
});
map
.selectAll("text")
.data(stateTiles.features)
.join("text")
.text((o) => o.properties.abbr)
.attr("font-size", `${0.022 * map_width}px`)
.attr("x", function (o) {
return path.centroid(o)[0];
})
.attr("text-anchor", "middle")
.attr("y", function (o) {
return path.centroid(o)[1];
})
.attr("dy", 0.008 * map_width)
.attr("pointer-events", "none");

map
.selectAll("path.tile")
.nodes()
.forEach(function (t) {
let content = html`<div>
<div style="font-weight:bold">${t.getAttribute("data-name")}</div>
<div>reproductive rights score: ${t.getAttribute("data-rrs")}</div>
${
covariate != "---"
? `<div>${covariate.replace(/_/g, " ")}: ${d3.format("0.2f")(
t.getAttribute("data-covariate")
)}</div>`
: ""
}
</div>`;
tippy(t, {
content: content,
theme: "light-border",
maxWidth: 500
});
});

let s = map_width / 10;
let legend = svg
.append("g")
.attr("transform", `translate(${map_width - 1.5 * s},${height - 1.2 * s})`);
let arrow_group = legend.append("g");
arrow_group // from http://thenewcode.com/1068/Making-Arrows-in-SVG
.append("svg:defs")
.append("marker")
.attr("id", "arrowhead")
.attr("markerWidth", 0.5 * 10)
.attr("markerHeight", 0.5 * 7)
.attr("refX", 0)
.attr("refY", 0.5 * 3.5)
.attr("orient", "auto")
.append("polygon")
// .attr("points", `0 0, 10 3.5, 0 7`);
.attr("points", `0 0, ${0.5 * 10} ${0.5 * 3.5}, 0 ${0.5 * 7}`);

let patches = legend.append("g");
let arrows = legend.append("g");

if (covariate != "---") {
let n = 5;
let dd = s / n;
let to_rr = d3
.scaleLinear()
.domain([0, n - 1])
.range([rr_scores.min, rr_scores.max]);
let to_covariate = d3
.scaleLinear()
.domain([0, n - 1])
.range([covariate_values.min, covariate_values.max]);
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
patches
.append("rect")
.attr(
"class",
"color" + color(to_rr(i), to_covariate(n - j - 1)).slice(1)
)
.attr("x", i * dd)
.attr("y", j * dd)
.attr("width", s / n)
.attr("height", s / n)
.attr("fill", color(to_rr(i), to_covariate(n - j - 1)))
.attr("stroke", "white")
.attr("stroke-width", 0.5);
}
}
arrows
.append("line")
.attr("x1", 0)
.attr("x2", s)
.attr("y1", s)
.attr("y2", s)
.attr("stroke", "black")
.attr("stroke-width", 2)
.attr("marker-end", "url(#arrowhead)");
arrows
.append("line")
.attr("x1", 0)
.attr("x2", 0)
.attr("y1", s)
.attr("y2", 0)
.attr("stroke", "black")
.attr("stroke-width", 2)
.attr("marker-end", "url(#arrowhead)");
arrows
.append("text")
.attr("x", 0.15 * s)
.attr("y", 1.2 * s)
.attr("font-size", 0.011 * width)
.text("rr-score");
arrows
.append("text")
.attr("x", -0.016 * width)
.attr("y", -0.012 * width)
.attr("font-size", 0.011 * width)
.text("covariate");
} else {
legend = Legend(
d3.scaleSequential(d3.interpolateRdBu).domain(rr_score_extent),
{
title: "rr-score",
tickFormat: ".0f"
}
);
d3.select(legend).selectAll("text").attr("font-size", "16px");
svg
.append("g")
.attr("transform", `translate(${0.3 * map_width},${0.05 * height})`)
.append("g")
.attr("transform", `scale(${max_size / 700})`)
.append(() => legend);
}

return div.node();
}
Insert cell
function make_chart(opts = {}) {
let { w = 900 } = opts;
let h = 0.6 * w;
let plot;
if (covariate != "---") {
let xs = filtered_data.map((o) => o.rr_score);
let xmin = d3.min(xs);
let xmax = d3.max(xs);
let xrange = xmax - xmin;
xmin = xmin - xrange / 10;
xmax = xmax + xrange / 10;
let ys = filtered_data.map((o) => o[covariate]);
let ymin = d3.min(ys);
let ymax = d3.max(ys);
let yrange = ymax - ymin;
ymin = ymin - yrange / 10;
ymax = ymax + yrange / 10;

let f = (x) => regression.coef[1] * x + regression.coef[0];
plot = Plot.plot({
width: w,
height: h,
x: { domain: [xmin, xmax] },
y: { domain: [ymin, ymax] },
marks: [
Plot.line([
[xmin + xrange / 40, f(xmin + xrange / 40)],
[xmax, f(xmax)]
]),
Plot.dot(filtered_data, {
x: "rr_score",
y: covariate,
fill: "black",
title: (o) => o.abbr,
r: 3 * Math.sqrt(width / 1152)
}),
Plot.ruleX([xmin]),
Plot.ruleY([ymin])
]
});
d3.select(plot)
.selectAll("circle")
.on("pointerenter", function () {
d3.select(this)
.attr("fill", "yellow")
.attr("stroke", "black")
.attr("r", 5 * Math.sqrt(width / 1152))
.raise();
})
.on("pointerleave", function () {
d3.select(this)
.attr("fill", "black")
.attr("r", 3 * Math.sqrt(width / 1152));
})
.nodes()
.forEach(function (c) {
let d3Circle = d3.select(c);
let value = d3Circle.select("title").text();
d3Circle.attr("id", value);
tippy(c, { content: d3Circle.attr("id"), theme: "light-border" });
d3Circle.select("title").remove();
});
} else {
plot = Plot.plot({
width: w,
height: h,
marks: [
Plot.rectY(
data,
Plot.stackY(
Plot.binX(
{ y: "count", thresholds: 7, sort: "first", title: "first" }, // title: "first",
{
x: "rr_score",
text: "abbr",
sort: "rr_score",
fill: "rr_score",
title: (d) => `${d.abbr}: RR score: ${d.rr_score}`
}
)
)
),
Plot.text(
data,
Plot.stackY(
Plot.binX(
{
y: "count",
thresholds: 7,
sort: "first",
text: "first"
// title: "first"
},
{
x: "rr_score",
sort: "rr_score",
z: "abbr",
text: "abbr",
fill: "black"
// title: (d) => `${d.name}. RR score: ${d.rr_score}`
}
)
)
),
Plot.ruleX([0]),
Plot.ruleY([0])
],
x: { ticks: 7 },
color: { scheme: "rdbu" }
});
d3.select(plot).selectAll("text").attr("pointer-events", "none");
d3.select(plot)
.selectAll("rect")
.nodes()
.forEach(function (r) {
let value = d3.select(r).select("title").text();
d3.select(r).attr("class", value.split(":")[0]);
tippy(r, {
content: value,
theme: "light-border"
});
});
d3.select(plot).selectAll("rect").select("title").remove();
}

return plot;
}
Insert cell
rr_shade = (abbr) =>
d3.interpolateRdBu(rr_scale(data.filter((o) => o.abbr == abbr)[0].rr_score))
Insert cell
rr_scale = {
let rr_scores = data.map((o) => o.rr_score);
let min = d3.min(rr_scores);
let max = d3.max(rr_scores);
return d3.scaleLinear().domain([min, max]).range([0, 1]);
}
Insert cell
rr_score_extent = d3.extent(data, d=>d.rr_score)
Insert cell
regression = {
if (covariate != "---") {
return jstat.models.ols(
filtered_data.map((o) => o[covariate]),
filtered_data.map((o) => [1, o["rr_score"]])
);
}
}
Insert cell
colors = [
"#d3d3d3",
"#b6cdcd",
"#97c5c5",
"#75bebe",
"#52b6b6",
"#cab6c5",
"#aeb0bf",
"#91aab9",
"#70a4b2",
"#4e9daa",
"#c098b9",
"#a593b3",
"#898ead",
"#6b89a6",
"#4a839f",
"#b77aab",
"#9e76a6",
"#8372a0",
"#666e9a",
"#476993",
"#ad5b9c",
"#955898",
"#7c5592",
"#60528d",
"#434e87"
]
Insert cell
function state_color(abbr) {
if (covariate != "---") {
let info = filtered_data.filter((o) => o.abbr == abbr);
if (info.length > 0) {
return color(info[0].rr_score, info[0][covariate]);
} else {
return "black";
}
} else {
return rr_shade(data.filter((o) => o.abbr == abbr));
}
}
Insert cell
color = (rr, cov) => colors[j(cov) + i(rr) * 5]
Insert cell
i = d3
.scaleQuantile()
.domain([rr_scores.min, rr_scores.max])
.range(d3.range(Math.sqrt(colors.length)))
Insert cell
rr_scores = {
let rr_scores = data.map((o) => o.rr_score);
rr_scores.min = d3.min(rr_scores);
rr_scores.max = d3.max(rr_scores);
return rr_scores;
}
Insert cell
j = d3
.scaleQuantile()
.domain([covariate_values.min, covariate_values.max])
.range(d3.range(Math.sqrt(colors.length)))
Insert cell
covariate_values = {
let covariate_values = filtered_data.map((o) => o[covariate]);
covariate_values.min = d3.min(covariate_values);
covariate_values.max = d3.max(covariate_values);
return covariate_values;
}
Insert cell
filtered_data = data.filter((o) => o[covariate] > 0)
Insert cell
data = FileAttachment("reproductive_rights_correlates@6.csv").csv({ typed: true })
Insert cell
stateTiles = {
let stateTiles = await FileAttachment("stateTiles2.json").json();
let tiles = topojson.feature(stateTiles, stateTiles.objects.tiles);
tiles.features = tiles.features.filter((o) => o.properties.abbr != "DC");
return tiles;
}
Insert cell
jstat = require("jstat")
Insert cell
tippy_style = html`<link rel="stylesheet" href="${await require.resolve(
`tippy.js/themes/light-border.css`
)}">`
Insert cell
tippy = require("tippy.js@6")
Insert cell
topojson = require("topojson-client@3")
Insert cell
import { Legend } from "@d3/color-legend"
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

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