File size: 5,419 Bytes
e1984f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
Below is a pragmatic “tinkerer’s tour” for the D3 + TopoJSON canvas world-map we just built.
I’ll point out the core building blocks (“knobs”) and show how changing each one affects the experience, so you can remix or extend the tool with confidence.


---

1. File Anatomy (what lives where)

Section	Purpose	Main Knobs

<style>	Colors, fonts, layout, line thickness	CSS custom properties (--water, --country, …); global ctx.lineWidth later in JS
Global JS prologue	Sets up high-DPI canvas, projection, path generator	dpr, projection.scale(), projection.translate()
Data loader	Fetches TopoJSON (countries-110m.json) and converts to GeoJSON	URLs array & dataset resolution
Zoom / Pan	Creates a d3.zoom() on the canvas	scaleExtent([min,max]), double-tap reset handler
Hit-testing helpers	Uses geoContains for precise picks	can swap to faster but coarser schemes
render()	The drawing pipeline	fill / stroke colors, line width, paint order
Event handlers	hover / click / double-click logic	where you wire callbacks, e.g. show tooltip


Because it’s plain HTML, everything is in one file—open it in any code editor and you’ll see the pieces in that order.


---

2. The Visual Knobs

2.1 Colors & Fonts

All map colors are CSS variables at the top of <style>:

:root {
  --water: #a0d3ff;          /* oceans  */
  --country: #f7f7f7;        /* default land */
  --country-hover: #d0d0d0;  /* under pointer */
  --country-selected: #7d7d7d;
  --stroke: #444;            /* border lines */
}

Change them once and both the base fill and subsequent re-renders pick up the new palette automatically.

2.2 Line Thickness

Search for:

ctx.lineWidth = .18;

Increase this for chunkier borders; decrease for hairlines.

> Tip: On retina/4-K screens we already multiply by devicePixelRatio, so 0.18 is genuinely thin but still visible.



2.3 Projection Size & Center

The projection is resized on every resizeCanvas() call:

projection
  .translate([w / 2, h / 2])          // center
  .scale(Math.min(w / 5.5, h / 3));   // overall size

Lower the divisor (5.5, 3) → larger globe footprint.

Change geoNaturalEarth1() to any other D3 projection (geoMercator, geoOrthographic, …) for a different look.



---

3. Interaction Knobs

3.1 Zoom & Pan Limits

d3.zoom().scaleExtent([1, 16])

First value = minimum zoom (1 = fit-canvas).

Second value = maximum zoom (we raised it from 8 → 16).


3.2 Reset Gesture

We override D3’s default double-click to make it a double-tap reset:

canvas.addEventListener('dblclick', () => {
  currentT = d3.zoomIdentity;      // zero out transform
  d3.select(canvas)
    .transition().duration(250)
    .call(d3.zoom().transform, d3.zoomIdentity);
});

Change the event (dblclick) or the easing/duration for a different feel.

3.3 Hit-testing Logic

Hit-testing is the expensive part:

const countryAt = (ev) =>
  countries.find(c => d3.geoContains(c, pointerLatLon(ev)));

For performance-first, replace with bounding-box checks or a spatial index (rbush).

For accuracy, keep geoContains, especially important when you zoom deep.



---

4. Data Knobs

4.1 Switching to Higher-Resolution Borders

countries-110m.json ≈ ~110 m resolution (about 50 KB).
Replace with:

countries-50m.json (finer, ~200 KB)

countries-10m.json (very fine, ~1 MB)


const sources = [
 "https://cdn.jsdelivr.net/npm/world-atlas@2/countries-50m.json",
 ...
];

More points → smoother borders and heavier CPU while zooming.

4.2 Adding Sub-Nation Shapes

Load provinces, states, whatever—just fetch a different TopoJSON layer and draw another pass above the country fill.


---

5. Extending the Tool (quick recipes)

Goal	What to Tweak

Show country names on hover	Inside the mousemove handler, when hovered changes, position a <div> tooltip at the pointer and fill hovered.properties.name.
Persist selection elsewhere	window.SELECTED_COUNTRY_ID already exposes the current pick; just read it from other scripts.
Add a search-box	Keep a map (id → feature) in memory, then on search zoom to the country using d3.geoPath.bounds() → compute a fitting transform.
Switch to WebGL for 100k shapes	Replace canvas commands with PixiJS or regl if you need insane performance.



---

6. Minimal “Hello World” Variant (for experiments)

<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
import * as topojson from "https://cdn.jsdelivr.net/npm/topojson@3/+esm";

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const projection = d3.geoNaturalEarth1()
                     .translate([400, 250])
                     .scale(200);
const path = d3.geoPath(projection, ctx);

const world = await d3.json(
  "https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json"
);

const countries = topojson.feature(world, world.objects.countries).features;
path({type:"FeatureCollection", features:countries});
ctx.fillStyle = "#ddd";
ctx.fill();
</script>

Drop that into an HTML file with a <canvas width="800" height="500"> tag—instant static map.


---

7. Where to Go from Here

D3’s “Geo Projections” docs – learn the knobs on every projection’s API.

ObservableHQ examples – live notebooks for hit-testing, zoom, labeling, etc.

TopoJSON documentation – see how to convert or simplify your own shapefiles.


Happy mapping—and feel free to ask if you want deeper dives into any of these levers!