Public
Edited
Jun 5, 2024
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// comparacionPorDepts = {
// return htl.html`${await Promise.all(
// Object.keys(deptsByCand).map(
// async (g) =>
// htl.html`<h3>${g}</h3>${await makeDeptsChart(dataConcatGrouped.get(g), {
// facet: true,
// width: width > 600 ? width * 0.26 : width * 0.3,
// columns: width > 600 ? 3 : 2
// }).render()}`
// )
// )}`;
// }
Insert cell
makeDeptsChartWithTitle = async (g) =>
htl.html`${await makeDeptsChart(dataConcatGrouped.get(g), {
facet: true,
width: width > 600 ? width * 0.26 : width * 0.3,
height: 100,
columns: width > 600 ? 3 : 2
}).render()}`
Insert cell
dataConcatGrouped = d3.group(dataConcat, (d) => d.Grupo);
Insert cell
dataConcatGroupedMun = d3.group(
dataConcat,
(d) => d.dpto,
(d) => d.mun
)
Insert cell
// JSON.stringify(
// Object.fromEntries(
// Array.from(dataConcatGroupedMun.entries()).map(([k, v]) => [k, v.size])
// )
// )
countMunsByDept = ({
CONSULADOS: 67,
BOLIVAR: 46,
MAGDALENA: 30,
ATLANTICO: 23,
CORDOBA: 30,
CALDAS: 27,
SUCRE: 26,
ANTIOQUIA: 125,
QUINDIO: 12,
RISARALDA: 14,
VALLE: 42,
TOLIMA: 47,
GUAINIA: 8,
"NORTE DE SAN": 40,
BOYACA: 123,
HUILA: 37,
CUNDINAMARCA: 116,
ARAUCA: 7,
META: 29,
CAQUETA: 16,
GUAVIARE: 4,
CESAR: 25,
CHOCO: 30,
VICHADA: 4,
CASANARE: 19,
SANTANDER: 87,
"LA GUAJIRA": 15,
AMAZONAS: 11,
"SAN ANDRES": 2,
NARIÑO: 64,
PUTUMAYO: 13,
"BOGOTA D.C.": 1,
CAUCA: 42,
VAUPES: 6
})
Insert cell
function makeDeptsChart(
data,
{
facet = true,
width = 200,
height = 30,
columns = 3,
order = false,
stroke = true
} = {}
) {
let x = vl
.x()
.fieldO("codigo")
.axis({ grid: false, ticks: false, domain: false, labels: false })
.title("Municipios");
if (order) {
const scaleOrdered = data
.filter((d) => d["Elección"] === "Primera")
.map((d) => d.codigo);

x = x.scale({ domain: scaleOrdered });
} else {
x = x.sort("fill");
}

let chart = vl
.markRect({ tooltip: true, strokeWidth: 0.7, step: 1, align: "left" })
.encode(
x,
vl
.fill()
.fieldN("Ganador")
.scale({
domain: ["GUSTAVO_PETRO", "RODOLFO_HERNÁNDEZ", "FEDERICO_GUTIÉRREZ"],
range: ["#d8241f", "#1776b6", "#e6ab02"]
}),

vl
.y()
.fieldN("Elección")
.title(null)
.axis({ grid: false, ticks: false, domain: false }),
// vl.row().fieldN("Grupo"),
vl.detail(["mun", "VotosGanador", "votant", "dane"]),
vl
.opacity()
.fieldQ("PctGanador")
.scale({ format: ".2%", range: [0.2, 1] })
.title("% Ganador"),
vl.order().fieldQ("i")
)
.data(data)
.width(width)
.height(height)
.config({ legend: { symbolSize: 300, orient: "top", tickCount: 3 } })
.resolve({ scale: { x: "independent", y: "independent" } });

if (facet)
chart = chart.encode(
vl.facet().fieldN("Departamento").columns(columns).sort("Grupo").title("")
);

if (stroke)
chart = chart.encode(
vl
.stroke()
.fieldN("Cambio")
.scale({
domain: [true, false],
range: ["#222a", null]
})
.legend(null)
);

return chart;
}
Insert cell
viewof distribution = Inputs.form(
Object.fromEntries(
primeraVuelta.candidates.map((d) => [
d,
Inputs.range([0, 100], { label: d })
])
)
)
Insert cell
distribution
Insert cell
deptsByCand = ({
"Departamentos Sorpresa": ["CALDAS", "QUINDIO", "VALLE"],
"Departamentos Divididos": [
"ATLANTICO",
"BOLIVAR",
"CONSULADOS",
"GUAVIARE",
"MAGDALENA",
"CESAR",
"CAQUETA",
"GUAINIA",
"RISARALDA"
],
"Departamentos Mayoría Petro": [
"AMAZONAS",
"BOGOTA D.C.",
"CAUCA",
"LA GUAJIRA",
"NARIÑO",
"PUTUMAYO",
"SUCRE",
"VAUPES",
"SAN ANDRES",
"CHOCO",
"CORDOBA"
],
"Departamentos Mayoría Hernández": [
"HUILA",
"ANTIOQUIA",
"ARAUCA",
"CUNDINAMARCA",
"NORTE DE SAN",
"SANTANDER",
"TOLIMA",
"VICHADA",
"CASANARE",
"META",
"BOYACA"
]
})
Insert cell
import { vl } from "@vega/vega-lite-api-v5"
Insert cell
navio(dataConcat)
Insert cell
primeraVuelta.candidates
Insert cell
dataConcat = primeraVuelta
.map((d) => ({ Elección: "Primera", ...d }))
.concat(segundaVuelta.map((d) => ({ Elección: "Segunda", ...d })))
.map((d) => {
const nd = {
primera: mapPrimera.get(d.codigo),
segunda: mapSegunda.get(d.codigo),
Grupo: Object.entries(deptsByCand)
.filter(([c, depts]) => depts.includes(d.dpto))
.map(([c, _]) => c)[0],
...d
};

nd.Grupo = nd.Grupo || "Departamentos Divididos";
nd.Ganadores = `${nd.primera.iWinner},${nd.segunda.iWinner}`;
nd.Cambio =
nd.primera.Ganador !== nd.segunda.Ganador &&
!(
nd.primera.Ganador === "FEDERICO_GUTIÉRREZ" &&
nd.segunda.Ganador === "RODOLFO_HERNÁNDEZ"
);
nd.Departamento = `${nd.dpto} (${countMunsByDept[nd.dpto]})`;
return nd;
})
.sort((a, b) => {
return (
d3.ascending(a.primera.iWinner, b.primera.iWinner) ||
d3.ascending(a.segunda.iWinner, b.segunda.iWinner) ||
// d3.ascending(a["Elección"], b["Elección"]) ||
// d3.ascending(a.iWinner, b.iWinner) ||
// d3.ascending(a.Ganadores, b.Ganadores) ||
// d3.ascending(a.primera.PctGanador, b.primera.PctGanador) ||
d3.ascending(a.segunda.PctGanador, b.segunda.PctGanador)
);
})
.map((d, i) => ({ ...d, i }))
Insert cell
chartPctByCand = {
const gridDash = {
condition: {
test: { field: "value", equal: 0 },
value: []
},
value: [1, 4]
};
return vl
.markPoint({ tooltip: true })
.encode(
vl
.x()
.fieldQ("diffPetro")
.scale({ domain: [-0.15, 0.8] })
.axis({
format: ".1%",
gridDash
}),
vl
.y()
.fieldQ("diffHernandez")
.scale({ domain: [-0.15, 0.8] })
.axis({ format: ".1%", gridDash }),
vl.size().fieldQ("segunda.votant"),
vl.detail(["primera.dpto", "primera.mun"]),
vl.facet().fieldN("primera.dpto").columns(4)
)
.data(dataJoined)
.width((width - 200) * 0.2)
.height((width - 200) * 0.2)
.render();
}
Insert cell
dataJoined = segundaVuelta
.map((d) => ({
primera: mapPrimera.get(d.codigo),
segunda: d,
id: d.codigo
}))
.map((d) => ({
diffPetro:
(d.segunda["GUSTAVO_PETRO_pvot"] - d.primera["GUSTAVO_PETRO_pvot"]) / 100,
diffHernandez:
(d.segunda["RODOLFO_HERNÁNDEZ_pvot"] -
d.primera["RODOLFO_HERNÁNDEZ_pvot"]) /
100,
...d
}))
Insert cell
navio(dataJoined)
Insert cell
mapPrimera = new Map(primeraVuelta.map((d) => [d.codigo, d]))
Insert cell
mapSegunda = new Map(segundaVuelta.map((d) => [d.codigo, d]))
Insert cell
segundaVuelta = FileAttachment(
"resultados_segunda_vuelta_2022_boletin_63@1.csv"
)
.csv({
typed: true
})
.then(preProcessing)
Insert cell
primeraVuelta.candidates
Insert cell
primeraVuelta = FileAttachment(
"resultados_primera_vuelta_2022_boletin_68_v2.csv"
)
.csv({
typed: true
})
.then(preProcessing)
Insert cell
function preProcessing(res) {
const candidates = res.columns
.filter((d) => d.includes("_vot"))
.map((d) => d.replace("_vot", ""));
res.candidates = candidates;
for (let d of res) {
const candidatesVotes = candidates
.map((c, i) => ({ vot: d[c + "_vot"], pct: d[c + "_pvot"], i, c }))
.sort((a, b) => +b.vot - a.vot);
d.Total = candidates.reduce((p, c) => p + d[c + "_vot"], 0);
d["Igual Tamaño"] = 1000;

d.iWinner = candidatesVotes.at(0).i;
d.VotosGanador = candidatesVotes.at(0).vot;
d.PctGanador = candidatesVotes.at(0).pct;
d.Ganador = candidates[d.iWinner].replace("_vot", "");
d.Ranking = candidatesVotes.map((d) => d.i).join(",");
d.candidatesVotes = candidatesVotes;
}
return res;
}
Insert cell
Insert cell
Insert cell
Insert cell
colors = ({
GUSTAVO_PETRO_pvot: adjustInterpolator(d3.interpolateReds),
RODOLFO_HERNÁNDEZ_pvot: adjustInterpolator(d3.interpolatePurples)
// pvotbla: adjustInterpolator(d3.interpolateGreys)
})
Insert cell
Insert cell
footer2 = {
return htl.html`<div
id="chart_footer"
style="opacity: 0.7; font-size:10pt; font-style: italic; color: #333; position: absolute; bottom: 1px; left: 5px;"
>Según el boletín 63 de la registraduría
<br>
<a href="https://johnguerra.co">John Alexis Guerra Gómez</a> &nbsp;&nbsp; <a href="https://twitter.com/guerravis"> @guerravis</a> &nbsp; <a href="https://twitter.com/duto_guerra"> @duto_guerra</a>
<br>
${
width > 800
? htl.html`<a href="https://johnguerra.co/viz/resultadosSegundaVuelta2022Bogota/">https://johnguerra.co/viz/resultadosSegundaVuelta2022Bogota/</a>`
: ""
}
<br>
<div ><a rel="license" href="http://creativecommons.org/licenses/by/4.0/"><img height="25px" alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by/4.0/88x31.png" /></a>
</div>
</div>
</div>`;
}
Insert cell
scales = ({
...Object.fromEntries(
Object.keys(colors).map((c) => [
c,
d3.scaleSequential(colors[c]).domain([0, 90])
])
)
// pvotbla: adjustInterpolator(d3.interpolateGreys)
})
Insert cell
Insert cell
candidates = Object.keys(colors)
Insert cell
containerPctPetro = htl.html`<div style="background-color: #ece6cf; position:relative;">
<h2>% De cambio Petro</h2>
${legend({
color: mapPctPetro.color,
title: "% de cambio de Votos Petro"
})}
${mapPctPetro}
${footer}
</div>`
Insert cell
mapPctPetro = MapaColombiaFuerzas({
data: dataJoined,
id: (d) => +d.primera.dane,
idFeatures: (d) => +d.properties.MPIO_CDPMP,
value: (d, i) => d.diffPetro,
// r: d3.scaleSqrt().domain([0, 90]).range([1, 10]),
// fill: (d) => selectiveColorScale(d),
color: d3
.scaleSequential((t) => d3.interpolatePRGn(1 - t))
.domain([-0.2, 0.2]),
warmup: 200,
height: width * 0.8,
width: width,
strokeNodes: null,
strokeMap: "#A69E8644",
rByPopulation: true,
rRange: [0.3, Math.sqrt(width * 1.6)],
parentInvalidation: invalidation,
collideMargin: 0.5,
margin: { left: 1, right: 1, bottom: 1, top: 1 },
fmtVal: d3.format(".1%")
})
Insert cell
containerPctHernandez = htl.html`<div style="background-color: #ece6cf; position:relative;">
<h2>Diferencia de Votos Segunda Vuelta 2022</h2>
${legend({
color: mapPctHernandez.color,
title: "% de cambio de Votos Hernández"
})}
${mapPctHernandez}
${footer}
</div>`
Insert cell
mapPctHernandez = MapaColombiaFuerzas({
data: dataJoined,
id: (d) => +d.primera.dane,
idFeatures: (d) => +d.properties.MPIO_CDPMP,
value: (d, i) => d.diffHernandez,
// r: d3.scaleSqrt().domain([0, 90]).range([1, 10]),
// fill: (d) => selectiveColorScale(d),
color: d3.scaleSequential(d3.interpolateRdBu).domain([-0.3, 0.3]),
warmup: 200,
height: width * 0.8,
width: width,
strokeNodes: null,
strokeMap: "#A69E8644",
rByPopulation: true,
rRange: [0.3, Math.sqrt(width * 1.6)],
parentInvalidation: invalidation,
collideMargin: 0.5,
margin: { left: 1, right: 1, bottom: 1, top: 1 },
fmtVal: d3.format(".1%")
})
Insert cell
dataJoined
Insert cell
Math.sqrt(width)
Insert cell
scalePctPetro = d3.scaleSequential(d3.interpolateRdBu).domain([-90, 90])

Insert cell
legendPctPetro =
Insert cell
function selectiveColorScale(d) {
if (d.data === undefined) {
console.log("undefined data", d);
return;
}
const scale = scales[candidates[d.data.iWinner]];
return scale(d.data.Ganador);
}
Insert cell
import { dataInput } from "@john-guerra/data-input"
Insert cell
import {
MapaColombia,
MapaColombiaFuerzas,
land
} from "@john-guerra/mapa-colombia"
Insert cell
import { swatches, legend } from "@d3/color-legend"
Insert cell
// https://stackoverflow.com/questions/4878756/how-to-capitalize-first-letter-of-each-word-like-a-2-word-city
toTitleCase = (text) =>
text
.toLocaleLowerCase()
.split(" ")
.map((s) => s.charAt(0).toLocaleUpperCase() + s.substring(1))
.join(" ")
Insert cell
adjustInterpolator = (t) => (d) => t(croppedScale(d))
Insert cell
croppedScale = d3.scaleLinear().domain([0, 1]).range([colorAdjust, 1])
Insert cell
import { navio } from "@john-guerra/navio"
Insert cell
width = 800
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