Public
Edited
Jun 20, 2022
3 forks
Importers
5 stars
Insert cell
Insert cell
map = MapaColombiaFuerzas({
id: (d) => d.properties.MPIO_CCDGO,
value: (d) => d.properties.DPTO_CCDGO,
drawCities: true,
color: d3
.scaleOrdinal(d3.quantize(d3.interpolateSpectral, depts.size))
.domain(depts),
warmup: 250,
drawMap: true,
strokeNodes: "#fff",
strokeWidthNodes: 1,
parentInvalidation: invalidation
})

Insert cell
map2 = MapaColombiaFuerzas({
data: land.features,
id: (d) => d.properties.DPTO_CCDGO + d.properties.MPIO_CCDGO,
value: (d) => +d.properties.STP27_PERS,
drawCities: true,
warmup: 150,
rByPopulation: false,
parentInvalidation: invalidation
})

Insert cell
map3 = MapaColombiaFuerzas({
data: land.features.map((d, i) => ({
id: d.properties.DPTO_CCDGO + d.properties.MPIO_CCDGO,
value: d.properties.AREA
})),
id: (d) => d.id,
value: (d) => d.value,
drawCities: true,
warmup: 200,
rByPopulation: false,
rRange: [0, 20],
parentInvalidation: invalidation
})
Insert cell
function MapaColombiaFuerzas({
data = null,
id = (d) => d.id, // Para el código DANE
drawCities = true,
drawDepts = true,
idFeatures = (d) =>
drawCities ? d.properties.MPIO_CDPMP : d.properties.DPTO_CDPMP,
value = (d) => d.properties.DPTO_CCDGO, // Atributo a usar para la visualización
color, // color Scale,
fill = null, // if provided, overrides the color scale and applies the fill based on the datum. Must be a function
projection,
drawMap = true,
fillMap = null, // null for transparent
strokeMap = "#aaa",
strokeMapDepts = "#333",
strokeNodes = "#eee",
strokeWidthNodes = 1,
alphaNodes = 1,
alphaMap = 0.3,
width = 600,
height = 600,
container = htl.html`<div style="position: relative"></div>`,
context = this?.context || DOM.context2d(width, height),
contextMap = this?.contextMap || DOM.context2d(width, height),
margin = { left: 10, top: 10, right: 10, bottom: 10 },
rRange = [1, 20],
rByPopulation = true, // set to false for radius by value
r = d3
.scaleSqrt()
.domain([
0,
d3.max(
(drawCities ? land : landDepts).features,
(d) => d.properties.STP27_PERS
)
])
.range(rRange),
forceToCentroid = 0.1,
collide = true,
collideMargin = 1,
warmup = 200,
labelColor = "#000",
labelFont = "9px sans-serif",
labelAlign = "center",
labelIfBiggerThan = 10,
parentInvalidation = invalidation,
backgroundColor = null,
drag = false,
fmtVal = d3.format(".2s")
} = {}) {
color = color || d3.scaleSequential(d3.interpolateReds);
r.range(rRange);

if (!rByPopulation) {
r.domain(d3.extent(data, value));
}

projection =
projection ||
d3
.geoTransverseMercator()
.rotate([74 + 30 / 60, -38 - 50 / 60])
.fitExtent(
[
[margin.left, margin.top],
[width - margin.right, height - margin.bottom]
],
landDepts
);
// Radius accessor
const rFn = (n) => {
if (!n) return;
if (rByPopulation) {
return r(n.properties.STP27_PERS);
} else {
if (n.data) return r(value(n.data));
}
};

let selected = null;
let dataMap = null;
if (data) {
dataMap = new Map(data.map((d) => [id(d), d]));
}

contextMap = this?.contextMap || DOM.context2d(width, height);
context = this?.context || DOM.context2d(width, height);
if (backgroundColor) {
contextMap.canvas.style["background-color"] = backgroundColor;
}

const nodes = drawCities
? land.features.map((d) => ({ ...d }))
: landDepts.features.map((d) => ({ ...d }));
const pathCanvas = d3.geoPath().projection(projection);
const path2D = new Path2D();
const path2DDepts = new Path2D();

if (drawMap) {
contextMap.save();
pathCanvas.context(path2D)(drawCities ? landMesh : landDeptsMesh);

contextMap.beginPath();
contextMap.strokeStyle = strokeMap;
contextMap.globalAlpha = alphaMap;
contextMap.stroke(path2D);
if (fillMap) {
contextMap.fillStyle = fillMap;
contextMap.fill(path2D);
}

if (drawDepts) {
pathCanvas.context(path2DDepts)(landDeptsMesh);
contextMap.beginPath();
contextMap.strokeStyle = strokeMapDepts;
contextMap.globalAlpha = alphaMap;
contextMap.stroke(path2DDepts);
}
contextMap.restore();
}

for (let n of nodes) {
n.centroid = pathCanvas.centroid(n);
[n.x, n.y] = n.centroid;
if (data) {
n.data = dataMap.get(idFeatures(n));
}
n.r = rFn(n);
}
// console.log("r.domain()", r.domain(), r.range());

const simulation = d3
.forceSimulation(nodes)
// .velocityDecay(0.3)
.force("x", d3.forceX((d) => d.centroid[0]).strength(forceToCentroid))
.force("y", d3.forceY((d) => d.centroid[1]).strength(forceToCentroid))
// .force("charge", d3.forceManyBody())
.force(
"collide",
collide ? d3.forceCollide((n) => n.r + collideMargin).iterations(1) : null
)
.stop();

for (let i = 0; i < warmup; i++) simulation.tick();
simulation.on("tick", ticked).alpha(0.3).restart();

if (data) {
if (color.domain && color.domain().join(",") === "0,1") {
color.domain(d3.extent(data, value));
}
}

function ticked() {
// console.log("tick", simulation.alpha());

context.clearRect(0, 0, width, height);

context.save();
for (const n of nodes) {
if (strokeNodes) context.strokeStyle = strokeNodes;
const nForColor = data ? dataMap.get(idFeatures(n)) : n;

if (!nForColor) {
console.log(`Couldn't find ${idFeatures(n)} in the data`);
continue;
}
context.fillStyle = fill ? fill(n) : color(value(nForColor));
context.beginPath();
context.lineWidth = strokeWidthNodes;
context.globalAlpha = !selected
? alphaNodes
: n === selected
? alphaNodes
: n.properties.DPTO_CCDGO === selected.properties.DPTO_CCDGO
? alphaNodes * 0.8
: 0.3;

context.arc(n.x, n.y, n.r, 0, 2 * Math.PI);
if (strokeNodes) context.stroke();
context.fill();
}
context.restore();

context.save();
context.fillStyle = labelColor;
context.textAlign = labelAlign;
context.font = labelFont;
for (const n of nodes) {
if (n.r > labelIfBiggerThan || n === selected) {
context.fillText(
n.properties[drawCities ? "MPIO_CNMBR" : "DPTO_CNMBR"],
n.x,
n.y
);
context.fillText(fmtVal(value(data ? n.data : n)), n.x, n.y + 10);
}
}
context.restore();
}

function _drag(simulation) {
function dragsubject(event) {
return simulation.find(event.x, event.y);
}

function dragstarted(event) {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}

function dragged(event) {
event.subject.fx = event.x;
event.subject.fy = event.y;
}

function dragended(event) {
if (!event.active) simulation.alphaTarget(0);
event.subject.fx = null;
event.subject.fy = null;
}

return d3
.drag()
.subject(dragsubject)
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}

if (drag) {
d3.select(context.canvas).call(_drag(simulation));
}

d3.select(context.canvas)
.on("mouseout", () => {
selected = null;
ticked();
// d3.select("#vTooltip").style("display", "none");
})
.on("click", onHighlight)
.on("mousemove", onHighlight);

function onHighlight(evt, d) {
const newSelected = simulation.find(evt.offsetX, evt.offsetY);
if (newSelected !== selected) {
selected = newSelected;
console.log("selected", selected);
// console.log("redraw", selected.properties.MPIO_CNMBR);
ticked();
}

// console.log("xy",d3.event, d3.event.offsetX, d3.event.offsetY);
// d3.select("#vTooltip")
// .style("display", "block")
// .select("p")
// .html(
// "Diferencia: " +
// fmtPct(Math.abs(selected.pct)) +
// " a favor de " +
// (selected.pct > 0 ? " Duque " : " Petro ") +
// "<br>" +
// "Total Votantes:" +
// fmt(selected.votantes)
// );

// d3.select("#vTooltip")
// .select("h3")
// .text(selected.municipio + ", " + selected.departamento);

// let bar = stackedBar().keys([
// "iván duque result",
// "votos_en_blanco result",
// "nulos_no_marcados",
// "gustavo petro result"
// ]);

// d3.select("#barChart").datum(selected).call(bar);
}

ticked();
parentInvalidation.then(() => {
console.log("invalidation stop");
simulation.stop();
});

// save the context for reusal
container.context = context;
container.contextMap = contextMap;
container.color = color;
container.dataMap = dataMap;

context.canvas.style.position = "absolute";
context.canvas.style.top = "0px";
context.canvas.style.left = "0px";
container.innnerHTML = "";
container.appendChild(contextMap.canvas);
container.appendChild(context.canvas);
return container;
}
Insert cell
landDepts.features[0].properties
Insert cell
Insert cell
params
Insert cell
d3
.geoMercator()
// .translate([params.tx, params.ty])
// .scale(params.s)
.center([params.cx, params.cy])
.scale()
Insert cell
MapaColombia({
drawCities: true
})
Insert cell
landSA = ({
type: "FeatureCollection",
features: land.features.filter(
(d) =>
d.properties.DPTO_CNMBR ===
"ARCHIPIELAGO DE SAN ANDRES PROVIDENCIA Y SANTA CATALINA" &&
d.properties.name === "SAN ANDRES"
)
})
Insert cell
d3.geoMercator().translate([params.tx, params.ty]).scale()
Insert cell
p = {
const margin = { left: 20, top: 20, right: 20, bottom: 20 };
return d3.geoTransverseMercator()
.rotate([74 + 30 / 60, -38 - 50 / 60])
.fitExtent(
[
[margin.left, margin.top],
[width - margin.right, height - margin.bottom]
],
landDepts
);
}
Insert cell
pSA.translate()
Insert cell
pSA.center()
Insert cell
pSA.scale()
Insert cell
pSA = d3.geoMercator().fitExtent(
[
[50, 50],
[200, 200]
],
landSA
)
Insert cell
pSA.translate()
Insert cell
function MapaColombia({
data = [],
id = (d) => d.id, // Para el código DANE
value = (d) => d.value, // Atributo a usar para la visualización
color, // color Scale
projection,
drawCities = true,
strokeCities = "#aaa",
fillCities = null, // null for transparent
strokeDepts = "#333",
fillDepts = null,
globalAlphaCities = 1,
globalAlphaDepts = 1,
width = 900,
height = 600,
context = this?.context || DOM.context2d(width, height),
margin = { left: 20, top: 20, right: 20, bottom: 20 }
} = {}) {
color =
color ||
d3.scaleOrdinal(d3.quantize(d3.interpolateTurbo, depts.size)).domain(depts);
projection =
projection ||
d3
.geoTransverseMercator()
.rotate([74 + 30 / 60, -38 - 50 / 60])
.fitExtent(
[
[margin.left, margin.top],
[width - margin.right, height - margin.bottom]
],
landDepts
);

context = this?.context || DOM.context2d(width, height);
const nodes = byCities.length ? land.features : landDepts.features;

const pathCanvas = d3.geoPath().projection(projection);

if (drawCities && strokeCities) {
const path2D = new Path2D();
pathCanvas.context(path2D)(land);
context.save();
context.beginPath();
context.globalAlpha = globalAlphaCities;

context.strokeStyle = strokeCities;
context.stroke(path2D);

if (fillCities) {
context.fillStyle = fillCities;
context.fill(path2D);
}
context.restore();
}

if (strokeDepts) {
const path2D = new Path2D();
pathCanvas.context(path2D)(landDepts);
context.save();
context.beginPath();
context.strokeStyle = strokeDepts;
context.globalAlpha = globalAlphaDepts;
context.stroke(path2D);
context.restore();
}

// save the context for reusal
context.canvas.context = context;
context.canvas.color = color;
return context.canvas;
}
Insert cell
geoColombia()([-71.98986048384427, 51.16459889789654])
Insert cell
function geoColombia() {
const epsilon = 1e-6;

// JSON.stringify(d3.geoBounds(landDepts))
const overallBbox = [
[-71.98986048384427, 33.561835919530445],
[-52.48199851189149, 52.05805566095018]
];

// Bounds San Andres
// JSON.stringify(
// d3.geoBounds({
// type: "FeatureCollection",
// features: landDepts.features.filter(
// (d) =>
// d.properties.DPTO_CNMBR ===
// "ARCHIPIELAGO DE SAN ANDRES PROVIDENCIA Y SANTA CATALINA"
// )
// })
// )
const SanAndresBbox = [
[-71.98986048384427, 51.16459889789654],
[-71.35188960027305, 52.05805566095018]
];

// JSON.stringify(
// d3.geoBounds({
// type: "FeatureCollection",
// features: landDepts.features.filter(
// (d) =>
// d.properties.DPTO_CNMBR !==
// "ARCHIPIELAGO DE SAN ANDRES PROVIDENCIA Y SANTA CATALINA"
// )
// })
// )
const mainBbox = [
[-68.63417265588559, 33.561835919530445],
[-52.48199851189149, 50.21194831636454]
];

function multiplex(streams) {
const n = streams.length;
return {
point(x, y) {
for (const s of streams) s.point(x, y);
},
sphere() {
for (const s of streams) s.sphere();
},
lineStart() {
for (const s of streams) s.lineStart();
},
lineEnd() {
for (const s of streams) s.lineEnd();
},
polygonStart() {
for (const s of streams) s.polygonStart();
},
polygonEnd() {
for (const s of streams) s.polygonEnd();
}
};
}
let cache,
cacheStream,
main = d3.geoTransverseMercator().rotate([74 + 30 / 60, -38 - 50 / 60]),
mainPoint,
sanAndres = d3.geoMercator().rotate([-81.7076, 125450]),
sanAndresPoint,
providencia = d3
.geoMercator()
.translate([130, 60])
.scale(40000)
.center([-81.35925292968751, 13.378931658431565]),
providenciaPoint,
point,
pointStream = {
point: function (x, y) {
point = [x, y];
}
};

function colombiaSA(coordinates) {
var x = coordinates[0],
y = coordinates[1];
return (
(point = null),
(mainPoint.point(x, y), point) ||
(sanAndresPoint.point(x, y), point) ||
(providenciaPoint.point(x, y), point)
);
}

colombiaSA.invert = function (coordinates) {
var k = main.scale(),
t = main.translate(),
x = (coordinates[0] - t[0]) / k,
y = (coordinates[1] - t[1]) / k;

//How are the return values calculated:
var c0 = SanAndresBbox[0];
var x0 = (c0[0] - t[0]) / k;
var y0 = (c0[1] - t[1]) / k;
console.info("p0 galapagos", x0 + " - " + y0);
var c1 = sanAndres(SanAndresBbox[1]);
var x1 = (c1[0] - t[0]) / k;
var y1 = (c1[1] - t[1]) / k;
console.info("p1 galapagos", x1 + " - " + y1);

return main.invert(coordinates);
// return (y >= 0.12 && y < 0.234 && x >= -0.425 && x < -0.214
// ? sanAndres
// : y >= 0.166 && y < 0.234 && x >= -0.214 && x < -0.115
// ? providencia
// : main
// ).invert(coordinates);
};

colombiaSA.stream = function (stream) {
return cache && cacheStream === stream
? cache
: (cache = multiplex([
main.stream((cacheStream = stream)),
sanAndres.stream(stream),
providencia.stream(stream)
]));
};

colombiaSA.precision = function (_) {
if (!arguments.length) return main.precision();
main.precision(_), sanAndres.precision(_), providencia.precision(_);
return reset();
};

colombiaSA.scale = function (_) {
if (!arguments.length) return main.scale();
main.scale(_), sanAndres.scale(_ * 0.35), providencia.scale(_);
return colombiaSA.translate(main.translate());
};

colombiaSA.translate = function (_) {
if (!arguments.length) return main.translate();
var k = main.scale(),
x = +_[0],
y = +_[1];

mainPoint = main
.translate(_)
.clipExtent([
[x - 0.455 * k, y - 0.238 * k],
[x + 0.455 * k, y + 0.238 * k]
])
.stream(pointStream);

sanAndresPoint = sanAndres
.translate([x - 0.307 * k, y + 0.201 * k])
.clipExtent([
[x - 0.425 * k + epsilon, y + 0.12 * k + epsilon],
[x - 0.214 * k - epsilon, y + 0.234 * k - epsilon]
])
.stream(pointStream);

providenciaPoint = providencia
.translate([x - 0.205 * k, y + 0.212 * k])
.clipExtent([
[x - 0.214 * k + epsilon, y + 0.166 * k + epsilon],
[x - 0.115 * k - epsilon, y + 0.234 * k - epsilon]
])
.stream(pointStream);

return reset();
};

colombiaSA.fitExtent = function (extent, object) {
return main.fitExtent(extent, object);
};

function reset() {
cache = cacheStream = null;
return colombiaSA;
}

return colombiaSA.scale(1070);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
swatches({ map.color })
Insert cell
height= 600
Insert cell
depts = new Set(landDepts.features.map(d => d.properties.DPTO_CNMBR))
Insert cell
land = topojson.feature(mapData, {
type: "GeometryCollection",
geometries: mapData.objects.MGN_ANM_MPIOS.geometries
})
Insert cell
landMesh = topojson.mesh(mapData, mapData.objects.MGN_ANM_MPIOS)
Insert cell
landDeptsMesh = topojson.mesh(mapData, mapData.objects.MGN_ANM_DPTOS)
Insert cell
landDepts = topojson.feature(mapData, {
type: "GeometryCollection",
geometries: mapData.objects.MGN_ANM_DPTOS.geometries
})
Insert cell
// mapData = FileAttachment("colombia-municipios.json").json()
Insert cell
// mgn_mpios = FileAttachment("MGN_AMN_MPIOS-topo-simpler3.json").json()
Insert cell
// mgn_dpts = FileAttachment(
// "MGN_ANM_DPTOS_topojson_simplified_10e4.topojson.json"
// ).json()
Insert cell
mapData = FileAttachment(
"Colombia_departamentos_municipios_poblacion-topov2_simplifiedmapshaer.json"
).json()
Insert cell
d3 = require("d3@7")
Insert cell
topojson = require("topojson@3")
Insert cell
import { swatches } from "@d3/color-legend"
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