D3.JS Click Event problem, using a svg map - javascript

Hi I'm fairly new to the D3 library, and I cannot figure out how to get a click event to work when I'm clicking on the country, I understand the 'd' variable contains the country information but how do I get it to console log on click. I've tried a few methods but cant figure it out. any help appreciated
const svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
// Map and projection
const projection = d3.geoNaturalEarth1()
.scale(width / 1.5 ) // Lower the num closer the zoom
.translate([200, 700]) // (Horizontal, Vertical)
// Load external data and boot
d3.json("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson").then( function(data) {
// Draw the map
svg.append("g")
.selectAll("path")
.data(data.features)
.join("path")
.attr("fill", "#348C31") // Color Of Country
.attr("d", d3.geoPath().projection(projection))
.style("stroke", "white")// Border Lines
.append("title")
.text(d => console.log(d))
//Want to run on click of country,
})
const $countyPaths = svg.append("g")
.selectAll("path")
.data(data.features)
.join("path")
$countyPaths
.attr("fill", d => color(data.get(d.id)))
.attr("d", path)
.append("title")
$countyPaths.on('click',d=>console.log(d.id))
function buttonClick() {
window.alert("Boom");
}
</script>

d3 provides the .on(event, fx(event, d)) method in order to handle events.
To log the country, you could do then the following:
svg.append("g")
.selectAll("path")
.data(data.features)
.join("path")
.attr("fill", "#348C31") // Color Of Country
.attr("d", d3.geoPath().projection(projection))
.style("stroke", "white")// Border Lines
.on("click", (_, d) => console.log(d))
Note that the first thing that the function from the event listener will receive is the event info, and the second one the data

Related

updating d3 chart with new data, the old data points not removed

I am trying to update my d3 bubble chart with new data getting from the backend. However, it seems that the new data gets added to the chart but the old data points are not removed? What is the issue here?
Here is my function:
function updateGeoData(ds, de) {
console.log("hit 233")
const size = d3.scaleLinear()
.domain([1, 100]) // What's in the data
.range([4, 50]) // Size in pixel
let updateData = $.get("/update_geo_data", {"ds":ds, "de":de});
updateData.done(function (result) {
var circles = svg.selectAll("myCircles").data(result, function(d) { return d; });
circles.attr("class", "update");
circles.enter().append("circle")
.merge(circles)
.attr("cx", d => projection([d.long, d.lat])[0])
.attr("cy", d => projection([d.long, d.lat])[1])
.attr("r", d => size(d.count))
.style("fill", "69b3a2")
.attr("stroke", "#c99614")
.attr("stroke-width", 2)
.attr("fill-opacity", .4);
circles.exit().remove();
});
}
This part right here is your problem:
svg.selectAll("myCircles")
myCircles isn't anything so the selection will always be empty, and you will always only append to it.
svg.selectAll("circle") should work as a selection for you. This will select all the circles currently plotted on and enter, update, remove appropriately.

Javascript D3 line generator generating 1 line for each data point in HTML

I have a d3 script which is supposed to create and update a line using d3.line().
However, it is currently generating one curve for every data point.
The data is in the form
let data = [{"x": 1,"y": 2}, {"x": 2,"y": 4}, {"x": 3,"y": 6}]
(data can be changed dynamically and the function will be called to update the line)
The script :
let line = d3.line()
.x(d => xScale(d["x"]))
.y(d => yScale(d["y"]))
.curve(d3.curveCardinal);
function updateLine(data){
let g1 = svg.selectAll("path.line").data(data);
g1
.enter().append('path')
.attr("class", "line")
.merge(g1)
.attr("d", line(data))
.attr("fill", "none")
.attr("stroke", "#348")
.attr("stroke-width", "1px");
}
When I inspected the HTML, there are multiple instances of the correct curve being drawn. As the dataset contains 200 data points, 200 lines were drawn (all of the lines are correct).
I tried googling and can't seem to find the error.
I managed to resolve the multiple lines by adding a small change to the script, so that only one line is drawn.
let line = d3.line()
.x(d => xScale(d["x"]))
.y(d => yScale(d["y"]))
.curve(d3.curveCardinal);
function updateLine(data){
let g1 = svg.selectAll("path.line").data(**[**data**]**);
g1
.enter().append('path')
.attr("class", "line")
.merge(g1)
.attr("d", line(data))
.attr("fill", "none")
.attr("stroke", "#348")
.attr("stroke-width", "1px");
}
Any idea why this works?

D3: zoom fails to include elements as expected

