----------------------------------------------------------
Leaflet map with d3.js elements that are overlaid on a map
The next example of a combination of d3.js and leaflet.js is one where we want to have an element overlaid on our map at a specific location, but have it remain a specific size over the map. For example, here we will display 5 circles which are centred at specific geographic locations.
d3.js circles fixed in geographic location on leaflet map but constant size |
When we zoom out of the map, those circles remain over the geographic location, but the same size on the screen.
Zoomed d3.js circles fixed in geographic location on leaflet map but constant size |
You may (justifiably) ask yourself why we would want to do this with d3.js when Leaflet could do the same job with a marker? The answer is that as cool as leaflet.js’s markers are, d3 elements have a wider range of features that make their use advantageous in some situations. For instance if you want to animate or rotate the icons or dynamically adjust some of their attributes, d3.js would have a greater scope for adjustments.
The following code draws circles at geographic locations;
<!DOCTYPE html>
<html>
<head>
<title>
d3.js with leaflet.js</title>
<link
rel=
"stylesheet"
href=
"http://cdn.leafletjs.com/leaflet-0.7/leaflet.css"
/>
<script
src=
"http://d3js.org/d3.v3.min.js"
></script>
<script
src=
"http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"
>
</script>
</head>
<body>
<div
id=
"map"
style=
"width: 600px; height: 400px"
></div>
<script
type=
"text/javascript"
>
var
map
=
L
.
map
(
'map'
).
setView
([
-
41.2858
,
174.7868
],
13
);
mapLink
=
'<a href="http://openstreetmap.org">OpenStreetMap</a>'
;
L
.
tileLayer
(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
,
{
attribution
:
'© '
+
mapLink
+
' Contributors'
,
maxZoom
:
18
,
}).
addTo
(
map
);
// Initialize the SVG layer
map
.
_initPathRoot
()
// We pick up the SVG from the map object
var
svg
=
d3
.
select
(
"#map"
).
select
(
"svg"
),
g
=
svg
.
append
(
"g"
);
d3
.
json
(
"circles.json"
,
function
(
collection
)
{
// Add a LatLng object to each item in the dataset
collection
.
objects
.
forEach
(
function
(
d
)
{
d
.
LatLng
=
new
L
.
LatLng
(
d
.
circle
.
coordinates
[
0
],
d
.
circle
.
coordinates
[
1
])
})
var
feature
=
g
.
selectAll
(
"circle"
)
.
data
(
collection
.
objects
)
.
enter
().
append
(
"circle"
)
.
style
(
"stroke"
,
"black"
)
.
style
(
"opacity"
,
.
6
)
.
style
(
"fill"
,
"red"
)
.
attr
(
"r"
,
20
);
map
.
on
(
"viewreset"
,
update
);
update
();
function
update
()
{
feature
.
attr
(
"transform"
,
function
(
d
)
{
return
"translate("
+
map
.
latLngToLayerPoint
(
d
.
LatLng
).
x
+
","
+
map
.
latLngToLayerPoint
(
d
.
LatLng
).
y
+
")"
;
}
)
}
})
</script>
</body>
</html>
There is also an associated json data file (called
circles.json
) that has the following contents;{"objects":[
{"circle":{"coordinates":[-41.28,174.77]}},
{"circle":{"coordinates":[-41.29,174.76]}},
{"circle":{"coordinates":[-41.30,174.79]}},
{"circle":{"coordinates":[-41.27,174.80]}},
{"circle":{"coordinates":[-41.29,174.78]}}
]}
The full code and a live example are available online at bl.ocks.org or GitHub. They are also available as the files ‘leaflet-d3-linked.html’ and ‘circles.json’ as a separate download with D3 Tips and Tricks. A a copy of all the files that appear in the book can be downloaded (in a zip file) when you download the book from Leanpub
While I will explain the code below, as with the previous example (which is similar, but different) please be aware that I will gloss over some of the simpler sections that are covered in other sections of either books and will instead focus on the portions that are important to understand the combination of d3 and leaflet.
Our code begins by setting up the html document in a fairly standard way.
<!DOCTYPE html>
<html>
<head>
<title>
d3.js with leaflet.js</title>
<link
rel=
"stylesheet"
href=
"http://cdn.leafletjs.com/leaflet-0.7/leaflet.css"
/>
<script
src=
"http://d3js.org/d3.v3.min.js"
></script>
<script
src=
"http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"
>
</script>
</head>
<body>
<div
id=
"map"
style=
"width: 600px; height: 400px"
></div>
Here we’re getting some css styling and loading our leaflet.js / d3.js libraries. The only configuration item is where we set up the size of the map (in the
<div>
section and as part of the map
div).
Then we break into the JavaScript code. The first thing we do is to project our Leaflet map;
var
map
=
L
.
map
(
'map'
).
setView
([
-
41.2858
,
174.7868
],
13
);
mapLink
=
'<a href="http://openstreetmap.org">OpenStreetMap</a>'
;
L
.
tileLayer
(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
,
{
attribution
:
'© '
+
mapLink
+
' Contributors'
,
maxZoom
:
18
,
}).
addTo
(
map
);
This is exactly the same as we have done in any of the simple map explanations in Leaflet Tips and Tricks and in this case we are using the OpenStreetMap tiles.
Then we start on the d3.js part of the code.
Firstly the Leaflet map is initiated as SVG using
map._initPathRoot()
. // Initialize the SVG layer
map
.
_initPathRoot
()
// We pick up the SVG from the map object
var
svg
=
d3
.
select
(
"#map"
).
select
(
"svg"
),
g
=
svg
.
append
(
"g"
);
Then we select the svg layer and append a
g
element to give a common reference point g = svg.append("g")
.
Then we load the json file with the coordinates for the circles;
d3
.
json
(
"circles.json"
,
function
(
collection
)
{
Then for each of the coordinates in the
objects
section of the json data we declare a new latitude / longitude pair from the associated coordinates
; collection
.
objects
.
forEach
(
function
(
d
)
{
d
.
LatLng
=
new
L
.
LatLng
(
d
.
circle
.
coordinates
[
0
],
d
.
circle
.
coordinates
[
1
])
})
Then we use a simple d3.js routine to add and place our circles based on the coordinates of each of our
objects
. var
feature
=
g
.
selectAll
(
"circle"
)
.
data
(
collection
.
objects
)
.
enter
().
append
(
"circle"
)
.
style
(
"stroke"
,
"black"
)
.
style
(
"opacity"
,
.
6
)
.
style
(
"fill"
,
"red"
)
.
attr
(
"r"
,
20
);
We declare each as a
feature
and add a bit of styling just to make them stand out.
The last ‘main’ part of our JavaScript makes sure that when our view of what we’re looking at changes (we zoom or pan) that our d3 elements change as well;
map
.
on
(
"viewreset"
,
update
);
update
();
Obviously when our view changes we call the function
update
. It’s the job of the update
function to ensure that whenever the leaflet layer moves, the SVG layer with the d3.js elements follows and the points that designate the locations of those objects move appropriately; function
update
()
{
feature
.
attr
(
"transform"
,
function
(
d
)
{
return
"translate("
+
map
.
latLngToLayerPoint
(
d
.
LatLng
).
x
+
","
+
map
.
latLngToLayerPoint
(
d
.
LatLng
).
y
+
")"
;
}
)
}
Here we are using the
transform
function on each feature
to adjust the coordinates on our LatLng
coordinates. We only need to adjust our coordinates since the size, shape, rotation and any other attribute or style is dictated by the objects themselves.
And there we have it!
d3.js circles fixed in geographic location on leaflet map but constant size |
The description above (and heaps of other stuff) is in the D3 Tips and Tricks book that can be downloaded for free (or donate if you really want to :-)).
On inspection I am able to point circles. But they are not visible. I tried changing the z-index but it doesn't help.
ReplyDeleteInteresting. Compare your code with the example here http://blockbuilder.org/d3noob/9267535 and see where the difference lies.
Delete