Public
Edited
Sep 24, 2023
Importers
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
{
if (all_results.length > 0) {
return setup_hexmap(all_results.slice(-1)[0].precincts);
} else {
return "Re-execute the code block below (after some legal maps have been generated) to see the most recently generated districts.";
}
}
Insert cell
Insert cell
unique_groups = {
let groups = d3.group(
all_results, // .filter((o) => o.minority_cnt >= 2),
(o) => o.yellow_cnt
);
let grouped_unique = new Map();
let keys = Array.from(groups.keys()).sort();
keys.forEach((k) =>
grouped_unique.set(
k,
_.uniqBy(groups.get(k), districts_to_unique_id).slice(0, 239)
)
);
return grouped_unique;
}
Insert cell
improve2(unique_groups.get(4)[0], "purple")
Insert cell
{
if (improved) {
return setup_hexmap(improved.precincts);
} else {
return setup_hexmap(
(await FileAttachment("improved_yellow_5to6.json").json()).precincts
);
}
}
Insert cell
improved
Insert cell
// setup_hexmap(all_results[0].precincts)
Insert cell
// all_results.slice(0, 2).map(async function (r) {
// if (r.purple_cnt > r.yellow_cnt) {
// let improved = await improve_more(r, "purple", 200);
// if (improved.purple_cnt > r.purple_cnt) {
// // all_results.push(improved);
// return improved;
// }
// }
// })
Insert cell
// setup_hexmap(five_to_six[14].precincts)
Insert cell
setup_hexmap(five_to_six[12].precincts)
Insert cell
new_results
Insert cell
new_results = []
Insert cell
five_to_six.filter((o) => o.yellow_cnt == 6)
Insert cell
seven_to_eight.filter((o) => o.purple_cnt == 8)
Insert cell
// seven_to_eight = Promise.all(
// unique_groups
// .get(3)
// .slice(0, 40)
// .map((r) =>
// r.purple_cnt > r.yellow_cnt
// ? improve2(r, "purple")
// : improve2(r, "yellow")
// )
// )

Insert cell
setup_hexmap(seven_to_eight[1].precincts)
Insert cell
unique_groups
Insert cell
Insert cell
improved = {
if (results[1].precincts) {
return improve_more(results[2], "yellow", 500);
}
}
Insert cell
// This file contains just the initial map and dummy yellow and
// purple maps that will be overwritten by the code.
results = (reset, FileAttachment("hexapolis_init.json").json())
Insert cell
all_results = (reset, [])

