Trouble appending d3 vectors to leaflet map - javascript

I'm trying to get points to overlay on a leaflet map.
I create the Map element in html, build the leaflet map, read in the data.
Here's where I'm getting tripped up. I'd like to display points on the map - I've already successfully displayed these points on a d3 map, but I want to re-display them on the above leaflet map. Rather then extract lat/longs, as I've seen in d3 + leaflet examples, I thought I'd just use the path generator function which I've used successfully before, in order to append points to leaflet.
Code sequence here:
<div id="map" class="sf" style="width: 600px; height: 400px"></div>
function ready(error) {
//Build Leaflet map
L.mapbox.accessToken =
'pk.eyJ1Ijoic3RhcnJtb3NzMSIsImEiOiJjaXFheXZ6ejkwMzdyZmxtNmUzcWFlbnNjIn0.IoKwNIJXoLuMHPuUXsXeug';
var mapboxUrl = 'https://api.mapbox.com/styles/v1/mapbox/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}';
//var accessToken = 'pk.eyJ1Ijoic3RhcnJtb3NzMSIsImEiOiJjam13ZHlxbXgwdncwM3FvMnJjeGVubjI5In0.-ridMV6bkkyNhbPfMJhVzw';
var map = L.map('map').setView([37.7701177, -122.40], 13);
mapLink =
'OpenStreetMap';
L.tileLayer(
'https://api.mapbox.com/styles/v1/mapbox/dark-v9/tiles/{z}/{x}/{y}?access_token=' + L.mapbox.accessToken, {
tileSize: 512,
zoomOffset: -1,
attribution: '© Mapbox © OpenStreetMap'
}).addTo(map);
// Read in the json data
d3.json("data/SanFrancisco_CoWorkingTenants.json",
function(SFData) {
var SFData = SFCoworking.features
console.log(SFData) // this prints successfully
})
var mapSVG = d3.select( "#map").select("svg")
mapG = mapSVG.append("g");
// Define d3 projection
var albersProjectionBay = d3.geoAlbers()
.scale( 282000)
.rotate ( [122.4077441,] )
.center( [0, 37.7701177] )
// Define d3 path
var geoPathBayArea = d3.geoPath()
.projection( albersProjectionBay );
var SFData = SFCoworking.features
console.log(SFData)
// draw points on map with d3 path generator
var feature = mapG.selectAll("path")
.data(SFCoworking.features)
.enter().append("path")
.style("stroke", "black")
.style("opacity", .6)
.style("fill", "red")
.attr("r", 20)
.attr( "d", geoPathBayArea )
console.log(feature) // nothing prints!
}
While the SFdata appears in the console, the features, when printed to the console, just appear as an empty array. This leads me to believe there might be an issue in how I'm appending the svg element to the map?

Thanks all - this seemed to work.
var svgLayer = L.svg();
svgLayer.addTo(map);
var svgMap = d3.select("#map").select("svg");
var g = svgMap.select('g');
d3.json("data/SanFrancisco_CoWorkingTenants.json", function(SFData) {
var SFData = SFCoworking.features
console.log(SFData)
})
SFData.forEach(function(d) {
d.latLong = new L.LatLng(d.properties.Latitude,
d.properties.Longitude);
//console.log(d.LatLng)
})
var feature = g.selectAll("circle")
.data(SFData)
.enter().append("circle")
.style("stroke", "black")
.style("opacity", .4)
.style("fill", "red")
.attr("r", 20)
.attr("class", 'features')

Related

How can I define map extent based on points displayed?

I have the following map I've made, by overlaying points on a mapbox map using d3.js.
I'm trying to get the map to zoom so that the map extent just includes the d3 markers (points).
I think the pseudocode would look something like this:
//find the northernmost, easternmost, southernmost, westernmost points in the data
//get some sort of bounding box?
//make map extent the bounding box?
Existing code:
<div id="map"></div>
<script>
mapboxgl.accessToken = "YOUR_TOKEN";
var map = new mapboxgl.Map({
container: "map",
style: "mapbox://styles/mapbox/streets-v9",
center: [-74.5, 40.0],
zoom: 9
});
var container = map.getCanvasContainer();
var svg = d3
.select(container)
.append("svg")
.attr("width", "100%")
.attr("height", "500")
.style("position", "absolute")
.style("z-index", 2);
function project(d) {
return map.project(new mapboxgl.LngLat(d[0], d[1]));
}
#Lat, long, and value
var data = [
[-74.5, 40.05, 23],
[-74.45, 40.0, 56],
[-74.55, 40.0, 1],
[-74.85, 40.0, 500],
];
var dots = svg
.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("r", 20)
.style("fill", "#ff0000");
function render() {
dots
.attr("cx", function (d) {
return project(d).x;
})
.attr("cy", function (d) {
return project(d).y;
});
}
map.on("viewreset", render);
map.on("move", render);
map.on("moveend", render);
render(); // Call once to render
</script>
Update
I found this code for reference, linked here at https://data-map-d3.readthedocs.io/en/latest/steps/step_03.html:
function calculateScaleCenter(features) {
// Get the bounding box of the paths (in pixels!) and calculate a
// scale factor based on the size of the bounding box and the map
// size.
var bbox_path = path.bounds(features),
scale = 0.95 / Math.max(
(bbox_path[1][0] - bbox_path[0][0]) / width,
(bbox_path[1][1] - bbox_path[0][1]) / height
);
// Get the bounding box of the features (in map units!) and use it
// to calculate the center of the features.
var bbox_feature = d3.geo.bounds(features),
center = [
(bbox_feature[1][0] + bbox_feature[0][0]) / 2,
(bbox_feature[1][1] + bbox_feature[0][1]) / 2];
return {
'scale': scale,
'center': center
};
}
However, when I run the function:
var scaleCenter = calculateScaleCenter(data);
console.log("scalecenter is", scaleCenter)
I get the error:
path is not defined
Furthermore, it seems like I would need to dynamically adjust the center and zoom parameters of the mapbox map. Would I just set these values dynamically with the values produced by the calculateScaleCenter function?
The readthedocs example code is erroneously missing a bit of code
Your javascript code is reporting that you have not defined the variable path before using it. You have copied it correctly from readthedocs, but the code you are copying contains an omission.
Fortunately, on the readthedocs version of the code snippet, there is a mention of a Stack Overflow answer http://stackoverflow.com/a/17067379/841644
that gives more information.
Does adding this information, or something similar corresponding to your situation, help fix your problem?
var projection = d3.geo.mercator()
.scale(1);
var path = d3.geo.path()
.projection(projection);

