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

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