I have a leaflet map with circle markers and a radial bar chart. I would like:
The circle markers move fine, but the radial chart is moving with the map which I don't want.
I have placed the circle markers within central_map_svg and the radial chart within chart_svg. Both are these are children of leaflet_svg which is I think is where the issue arises. However, if they don't have the same parent, then they appear separately.
I have included simplified reproducible code below.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="https://d19vzq90twjlae.cloudfront.net/leaflet-0.7/leaflet.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v5.js"></script>
<!-- Function for radial charts -->
<script src="https://cdn.jsdelivr.net/gh/holtzy/D3-graph-gallery@master/LIB/d3-scale-radial.js"></script>
<!-- Leaflet -->
<script src="https://d19vzq90twjlae.cloudfront.net/leaflet-0.7/leaflet.js"></script>
</head>
<body>
<div id="map" style="width: 800px; height: 800px"></div>
<script type="text/javascript">
// set the dimensions and margins of the graph
var size = 800;
var margin = { top: 100, right: 0, bottom: 0, left: 0 },
width = size - margin.left - margin.right,
height = size - margin.top - margin.bottom,
innerRadius = 240,
outerRadius = Math.min(width, height) / 2;
var mapCenter = new L.LatLng(52.482672, -1.897517);
var places = [
{
"id": 1,
"value": 15,
"latitude": 52.481,
"longitude": -1.899
},
{
"id": 2,
"value": 50,
"latitude": 52.486,
"longitude": -1.897
},
{
"id": 3,
"value": 36,
"latitude": 52.477,
"longitude": -1.902
},
{
"id": 4,
"value": 65,
"latitude": 52.486,
"longitude": -1.894
}]
var map = L.map('map').setView(mapCenter, 15);
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);
// Disable mouse zoom as this causes drift
map.scrollWheelZoom.disable();
// Initialize the SVG layer
map._initPathRoot();
/* We simply pick up the SVG from the map object */
var leaflet_svg = d3.select("#map").select("svg"),
central_map_svg = leaflet_svg.append("g"),
chart_svg = leaflet_svg.append("g")
.attr("transform", "translate(" + (width / 2 + margin.left) + "," + (height / 2 + margin.top) + ")");
function plot(data) {
/* Add a LatLng object to each item in the dataset */
data.forEach(function (d) {
d.LatLng = new L.LatLng(d.latitude, d.longitude)
})
// X scale
var x = d3.scaleBand()
.range([0, 2 * Math.PI])
.domain(data.map(function (d) { return d.id; }));
// Y scale
var y = d3.scaleRadial()
.range([innerRadius, outerRadius])
.domain([0, 68]);
// Add the bars
chart_svg.append("g")
.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("d", d3.arc()
.innerRadius(y(0))
.outerRadius(function (d) { return y(d.value); })
.startAngle(function (d) { return x(d.id); })
.endAngle(function (d) { return x(d.id) + x.bandwidth(); })
.padAngle(0.02)
.padRadius(innerRadius))
// Add the circles
var feature = central_map_svg
.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("r", 15)
// Ensure circles correctly positoned after map zoom / update
map.on("viewreset", update);
update();
function update() {
feature.attr("transform",
function (d) {
return "translate(" +
map.latLngToLayerPoint(d.LatLng).x + "," +
map.latLngToLayerPoint(d.LatLng).y + ")";
}
);
}
}
plot(places)
</script>
</body>
A simple solution would be to stack a container on top of your map to accommodate your chart.
First, change your HTML to
<div id='scene'>
<div id="map"></div>
<div id='chart'></div>
</div>
Then add styles to display #chart
on top of #map
#scene {width: 800px; height: 800px; position: relative;}
#map {width: 100%; height: 100%; z-index: 1;}
#chart {width: 100%; height: 100%; position: absolute; z-index: 2; top:0; left: 0; pointer-events: none;}
#chart svg {width: 100%; height: 100%;}
If you want to keep some parts of your chart interactive, add a rule to re-enable pointer events on those elements. For example:
#chart path {pointer-events: auto;}
Finally, point chart_svg
to the correct element:
chart_svg = d3.select("#chart").append("svg").append("g")
And a demo
// set the dimensions and margins of the graph
var size = 400;
var margin = { top: 50, right: 0, bottom: 0, left: 0 },
width = size - margin.left - margin.right,
height = size - margin.top - margin.bottom,
innerRadius = 120,
outerRadius = Math.min(width, height) / 2;
var mapCenter = new L.LatLng(52.482672, -1.897517);
var places = [
{
"id": 1,
"value": 15,
"latitude": 52.481,
"longitude": -1.899
},
{
"id": 2,
"value": 50,
"latitude": 52.486,
"longitude": -1.897
},
{
"id": 3,
"value": 36,
"latitude": 52.477,
"longitude": -1.902
},
{
"id": 4,
"value": 65,
"latitude": 52.486,
"longitude": -1.894
}]
var map = L.map('map').setView(mapCenter, 15);
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);
// Disable mouse zoom as this causes drift
map.scrollWheelZoom.disable();
// Initialize the SVG layer
map._initPathRoot();
/* We simply pick up the SVG from the map object */
var leaflet_svg = d3.select("#map").select("svg"),
central_map_svg = leaflet_svg.append("g"),
chart_svg = d3.select("#chart").append("svg").append("g")
.attr("transform", "translate(" + (width / 2 + margin.left) + "," + (height / 2 + margin.top) + ")");
function plot(data) {
/* Add a LatLng object to each item in the dataset */
data.forEach(function (d) {
d.LatLng = new L.LatLng(d.latitude, d.longitude)
})
// X scale
var x = d3.scaleBand()
.range([0, 2 * Math.PI])
.domain(data.map(function (d) { return d.id; }));
// Y scale
var y = d3.scaleRadial()
.range([innerRadius, outerRadius])
.domain([0, 68]);
// Add the bars
chart_svg.append("g")
.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("d", d3.arc()
.innerRadius(y(0))
.outerRadius(function (d) { return y(d.value); })
.startAngle(function (d) { return x(d.id); })
.endAngle(function (d) { return x(d.id) + x.bandwidth(); })
.padAngle(0.02)
.padRadius(innerRadius))
chart_svg
.selectAll("path")
.on("mouseover", function() {
d3.select(this).style("fill", "red");
})
.on("mouseout", function() {
d3.select(this).style("fill", "black");
})
.on("touchend", function() {
var el = d3.select(this);
el.style("fill", el.style("fill") === "red" ? "black" : "red");
})
;
// Add the circles
var feature = central_map_svg
.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("r", 15)
// Ensure circles correctly positoned after map zoom / update
map.on("viewreset", update);
update();
function update() {
feature.attr("transform",
function (d) {
return "translate(" +
map.latLngToLayerPoint(d.LatLng).x + "," +
map.latLngToLayerPoint(d.LatLng).y + ")";
}
);
}
}
plot(places)
#scene {width: 400px; height: 400px; position: relative;}
#map {width: 100%; height: 100%; z-index: 1;}
#chart {width: 100%; height: 100%; position: absolute; z-index: 2; top:0; left: 0; pointer-events: none;}
#chart svg {width: 100%; height: 100%;}
#chart path {pointer-events: auto;}
<link rel="stylesheet" href="https://d19vzq90twjlae.cloudfront.net/leaflet-0.7/leaflet.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v5.js"></script>
<!-- Function for radial charts -->
<script src="https://cdn.jsdelivr.net/gh/holtzy/D3-graph-gallery@master/LIB/d3-scale-radial.js"></script>
<!-- Leaflet -->
<script src="https://d19vzq90twjlae.cloudfront.net/leaflet-0.7/leaflet.js"></script>
<div id='scene'>
<div id="map"></div>
<div id='chart'></div>
</div>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With