Error displaying geotiff raster data on leaflet map

I hope someone can help me. I am trying to place isolines with labels from a geotiff file onto a leaflet map. The webpage https://geoexamples.com/d3-raster-tools-docs/plot/isolines.html is the example I am looking at, but the problem is that they perform this task on a non-moving leaflet map (no zoom feature). I have found the page https://bost.ocks.org/mike/leaflet/ where he places JSON data over a leaflet map, but does not cover how to transform geotiff data.
I can get the leaflet map to work and zoomable the JSON data remaps correctly but the geotiff data, while it displays, is not remapping correctly in my code and I don't quite understand how to make the isolines resize correctly. I thought it was taken care of with the svg.insert command but it doesn't. I know I am missing a step somewhere but I'm not sure where.
It seems like I am trying to do two things that are not quite explained anywhere combined. I eventually want to use the streamline code, too, which is why I am not using the XML code in the simple isolines code (and I need the labels). I got that version to work fine. I have also tried adding a canvas layer but that didn't work either, but perhaps I did something wrong with that.
I would greatly appreciate any suggestions on this. I think I have provided the necessary code and links to the files.
https://geoexamples.com/d3-raster-tools-docs/code_samples/tz850.tiff
https://geoexamples.com/d3-raster-tools-docs/code_samples/world-110m.json
var map = L.map('map').setView([-0.2858, 60.7868], 3);
mapLink =
'OpenStreetMap';
// Add an SVG element to Leaflet’s overlay pane
var svg = d3.select(map.getPanes().overlayPane).append("svg"),
g = svg.append("g").attr("class", "leaflet-zoom-hide");
d3.request("http://geoexamples.com/d3-raster-tools-docs/code_samples/tz850.tiff")
.responseType('arraybuffer')
.get(function(tiffData) {
d3.json("https://raw.githubusercontent.com/datasets/geo-countries/master/data/countries.geojson", function(geoShape) {
// create a d3.geo.path to convert GeoJSON to SVG
var transform = d3.geo.transform({
point: projectPoint
}),
path = d3.geo.path().projection(transform);
// create path elements for each of the features
d3_features = g.selectAll("path")
.data(geoShape.features)
.enter().append("path");
map.on("viewreset", reset);
reset();
// fit the SVG element to leaflet's map layer
function reset() {
bounds = path.bounds(geoShape);
var topLeft = bounds[0],
bottomRight = bounds[1];
svg.attr("width", bottomRight[0] - topLeft[0])
.attr("height", bottomRight[1] - topLeft[1])
.style("left", topLeft[0] + "px")
.style("top", topLeft[1] + "px");
g.attr("transform", "translate(" + -topLeft[0] + "," +
-topLeft[1] + ")");
// initialize the path data
d3_features.attr("d", path)
.style("fill-opacity", 0.7)
.attr('fill', 'blue');
}
// Use Leaflet to implement a D3 geometric transformation.
function projectPoint(x, y) {
var point = map.latLngToLayerPoint(new L.LatLng(y, x));
this.stream.point(point.x, point.y);
}
var tiff = GeoTIFF.parse(tiffData.response);
var image = tiff.getImage();
var rasters = image.readRasters();
var tiepoint = image.getTiePoints()[0];
var pixelScale = image.getFileDirectory().ModelPixelScale;
var geoTransform = [tiepoint.x, pixelScale[0], 0, tiepoint.y, 0, -1 * pixelScale[1]];
var zData = new Array(image.getHeight());
for (var j = 0; j < image.getHeight(); j++) {
zData[j] = new Array(image.getWidth());
for (var i = 0; i < image.getWidth(); i++) {
zData[j][i] = rasters[0][i + j * image.getWidth()];
}
}
var invGeoTransform = [-geoTransform[0] / geoTransform[1], 1 / geoTransform[1], 0, -geoTransform[3] / geoTransform[5], 0, 1 / geoTransform[5]];
var intervalsZ = [1400, 1420, 1440, 1460, 1480, 1500, 1520, 1540];
var linesZ = rastertools.isolines(zData, invGeoTransform, intervalsZ);
var colorScale = d3.scaleSequential(d3.interpolateYlOrRd)
.domain([1400, 1540]);
linesZ.features.forEach(function(d, i) {
svg.insert("path", ".streamline")
.datum(d)
.attr("d", path)
.style("stroke", colorScale(intervalsZ[i]))
.style("stroke-width", "2px")
.style("fill", "None");
});
})
})
<link rel="stylesheet" href="https://d19vzq90twjlae.cloudfront.net/leaflet-0.7/leaflet.css" />
<div id="map" style="width: 600px; height: 400px"></div>
<script src="http://bl.ocks.org/rveciana/raw/bef48021e38a77a520109d2088bff9eb/98a0b74b01109afae76d28bc27c4d1dbc7a87da8/geotiff.min.js"></script>
<script src="http://bl.ocks.org/rveciana/raw/bef48021e38a77a520109d2088bff9eb/98a0b74b01109afae76d28bc27c4d1dbc7a87da8/d3-marching-squares.min.js"></script>
<script src="http://bl.ocks.org/rveciana/raw/bef48021e38a77a520109d2088bff9eb/98a0b74b01109afae76d28bc27c4d1dbc7a87da8/path-properties.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script src="https://d19vzq90twjlae.cloudfront.net/leaflet-0.7/leaflet.js">
</script>

