{
const container = d3.create("div")
.style("width", "100%")
.style("height", "100%")
.style("min-height", "600px");
const mapDiv = container.append("div")
.attr("id", "map-" + Math.random().toString(36).substr(2, 9))
.style("width", "100%")
.style("height", "100%");
yield container.node();
const mapId = mapDiv.attr("id");
const map = L.map(mapId).setView([8.537981, -80.782127], 7);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
maxZoom: 19
}).addTo(map);
let geojsonLayer = null;
let legend = null;
function getColor(value, min, max) {
if (value === null || value === undefined || isNaN(value)) {
return '#808080';
}
const viridis = [
'#440154', '#482878', '#3e4989', '#31688e',
'#26828e', '#1f9e89', '#35b779', '#6ece58',
'#b5de2b', '#fde724'
];
const normalized = (value - min) / (max - min);
const index = Math.floor(normalized * (viridis.length - 1));
return viridis[Math.max(0, Math.min(index, viridis.length - 1))];
}
function updateMap() {
if (geojsonLayer) {
map.removeLayer(geojsonLayer);
}
if (legend) {
map.removeControl(legend);
}
const values = geodata.features
.map(f => f.properties[selectedKPI])
.filter(v => v !== null && v !== undefined && !isNaN(v));
if (values.length === 0) {
geojsonLayer = L.geoJSON(geodata, {
style: {
fillColor: '#cccccc',
weight: 1,
opacity: 1,
color: 'white',
fillOpacity: 0.5
}
}).addTo(map);
return;
}
const min = Math.min(...values);
const max = Math.max(...values);
geojsonLayer = L.geoJSON(geodata, {
style: function(feature) {
return {
fillColor: getColor(feature.properties[selectedKPI], min, max),
weight: 1,
opacity: 1,
color: 'white',
fillOpacity: 0.5
};
},
onEachFeature: function(feature, lyr) {
const value = feature.properties[selectedKPI];
const displayValue = value !== null && value !== undefined && !isNaN(value)
? value.toLocaleString('es-PA', {maximumFractionDigits: 2})
: 'Sin datos';
const kpiLabel = kpis.find(k => k.name === selectedKPI)?.label || selectedKPI;
lyr.bindTooltip(
`<strong>${feature.properties.PROVINCIA ?? feature.properties.NOMBRE}</strong><br/>` +
`${kpiLabel}: ${displayValue}`,
{
permanent: false,
sticky: true
}
);
lyr.on({
mouseover: function(e) {
e.target.setStyle({
weight: 3,
color: '#666',
fillOpacity: 0.7
});
if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
e.target.bringToFront();
}
},
mouseout: function(e) {
geojsonLayer.resetStyle(e.target);
}
});
}
}).addTo(map);
legend = L.control({position: 'bottomright'});
legend.onAdd = function(m) {
const div = L.DomUtil.create('div', 'info legend');
div.style.cssText = 'padding: 10px; background: white; border-radius: 5px; box-shadow: 0 0 15px rgba(0,0,0,0.2);';
const kpiLabel = kpis.find(k => k.name === selectedKPI)?.label || selectedKPI;
div.innerHTML = `<strong style="font-size: 12px;">${kpiLabel}</strong><br/>`;
const steps = 5;
for (let i = 0; i < steps; i++) {
const value = min + (max - min) * (i / (steps - 1));
const color = getColor(value, min, max);
div.innerHTML +=
`<i style="background:${color}; width: 18px; height: 18px; float: left; margin-right: 8px; opacity: 0.7;"></i>` +
`<span style="font-size: 11px;">${value.toLocaleString('es-PA', {maximumFractionDigits: 1})}</span><br/>`;
}
return div;
};
legend.addTo(map);
}
updateMap();
selectedKPI, updateMap();
setTimeout(() => map.invalidateSize(), 100);
return container.node();
}