Public
Edited
Jun 20, 2022
4 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

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