d3-attrTween with custom function.(What did I misunderstand about tween function?)

I have a question about attrTween (sometimes tween()).
I understood custom tween function as
after " attrTween('d' " argument,
I define the custom function.
So, I wrote the custom function as below.
d3.selectAll('circle#circles1')
.transition()
.attrTween('d',function(){
let interpolator=d3.interpolateArray(sdata.vader,sdata1.vader);
return function(t){
return d3.select(this).attr('cy',interpolator(t))
}
})
What I intended is
For All the circles I drew, makes a transition. The transition
is attrTween. The changes is based on data array tied into the
circles. Original data array is sdata and the cy value in the
sdata is sdata.vader. And the transition is heading toward
sdata1.and cy value for sdata1 is sdata1.vader.
To access all the cy value for every single circle, I used
d3.select(this).attr('cy')
However, no error message is shown but no animation was made either.
What did I misunderstand for the custom tween function?
Can anyone help me to fix this code?
Thank you inadvance.
Full code is in the following link.
https://codepen.io/jotnajoa/pen/WNQeEBE
There are multiple problems in the example code, which is not minimal. Providing a minimal, reproducible example would really help solve the problems.
usage of HTML Id to multiple elements.
In HTML, and id attribute must be unique. Here, ids are assigned to groups of circles. A class attribute should be used for this purpose, not an id.
.attr('id','circles1')
should be:
.attr('class','circles1')
Accordingly, the attrTween should lookup the circles with class circle1, rather than the unique circle with id #circle1
d3.selectAll('circle#circles1')
should be
d3.selectAll('.circles1')
Id (or class) assigned in the wrong place.
The circles1 class is assigned before the creation of the circle, hence the instructions applies to an empty selection. The class attribute should be set right after circles have been created.
.attr('id','circles1')
.enter()
.append('circle')
should be
.enter()
.append('circle')
.attr('class','circles1')
Wrong attribute tweened
The attribute to transition is the circle's cy attribute, not a path's d attribute. Hence
.attrTween('d',function(){
should be
.attrTween('cy',function(){
Wrong data interpolated
sdata.vader and sdata1.vader do not exist, sdata and sdata1 seem to be arrays of objects, which in turn do have a vader property.
You probably want d.vader, and the corresponding .vader in sdata1, which would be sdata1[i].vader, in case items are the same order in both arrays.
Interpolating original measures instead of coordinates.
cy is originally defined as:
height-yscale(d.vader)
In the interpolator function, the scale function should also be used.
The attrTween function calls becomes:
.attrTween('cy',function(d, i){
//console.log( i, height-yscale(d.vader), height-yscale(sdata1[i].vader))
let interpolator=d3.interpolateArray(height-yscale(d.vader), height-yscale(sdata1[i].vader));
return function(t) { return interpolator(t)}
})
Using attrTween where not needed.
Simply transitioning the circles with attr is sufficient for this use case, there is no need to define an interpolator.
d3 will move the position of circles from the original position to the destination, interpolating implicitly.
d3.selectAll('.circles1')
.transition()
.duration(2000)
.attr('cy',function(d, i){
return height-yscale(sdata1[i].vader)
})
I added a long duration for demo purpose, to make obvious that the circles move to the correct location. Once in their final position, they disappear, because they are under the pink circles.
P.S. Same set of corrections is applicable to circles2 set whenever relevant.
Demo of the solution in the snippet below, as codepen does not allow to save modifications without creating an account.
var svg;
var xscale;
var yscale;
var sdata;
var xAxis;
var yAxis;
var width=1500;
var height=500;
var margin=50;
var duration =250;
var vader ='vader'
var textblob='textblob'
var delay =5000;
var tbtrue=false;
var areas
var circles1,circles2;
var sdata1,sdata2
d3.csv('https://raw.githubusercontent.com/jotnajoa/Javascript/master/tweetdata.csv').then(function(data){
svg=d3.select('body').append('svg').attr('width',width).attr('height',height)
var parser = d3.timeParse("%m/%d/%y")
// data를 처리했고, date parser 하는 법 다시한번 명심하자.
sdata = data;
sdata.forEach(function(d){
d.vader = +d.vader;
d.textblob= + d.textblob;
d.date=parser(d.date)
})
// scale을 정해야 함. 나중에 brushable한 범위로 고쳐야함. nice()안하면 정렬도안되고, 첫번째 엔트리 미싱이고
// 난리도 아님.
xscale=d3.scaleTime()
.domain(d3.extent(sdata, function(d) {return d.date }))
.range([0,width*9/10])
.nice()
yscale =d3.scaleLinear()
.domain(d3.extent([-1,1]))
.range([height*4/5,height*1/5])
.nice()
//yaxis는 필요 없을 것 같은데.
//캔버스에 축을 그려야 함 단, translate해서 중간에 걸치게 해야함.
svg.append('g').attr('class','xaxis')
.call(d3.axisBottom(xscale))
.attr('transform','translate('+margin+','+height*1/2+')')
//sdata plotting
var circles = svg.append('g').attr('class','circles')
var area = svg.append('g').attr('class','pathline')
firststage();
//generator로 데이터를 하나씩 떨어뜨리도록 한다.
function firststage(){
function* vaderdropping(data){
for( let i=0;i<data.length;i++){
if( i%50==0) yield svg.node();
let cx = margin+xscale(data[i].date)
let cy = height-yscale(data[i].vader)
circles.append('circle')
.attr('cx',cx)
.attr('cy',0)
.transition()
.duration(duration)
.ease(d3.easeBounce)
.attr('cy',cy)
.attr('r',3)
.style('fill','rgba(230, 99, 99, 0.528)')
}
yield svg.node()
}
//generator 돌리는 부분
let vadergen = vaderdropping(sdata);
let result = vadergen.next()
let interval = setInterval(function(){
if(!result.done) {
vadergen.next();
}
else {
clearInterval(interval)
}
}, 100);
setTimeout(secondstage,5000)
}
function secondstage(){
function* textblobdropping(data){
for( let i=0;i<data.length;i++){
if( i%50==0) yield svg.node();
let cx = margin+xscale(data[i].date)
let cy = height-yscale(data[i].textblob)
circles.append('circle')
.attr('cx',cx)
.attr('cy',0)
.transition()
.duration(duration)
.ease(d3.easeBounce)
.attr('cy',cy)
.attr('r',3)
.style('fill','rgba(112, 99, 230, 0.528)')
}
yield svg.node()
}
//generator 돌리는 부분
let textblobgen = textblobdropping(sdata);
let tresult = textblobgen.next()
let tinterval = setInterval(function(){
if(!tresult.done) {
textblobgen.next();
}
else {
clearInterval(tinterval)
}
}, 100);
setTimeout(thirdstage,2500)
}
function thirdstage(){
//진동을 만들기 위해서,
//베이다와 텍스트 블랍 값을 플립한거다 (제발 워크 아웃하길...)
//그 다음 트윈으로 sdata 와 sdata1을 왔다갔다 하게하면 되지않을까?
sdata1 = sdata.map(function(x){
var y={};
y['date']=x.date;
y['vader']=x.textblob;
y['textblob']=x.vader;
return y});
sdata2 = sdata.map(function(x){
var y={};
y['date']=x.date;
y['vader']=0;
y['textblob']=0;
return y});
d3.selectAll('circle').transition()
.duration(3500)
.style('fill','rgba(1, 1, 1, 0.228)')
//areas는 일종의 함수다, 에리아에다가 데이터를 먹이면,
//에리아를 그리는 역할을 하는것임.
areas = d3.area()
.x(function(d){return margin+xscale(d.date)})
.y0(function(d){return height-yscale(d.vader)})
.y1(function(d){return height-yscale(d.textblob)})
.curve(d3.curveCardinal)
//이렇게 하지말고, sdata2도 만들었으니까 2->1->0 반복하는
// 무한반복 on('end','repeat') loop를 만들어보자.
var uarea=area.append('path')
setTimeout(repeat,500)
function repeat(){
uarea
.style('fill','rgba(112, 99, 230, 0.4)')
.attr('d', areas(sdata))
.transition()
.duration(500)
.attrTween('d',function(){
var interpolator=d3.interpolateArray(sdata,sdata1);
return function(t){
return areas(interpolator(t))
}
})
.transition()
.duration(500)
.attrTween('d',function(){
var interpolator=d3.interpolateArray(sdata1,sdata2);
return function(t){
return areas(interpolator(t))
}
})
.transition()
.duration(500)
.attrTween('d',function(){
var interpolator=d3.interpolateArray(sdata2,sdata);
return function(t){
return areas(interpolator(t))
}
})
.on('end',repeat)
}
setTimeout(fourthstage,500)
}
function fourthstage(){
// console.log(d3.selectAll('circle#circles1').node())
circles1=svg.append('g').selectAll('circle').data(sdata)
.enter().append('circle').attr('class','circles1')
.attr('cx',function(d){return margin+xscale(d.date)})
.attr('cy',function(d){return height-yscale(d.vader)})
.style('fill','green')
.attr('r',3)
circles2=svg.append('g').selectAll('circle').data(sdata)
.enter().append('circle').attr('class','circles2')
.attr('cx',function(d){return margin+xscale(d.date)})
.attr('cy',function(d){return height-yscale(d.textblob)})
.style('fill','pink')
.attr('r',3)
d3.selectAll('.circles1')
.transition()
.duration(5000)
.attr('cy',function(d, i){
return height-yscale(sdata1[i].vader)
})
// d3.selectAll('circle#circles2')
// .transition()
// .attr('cy',function(d){return 0})
//tween 팩토리를 정의해야한다.
//주의사항, 리턴을 갖는 함수여야한다는 것.
//왜 꼭 return function(){}을 해야하나?
/*
function movey(d2){
let y1 = this.attr('cy')
let y2 = d2.vader
let interpolate=d3.interpolate(y1,y2);
interpolate;
} 하면 안되나??
*/
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

d3js v5 + Topojson v3 Show/Hide element on click legend

I would like this reverse action http://bl.ocks.org/d3noob/5d621a60e2d1d02086bf. When I click on an item of my legend, I want focus all elements of the map within these class.
However, how isolate just one class compared to others?
For example in this image, just show elements where the value is included between 23500 and 29000 and hide other elements.
I suggest to 1/ filter data and 2/ fill with the same color these elements but surely it's easier.
Here is my code : https://plnkr.co/edit/ga82Syjc8zxTdAxTVNXu?p=preview
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://unpkg.com/d3#5.0.0/dist/d3.min.js"></script>
<script src="https://unpkg.com/topojson#3.0.0/dist/topojson.min.js"></script>
</head>
<svg class="carte_evolution">
<style>
</style>
<body>
<script>
let w = 600;
let h = 600;
let view = [0,0,600,600];
//Dimension
let svgCarteEvolution = d3.select(".carte_evolution")
.attr("width", "100%")
.attr("height", h)
.attr("preserveAspectRatio","xMidYMid meet")
.attr("viewBox", `${view[0]},${view[1]},${view[2]},${view[3]}`);
//Map projection
choro = d3.map();
projection = d3.geoConicConformal()
.center([1.282743, 46.328377])
.scale(2600)
.translate([w / 2, h / 2]);
path = d3.geoPath()
.projection(projection);
//Load csv and topojson
promises1 = d3.json("ze_WGS84_UTF8.topojson");
promises2 = d3.csv("data.csv");
Promise.all([promises1, promises2]).then(function(fr){
//Join csv + topojson
let featureCollection = topojson.feature(fr[0],fr[0].objects.ze_WGS84_UTF8)
for (var i=0; i< fr[1].length;i++){
var csvId = fr[1][i].codgeo;
var csvValue0 = parseFloat(fr[1][i].value0);
var csvYear0 = fr[1][i].year0;
for (var j=0; j<featureCollection.features.length;j++){
var jsonId = featureCollection.features[j].properties.codgeo;
if (csvId === jsonId) {
featureCollection.features[j].properties.value0 = csvValue0;
featureCollection.features[j].properties.year0 = csvYear0;
break;
}
}
}
let color = d3.scaleQuantile()
.range(["#d1e0c9","#b3cea8","#8fb983","#539f53","#008d36"]);
color.domain([
12000,17500,19500,21500,23500,29000
]);
//Map
svgCarteEvolution.append("g")
.selectAll("path")
.data(featureCollection.features)
.enter()
.append("path")
.attr("class", "dep")
.attr("d", path)
.style("fill", function(d){
var value = d.properties["value0"];
return value? color(value):"#ccc";
});
//legend
let legend = svgCarteEvolution.selectAll("g.legend_entree")
.data(color.range().reverse())
.enter()
.append("g")
.attr("class","legend_entree");
legend
.append('rect')
.attr("x", 30)
.attr("y", function(d, i) {
return 65 + i * 20;
})
.attr("width", 20)
.attr("height", 10)
.style("stroke", "black")
.style("stroke-width", 0.1)
.style("fill", function(d){return d;});
//the data objects are the fill colors
legend
.append('text')
.attr("x", 55) //leave 5 pixel space after the <rect>
.attr("y", function(d, i) {
return 65 + i * 20;
})
.attr("dy", "0.8em") //place text one line *below* the x,y point
.style("font-family","latomedium")
.style("font-size","0.9em")
.text(function(d,i) {
var extent = color.invertExtent(d);
//extent will be a two-element array, format it however you want:
var format = d3.format("");
return `${format(+extent[0])} - ${format(+extent[1])}`
});
});
</script>
</body>
</html>
data.csv
"codgeo","year0","value0","year1","value1"
"0050",2012,19162,2014,19698.8
"0051",2012,18501.125,2014,19125.5
"0052",2012,18684.6666666667,2014,19454
"0053",2012,19826,2014,20573.9
"0054",2012,18881.1111111111,2014,19513
"0055",2012,17942.5,2014,18657.3
"0056",2012,19299.0476190476,2014,19703.9
"0057",2012,18873.8095238095,2014,19539.3
"0059",2012,18199,2014,18719
"0060",2012,18921,2014,19563.2
"0061",2012,21259.5238095238,2014,21748.3
"0101",2012,,2014,
"0102",2012,,2014,
"0103",2012,,2014,
"0106",2012,,2014,
"0201",2012,,2014,17250
"0202",2012,,2014,17246.7
"0203",2012,,2014,14972
"0204",2012,,2014,14612.6
"0205",2012,,2014,15617.3
"0206",2012,,2014,13369
"0301",2012,,2014,
"0302",2012,,2014,
"0303",2012,,2014,
"0401",2012,,2014,12522.3
"0402",2012,,2014,15483.8
"0403",2012,,2014,14344.6
"0404",2012,,2014,13134
"0601",2012,,2014,
"1101",2012,23144.7619047619,2014,23464
"1102",2012,22223.5,2014,22729.1
"1103",2012,20637.2222222222,2014,21088
"1104",2012,20402,2014,20901
"1105",2012,21782.7777777778,2014,22274.4
"1106",2012,18304.375,2014,18918
"1107",2012,20038.6666666667,2014,20406.5
"1108",2012,19493,2014,19936.5
"1109",2012,28156.8901098901,2014,28660
"1110",2012,20294.8,2014,20639.5
"1111",2012,22490.6666666667,2014,22812.4
"1112",2012,25996.6666666667,2014,26503.5
"1113",2012,25301.7391304348,2014,25639.6
"1114",2012,21184,2014,21611.9
"1115",2012,20586,2014,20954
"1116",2012,25449.5,2014,25742.4
"1117",2012,22279.3333333333,2014,22665
"1118",2012,19040,2014,19307.2
"1119",2012,22059.0909090909,2014,22519
"2101",2012,17416,2014,18133.9
"2102",2012,18721,2014,19275.4
"2103",2012,19554.7826086957,2014,20204
"2104",2012,20352.8571428571,2014,20892.8
"2105",2012,19630.5555555556,2014,20157.6
"2106",2012,18689,2014,19445.2
"2107",2012,17676.6666666667,2014,18309.1
"2201",2012,19209.5238095238,2014,19791
"2202",2012,17090,2014,17820
"2203",2012,16258.6666666667,2014,17014
"2204",2012,17987.619047619,2014,18675.2
"2205",2012,17320.8333333333,2014,18056
"2206",2012,18163.2,2014,18851
"2207",2012,19732.1428571429,2014,20284.7
"2208",2012,19973.3333333333,2014,20690
"2209",2012,17625,2014,18402
"2210",2012,18778.0952380952,2014,19447.6
"2211",2012,17420,2014,18158.8
"2301",2012,18978.8461538462,2014,19609.3
"2302",2012,19920,2014,20419
"2303",2012,18947,2014,19704.7
"2304",2012,20011.7391304348,2014,20639
"2305",2012,18633.4166666667,2014,19348.6
"2306",2012,19222.6666666667,2014,19937.6
"2307",2012,19716,2014,20349.3
"2401",2012,19496,2014,20116.7
"2402",2012,17587.3913043478,2014,18264.3
"2403",2012,18039.3333333333,2014,18668.9
"2404",2012,20862,2014,21386.8
"2405",2012,19088,2014,19617.5
"2406",2012,19682,2014,20246
"2407",2012,17765,2014,18244
"2408",2012,18813.3333333333,2014,19590.5
"2409",2012,18460.8695652174,2014,19134
"2410",2012,19341.3333333333,2014,20286.2
"2411",2012,18503.5,2014,19170
"2412",2012,20074.7619047619,2014,20669
"2413",2012,19886,2014,20426.5
"2414",2012,18549,2014,19111.7
"2415",2012,19298.0952380952,2014,19929
"2416",2012,19159.5833333333,2014,19763.3
"2417",2012,19013.3333333333,2014,19521.9
"2418",2012,20852,2014,21314
"2419",2012,19696.8,2014,20135.4
"2501",2012,18832.9166666667,2014,19616.7
"2502",2012,19839.6428571429,2014,20447.3
"2503",2012,19357,2014,19949.3
"2504",2012,17890,2014,18668.5
"2505",2012,17836.1904761905,2014,18619.5
"2506",2012,18373.0434782609,2014,19247.3
"2507",2012,18855,2014,19788.7
"2508",2012,18192.0833333333,2014,19076
"2509",2012,19105,2014,19896.6
"2510",2012,18837.2222222222,2014,19621.4
"2511",2012,18071.5,2014,18835
"2512",2012,18059.5,2014,18796.7
"2513",2012,17989.5652173913,2014,18533.5
"2601",2012,20356,2014,21106.7
"2602",2012,18218.4,2014,18926.7
"2603",2012,20719.6666666667,2014,21264.4
"2604",2012,18828.3333333333,2014,19445.7
"2605",2012,17496,2014,18232
"2606",2012,18618.3333333333,2014,19419.2
"2607",2012,18150,2014,18717
"2608",2012,19290,2014,20024
"2609",2012,18376,2014,19020
"2610",2012,18123.5,2014,18997.2
"2611",2012,18232,2014,19093.1
"2612",2012,19023.3962264151,2014,19581.7
"2613",2012,18402.2222222222,2014,19055
"2614",2012,19352.6666666667,2014,19970
"3110",2012,17632,2014,18171.3
"3111",2012,19872.3214285714,2014,20498.6
"3112",2012,17909.5,2014,18786
"3113",2012,19370.8695652174,2014,20029
"3114",2012,17411.6666666667,2014,18153.1
"3115",2012,16527.5,2014,17161.7
"3116",2012,17494,2014,18124.5
"3117",2012,16522.5,2014,17252.5
"3121",2012,18958.5714285714,2014,19749.5
"3122",2012,15848,2014,16471.3
"3123",2012,17325,2014,18070
"3124",2012,17319,2014,18080.7
"3125",2012,16943,2014,17776.5
"3126",2012,17054,2014,17783.8
"3127",2012,17860.6666666667,2014,18527.8
"4101",2012,19057,2014,20396.5
"4102",2012,18167.3913043478,2014,18923.8
"4103",2012,20100,2014,20734.3
"4104",2012,19253.4615384615,2014,19914.7
"4105",2012,18178,2014,18899.6
"4106",2012,18415.5555555556,2014,19180
"4107",2012,19765.3333333333,2014,20611.4
"4108",2012,17936.3043478261,2014,18670.5
"4109",2012,19220,2014,19884
"4110",2012,19699,2014,20456
"4111",2012,19975.5555555556,2014,21490
"4112",2012,18561.9047619048,2014,19239.6
"4113",2012,18533,2014,19207
"4114",2012,17932,2014,18625
"4115",2012,18513.6,2014,19304
"4201",2012,21662,2014,22259.4
"4202",2012,22121.4285714286,2014,22678
"4203",2012,20694.9743589744,2014,21249.2
"4204",2012,20996.6666666667,2014,21554
"4205",2012,20637.2,2014,20939.5
"4206",2012,22284.4,2014,22998
"4207",2012,21402,2014,21892
"4208",2012,20530.4761904762,2014,20987.7
"4209",2012,27116,2014,27578
"4301",2012,19866.7857142857,2014,20474.7
"4302",2012,19037.6923076923,2014,19688.7
"4303",2012,26342.380952381,2014,26900.4
"4304",2012,23223.3333333333,2014,24219
"4305",2012,19166.4516129032,2014,19840
"4306",2012,19092.2222222222,2014,19879.5
"4307",2012,20652,2014,21500.8
"4308",2012,18128,2014,18743
"4309",2012,18185,2014,18844.7
"5201",2012,18881.4814814815,2014,19667.6
"5202",2012,17665.6,2014,18286
"5203",2012,20616,2014,21241
"5204",2012,19844.8,2014,20508
"5205",2012,19443,2014,20094.4
"5206",2012,18905.7142857143,2014,19605
"5207",2012,17964.6666666667,2014,18666
"5208",2012,18107.3333333333,2014,18734.4
"5209",2012,18951.7857142857,2014,19703.8
"5210",2012,18635.7142857143,2014,19332
"5211",2012,18973.2258064516,2014,19556.5
"5212",2012,18178.6666666667,2014,18824
"5213",2012,19304.6666666667,2014,19966.7
"5214",2012,18363.3333333333,2014,19098.9
"5215",2012,19146.6666666667,2014,19786.7
"5216",2012,18103.3333333333,2014,18876.7
"5217",2012,18984,2014,19686
"5218",2012,19186.1904761905,2014,19813.8
"5219",2012,20042.380952381,2014,20712.9
"5301",2012,19939.2,2014,20655.3
"5302",2012,18221.4285714286,2014,18960
"5303",2012,20016,2014,20691.3
"5304",2012,18641.5,2014,19282.2
"5305",2012,19581.3333333333,2014,20232.9
"5306",2012,19916.0869565217,2014,20649.4
"5307",2012,17941.3333333333,2014,18671
"5308",2012,19373.3333333333,2014,19991.9
"5309",2012,19634.347826087,2014,20377.6
"5310",2012,18645.3846153846,2014,19283.8
"5311",2012,18720,2014,19397.2
"5312",2012,20578,2014,21143.8
"5313",2012,19380.6666666667,2014,20159.3
"5314",2012,19439,2014,20248.1
"5315",2012,19573,2014,20304.3
"5316",2012,18751.25,2014,19417.2
"5317",2012,18940,2014,19701.3
"5318",2012,20075.4166666667,2014,20740
"5401",2012,17907.5,2014,18522.9
"5402",2012,18704.6666666667,2014,19349
"5403",2012,18580.3846153846,2014,19280
"5404",2012,17407,2014,18222
"5405",2012,19327.5,2014,20055.3
"5406",2012,18512.4,2014,19128
"5407",2012,19217.6,2014,19903.5
"5408",2012,18673,2014,19379.3
"5409",2012,20270,2014,20855.6
"5410",2012,19489.1666666667,2014,20238.1
"5411",2012,17890,2014,18620.5
"5412",2012,18078.0952380952,2014,18834
"5413",2012,19446.5,2014,20043
"7201",2012,18008.4637681159,2014,18697.3
"7202",2012,18425,2014,19159
"7203",2012,18106.6666666667,2014,18658.7
"7204",2012,20667,2014,21249.5
"7205",2012,18035.3333333333,2014,18711.3
"7206",2012,18450.9523809524,2014,19101.3
"7207",2012,20320.8695652174,2014,20948
"7208",2012,19536,2014,20100
"7209",2012,18869.3333333333,2014,19480.7
"7210",2012,17737.5,2014,18362
"7211",2012,17410,2014,17987.6
"7212",2012,20164.6666666667,2014,20683.9
"7213",2012,19245,2014,19834
"7214",2012,20302,2014,20867.6
"7301",2012,18256,2014,18876.3
"7302",2012,17957,2014,18514
"7303",2012,18034.6666666667,2014,18724.8
"7304",2012,19017.2222222222,2014,19799.3
"7305",2012,17916.1904761905,2014,18698
"7306",2012,18578,2014,19336.7
"7307",2012,18857.619047619,2014,19230
"7308",2012,18793.3333333333,2014,19457.6
"7309",2012,19058.6956521739,2014,19787.6
"7310",2012,18895.3333333333,2014,19429.5
"7311",2012,18878.1780538302,2014,19430.7
"7312",2012,18183.8461538462,2014,18879.2
"7313",2012,18197.3333333333,2014,18632
"7401",2012,19040,2014,19716
"7402",2012,18143.3333333333,2014,18897.2
"7403",2012,17301.3043478261,2014,17984
"7404",2012,19118,2014,19698
"8201",2012,20643.8095238095,2014,21227.1
"8202",2012,20174.6666666667,2014,20682.4
"8203",2012,18474.4444444444,2014,19119.6
"8204",2012,18964.6153846154,2014,19694
"8205",2012,17833.8461538462,2014,18486
"8206",2012,18796,2014,19433
"8207",2012,18750.4347826087,2014,19414.5
"8208",2012,19158.8,2014,19833.1
"8209",2012,20058.9655172414,2014,20741.1
"8210",2012,21119.1153846154,2014,21684.5
"8211",2012,19926,2014,20499.3
"8212",2012,18585.7142857143,2014,19272
"8213",2012,21155,2014,21768
"8214",2012,20965,2014,21470
"8215",2012,20356.9230769231,2014,20849.2
"8216",2012,21009.6,2014,21576.5
"8217",2012,20380.7692307692,2014,20996
"8218",2012,23005.2173913043,2014,23712.7
"8219",2012,27201.9230769231,2014,27749.3
"8220",2012,21017,2014,21626.5
"8221",2012,21245.0909090909,2014,21706.7
"8222",2012,23036.0869565217,2014,23862.9
"8301",2012,18453.9393939394,2014,19023.3
"8302",2012,18534.3333333333,2014,19190
"8303",2012,18492,2014,19091.3
"8304",2012,18855.7142857143,2014,19606.7
"8305",2012,17307.5595238095,2014,18159
"8306",2012,17459.1666666667,2014,18457
"8307",2012,17890.4761904762,2014,18484.8
"8308",2012,18963,2014,19466.1
"8309",2012,18347.1428571429,2014,19084
"8310",2012,20412,2014,21071.7
"8311",2012,19008.5,2014,19795.7
"8312",2012,18111.6666666667,2014,18856.5
"9101",2012,17408.5714285714,2014,17945.3
"9102",2012,16617.3333333333,2014,17173
"9103",2012,17123.3333333333,2014,17661
"9104",2012,16840,2014,17349.6
"9105",2012,18586,2014,19386.7
"9106",2012,18106.6666666667,2014,18625
"9107",2012,17128.4615384615,2014,17763
"9108",2012,16940.8695652174,2014,17503.5
"9109",2012,17352,2014,17867.5
"9110",2012,16858,2014,17391.3
"9111",2012,19566.6666666667,2014,20024.4
"9112",2012,17690.4,2014,18320.7
"9113",2012,18336.5217391304,2014,19110
"9114",2012,18100.4545454545,2014,18640.7
"9115",2012,17447.2222222222,2014,17903.9
"9116",2012,17108,2014,17654.6
"9301",2012,18502,2014,19034.2
"9302",2012,18934.8,2014,19401.5
"9303",2012,18952.3076923077,2014,19365.3
"9304",2012,18794.7619047619,2014,19335
"9305",2012,20466.1111111111,2014,20830.8
"9306",2012,20292.8,2014,20668.7
"9307",2012,19959,2014,20333.3
"9308",2012,21603.3333333333,2014,22211.1
"9309",2012,18084,2014,18679.5
"9310",2012,19009,2014,19507
"9311",2012,19514.6666666667,2014,20090
"9312",2012,19813.6,2014,20490.6
"9313",2012,18840,2014,19254.8
"9314",2012,19946.0357142857,2014,20238
"9315",2012,19492,2014,20070.5
"9316",2012,17739.1304347826,2014,18245.7
"9317",2012,17904,2014,18510.6
"9401",2012,19633.8461538462,2014,20418.3
"9402",2012,17570,2014,17890
"9403",2012,17200.8333333333,2014,17640.5
"9404",2012,17777.2666666667,2014,18376.4
"9405",2012,17254.5,2014,17473.5
"9406",2012,17553.3333333333,2014,18426
"9407",2012,16885,2014,17577.7
Your instincts will work - filter the path data according to the color:
features.filter(function(feature) {
return color(feature.properties["value0"]) == d;
})
I saved your feature paths in the variable features in the plunkrs below
As the datum of each legend entry is a color, we can just filter the paths based on which datum would produce the same color when scaled. We could alternatively give class names or apply other indicators to filter.
Color the filtered paths on the event, revert all paths on some sort anti-event. Since you haven't said what will reset the map, I'll assume if you click on the same legend entry twice in a row the map should reset (this lets you toggle between each legend entry). To do so we'll need to keep track of which legend entry is currently in focus:
var highlighted = "";
Then we just listen for a click event on the legend entry and:
.on("click",function(d,i) {
highlighted = ""; //reset
// revert map back to default state
}
else {
highlighted = d;
// filter features to highlight certain ones.
})
Altogether that might look like:
.on("click",function(d,i) {
// clicking an active entry: reset:
if(highlighted == d) {
// reset opacity on each path:
features.style("opacity",1);
// reset legend entries' fills:
legend.selectAll("rect")
.style("fill",function(d) { return d; });
// reset highlight variable since nothing is highlighted
highlighted = "";
}
// clicking a different entry: highlight that entry:
else {
//update highlighted variable
highlighted = d;
// set opacity low for all features, filter for chosen features and style accordingly:
features.style("opacity",0.2)
.filter(function(f) {
return color(f.properties["value0"]) == d;
})
.style("opacity",1);
// hollow legend entries
legend.selectAll("rect")
.style("fill","white")
// fill selected option:
d3.select(this).select("rect")
.style("fill",function(d) { return d;});
}
})
Which looks something like:
(I'm formatting the legend entries to show what is highlighted - I changed your stroke to accomodate this)
Here's an updated plunkr.
If you want to be able to toggle multiple legend items at once, then the logic gets a bit more detailed and the highlighted variable becomes an array
As an alternative with mouseover/off events rather than clicks (so no need to track what is actively shown), here's a slightly different approach using the same principles: plunkr

Updating several d3.js cartograms in the same container

I'm trying to update 4 cartograms in the same div.
First I create 4 original maps, each in its own svg, like this:
function makeMaps(data){
var mapsWrapper = d3.select('#maps');
data.forEach(function(topoJSON,i){
var quantize = d3.scale.quantize()
.domain([0, 1600000])
.range(d3.range(5).map(function(i) { return "q" + i; }));
var layer = Object.keys(topoJSON.objects)[0]
var svg = mapsWrapper.append('svg')
.attr("class","mapa")
.attr({
width: "350px",
height: "350px"
});
var muns = svg.append("g")
.attr("class", "muns")
.attr("id",layer)
.selectAll("path");
var geometry = topoJSON.objects[layer].geometries;
var carto = cartos[i]
var features = carto.features(topoJSON, geometry),
path = d3.geo.path()
.projection(projections[i]);
muns.data(features)
.enter()
.append("path")
.attr("class", function(d) {
return quantize(d.properties['POB1']);
})
.attr("d", path);
});
}
This part works well and creates 4 maps. After this, I want to update the paths in each map with the paths calculated by cartogram.js. The problem is that I can't get to the path data in each of the maps, here's what I'm trying to do:
...some code to calculate cartogram values
var cartograms = d3.selectAll(".mapa").selectAll("g").selectAll("path")
cartograms.forEach(function(region,i){
region.data(carto_features[i])
.select("title")
.text(function (d) {
return d.properties.nom_mun+ ': '+d.properties[year];
});
In cartograms I'm getting a nested selection: an array of paths for each map, which is what I though I needed, but inside the forEach I get the error region.data() is not a function. Each region is an array of paths each of which has a data property. I've tried with several ways of selecting the paths to no avail and I'm a little lost now. Thanks for your help

Categories