I have a map of the US with selectable counties that when clicked change their background to red. What I want to happen is when the user clicks on another county it deselects the current county and then selects the new one only. Currently right now when clicking on the county class it changes the class which gives it a background of red, but when you click another county then both are red.
Here is the code where I draw the map and change classes when clicked:
//DRAW MAP
d3.json("js/map.json", function(error, mapData){
if (error) throw error;
//draw counties
edit.map.append("g")
.selectAll("path")
.data(topojson.feature(mapData, mapData.objects.counties).features)
.enter().append("path")
.attr("class", "counties")
.attr("d", edit.path)
.on("click", function(d){
sFips = d.properties.STATEFP;
cFips = d.properties.COUNTYFP;
//display values in text boxes
$("#locationCountySelect").val(cFips);
$("#locationStateSelect").val(sFips);
//change clicked county class name
if (this.className.baseVal == "counties") {
this.className.baseVal = "selectedCounty";
//send new county to db
} else {
this.className.baseVal = "counties";
}
});
});
Again, how can I only have one county selected at a time?
For this purpose I suggest you ditch jQuery in favor of D3. The following two lines in your click listener will do the job:
d3.select(".selectedCounty").attr("class", "counties");
d3.select(this).attr("class", "selectedCounty");
The first statement selects the element having class .selectedCounty and sets the class attribute to counties instead. The second one selects the element clicked upon and set its class to selectedCounty.
It might also be worth considering to keep a reference to the currently selected element in a variable in the outer scope to not having to reselect on every click:
var selectedCounty = d3.select(".selectedCounty");
edit.map.append("g")
// ...
.on("click", function(d) {
selectedCounty.attr("class", "counties");
selectedCounty = d3.select(this).attr("class", "selectedCounty");
}
As requested by Teton-Coder's comment there might be the need to toggle the class instead of just replacing it. Using selection.attr("class", "selectedCounty") will set the value of the class attribute, whereby replacing any other classes set on the element. Although you are allowed to pass a space-separated list to the attribute via this function the easiest way to toggle a specific class on an element is the use of selection.classed(). The second argument to that function is a boolean value determining if the class should either be assigned to the element or be removed from it while leaving all other classes intact. The above code can thus be rewritten as:
var selectedCounty = d3.select(".selectedCounty");
edit.map.append("g")
// ...
.on("click", function(d) {
selectedCounty.classed("selectedCounty", false);
selectedCounty = d3.select(this).classed("selectedCounty", true);
}
Related
I'm trying to change the color of a node with a specific ID in D3, depending on what the user selects in a survey. I have this line of code:
$("#" + type).find("path, circle").attr("fill", "#333333");
where "type" is the name of the node I want to target (this name could change depending on the survey options selected, obviously). Looking at the console, the correct node is selected, but I just can't change the color. I've also tried .style("fill","#333333") instead of .attr("fill", #333333), but nothing seems to work!
the d3 way is this:
d3.select("#" + type).select("circle").style("fill", "#333333");
I'm new to D3.js and following the tutorial.
I'm confused why the circle.style("fill", "steelblue"); below doesn't take effect; but if put the style line after circle.enter().append("circle") line it can fill colors to all circles. To my understanding, the style is global, should be applied to all circles, or at least affect the existing circles in html in the following code.
//js
var svg = d3.select("svg");
var circle = svg.selectAll("circle").data([32, 57, 112, 293], function(d) { return d; });
circle.style("fill", "steelblue");
circle.enter().append("circle")
.attr("cy", 60)
.attr("cx", function(d, i) { return i * 100 + 30; })
.attr("r", function(d) { return Math.sqrt(d); });
circle.exit().remove();
//html
<svg width="720" height="120">
<circle cx="40" cy="60" r="10"></circle>
<circle cx="80" cy="60" r="10"></circle>
<circle cx="120" cy="60" r="10"></circle>
</svg>
Thanks!
The first time the code runs, the size of the circle selection is zero because there are no elements matching the key. All of the circle elements will be put in the exit selection. The data method returns the update selection which is initially empty, it's (second dimension) length is equal to the length of the first dimension of the first argument passed to .data, but each element of the selection is null. It's not until the enter method is executed that new nodes are created and the enter and update selections are merged.
If the code runs again, with one data element replaced with a new value for example, then the three matching nodes will be placed in the update selection and that will be returned by .data. The one that doesn't match will be placed in the exit selection and its old position in the update selection will be set to null. One placeholder object, with the new data element attached, will be placed in the enter selection which will end up with a reference to a newly created node after the .append and which will be merged into the update selection by .enter.
If you don't use a key function, then, first time, the three existing nodes will be in the update selection, along with one null value, and one placeholder will be put in the enter selection with data attached (with value of 293 in this case). The exit selection would be empty (length 0) and a new node will be created and added to the enter selection with the new data element bound to it. At this point, the update selection (circle) will have the enter selection merged with it. It's null entry having been replaced by the new node with the new data bound to it. This is a side effect of calling .enter.
However, if you then called the code again, with the order of the data element changed (still with the same set of values but all in different positions in the array), then the exit and enter selections will be empty, but the data value bound to all nodes will be changed. If you used a key in this case then nothing would be changed.
I have recently created a node map which has multiple nodes with line/arrows going between each node. I have recreated my code here: https://jsfiddle.net/GarrettUK/51j2rx1t/. I was wondering if anyone could show me how I could go about creating an on-click function on the lines so that when a line is clicked... 2 more lines are shown. So when its clicked we would have the line we just clicked in the middle and the 2 new lines either side.
I've already started creating the on-click function as seen in the example. I had a theory that you would create the 2 extra lines that connect to each node but have them hidden. Then when you click on the line. The function switches these lines from hidden to show.
My on-click function looks like this in the example:
.on("click", function(){
// Determine if current line is visible
var active = redLine.active ? false : true ,
newOpacity = active ? 0 : 1;
// Hide or show the elements
d3.select("#redLine").style("opacity", newOpacity);
// Update whether or not the elements are active
redLine.active = active;
});
I created a dropdown menu of some months as options, but wanted to predesignate one option as the default value. However, the default selection seemed to stubbornly remain the first option in the list.
I tried the below code, which made a lot of sense to me because for any other attribute, setting up a simple comparison is sufficient to alter that attribute's value.
var defaultOptionName; // desired default dropdown name
d3.select("#dropdown").append("select")
.on("change", someFunction)
.selectAll("option").data(dataset)
.enter().append("option")
.attr("value", function(d){return d;})
.attr("selected", function(d){
return d === defaultOptionName;
})
.text(function(d){ return d; });
I knew that my problem was just a matter of the right syntax, but when I tried to search the internet and stackoverflow, but couldn't figure out what I was missing.
I found that instead of using .attr, I needed to use .property in order to access the default selection option. A simple substitution was all that was required, so your code snippet would look something like:
.property("selected", function(d){ return d === defaultOptionName; })
It's been a while since I picked up d3.js and it seems I'm a bit rusty. I'm trying to do a form where I can add more input fields by clicking + and removing the existing ones by clicking -.
To try and discover what I had wrong, I started colouring the enter(), update and exit() with green, yellow and red respectively.
The original data array has two elements, so they show up as green:
Then I click on the plus sign which pushes a new element to the array, and I expected to see two yellows and one green, but instead I see all the elements deleted besides the last one, and this repeats if I click + again:
And plus again:
I've compared my code with the classic General Update Pattern and I can't see anything significant apart from the way I set the keys, in which I use the e-mail. This is code I've added to fix another underlying issue where not all the boxes were being added, just one out of each 3.
My commented code is as follows:
var renderFriends = function () {
console.log("Rendering friends:" + friendsList)
var friends = d3.select('.friends-container')
.selectAll('div')
.data(friendsList, function(d,i) {
// this was something I added when I thought the problem were the keys
return d
})
// updates will be yellow
friends.classed("update", true)
var enter = friends.enter()
// Friend box
// all the divs are because I'm using foundation css
// the new class is what marks the font green
var friendBox = enter.append('div').classed('large-12 columns new', true)
friendBox.append('div').classed('large-8 columns', true)
.append("input")
.attr("type", "text")
.attr("value", String)
// Icon box
var iconBox = friendBox.append('div').classed('large-2 left columns', true)
.append("i")
.classed('fi-minus', true)
.on("click", function(d) {
// out of scope for this question
console.log("Removing:" + d)
friendsList.remove(friendsList.indexOf(d))
renderFriends()
})
// exit state should colour the fonts red
friends.exit().classed('remove', true)
}
I did a small test with custom styles and this is what got (when I clicked minus button):
All elements have green background since they all have "new" class, the "update" elements have yellow border, and the "remove" red background.
So what I've noticed is that you have a various Divs nested, and the problem is that when you do a selectAll('div') is going to select all divs and d3 is expecting for each div element being selected to be a data element corresponding to it.
So if you want to add another element and your friendsList is:
friendsList = ['a#test.com','b#test.com','c#test.com'];
d3.selectAll('div') is going to take 6 divs (when you had 2 friends and added one), and its going to bind only 3 elements because your dataset contains only 3 friends, and its going to target the rest of elements as "exiting".
To solve this, simply change your select using a class like '.friend' and also add it to each element being inserted (only the main container div);
Like this:
http://jsfiddle.net/2codv59e/