// all_results = FileAttachment("five_thirty_eight.json").json()
Insert cell
function districts_to_unique_id(p) {
let districts = d3.group(p.precincts, (o) => o.district);
return d3
.range(1, 10)
.map((i) =>
districts
.get(i)
.map((o) => [o.i, o.j])
.sort()
)
.sort()
.flat(2)
.map((i) => (i < 10 ? `0${i}` : `${i}`))
.join("");
}
Insert cell
all_results
Insert cell
d3.sum(Array.from(unique_groups.values()).map((a) => a.length))
Insert cell
Array.from(unique_groups.values()).flat()
Insert cell
state = (reset,
{
button_pushes: 0,
button_text: ["Start random redistricting", "Stop random redistricting"]
})
Insert cell
intervalIDs = []
Insert cell
function random_precincts(optimize_color = false) {
let assoc = new Map();
assoc.set(undefined, 1);
let precincts;
while (assoc.has(undefined)) {
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}`)
}));

// Failed
// let so_far = get_first_15();
// so_far.forEach((p) => (p.district = 1));

let so_far = [];
for (let i = 1; i <= 9; i++) {
try {
let boundary = get_boundary_precincts(so_far, precincts);
let new15 = get15(boundary, so_far, precincts);
so_far = so_far.concat(new15);
new15.forEach((p) => (p.district = i));
} catch (e) {
("pass");
}
}
assoc = d3.group(precincts, (o) => o.district);
}

let groups = d3.group(precincts, (p) => p.district);
let purple_cnt = 0;
let yellow_cnt = 0;
let minority_cnt = 0;
groups.forEach(function (g) {
if (g.filter((p) => p.minority).length > 7) {
minority_cnt++;
}
let gg = d3.group(g, (p) => p.color);
let p = gg.get("purple");
let y = gg.get("yellow");
if (!y) {
purple_cnt++;
} else if (p && p.length > y.length) {
purple_cnt++;
} else {
yellow_cnt++;
}
});

let new_result = { purple_cnt, yellow_cnt, minority_cnt, precincts };

return new_result;
}
Insert cell
results
Insert cell
function get_boundary_precincts(deleted, precincts) {
let remaining = set_difference(precincts, deleted);
let deleted_ids = deleted.map((o) => o.id);
return remaining.filter(
(o) =>
o.neighbors.length < 6 ||
_.intersection(
deleted_ids,
o.neighbors.map((o) => o.id)
).length > 0
);
}
Insert cell
get15([all_precincts[3]], [], all_precincts)
Insert cell
function get15(start, avoid, all_precincts) {
let precincts_left = set_difference(all_precincts, avoid);
let precincts_start = set_difference(start, avoid);
// let output = [precincts_start[random(0, precincts_start.length)()]];
let output = [random_choice(precincts_start)];
//let output = [random_choice(precincts_left)];

let cnt = 0;
while (output.length < 15 && cnt++ < 16) {
let neighbors = set_difference(
get_all_neighbors(output, all_precincts),
avoid
);
output = output.concat(neighbors);
if (output.length > 15) {
console.log(output.length);
output = output.slice(0, 15);
} else if (cnt > 12) {
throw "Not found";
}
}
return output;
}
Insert cell
async function improve_more(input, color, n) {
let current = _.cloneDeep(input);
for (let i = 0; i < n; i++) {
await Promises.delay().then(function () {
let this_try = improve(current, color);
let indicator1 =
color == "purple" ? current.purple_cnt : current.yellow_cnt;
let indicator2 =
color == "purple" ? this_try.purple_cnt : this_try.yellow_cnt;
if (indicator1 < indicator2) {
current = this_try;
current.improvement_iterations = i;
}
});
}
return current;
}
Insert cell
// setup_hexmap(all_results[2].precincts)
Insert cell
// setup_hexmap(atry.precincts)
Insert cell
console.log("-----")
Insert cell
// atry = improve2(all_results[3], "yellow")
Insert cell
function improve2(input, color) {
let best_result = _.cloneDeep(input);
let precincts = _.cloneDeep(best_result.precincts);

for (let i = 0; i < 500; i++) {
let precincts0 = _.cloneDeep(precincts);
let districts0 = d3.group(precincts0, (p) => p.district);
let choice1 = random(1, 9)();
let district1 = districts0.get(choice1);
let choice2 = random(choice1 + 1, 10)();
let district2 = districts0.get(choice2);
let border12 = get_border(choice1, choice2, precincts0);
if (border12.length > 0) {
let border21 = get_border(choice2, choice1, precincts0);
let p1 = border12[random(0, border12.length)()];
p1.district = choice2;
let p2 = border21[random(0, border21.length)()];
p2.district = choice1;

let new_district1 = precincts0.filter((o) => o.district == choice1);
let rest1 = set_difference(precincts0, new_district1);
let test1;
try {
test1 = get15([new_district1[0]], rest1, precincts0).length == 15;
} catch {
test1 = false;
}
let new_district2 = precincts0.filter((o) => o.district == choice2);
let rest2 = set_difference(precincts0, new_district2);
let test2;
try {
test2 = get15([new_district2[0]], rest2, precincts0).length == 15;
} catch {
test2 = false;
}
if (test1 && test2) {
precincts = precincts0;
let groups = d3.group(precincts, (p) => p.district);
let purple_cnt = 0;
let yellow_cnt = 0;
let minority_cnt = 0;
groups.forEach(function (g) {
if (g.filter((p) => p.minority).length > 7) {
minority_cnt++;
}
let gg = d3.group(g, (p) => p.color);
let p = gg.get("purple");
let y = gg.get("yellow");
if (!y) {
purple_cnt++;
} else if (p && p.length > y.length) {
purple_cnt++;
} else {
yellow_cnt++;
}
});
if (minority_cnt >= 2) {
let new_result = {
purple_cnt,
yellow_cnt,
minority_cnt,
precincts
};
new_results.push(new_result);
// let indicator1 =
// color == "purple" ? best_result.purple_cnt : best_result.yellow_cnt;
// let indicator2 = color == "purple" ? purple_cnt : yellow_cnt;
// if (indicator1 < indicator2) {
// best_result = new_result;
// }
if (
score(best_result.precincts, color) <
score(new_result.precincts, color)
) {
best_result = new_result;
}
}
}
}
}

return best_result;

function get_border(i, j, precints_in) {
let groups = d3.group(precints_in, (p) => p.district);
let jids = groups.get(j).map((p) => p.id);
return groups
.get(i)
.filter((p) => _.intersection(p.neighbors, jids).length > 0);
}
}
Insert cell
function improve(input, color) {
let changed = false;
let precincts = _.cloneDeep(input.precincts);

for (let i = 0; i < random(16, 32)(); i++) {
let precincts0 = _.cloneDeep(precincts);
let districts0 = d3.group(precincts0, (p) => p.district);
let choice1 = random(1, 9)();
let district1 = districts0.get(choice1);
let choice2 = random(choice1 + 1, 10)();
let district2 = districts0.get(choice2);
let border12 = get_border(choice1, choice2, precincts0);
if (border12.length > 0) {
let border21 = get_border(choice2, choice1, precincts0);
let p1 = border12[random(0, border12.length)()];
p1.district = choice2;
let p2 = border21[random(0, border21.length)()];
p2.district = choice1;

let new_district1 = precincts0.filter((o) => o.district == choice1);
let rest1 = set_difference(precincts0, new_district1);
let test1;
try {
test1 = get15([new_district1[0]], rest1, precincts0).length == 15;
} catch {
test1 = false;
}
let new_district2 = precincts0.filter((o) => o.district == choice2);
let rest2 = set_difference(precincts0, new_district2);
let test2;
try {
test2 = get15([new_district2[0]], rest2, precincts0).length == 15;
} catch {
test2 = false;
}
if (test1 && test2) {
precincts = precincts0;
// atry.push(precincts);
}
}
}

let groups = d3.group(precincts, (p) => p.district);
let purple_cnt = 0;
let yellow_cnt = 0;
let minority_cnt = 0;
groups.forEach(function (g) {
if (g.filter((p) => p.minority).length > 7) {
minority_cnt++;
}
let gg = d3.group(g, (p) => p.color);
let p = gg.get("purple");
let y = gg.get("yellow");
if (!y) {
purple_cnt++;
} else if (p && p.length > y.length) {
purple_cnt++;
} else {
yellow_cnt++;
}
});

let original_score, new_score;
// let idx = color == "purple" ? 1 : 2;
if (minority_cnt >= 2) {
// original_score = score(results[idx].precincts, color);
// new_score = score(precincts, color);
// if (original_score < new_score) {
let indicator1 = color == "purple" ? input.purple_cnt : input.yellow_cnt;
let indicator2 = color == "purple" ? purple_cnt : yellow_cnt;
if (indicator1 < indicator2) {
// results[idx].precincts = precincts;
// results[idx].purple_cnt = purple_cnt;
// results[idx].yellow_cnt = yellow_cnt;
// results[idx].minority_cnt = minority_cnt;
changed = true;
}
}
if (changed) {
return {
purple_cnt,
yellow_cnt,
minority_cnt,
precincts
};
} else {
return input;
}
// return [choice1, choice2, district1, district2];

function get_border(i, j, precints_in) {
let groups = d3.group(precints_in, (p) => p.district);
let jids = groups.get(j).map((p) => p.id);
return groups
.get(i)
.filter((p) => _.intersection(p.neighbors, jids).length > 0);
}
}
Insert cell
function score(precincts, color) {
let groups = d3.group(precincts, (p) => p.district);
let purple_proportions = [];
let yellow_proportions = [];
let minority_cnt = 0;
groups.forEach(function (g) {
if (g.filter((p) => p.minority).length > 7) {
minority_cnt++;
}
let gg = d3.group(g, (p) => p.color);
let p = gg.get("purple");
let y = gg.get("yellow");
if (!y) {
purple_proportions.push(1);
} else if (!p) {
yellow_proportions.push(1);
} else if (p.length > y.length) {
purple_proportions.push(p.length / 15);
} else {
yellow_proportions.push(y.length / 15);
}
});
if (minority_cnt >= 2) {
if (color == "yellow") {
return (
yellow_proportions.length -
2 * d3.mean(yellow_proportions) +
d3.mean(purple_proportions)
);
} else if (color == "purple") {
return (
purple_proportions.length -
2 * d3.mean(purple_proportions) +
d3.mean(yellow_proportions)
);
}
} else {
return 0;
}
}

Insert cell
// Version that generates crazier shapes but never seems to
// generate 9 separate districts
function get_first_15() {
// let precincts_left = set_difference(all_precincts, avoid);
// let precincts_start = set_difference(start, avoid);
// let output = [precincts_start[random(0, precincts_start.length)()]];
// let output = [random_choice(precincts_start)];
let output = [random_choice(all_precincts)];
let cnt = 0;
while (output.length < 15 && cnt++ < 16) {
let neighbors = get_all_neighbors(output, all_precincts);
// console.log(neighbors);
output.push(random_choice(neighbors));
if (output.length > 15) {
output = output.slice(0, 15);
} else if (cnt > 16) {
throw "Not found";
}
}
return output;
}
Insert cell
function get_all_neighbors(input_precincts, all_precincts) {
let inputs_ids = input_precincts.map((o) => o.id);
let all_neighbor_ids = input_precincts
.map((o) => o.neighbors)
.flat()
.filter((id) => inputs_ids.indexOf(id) == -1);
let all_neighbors = Array.from(new Set(all_neighbor_ids)).map(
(id) => all_precincts.filter((o) => o.id == id)[0]
);

return all_neighbors;
}
Insert cell
function set_difference(in_set, out_set) {
return in_set.filter((o) => out_set.map((p) => p.id).indexOf(o.id) == -1);
}
Insert cell
function pair_in_array([i, j], A) {
return _.findIndex(A, (pair) => pair[0] == i && pair[1] == j) > -1;
}
Insert cell
function random_choice(list) {
let c = random(0, list.length)();
return list[c];
}
Insert cell
random = {
reset;
let random = d3.randomInt;
let source = d3.randomLcg(s);
random = random.source(source);
return random;
}
Insert cell
import {
all_indices,
purple,
yellow,
minorities,
setup_hexmap,
all_precincts
} from "@mcmcclur/setting-up-hexapolis"
Insert cell
polygonClipping = require("polygon-clipping")
Insert cell
all_results
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