Public
Edited
Apr 12, 2023
1 fork
Importers
8 stars
Also listed in…
hex maps
Political Issues
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// The numbers
[
all_indices.length,
yellow.length,
yellow.length / all_indices.length,
purple.length,
purple.length / all_indices.length,
minorities.length,
4 / 9
]
Insert cell
// The precincts argument should be a list of objects, each of which
// extends one of the objects in all_precincts by adding a district
// property. The district should be a number between 1 and 9 indicating
// which district you want the precinct to be in.
function setup_hexmap(precincts) {
let W = 1000;
let w = width <= W ? width : W;
let h = 0.65 * w;
let svg = d3.create("svg:svg").attr("width", w).attr("height", h);

let r = (36 * w) / W;
svg
.append("g")
.selectAll("path.outer")
.data(precincts)
.join("path")
.attr("class", "outer")
.attr("d", (o) => hex(o.i, o.j, r))
.attr("stroke", "white")
.attr("stroke-width", 2)
.attr("fill", "#ddd");
svg
.append("g")
.selectAll("path.inner")
.data(precincts)
.join("path")
.attr("class", "inner")
.attr("d", (o) => hex(o.i, o.j, r, 0.5))
.attr("stroke", "black")
.attr("stroke-width", (o) => (o.minority ? 5 : 1))
//.attr("fill", (o) => d3.schemeCategory10[o.district]);
.attr("fill", (o) => (o.color == "yellow" ? "#EBB35F" : "#B180B0"));

// If you successfully label each precinct with a number from 1-9 as the
// district property, then the following code should automatically group
// and draw them and draw the outlines of the districts.
// As originally set up, the district property of the precincts is
// undefined. Thus, we should get a single district so that the whole
// collection is outlined.
let groups = d3.group(precincts, (o) => o.district);
let good_partition = true;
d3.range(1, 10).forEach(function (i) {
if (groups.has(i)) {
if (groups.get(i).length != 15) {
good_partition = false;
}
} else {
good_partition = false;
}
});
if (good_partition) {
groups.forEach(function (g) {
let purples = g.filter((p) => p.color == "purple");
let yellows = g.filter((p) => p.color == "yellow");
let color = purples.length > yellows.length ? "purple" : "yellow";
let merged_precinct = polygonClipping.union(
// Always use a constant size to keep polygonClipping happy.
g.map((o) => [vertices(o.i, o.j, 1)])
);
svg
.append("path")
.attr(
"d",
d3.line().curve(d3.curveLinearClosed)(merged_precinct[0][0].map(([x, y]) => [r * x, r * y]))
)
.attr("stroke", "black")
.attr("stroke-width", 5)
.attr("fill", color)
.attr("fill-opacity", 0.1)
.on("pointerenter", function () {
d3.select(this).attr("fill-opacity", 0.2);
})
.on("pointerleave", function () {
d3.select(this).attr("fill-opacity", 0.1);
});
});
}

return svg.node();
}
Insert cell
// The precincts of Hexapolis are hexagonal. Each precinct is drawn as
// a large hexagon with a smaller hexagon on top. The radius of the larger
// hexagon is the parameter r. The smaller hexagon is a copy of the large
// scaled by the factor r0 about its center.
function hex(i, j, r, r0 = 1) {
return d3.line()(vertices(i, j, r, r0)) + "Z";
}
Insert cell
vertices(0, 0, 1)
Insert cell
// The vertices are generated separately so they can also be easily access
// by polygonClipping's union method.
function vertices(i, j, r, r0 = 1) {
let s = (Math.sqrt(3) * r) / 2;
let x = 2 * (i - 3) * s + (j + 1) * s;
let y = (j + 1) * Math.sqrt(3) * s;

return d3
.range(6)
.map((t) => [
parseFloat(
(x + r * r0 * Math.cos((2 * Math.PI * t) / 6 + Math.PI / 6)).toFixed(5)
),
parseFloat(
(y + r * r0 * Math.sin((2 * Math.PI * t) / 6 + Math.PI / 6)).toFixed(5)
)
]);
}
Insert cell
// Each precinct is an object with i and j properties indicating its
// position in a hexagonal lattice.
// There are also color and minority properties indicating to which party
// the precinct swings and whether it's a minority district.
// Finally, there's a list of each precinct's neighbors, as indicated by
// the id property. While not used here, that's probably pretty key
// information for any gerrymandering algorithm.
all_precincts = all_indices.map(([i, j]) => ({
id: `precinct_${i}_${j}`,
i: i,
j: j,
color: pair_in_array([i, j], purple) ? "purple" : "yellow",
minority: pair_in_array([i, j], minorities) ? true : false,
neighbors: [
[i - 1, j],
[i + 1, j],
[i, j - 1],
[i, j + 1],
[i - 1, j + 1],
[i + 1, j - 1]
]
.filter(([i0, j0]) => pair_in_array([i0, j0], all_indices))
.map(([i0, j0]) => `precinct_${i0}_${j0}`)
}))
Insert cell
function pair_in_array([i, j], A) {
return _.findIndex(A, (pair) => pair[0] == i && pair[1] == j) > -1;
}
Insert cell
// All the indices of the precints, defined in terms of the
// purple and yellow indices.
all_indices = purple.concat(yellow)
Insert cell
minorities = [
[7, 0],
[8, 0],
[9, 0],
[14, 0],
[12, 1],
[13, 1],
[14, 1],
[6, 2],
[11, 2],
[12, 2],
[5, 3],
[6, 3],
[7, 3],
[10, 3],
[11, 3],
[12, 3],
[6, 4],
[2, 5],
[3, 5],
[15, 5],
[1, 6],
[2, 6],
[8, 6],
[9, 6],
[10, 6],
[13, 6],
[14, 6],
[1, 7],
[7, 7],
[8, 7],
[12, 7],
[13, 7],
[14, 7],
[3, 8],
[4, 8],
[6, 8],
[7, 8],
[8, 8],
[11, 8],
[2, 9],
[3, 9]
]
Insert cell
purple = [
[7, 0],
[8, 0],
[9, 0],
[14, 0],
[5, 1],
[6, 1],
[7, 1],
[9, 1],
[10, 1],
[14, 1],
[15, 1],
[4, 2],
[5, 2],
[8, 2],
[9, 2],
[11, 2],
[13, 2],
[14, 2],
[15, 2],
[16, 2],
[3, 3],
[4, 3],
[7, 3],
[8, 3],
[11, 3],
[12, 3],
[13, 3],
[15, 3],
[16, 3],
[2, 4],
[7, 4],
[8, 4],
[10, 4],
[11, 4],
[14, 4],
[15, 4],
[1, 5],
[2, 5],
[6, 5],
[7, 5],
[8, 5],
[9, 5],
[10, 5],
[12, 5],
[1, 6],
[2, 6],
[4, 6],
[5, 6],
[6, 6],
[7, 6],
[8, 6],
[12, 6],
[1, 7],
[3, 7],
[4, 7],
[5, 7],
[10, 7],
[11, 7],
[12, 7],
[2, 8],
[3, 8],
[4, 8],
[5, 8],
[6, 8],
[9, 8],
[10, 8],
[1, 9],
[2, 9],
[5, 9],
[6, 9],
[7, 9],
[8, 9],
[9, 9],
[7, 10],
[8, 10]
]
Insert cell
yellow = [
[12, 0],
[13, 0],
[4, 1],
[8, 1],
[11, 1],
[12, 1],
[13, 1],
[3, 2],
[6, 2],
[7, 2],
[10, 2],
[12, 2],
[5, 3],
[6, 3],
[9, 3],
[10, 3],
[14, 3],
[3, 4],
[4, 4],
[5, 4],
[6, 4],
[9, 4],
[12, 4],
[13, 4],
[3, 5],
[4, 5],
[5, 5],
[11, 5],
[13, 5],
[14, 5],
[15, 5],
[3, 6],
[9, 6],
[10, 6],
[11, 6],
[13, 6],
[14, 6],
[2, 7],
[6, 7],
[7, 7],
[8, 7],
[9, 7],
[13, 7],
[14, 7],
[0, 8],
[1, 8],
[7, 8],
[8, 8],
[11, 8],
[12, 8],
[13, 8],
[0, 9],
[3, 9],
[4, 9],
[10, 9],
[11, 9],
[2, 10],
[3, 10],
[4, 10],
[9, 10]
]
Insert cell
polygonClipping = require("polygon-clipping")
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