-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex_zoom_test.html
302 lines (284 loc) · 11 KB
/
index_zoom_test.html
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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/topojson/3.0.2/topojson.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/tinycolor/1.4.2/tinycolor.min.js"></script>
<style>
.state {
stroke-width: 0.5;
stroke: #000000;
}
.state:hover {
fill: #92a5e3;
cursor: pointer;
}
.slider {
width: 65%;
height: 25px;
background: #d3d3d3;
}
.tooltip {
position: absolute;
width: 200px;
height: 60px;
padding: 8px;
background: rgb(255,255,255);
font-family: "Helvetica Neue", "Helvetica", Arial sans-serif;
font-size: 12px;
border: 1px solid rgba(0,0,0,0.2);
box-shadow: 0 3px 5px rgba(0,0,0,0.5),0 0 0 1px rgba(0,0,0,.08);
border-radius: 2px;
pointer-events: none;
}
.g-place {
border-bottom: 1px solid rgb(130,130,130);
padding-bottom: 3px;
margin-bottom: 5px;
}
.g-headline {
font-size: 16px;
}
.g-sub {
color: rgb(130,130,130);
}
.g-value {
float: right;
}
.arrow-right {
position: absolute;
width: 0px;
height: 0px;
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
border-left: 10px solid green;
}
#map-holder {
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<script type="text/javascript">
// DEFINE VARIABLES
// Define size of map group
// Full world map is 2:1 ratio
// Using 12:5 because we will crop top and bottom of map
w = 3000;
h = 1250;
// variables for catching min and max zoom factors
var minZoom;
var maxZoom;
// DEFINE FUNCTIONS/OBJECTS
// Define map projection
var projection = d3
.geoEquirectangular()
.center([0, 15]) // set centre to further North as we are cropping more off bottom of map
.scale([w / (2 * Math.PI)]) // scale to fit group width
.translate([w / 2, h / 2]) // ensure centred in group
;
// Define map path
var path = d3
.geoPath()
.projection(projection)
;
// Create function to apply zoom to countriesGroup
function zoomed() {
t = d3
.event
.transform
;
countriesGroup
.attr("transform","translate(" + [t.x, t.y] + ")scale(" + t.k + ")")
;
}
// Define map zoom behaviour
var zoom = d3
.zoom()
.on("zoom", zoomed)
;
function getTextBox(selection) {
selection
.each(function(d) {
d.bbox = this
.getBBox();
})
;
}
// Function that calculates zoom/pan limits and sets zoom to default value
function initiateZoom() {
// Define a "minzoom" whereby the "Countries" is as small possible without leaving white space at top/bottom or sides
minZoom = Math.max($("#map-holder").width() / w, $("#map-holder").height() / h);
// set max zoom to a suitable factor of this value
maxZoom = 20 * minZoom;
// set extent of zoom to chosen values
// set translate extent so that panning can't cause map to move out of viewport
zoom
.scaleExtent([minZoom, maxZoom])
.translateExtent([[0, 0], [w, h]])
;
// define X and Y offset for centre of map to be shown in centre of holder
midX = ($("#map-holder").width() - minZoom * w) / 2;
midY = ($("#map-holder").height() - minZoom * h) / 2;
// change zoom transform to min zoom and centre offsets
svg.call(zoom.transform, d3.zoomIdentity.translate(midX, midY).scale(minZoom));
}
// zoom to show a bounding box, with optional additional padding as percentage of box size
function boxZoom(box, centroid, paddingPerc) {
minXY = box[0];
maxXY = box[1];
// find size of map area defined
zoomWidth = Math.abs(minXY[0] - maxXY[0]);
zoomHeight = Math.abs(minXY[1] - maxXY[1]);
// find midpoint of map area defined
zoomMidX = centroid[0];
zoomMidY = centroid[1];
// increase map area to include padding
zoomWidth = zoomWidth * (1 + paddingPerc / 100);
zoomHeight = zoomHeight * (1 + paddingPerc / 100);
// find scale required for area to fill svg
maxXscale = $("svg").width() / zoomWidth;
maxYscale = $("svg").height() / zoomHeight;
zoomScale = Math.min(maxXscale, maxYscale);
// handle some edge cases
// limit to max zoom (handles tiny countries)
zoomScale = Math.min(zoomScale, maxZoom);
// limit to min zoom (handles large countries and countries that span the date line)
zoomScale = Math.max(zoomScale, minZoom);
// Find screen pixel equivalent once scaled
offsetX = zoomScale * zoomMidX;
offsetY = zoomScale * zoomMidY;
// Find offset to centre, making sure no gap at left or top of holder
dleft = Math.min(0, $("svg").width() / 2 - offsetX);
dtop = Math.min(0, $("svg").height() / 2 - offsetY);
// Make sure no gap at bottom or right of holder
dleft = Math.max($("svg").width() - w * zoomScale, dleft);
dtop = Math.max($("svg").height() - h * zoomScale, dtop);
// set zoom
svg
.transition()
.duration(500)
.call(
zoom.transform,
d3.zoomIdentity.translate(dleft, dtop).scale(zoomScale)
);
}
// on window resize
$(window).resize(function() {
// Resize SVG
svg
.attr("width", $("#map-holder").width())
.attr("height", $("#map-holder").height())
;
initiateZoom();
});
// create an SVG
var svg = d3
.select("#map-holder")
.append("svg")
// set to the same size as the "map-holder" div
.attr("width", $("#map-holder").width())
.attr("height", $("#map-holder").height())
// add zoom functionality
.call(zoom)
;
// get map data
d3.json(
"https://raw.githubusercontent.com/andybarefoot/andybarefoot-www/master/maps/mapdata/custom50.json", function(json) {
//Bind data and create one path per GeoJSON feature
countriesGroup = svg.append("g").attr("id", "map");
// add a background rectangle
countriesGroup
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", w)
.attr("height", h);
// draw a path for each feature/country
countries = countriesGroup
.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path)
.attr("id", function(d, i) {
return "country" + d.properties.iso_a3;
})
.attr("class", "country")
// .attr("stroke-width", 10)
// .attr("stroke", "#ff0000")
// add a mouseover action to show name label for feature/country
.on("mouseover", function(d, i) {
d3.select("#countryLabel" + d.properties.iso_a3).style("display", "block");
})
.on("mouseout", function(d, i) {
d3.select("#countryLabel" + d.properties.iso_a3).style("display", "none");
})
// add an onclick action to zoom into clicked country
.on("click", function(d, i) {
d3.selectAll(".country").classed("country-on", false);
d3.select(this).classed("country-on", true);
boxZoom(path.bounds(d), path.centroid(d), 20);
});
// Add a label group to each feature/country. This will contain the country name and a background rectangle
// Use CSS to have class "countryLabel" initially hidden
countryLabels = countriesGroup
.selectAll("g")
.data(json.features)
.enter()
.append("g")
.attr("class", "countryLabel")
.attr("id", function(d) {
return "countryLabel" + d.properties.iso_a3;
})
.attr("transform", function(d) {
return (
"translate(" + path.centroid(d)[0] + "," + path.centroid(d)[1] + ")"
);
})
// add mouseover functionality to the label
.on("mouseover", function(d, i) {
d3.select(this).style("display", "block");
})
.on("mouseout", function(d, i) {
d3.select(this).style("display", "none");
})
// add an onlcick action to zoom into clicked country
.on("click", function(d, i) {
d3.selectAll(".country").classed("country-on", false);
d3.select("#country" + d.properties.iso_a3).classed("country-on", true);
boxZoom(path.bounds(d), path.centroid(d), 20);
});
// add the text to the label group showing country name
countryLabels
.append("text")
.attr("class", "countryName")
.style("text-anchor", "middle")
.attr("dx", 0)
.attr("dy", 0)
.text(function(d) {
return d.properties.name;
})
.call(getTextBox);
// add a background rectangle the same size as the text
countryLabels
.insert("rect", "text")
.attr("class", "countryLabelBg")
.attr("transform", function(d) {
return "translate(" + (d.bbox.x - 2) + "," + d.bbox.y + ")";
})
.attr("width", function(d) {
return d.bbox.width + 4;
})
.attr("height", function(d) {
return d.bbox.height;
});
initiateZoom();
}
);
</script>
</body>
</html>