Geographic Distribution Chart
A geographic distribution chart with a country map and horizontal region bars. Features configurable regions, animated bars, color-coded map areas, and a responsive layout for dashboard analytics.
MCP
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--bg: #f0f0f0;
--surface: #ffffff;
--text: #1a1a2e;
--text-muted: #6b7280;
--accent: #2563eb;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
}
/* โโ Card โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.geo-card {
background: var(--surface);
border-radius: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
width: 100%;
max-width: 780px;
padding: 28px 32px 36px;
}
/* โโ Header โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.geo-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 24px;
}
.geo-header__icon {
width: 36px;
height: 36px;
color: #2563eb;
flex-shrink: 0;
}
.geo-header__icon svg {
width: 100%;
height: 100%;
}
.geo-header__accent {
width: 3px;
height: 28px;
background: var(--accent);
border-radius: 2px;
flex-shrink: 0;
}
.geo-header__title {
font-size: 1.2rem;
font-weight: 700;
color: #2563eb;
}
/* โโ Body layout โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.geo-body {
display: flex;
gap: 24px;
align-items: flex-start;
}
/* โโ Map โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.geo-map {
flex: 0 0 44%;
display: flex;
align-items: flex-start;
justify-content: center;
}
.geo-map svg {
width: 100%;
height: auto;
}
.geo-map svg path {
stroke: var(--surface);
stroke-width: 1;
cursor: pointer;
transition: opacity .2s, filter .2s;
}
.geo-map svg path:hover {
opacity: 0.8;
filter: brightness(1.15);
}
/* โโ Bars โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.geo-bars {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
padding-top: 4px;
}
.bar-row {
cursor: default;
padding: 2px 4px;
border-radius: 4px;
transition: background .15s;
}
.bar-row:hover {
background: rgba(16, 185, 129, 0.06);
}
.bar-name {
font-size: .85rem;
font-weight: 500;
color: var(--text);
margin-bottom: 2px;
line-height: 1.2;
}
.bar-fill {
height: 16px;
border-radius: 4px;
transform-origin: left center;
transform: scaleX(0);
transition: transform .65s cubic-bezier(0.22, 1, 0.36, 1);
}
.bar-fill.show {
transform: scaleX(1);
}
/* โโ Tooltip โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.geo-tip {
position: fixed;
pointer-events: none;
background: var(--text);
color: #fff;
font-size: .75rem;
font-weight: 500;
padding: 5px 10px;
border-radius: 6px;
white-space: nowrap;
opacity: 0;
transition: opacity .15s;
z-index: 100;
}
.geo-tip.on {
opacity: 1;
}
/* โโ Responsive โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
@media (max-width: 560px) {
.geo-body {
flex-direction: column;
}
.geo-map {
flex: none;
width: 100%;
}
.geo-map svg {
max-height: 260px;
}
}/**
* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
* Geographic Distribution Chart โ Real Colombia SVG Map
* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
*
* Uses a real Colombia departments SVG map (from MapSVG.com,
* Creative Commons). Departments are grouped into macro-regions
* and colored by value. Horizontal bars match each region.
*
* HOW TO CHANGE COUNTRY:
* 1. Replace COLOMBIA_SVG_URL with your country SVG path
* 2. Update REGIONS and the department-to-region mapping
* 3. Adjust viewBox if needed
*
* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
*/
/**
* Path to the SVG map. The SVG must have <path> elements with
* `id` attributes matching department codes (e.g. CO-ANT).
*/
var COLOMBIA_SVG_URL = "/colombia.svg";
/**
* Regions with display name and value.
* Each region maps to one or more department IDs in the SVG.
*/
var REGIONS = [
{
region: "East",
value: 95,
depts: ["CO-SAN", "CO-NSA", "CO-ARA"],
},
{
region: "Central",
value: 88,
depts: ["CO-CUN", "CO-DC", "CO-TOL", "CO-HUI"],
},
{
region: "West",
value: 76,
depts: ["CO-CHO", "CO-VAC"],
},
{
region: "Coffee Axis",
value: 68,
depts: ["CO-CAL", "CO-RIS", "CO-QUI"],
},
{
region: "North",
value: 60,
depts: ["CO-ATL", "CO-BOL", "CO-CES", "CO-COR", "CO-GUJ", "CO-MAG", "CO-SUC", "CO-SAP"],
},
{
region: "Antioquia",
value: 52,
depts: ["CO-ANT"],
},
{
region: "Boyaca",
value: 40,
depts: ["CO-BOY", "CO-CAS"],
},
{
region: "Bogota",
value: 34,
depts: ["CO-DC"],
},
{
region: "South",
value: 25,
depts: ["CO-NAR", "CO-CAU", "CO-PUT", "CO-CAQ", "CO-AMA", "CO-GUV", "CO-GUA", "CO-VAU"],
},
{
region: "Plains",
value: 18,
depts: ["CO-MET", "CO-VIC"],
},
];
/* โโ Color scale (dark โ light blue) โโโโโโโโโโโโโโโโ */
var BLUES = [
"#1e3a5f",
"#1e40af",
"#1d4ed8",
"#2563eb",
"#3b82f6",
"#60a5fa",
"#93c5fd",
"#bfdbfe",
];
function pickColor(value, max) {
var ratio = 1 - value / max;
var idx = Math.min(Math.floor(ratio * (BLUES.length - 1)), BLUES.length - 1);
return BLUES[idx];
}
/* โโ Build deptโcolor lookup โโโโโโโโโโโโโโโโโโโโโโโโ */
var maxVal = Math.max.apply(
null,
REGIONS.map(function (r) {
return r.value;
})
);
var deptColorMap = {};
REGIONS.forEach(function (r) {
var color = pickColor(r.value, maxVal);
r.depts.forEach(function (id) {
deptColorMap[id] = { color: color, region: r.region };
});
});
/* โโ Load and render SVG map โโโโโโโโโโโโโโโโโโโโโโโโ */
var mapContainer = document.getElementById("geoMap");
var barsContainer = document.getElementById("geoBars");
var xhr = new XMLHttpRequest();
xhr.open("GET", COLOMBIA_SVG_URL, true);
xhr.onload = function () {
if (xhr.status !== 200) return;
// Parse SVG
var parser = new DOMParser();
var doc = parser.parseFromString(xhr.responseText, "image/svg+xml");
var svg = doc.querySelector("svg");
// Set viewBox from the original width/height so the SVG scales
var origW = svg.getAttribute("width") || 612;
var origH = svg.getAttribute("height") || 693;
svg.setAttribute("viewBox", "0 0 " + origW + " " + origH);
svg.removeAttribute("width");
svg.removeAttribute("height");
svg.setAttribute("preserveAspectRatio", "xMidYMid meet");
svg.style.width = "100%";
svg.style.height = "auto";
// Hide San Andrรฉs island โ it's far off-screen and breaks the layout
var sanAndres = svg.querySelector("#CO-SAP");
if (sanAndres) sanAndres.style.display = "none";
// Color each department path
var paths = svg.querySelectorAll("path");
paths.forEach(function (p) {
if (p.style.display === "none") return;
var id = p.getAttribute("id");
var info = deptColorMap[id];
if (info) {
p.setAttribute("fill", info.color);
p.dataset.region = info.region;
} else {
// Departments not in any region get a neutral light blue
p.setAttribute("fill", "#dbeafe");
p.dataset.region = "";
}
p.setAttribute("stroke", "#ffffff");
p.setAttribute("stroke-width", "1");
p.style.cursor = "pointer";
p.style.transition = "opacity .2s, filter .2s";
});
mapContainer.appendChild(svg);
// After map is in DOM, set up interactions
setupTooltip(svg);
setupHighlightSync(svg);
};
xhr.send();
/* โโ Render bars โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
REGIONS.forEach(function (item, i) {
var row = document.createElement("div");
row.className = "bar-row";
row.dataset.region = item.region;
var name = document.createElement("div");
name.className = "bar-name";
name.textContent = item.region;
var fill = document.createElement("div");
fill.className = "bar-fill";
fill.style.width = (item.value / maxVal) * 100 + "%";
fill.style.background = pickColor(item.value, maxVal);
row.appendChild(name);
row.appendChild(fill);
barsContainer.appendChild(row);
setTimeout(function () {
fill.classList.add("show");
}, 80 * i);
});
/* โโ Tooltip โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
function setupTooltip(svg) {
var tip = document.createElement("div");
tip.className = "geo-tip";
document.body.appendChild(tip);
var paths = svg.querySelectorAll("path");
paths.forEach(function (p) {
p.addEventListener("mouseenter", function () {
var title = p.getAttribute("title") || "";
var region = p.dataset.region || "";
tip.textContent = title + (region ? " โ " + region : "");
tip.classList.add("on");
});
p.addEventListener("mousemove", function (e) {
tip.style.left = e.clientX + 14 + "px";
tip.style.top = e.clientY - 30 + "px";
});
p.addEventListener("mouseleave", function () {
tip.classList.remove("on");
});
p.addEventListener("mouseenter", function () {
p.style.opacity = "0.8";
p.style.filter = "brightness(1.15)";
});
p.addEventListener("mouseleave", function () {
p.style.opacity = "";
p.style.filter = "";
});
});
}
/* โโ Highlight sync: bar โ map โโโโโโโโโโโโโโโโโโโโโโ */
function setupHighlightSync(svg) {
var barRows = barsContainer.querySelectorAll(".bar-row");
barRows.forEach(function (row) {
var regionName = row.dataset.region;
row.addEventListener("mouseenter", function () {
// Highlight matching departments on map
svg.querySelectorAll("path").forEach(function (p) {
if (p.dataset.region === regionName) {
p.style.filter = "brightness(1.3)";
p.style.stroke = "#1a1a2e";
p.style.strokeWidth = "2";
}
});
});
row.addEventListener("mouseleave", function () {
svg.querySelectorAll("path").forEach(function (p) {
if (p.dataset.region === regionName) {
p.style.filter = "";
p.style.stroke = "#ffffff";
p.style.strokeWidth = "1";
}
});
});
});
}<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Distribuciรณn geogrรกfica</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="geo-card">
<div class="geo-header">
<div class="geo-header__icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round">
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z" />
<circle cx="12" cy="9" r="2.5" />
</svg>
</div>
<div class="geo-header__accent"></div>
<h2 class="geo-header__title">Geographic Distribution</h2>
</div>
<div class="geo-body">
<div class="geo-map" id="geoMap"></div>
<div class="geo-bars" id="geoBars"></div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>Geographic Distribution Chart
A configurable geographic distribution chart combining a real SVG map with animated horizontal bar charts. Designed for dashboard analytics showing regional data breakdowns.
Features
- Real SVG map โ Uses a detailed Colombia departments map from MapSVG (Creative Commons)
- Two-column layout: interactive SVG map on the left, horizontal bar chart on the right
- Departments are grouped into 10 macro-regions and colored by value
- Animated bar chart with staggered entrance animations
- Hover sync โ hovering a bar highlights its departments on the map, and vice versa
- Tooltip shows department name and region on map hover
- Responsive layout โ stacks vertically on narrow viewports
- Zero dependencies โ vanilla JS
Customization
Edit the REGIONS array in script.js to change region names, values, and which departments belong to each region. Each region has a depts array with department IDs matching the SVG (e.g. CO-ANT for Antioquia).
To use a different country, replace the SVG file and update the REGIONS array with the new department/region IDs from your SVG.
Map Source
The Colombia SVG map is from MapSVG โ Colombia, licensed under Creative Commons.
Color Palette
The green gradient ranges from #064e3b (highest value) to #86efac (lowest value), automatically interpolated based on each regionโs relative value.