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

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