I am trying to adjust my D3 zoom so that ALL elements zoom in as expected. My elements are as follows: countries, markers (circles) and flows (polygons).
So far, all elements load as expected. The countries first, then the circles and flows upon subsequent interaction. But the zoom only works for the countries. The circles and flows do not zoom but just stay static. What am I doing wrong?
Link to my jsfiddle
Countries I add to map as follows:
var country = g.append("g");
d3.json("countries.json", function(collection) {
country.selectAll("path")
.data(collection.features)
.enter().append("path")
.attr("d", path);
});
Circles I add after user interaction, as follows:
var g_circles = svg.append("g").attr("class", "circles");
$.each(circles, function(i, d) {
dz = projection(d);
g_circles.append("circle")
.attr("class", "marker")
.attr("d", path)
.attr("cx", dz[0])
.attr("cy", dz[1])
.call(zoom);
});
Flows I add to the map as follows:
var g_lines = svg.append("g").attr("class", "lines");
g_lines.selectAll(".link_line")
.data(links)
.enter()
.append("path")
.attr("class", "link_line")
.style('fill-opacity', 0.3)
.attr("d", "path")
.call(zoom);
Zoom is as follows:
var zoom = d3.behavior.zoom()
.translate(projection.translate())
.scale(projection.scale())
.scaleExtent([h, 350000 * h])
.on("zoom", zoomed);
function zoomed() {
projection.translate(d3.event.translate).scale(d3.event.scale);
svg.selectAll("path, circle, .link_line").attr("d", path);
}
Have you tried to add all the expected groups (g_circles and g_lines) into a new single group (g) and use the dimensions of this group to do the focus?
Zoom should be applied to the SVG:
See this other question as it's very similar to yours.
Zooms are commonly expected to work as a translation of the svg like in this example.

Zooming datasets in d3.js

I have overlayed two datasets, a boundary map and a point map in d3.js. I want to be able to zoom both datasets at the same time. With the current code, only the point map responds to the zoom. How can I zoom both datasets at the same time
The code is shown below
var canvas = d3.select("body").append("svg")
.attr("width",260)
.attr("height",400)
d3.json("/Maps/iowastate.json",function (data){
var group = canvas.selectAll("g")
.data(data.features)
.enter()
.append("g")
var projection =d3.geo.mercator()
.scale(250)
//.translate([0,0]);
var path = d3.geo.path().projection(projection);
var areas = group.append("path")
.attr("d", path)
.attr("class","area")
.attr("fill","black");
d3.csv("/Maps/detectors.csv",function (d){
var group = canvas.selectAll("g")
.data(d)
.enter()
.append("circle")
.attr("cx", function(d) {
return projection([d.StartLong,d.StartLat])[0];
})
.attr("cy", function(d,i) {
return projection([d.StartLong,d.StartLat])[1];
})
.attr("r", 0.1)
.style("fill", "red");
//console.log(projection(d[0].StartLat))
var zoom = d3.behavior.zoom()
.on("zoom",function(){
group.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
group.selectAll("path")
.attr("d", path.projection(projection));
});
canvas.call(zoom)
})
var zoom = d3.behavior.zoom()
.on("zoom",function(){
group.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
group.selectAll("path")
.attr("d", path.projection(projection));
});
canvas.call(zoom)
})
You are applying the right modifications, but twice to the same set of elements instead of the two different layers. To make it work, keep a reference to the other group (e.g. by using different variable names) and apply the transformations to both groups.

d3.js create objects on top of each other

I create rectangles in my SVG element using this code:
var rectangles = svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect");
rectangles.attr("x", function (d) {
return xScale(getDate(d));
//return xScale(d.start);
})
.attr("y", function (d, i) {
return (i * 33);
})
.attr("height", 30)
.transition()
.duration(1000)
.attr("width", function (d) {
return d.length;
})
.attr("rx", 5)
.attr("ry", 5)
.attr("class", "rectangle")
.attr("onclick", function (d) {
return "runaction(" + d.start + ")";
});
How can I create new rectangles on top of the previous ones?
This is an answer to this question I got from Scott Murray, author of great introductory tutorials for d3.js http://alignedleft.com/tutorials/d3/ which helped me a lot with understanding its functionality. I hope he won't mind me putting his answer here for everyone's benefit.
Thank you very much Scott!
And yes, that's absolutely possible. Taking your example, let's say you want to draw one set of circles with the dataset called "giraffeData" bound to them. You would use:
svg.selectAll("circle")
.data(giraffeData)
.enter()
.append("circle");
But then you have a second data set (really just an array of values) called "zebraData". So you could use the same code, but change which data set you reference here:
svg.selectAll("circle")
.data(zebraData)
.enter()
.append("circle");
Of course, this will inadvertently select all the circles you already created and bind the new data to them — which isn't really what you want. So you'll have to help D3 differentiate between the giraffe circles and the zebra circles. You could do that by assigning them classes:
svg.selectAll("circle.giraffe")
.data(giraffeData)
.enter()
.append("circle")
.attr("class", "giraffe");
svg.selectAll("circle.zebra")
.data(zebraData)
.enter()
.append("circle")
.attr("class", "zebra");
Or, you could group the circles of each type into a separate SVG 'g' element:
var giraffes = svg.append("g")
.attr("class", "giraffe");
giraffes.selectAll("circle")
.data(giraffeData)
.enter()
.append("circle");
var zebras = svg.append("g")
.attr("class", "zebra");
zebras.selectAll("circle")
.data(zebraData)
.enter()
.append("circle");
I'd probably choose the latter, as then your DOM is more cleanly organized, and you don't have to add a class to every circle. You could just know that any circle inside the g with class zebra is a "zebra circle".

Categories