I am using markerCluster for Leaflet with the showCoverageOnHover option set to true. However, in Firefox (v 46.0.1), the event showCoverageOnHover is not triggered correctly, meaning that the cluster area is shown not only when the mouse is hovered over the cluster, but also if the mouse is far away from that cluster.
Basically, I am using the standard procedure to create a markerClusterGroup, but with a customized icon creation function (Using d3 to draw a Pie chart). My code looks as follows:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style type="text/css">
#mapid {
height: 60vh;
}
</style>
<script src='https://api.mapbox.com/mapbox.js/v2.4.0/mapbox.js'></script>
<link href='https://api.mapbox.com/mapbox.js/v2.4.0/mapbox.css' rel='stylesheet' />
<script src='https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/0.5.0/leaflet.markercluster.js'></script>
<link href='https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/0.5.0/MarkerCluster.css' rel='stylesheet' />
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<title>WorldMap</title>
<script type="text/javascript">
function defineClusterIcon(cluster) {
var color = d3.scale.category20b();
//some dummy json
var myjson = '[{ "label":"Monday", "count": 15 },{ "label":"Tuesday", "count": 20 }]';
var dataset = JSON.parse( myjson );
var size = 40;
var radius= size / 2;
var svgres = document.createElementNS(d3.ns.prefix.svg, 'svg');
var svg = d3.select(svgres).append('svg')
.attr('width', size)
.attr('height', size)
.append('g')
.attr('transform','translate(' + (size / 2) + ',' + (size / 2) + ')'); //center g
var arc = d3.svg.arc().outerRadius(radius);
var pie = d3.layout.pie().value(function(d) {
return d.count;
});
//create final chart
svg.selectAll('path').data(pie(dataset)) //fill dataset into path
.enter() //create placeholder for data
.append('path') //fill placeholder with data in path
.attr('d', arc) //define an attribute d
.attr('fill', function(d, i) {
return color(d.data.label);
});
var html = serializeXmlNode(svgres);
var myIcon = new L.DivIcon({
html : html,
className : 'mycluster',
iconSize : new L.Point(size, size)
});
return myIcon;
}
function serializeXmlNode(xmlNode) {
if (typeof window.XMLSerializer != "undefined") {
return (new window.XMLSerializer()).serializeToString(xmlNode);
} else if (typeof xmlNode.xml != "undefined") {
return xmlNode.xml;
}
return "";
}
</script>
</head>
<body>
<div id="mapid"></div>
<script type="text/javascript">
var map = L.map('mapid', {
center: [40, 40],
maxZoom : 10,
zoom: 2
});
//create markercluster
var markers = new L.markerClusterGroup({
showCoverageOnHover: true,
iconCreateFunction: defineClusterIcon
});
//some example markers
var marker = new L.marker([40.0,10.0]);
markers.addLayer(marker);
var marker = new L.marker([42.0,-12.0]);
markers.addLayer(marker);
var marker = new L.marker([50.0,30.0]);
markers.addLayer(marker);
map.addLayer(markers);
</script>
</body>
</html>
Any ideas why the showCoverageOnHover event is not triggered correctly in Firefox?
Thanks!
Looks like the SVG element you created overflows the Leaflet icon.
Simply setting overflow: hidden CSS rule on your icon class seems to solve your issue.
.mycluster {
overflow: hidden;
}
Updated JSFiddle: https://jsfiddle.net/sqeypmrn/1/
Note: question also posted on Leaflet.markercluser GitHub page as issue #677.
Related
im trying to export a NVD3 graph using this tutorials:
http://www.coffeegnome.net/converting-svg-to-png-with-canvg/
SVG to Canvas with d3.js
It works perfectly but my result image is cropped to 300 x 150, how can I export the whole image?
My code is this:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Export D3 to image</title>
<link rel="stylesheet" type="text/css" href="css/nv.d3.css">
<script language="javascript" type="text/javascript" src="js/jquery.min.js"></script>
<script language="javascript" type="text/javascript" src="js/d3.min.js"></script>
<script language="javascript" type="text/javascript" src="js/nv.d3.js"></script>
</head>
<body>
<svg id="basicChart1"></svg>
<script>
createGraphs();
function createGraphs() {
nv.addGraph(function () {
chart1 = nv.models.lineChart()
.useInteractiveGuideline(true)
;
chart1.xAxis
.axisLabel('Time (ms)')
.tickFormat(d3.format(',r'))
;
chart1.yAxis
.axisLabel('Voltage (v)')
.tickFormat(d3.format('.02f'))
;
d3.select('#basicChart1')
.datum(data())
.call(chart1)
;
nv.utils.windowResize(chart1.update);
return chart1;
});
console.log("Graficas Creadas");
}
function data() {
var sin = [];
for (var i = 0; i < 100; i++) {
sin.push({x: i, y: Math.sin(i / 10)});
}
return [
{
values: sin,
key: 'Sine Wave',
color: '#ff7f0e'
}
];
}
// Tutorials:
// http://www.coffeegnome.net/converting-svg-to-png-with-canvg/
// Create an export button
d3.select('body')
.append("button")
.html("Export")
.on("click", saveSVG)
.attr('class', 'btn btn-success');
var width = 300, height = 100;
// Create the export function - this will just export
// the first svg element it finds
function saveSVG() {
// get styles from all required stylesheets
// http://www.coffeegnome.net/converting-svg-to-png-with-canvg/
var style = "\n";
var requiredSheets = ['nv.d3.css']; // list of required CSS
for (var i = 0; i < document.styleSheets.length; i++) {
var sheet = document.styleSheets[i];
if (sheet.href) {
if (requiredSheets.indexOf(sheet.href.split('/').pop()) !== -1) {
if (sheet.rules) {
for (var j = 0; j < sheet.rules.length; j++) {
style += (sheet.rules[j].cssText + '\n');
}
}
}
}
}
var svg = d3.select("#basicChart1"),
serializer = new XMLSerializer();
// prepend style to svg
svg.insert('defs', ":first-child");
d3.select("svg defs")
.append('style')
.attr('type', 'text/css')
.html(style);
// generate IMG in new tab
var svgStr = serializer.serializeToString(svg.node());
var imgsrc = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgStr)));
var image = new Image();
image.src = imgsrc;
window.open().document.write('<img src="' + image.src + '"/>');
}
;
</script>
</body>
</html>
I'll answer my own question:
The problem was on the SVG Width and Height attributes:
<svg id="basicChart1"></svg>
So i only needed to add the calculated Width and Height values to the svg tag before the generation of the image:
var serializer = new XMLSerializer();
var svg = d3.select("#basicChart1");
svg.attr('width', svg.node().clientWidth);
svg.attr('height', svg.node().clientHeight);
svg.insert('defs', ":first-child");
d3.select("svg defs")
.append('style')
.attr('type', 'text/css')
.html(style);
Context:
I've made a map, and populated it with around 300 random markers. I can 'select' the markers by clicking on a link in the popup and activate a selection to display data from. I also have the Leaflet.draw plugin to draw shapes like circles, rectangles and custom shapes, and I would like to use it to 'select' a couple of markers.
The issue
How can I grab the leaflet marker object of the markers that fall inside a drawn leaflet.draw shape so I can edit them? I cannot seem to make a selection, It either selects none of the markers, or all of them.
Code snippet, stripped from unnecessary code:
const drawControl = new L.Control.Draw({
draw: {
marker : false,
polygon : true,
polyline : false,
rectangle: true,
circle : {
metric: 'metric'
}
},
edit: false
});
const map = L.map('map', {
layers: [streets, light]
}).setView([CONFIG.MAP.LATITUDE, CONFIG.MAP.LONGITUDE], CONFIG.MAP.ZOOMLEVEL)
map.addControl(drawControl);
map.on(L.Draw.Event.DRAWSTOP, e => {
const hello = e.target;
console.log(hello);
e.target.eachLayer(layer => {
if (layer.options.icon) {
console.log(layer);
}
});
});
Most of what you want can quite easily be done using Leaflet's utility methods. If you want to do this with a complex shape like L.Polygon you're going to need something like TurfJS
For L.Circle you need to calculate the distance between the circle's center and compare it to the radius:
var marker = new L.Marker(...),
circle = new L.Circle(...);
var contains = circle.getLatLng().distanceTo(marker.getLatLng()) < circle.getRadius();
For L.Rectangle you need to fetch it's bounds object and use the contains method:
var marker = new L.Marker(...),
rectangle = new L.Rectangle(...);
var contains = rectangle.getBounds().contains(marker.getLatLng());
As said for complex polygons i'de use Turf but there are more libraries and plugins out there. Here's an example using Turf's inside method. It take a GeoJSON point and polygon feature as parameters so mind the conversion:
var marker = new L.Marker(...),
polygon = new L.Polygon(...);
var contains = turf.inside(marker.toGeoJSON(), polygon.toGeoJSON());
You could wrap those into convenience methods for each respective class:
L.Polygon.include({
contains: function (latLng) {
return turf.inside(new L.Marker(latLng).toGeoJSON(), this.toGeoJSON());
}
});
L.Rectangle.include({
contains: function (latLng) {
return this.getBounds().contains(latLng);
}
});
L.Circle.include({
contains: function (latLng) {
return this.getLatLng().distanceTo(latLng) < this.getRadius();
}
});
var marker = new L.Marker(...),
polygon = new L.Polygon(...),
rectangle = new L.Rectangle(...),
circle = new L.Circle(...);
polygon.contains(marker.getLatLng());
rectangle.contains(marker.getLatLng());
circle.contains(marker.getLatLng());
Note that if you implement the polygon method that there is no need for the rectangle method. Since rectangle is extended from polygon it will inherit the method. I left it in there to be complete.
Now iterating your markers and comparing them is easy:
map.on(L.Draw.Event.CREATED, function (e) {
markers.eachLayer(function (marker) {
if (!e.layer.contains(marker.getLatLng())) {
marker.remove();
}
});
});
Hope that helps, here's a working snippet:
var map = new L.Map('leaflet', {
'center': [0, 0],
'zoom': 0
});
var markers = new L.LayerGroup().addTo(map);
for (var i = 0; i < 300; i++) {
var marker = new L.Marker([
(Math.random() * (90 - -90) + -90).toFixed(5) * 1,
(Math.random() * (180 - -180) + -180).toFixed(5) * 1
]).addTo(markers);
}
new L.Control.Draw({
draw: {
marker : false,
polygon : true,
polyline : false,
rectangle: true,
circle : {
metric: 'metric'
}
},
edit: false
}).addTo(map);
L.Polygon.include({
contains: function (latLng) {
return turf.inside(new L.Marker(latLng).toGeoJSON(), this.toGeoJSON());
}
});
L.Rectangle.include({
contains: function (latLng) {
return this.getBounds().contains(latLng);
}
});
L.Circle.include({
contains: function (latLng) {
return this.getLatLng().distanceTo(latLng) < this.getRadius();
}
});
map.on(L.Draw.Event.CREATED, function (e) {
markers.eachLayer(function (marker) {
if (!e.layer.contains(marker.getLatLng())) {
marker.remove();
}
});
});
body {
margin: 0;
}
html, body, #leaflet {
height: 100%;
}
<!DOCTYPE html>
<html>
<head>
<title>Leaflet 1.0.3</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/leaflet#1.0.3/dist/leaflet.css" />
<link type="text/css" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.4.9/leaflet.draw.css" />
</head>
<body>
<div id="leaflet"></div>
<script type="application/javascript" src="//unpkg.com/leaflet#1.0.3/dist/leaflet.js"></script>
<script type="application/javascript" src="//cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.4.9/leaflet.draw.js"></script>
<script type="application/javascript" src="//unpkg.com/#turf/turf#latest/turf.min.js"></script>
</body>
</html>
Thanks #iH8 for the cool example. I went further to avoid some repetition with
markers.eachLayer(function (marker) {
...
}
and extended the wrappers with additionnal methods using arrays of markers instead:
First I noticed that a LayerGroup has an object with key-values containing all the markers. I simply use that object to create an array of markers :
// In the on draw event
...
// Set an array containing all the markers
var markers = jsonToArray(layerGroup._layers);
...
function jsonToArray(jsonObject) {
var result = [];
var keys = Object.keys(jsonObject);
keys.forEach(function (key) {
result.push(jsonObject[key]);
});
return result;
}
I then re-use the wrappers with modified contains() methods :
L.Rectangle.include({
// Single marker case
contains: function (marker) {
return this.getBounds().contains(marker.getLatLng());
},
// Array of markers
contains: function (markers) {
var markersContained = [];
markers.forEach(marker => {
markersContained.push(this.getBounds().contains(marker.getLatLng()));
})
return markersContained;
}
});
L.Circle.include({
contains: function (marker) {
return this.getLatLng().distanceTo(marker.getLatLng()) < this.getRadius();
},
contains: function (markers) {
var markersContained = [];
markers.forEach(marker => {
markersContained.push(this.getLatLng().distanceTo(marker.getLatLng()) < this.getRadius());
})
return markersContained;
}
});
and finally on the draw event, I check whether my markers are contained within or not :
map.on(L.Draw.Event.CREATED, function (geometry) {
// Set an array containing all the markers
var markers = jsonToArray(layerGroup._layers);
var result = geometry.layer.contains(markers);
console.log('result => ', result);
});
function jsonToArray(jsonObject) {
var result = [];
var keys = Object.keys(jsonObject);
keys.forEach(function (key) {
result.push(jsonObject[key]);
});
return result;
}
var map = new L.Map('leaflet', {
'center': [0, 0],
'zoom': 0
});
var layerGroup = new L.LayerGroup().addTo(map);
for (var i = 0; i < 10; i++) {
var marker = new L.Marker([
(Math.random() * (90 - -90) + -90).toFixed(5) * 1,
(Math.random() * (180 - -180) + -180).toFixed(5) * 1
]).addTo(layerGroup);
}
new L.Control.Draw({
draw: {
marker : false,
polygon : false,
polyline : false,
rectangle: true,
circle : {
metric: 'metric'
}
},
edit: false
}).addTo(map);
// Define contains() method for each geometry
L.Rectangle.include({
contains: function (marker) {
return this.getBounds().contains(marker.getLatLng());
},
contains: function (markers) {
var markersContained = [];
markers.forEach(marker => {
markersContained.push(this.getBounds().contains(marker.getLatLng()));
})
return markersContained;
}
});
L.Circle.include({
contains: function (marker) {
return this.getLatLng().distanceTo(marker.getLatLng()) < this.getRadius();
},
contains: function (markers) {
var markersContained = [];
markers.forEach(marker => {
markersContained.push(this.getLatLng().distanceTo(marker.getLatLng()) < this.getRadius());
})
return markersContained;
}
});
map.on(L.Draw.Event.CREATED, function (geometry) {
// Set an array containing all the markers
var markers = jsonToArray(layerGroup._layers);
var result = geometry.layer.contains(markers);
console.log('result => ', result);
});
body {
margin: 0;
}
html, body, #leaflet {
height: 100%;
}
<!DOCTYPE html>
<html>
<head>
<title>Leaflet 1.0.3</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/leaflet#1.0.3/dist/leaflet.css" />
<link type="text/css" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.4.9/leaflet.draw.css" />
</head>
<body>
<div id="leaflet"></div>
<script type="application/javascript" src="//unpkg.com/leaflet#1.0.3/dist/leaflet.js"></script>
<script type="application/javascript" src="//cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.4.9/leaflet.draw.js"></script>
</body>
</html>
I used it:
L.Circle.include({
contains: function (latLng) {
return this.getLatLng().distanceTo(latLng) < this.getRadius();
}
});
Points that are on the edge but not in the circle are also judged.
I have a nvd3 chart example, however i have no idea how to enter the data into the graph and make use of it.
HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link href="build/nv.d3.css" rel="stylesheet" type="text/css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.2/d3.min.js" charset="utf-8"></script>
<script src="build/nv.d3.js"></script>
<script src="lib/stream_layers.js"></script>
<style>
text {
font: 12px sans-serif;
}
svg {
display: block;
}
html, body, #test1, svg {
margin: 0px;
padding: 0px;
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<div id="test1">
<svg></svg>
</div>
<script>
//var test_data = stream_layers(3,128,.1).map(function(data, i) {
var test_data = stream_layers(3, 128, .1).map(function (data, i) {
return {
key: (i == 1) ? 'Non-stackable Stream' + i : 'Stream' + i,
nonStackable: (i == 1),
values: data
};
});
nv.addGraph({
generate: function () {
var width = nv.utils.windowSize().width,
height = nv.utils.windowSize().height;
var chart = nv.models.multiBarChart()
.width(width)
.height(height)
.stacked(true)
;
chart.dispatch.on('renderEnd', function () {
console.log('Render Complete');
});
var svg = d3.select('#test1 svg').datum(test_data);
console.log('calling chart');
svg.transition().duration(0).call(chart);
return chart;
},
callback: function (graph) {
nv.utils.windowResize(function () {
var width = nv.utils.windowSize().width;
var height = nv.utils.windowSize().height;
graph.width(width).height(height);
d3.select('#test1 svg')
.attr('width', width)
.attr('height', height)
.transition().duration(0)
.call(graph);
});
}
});
</script>
</body>
</html>
Java(Where the data is taken from)
/* Inspired by Lee Byron's test data generator. */
function stream_layers(n, m, o) {
if (arguments.length < 3) o = 0;
function bump(a) {
var x = 1 / (.1 + Math.random()),
y = 2 * Math.random() - .5,
z = 10 / (.1 + Math.random());
for (var i = 0; i < m; i++) {
var w = (i / m - y) * z;
a[i] += x * Math.exp(-w * w);
}
}
return d3.range(n).map(function() {
var a = [], i;
for (i = 0; i < m; i++) a[i] = o + o * Math.random();
for (i = 0; i < 5; i++) bump(a);
return a.map(stream_index);
});
}
/* Another layer generator using gamma distributions. */
function stream_waves(n, m) {
return d3.range(n).map(function(i) {
return d3.range(m).map(function(j) {
var x = 20 * j / m - i / 3;
return 2 * x * Math.exp(-.5 * x);
}).map(stream_index);
});
}
function stream_index(d, i) {
return {x: i, y: Math.max(0, d)};
}
So as seen above the data for the example is randomly generated.
If someone can give me a reference, or an example of how do i enter data into the grouped bar chart. It will really help me a lot.
What im trying to add in my data into is this
http://nvd3.org/livecode/index.html#codemirrorNav
The grouped bar chart example.
I really am new to this javascript coding, so all help is truly appreciated.
An example from the NVD3 website:
nv.addGraph(function() {
var chart = nv.models.multiBarChart()
.transitionDuration(350)
.reduceXTicks(true) //If 'false', every single x-axis tick label will be rendered.
.rotateLabels(0) //Angle to rotate x-axis labels.
.showControls(true) //Allow user to switch between 'Grouped' and 'Stacked' mode.
.groupSpacing(0.1) //Distance between each group of bars.
;
chart.xAxis
.tickFormat(d3.format(',f'));
chart.yAxis
.tickFormat(d3.format(',.1f'));
d3.select('#chart1 svg')
.datum(test_data)
.call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
So, crucial here is to have element with id="chart1" and inside that element to have an empty svg element (I'm explaining the above setup, it can be different id's and directly only svg element)
The important part is that the data format must be in a specific format. So it should be a JSON object, something like this:
test_data = [
{
values: [{x,y},{x,y}],
key: 'some key',
color: 'some color'
},....
{
values: [{x,y},{x,y}],
key: 'some key',
color: 'some color'
}
];
In your case I see strange the generate and callback functions, which I see that it is a little bit mixed up.
Reference for the above example:
http://nvd3.org/examples/multiBar.html
And refer to the latest documentation and examples:
http://nvd3-community.github.io/nvd3/examples/documentation.html
I'm building a Voronoi map inspired by http://chriszetter.com/blog/2014/06/15/building-a-voronoi-map-with-d3-and-leaflet/. I'd like to make an option to turn off the background map as the location of the data may not be relevant in all my use cases. Furthermore, it would be great if the visualization could work offline this way. After toggling the switch, the entire background would be white. The Voronoi overlap would be the same. How do I do that? Here's the code (zip-file contains the csv-files): https://www.dropbox.com/s/i8vtfh8mkxazfr0/voronoi-maps-master.zip?dl=0
EDIT: There's two layer variables in the original code as I was trying to split the visualization into two. However, that attempt was unsuccessful and only mapLayer is used. This may not have been clear in the original question.
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link href="base.css" rel="stylesheet" />
<link href='https://api.tiles.mapbox.com/mapbox.js/v1.6.3/mapbox.css' rel='stylesheet' />
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id='map'>
</div>
<div id='loading'>
</div>
<!-- <div id='selected'>
<h1>...</h1>
</div> -->
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min.js"></script>
<script src="https://api.tiles.mapbox.com/mapbox.js/v1.6.3/mapbox.js"></script>
<!-- <script src="/voronoi-map/lib/voronoi_map.js"></script> -->
<script type="text/javascript" src="voronoi_map.js"></script>
<script>
map = L.mapbox.map('map', 'zetter.i73ka9hn') // <- dur ikke!
.fitBounds([[59.355596 , -9.052734], [49.894634 , 3.515625]]);
url = 'supermarkets.csv';
initialSelection = d3.set(['Tesco', 'Sainsburys']);
voronoiMap(map, url, initialSelection);
</script>
</body>
</html>
voronoi_map.js
voronoiMap = function(map, url, initialSelections) {
var pointTypes = d3.map(),
points = [],
lastSelectedPoint;
var voronoi = d3.geom.voronoi()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; });
var selectPoint = function() {
d3.selectAll('.selected').classed('selected', false);
var cell = d3.select(this),
point = cell.datum();
lastSelectedPoint = point;
cell.classed('selected', true);
d3.select('#selected h1')
.html('')
.append('a')
.text( /*point.name*/ "8 interactions from this cell")
/* .attr('href', point.url)
.attr('target', '_blank') */
}
var drawWithLoading = function(e){
d3.select('#loading').classed('visible', true);
if (e && e.type == 'viewreset') {
d3.select('#overlay').remove();
}
setTimeout(function(){
draw();
d3.select('#loading').classed('visible', false);
}, 0);
}
var draw = function() {
d3.select('#overlay').remove();
var bounds = map.getBounds(),
topLeft = map.latLngToLayerPoint(bounds.getNorthWest()),
bottomRight = map.latLngToLayerPoint(bounds.getSouthEast()),
existing = d3.set(),
drawLimit = bounds.pad(0.4);
filteredPoints = points.filter(function(d) {
var latlng = new L.LatLng(d.latitude, d.longitude);
if (!drawLimit.contains(latlng)) { return false };
var point = map.latLngToLayerPoint(latlng);
key = point.toString();
if (existing.has(key)) { return false };
existing.add(key);
d.x = point.x;
d.y = point.y;
return true;
});
voronoi(filteredPoints).forEach(function(d) { d.point.cell = d; });
var svg = d3.select(map.getPanes().overlayPane).append("svg")
.attr('id', 'overlay')
.attr("class", "leaflet-zoom-hide")
.style("width", map.getSize().x + 'px')
.style("height", map.getSize().y + 'px')
.style("margin-left", topLeft.x + "px")
.style("margin-top", topLeft.y + "px");
var g = svg.append("g")
.attr("transform", "translate(" + (-topLeft.x) + "," + (-topLeft.y) + ")");
var svgPoints = g.attr("class", "points")
.selectAll("g")
.data(filteredPoints)
.enter().append("g")
.attr("class", "point");
var buildPathFromPoint = function(point) {
return "M" + point.cell.join("L") + "Z";
}
svgPoints.append("path")
.attr("class", "point-cell")
.attr("d", buildPathFromPoint)
//.style('fill', function(d) { return '#' + d.color } )
.on('click', selectPoint)
.classed("selected", function(d) { return lastSelectedPoint == d} );
/* svgPoints.append("circle")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.style('fill', function(d) { return '#' + d.color } )
.attr("r", 2); */
}
/* function interactionGradient() {
color =
if
return
} */
var mapLayer = {
onAdd: function(map) {
map.on('viewreset moveend', drawWithLoading);
drawWithLoading();
}
};
var voronoiLayer = {
onAdd: function(map) {
map.on('viewreset moveend', drawWithLoading);
drawWithLoading();
}
};
map.on('ready', function() {
d3.csv(url, function(csv) {
points = csv;
points.forEach(function(point) {
pointTypes.set(point.type, {type: point.type, color: point.color});
})
map.addLayer(mapLayer);
})
});
}
You need to load the tileLayer seperately so you can create a reference to it which you can then in turn use to create a layercontrol which can easily enable/disable layers:
L.mapbox.accessToken = 'pk.eyJ1IjoicGF1bC12ZXJob2V2ZW4iLCJhIjoiZ1BtMWhPSSJ9.wQGnLyl1DiuIQuS0U_PtiQ';
// Create the tileLayer.
var tileLayer = L.mapbox.tileLayer('examples.map-i86nkdio');
var map = L.mapbox.map('mapbox', null, { // Do not add as parameter
'center': [0, 0],
'zoom': 1,
// Add here so it still gets added to the map initially
// You could skip this so it won't be added and you can
// turn it on via the layercontrol
'layers': [tileLayer]
});
// Create layer control
var layerControl = L.control.layers(null, {
'Tilelayer': tileLayer // Add tile layer to overlays
}).addTo(map);
Here's a working example on Plunker: http://plnkr.co/edit/5re3o6qnyCwAqXNYXrkP?p=preview
L.mapbox.tileLayer reference: https://www.mapbox.com/mapbox.js/api/v2.1.5/l-mapbox-tilelayer/
L.control.layers reference: http://leafletjs.com/reference.html#control-layers
Edit because of the comments:
You're already working with separate layers, the tilelayer (background) gets added upon map initalization L.mapbox.map('map', 'zetter.i73ka9hn') that in fact calls: L.mapbox.tileLayer('zetter.i73ka9hn').addTo(map). You'll need to do it that way because you'll need a reference to the layer so you can add it to L.control.layers like shown above. Your voronoi layer gets added in the voronoiMap method in the ready handler of the map: map.addLayer(mapLayer);
Thus as you can see they are already separated. Now if you also want to be able to toggle the voronoi layer in your layer control you'll need to add it to the layer control:
map.on('ready', function() {
d3.csv(url, function(csv) {
points = csv;
points.forEach(function(point) {
pointTypes.set(point.type, {
type: point.type,
color: point.color
});
});
map.addLayer(mapLayer);
layerControl.addOverlay(mapLayer, 'Voronoi'); // Here
})
});
But that in itself is not enough in your case because your layer doesn't have a onRemove method as prescribed by the ILayer interface:
http://leafletjs.com/reference.html#ilayer
Now if we add a onRemove method to your layer like this:
var mapLayer = {
onAdd: function(map) {
map.on('viewreset moveend', drawWithLoading);
drawWithLoading();
},
onRemove: function (map) {
d3.select('#overlay').remove();
}
};
It works: http://plnkr.co/edit/3z3pCAo0gGuA7xqnqiqb?p=preview (note i've commented out the ready handler because the map is ready before the function call so it wouldn't fire and changed some colors to make things more clear.) Hope this helps.
I am trying to create a map where the user can select a new projection and it'll be applied on the fly.
here is what I did (you can find a demo here)
<!DOCTYPE html>
<html>
<head>
<meta content="utf-8">
<style type="text/css">
svg {
border: 1px solid #ccc;
}
path {
fill: #ccc;
stroke: #fff;
stroke-width: .5px;
}
path:hover {
fill: #00AEEC;
}
#projectionList {
width: 200px;
height: 500px;
float: left;
border: 1px solid grey;
}
</style>
</head>
<body>
<div id="mainContainer">
<div id="projectionList">
</div>
<div id="graphContainer">
</div>
</div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script type="text/javascript">
// d3.js graph
var width = 900,
height = 500;
var path = d3.geo.path();
var svg = d3.select("#graphContainer").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("tunisia.json", function(error, topology) {
console.log(topology);
//console.clear();
var featureCollection = topojson.feature(topology, topology.objects.governorates);
var bounds = d3.geo.bounds(featureCollection);
var centerX = d3.sum(bounds, function(d) {return d[0];}) / 2,
centerY = d3.sum(bounds, function(d) {return d[1];}) / 2;
var projection = d3.geo.mercator()
.scale(3000)
.center([centerX, centerY]);
path.projection(projection);
svg.selectAll("path")
.data(featureCollection.features)
.enter().append("path")
.attr("d", path);
});
//end of d3.js graph
var selectProjection = {projection_list: []};
// add the list of supported projection
/*schema of objects:
{
id: "unique_int",
projection_name: "a name",
projection_function: "a function name",
projection_description: "a description"
}
*/
//add first projection
selectProjection.projection_list.push({
id: 1,
projection_name: "azimuthalEqualArea", // to be updated with a more descriptive name
projection_function: "azimuthalEqualArea",
projection_description: "azimuthalEqualArea" // to be updated with more detailed description
});
// add second projection
selectProjection.projection_list.push({
id: 2,
projection_name: "conicConformal", // to be updated with a more descriptive name
projection_function: "conicConformal",
projection_description: "conicConformal" // to be updated with more detailed description
});
// add thrid projection
selectProjection.projection_list.push({
id: 3,
projection_name: "equirectangular", // to be updated with a more descriptive name
projection_function: "equirectangular",
projection_description: "equirectangular" // to be updated with more detailed description
});
selectProjection.projection_list.push({
id: 4,
projection_name: "Mercator", // to be updated with a more descriptive name
projection_function: "mercator",
projection_description: "mercator" // to be updated with more detailed description
});
// the above storage method is used temporary in order to leverage a demo, in production mode, there will be a database table to hold these information
function set_projection(option) {
proj = option.value
projection = eval("d3.geo."+proj+"().scale(500);");
path = d3.geo.path()
.projection(projection);
svg.selectAll("path").transition()
.duration(1000)
.attr("d", path);
/*var link = "https://github.com/mbostock/d3/wiki/Geo-Projections#wiki-" + proj
d3.selectAll("div#current_proj_info").html("Current projection: "+option.text+"");*/
}
function apply_projection(e){
// applying a new projection
set_projection(e.target[e.target.selectedIndex]);
}
var newSelect = document.createElement("select");
newSelect.id = "selectProjection";
newSelect.onchange = apply_projection;
for (var i = selectProjection.projection_list.length - 1; i >= 0; i--) {
console.log(selectProjection.projection_list[i].projection_name);
newSelect[newSelect.length] = new Option(selectProjection.projection_list[i].projection_name, selectProjection.projection_list[i].projection_function, false, false);
};
console.log("newSelect content");
console.log(newSelect);
document.getElementById('projectionList').appendChild(newSelect);
</script>
</body>
</html>
the issue is that when I try to scale graph after applying the new projection, it gets outside of the visible area of the div.
Could you please check what's the issue.