The example looks like this:
a=[1,2,3,4,5];
svg.selectAll("rect").data(a)
a[1]=10;
a[4]=50;
svg.selectAll("rect").data(a)
The second and the fifth elements of a are changed. And I want to ONLY select these two elements and set their color as red. However, I don't know how to do that, or in other words, select these changed element in d3.js. (As I understand, enter() can't do this job). Does anyone have ideas about this?
There might be a better way of doing this:
//update data array
a[4]=50;
//color update elements
svg.selectAll('rect')
.filter(function(d, i){ return d != a[i]; })
.style('color', 'red')
//bind updated data
svg.selectAll('rect').data(a)
You need a way to store the old data value so that you can compare it against the new one. Maybe add a custom data attribute like this:
a=[1,2,3,4,5];
svg.selectAll("rect").data(a)
.attr("data-currentVal", function(d){return d});
a[1]=10;
a[4]=50;
svg.selectAll("rect").data(a)
.style("fill", function(d) {
if (d3.select(this).attr("data-currentVal") != d) {return red;}
else {return black;}
});
Live example (slightly fancied up so you can see the changes happening):
http://fiddle.jshell.net/5Jm5w/1/
Of course, for the more common example where d is a complex object, you would need to have a way of accessing it's value(s) as a unique string, since the attribute value would always be coerced to string. For example, if you have an (x,y) point, you would need to create a named helper function like dToString = function(d) {return "(" + d.x + "," + d.y + ")";}, and then pass in the name of that function when you set the attribute, and use it again when you compare the old and new.
Related
As project to get to know d3.js, I’m displaying tweets on a map in real-time. Everything has worked this far, and I’m very, very pleased with the library.
On the page, I’m trying to list all languages. On hover, I want all tweets of that language to pop up. All of this is done, except some items in the list pops up the tweets of another language. A wrong one, I might add.
This is how I project the dots on the map:
points.selectAll('circle')
.data(tweets)
.enter()
.append('circle')
// Attach node to tweet, so I can use refer to the nodes later
.each(function(d) {
d.node = this;
})
.attr('r', 1);
This is how I create the list:
var data = d3.nest()
// Group by language code
.key(function(d) { return d.lang.code; })
.entries(tweets)
// Sort list by amount of tweets in that language
.sort(function(a, b) {
return b.values.length - a.values.length;
});
var items = languages_dom
// Add items
.selectAll('li')
.data(data)
.enter()
.append('li');
// Used for debugging
.attr('data-lang', function(d) {
return d.key; // Group key = language code
})
// Set text
.text(function(d) {
var dt = d.values[0];
return dt.lang.name;
})
// Mouseover handler
.on('mouseover', function(d) {
// Compare attribute with
// These values are actually different
var attr = d3.select(this).attr('data-lang');
console.log(attr, d.key);
// Pop up each node
d.values.forEach(function(d) {
d = d3.select(d.node);
d.transition()
.duration(200)
.attr('opacity', 0.5)
.attr('r', 8);
});
});
Note that the script above is run several times. d.key refers to another value later in the chain, while I’m not modifying data in that chain.
Edit 22:08
Things seems to work fine when I’m not sorting the data. At least it’s a lead.
As noted in the comments, you're overwriting d in your forEach function. Instead, you should try something like
d.values.forEach(function(p) {
d3.select(p.node)
.transition()
.duration(200)
.attr('opacity', 0.5)
.attr('r', 8);
});
Notice the forEach variable is named p instead of d.
As the data changed, the old data seems to be kept somehow.
Either way, I simply deleted the list before applying the new data:
languages_dom
.selectAll('li')
.remove();
Can’t say this is graceful, nor performant, but it gets the job done :)
I'm using d3 library and I want to create different elements by checking some values. If I do:
elements.append("rect").attr(...);
What happens if I want to create different elements? I tried:
elements.append(function (d) {
if (d.foo) {
return "rect";
}
return "circle";
});
This seems not to work.
What's the alternative to this?
As you've already pointed out, .append() accepts a function that returns the DOM element to append. This looks as follows.
var data = ["circle", "rect"];
d3.select("svg").selectAll(".shape").data(data)
.enter()
.append(function(d) {
return document.createElementNS(d3.ns.prefix.svg, d);
});
Of course you also need to set all the attributes correctly, which is the crux of the method -- in principle you don't need lots of if/switch statements to handle each element type, but in practice you do because the attributes you need to set differ (unless you want to set everything for everything).
Complete demo here.
Even append function accepts a function, I'm not sure how that should work (maybe someone brings the light here). However the following solution is human readable and easy to implement:
elements.each(function (d) {
var el = d3.select(this);
var type = "circle";
if (el.foo) {
type = "rect";
}
var aType = el.append(type);
if (type === "rect") {
aType.attr("rx", 5)
.attr("ry", 5)
.attr("height", ...)
.attr("width", ...);
} else if (type === "circle") {
aType.attr("r", ...);
}
});
I have been following the guide for choropleth using D3 from this link.
http://synthesis.sbecker.net/articles/2012/07/18/learning-d3-part-7-choropleth-maps
instead of unemployment, I have a json file that lists the number of automobile crashes per county per state. The format of this json file is
{
"id":1001,
"crashTotal":2
},
And this is for each of the elements in the json file; one for each county. The ID is the State+County FIPS Code and the crashTotal is its namesake.
I have been following the example code closely and have come upon the quantize function
// quantize function takes a data point and returns a number
// between 0 and 8, to indicate intensity, the prepends a 'q'
// and appends '-9'
function quantize(d) {
return "q" + Math.min(8, ~~(data[d.id] * 9 / 12)) + "-9";
}
For me, data is a variable set equal to the crashes.json file. I'm confused as to why I cannot use the crashTotal values from my data to use according to the quantize function.
When I try to use the following code
~~data[d.id] or +data[d.id]
I get 0 or NaN. Why is this? I'm fairly new to using d3 so I'm not sure how this is meant to work. Thanks.
My code is quite close to the example code, but with my own US country and state JSON files converted from the census shapefiles. Can someone help?
EDIT: I'd figure I explain the issue a little bit more. Its not a problem between using a quantize function or d3 scale quantize, but rather how to access my data to color each county. As stated, my data file is a JSON in the format above. The following is how I set the data and how I call quantize
d3.json("Crashes.json", function(crashes) {
max = +crashes[0].crashTotal;
min = +crashes[0].crashTotal;
maxFIPS = +crashes[0].id;
minFIPS = +crashes[0].id;
for(i = 0; i < crashes.length; i++) {
if(+crashes[i].crashTotal > max) {
maxFIPS = +crashes[i].id;
max = +crashes[i].crashTotal;
}
if(+crashes[i].crashTotal < min) {
minFIPS = +crashes[i].id;
min = +crashes[i].crashTotal;
}
}
data=crashes;
//for(i = 0; i < data.length; i++) {
// document.writeln(data[i].id + " " + data[i].crashTotal);
// }
counties.selectAll("path")
.attr("class", quantize);
//.text(function (d){return "" + d.value;});
//console.log("maxFIPS:" + maxFIPS + " minFIPS:" + minFIPS + "\n" + "max:" + max + " min:" + min);
});
function quantize(d) {
return "q" + Math.min(8, ~~data[d.id]) + "-9";
}
If I were to replace data[d.id] in the quantize function above, it would actually color based on the color scheme specified in the bracket or CSS document. How would I get this to use the CrashTotal numbers from my data?
EDIT[3-6-2014]
Following the answer from Amelia, I now have the following code bracket.
d3.json("Crashes.json", function(crashes) {
crashDataMap = d3.map();
crashes.forEach(function(d) {crashDataMap.set(d.id, d);});
data = crashDataMap.values();
quantize = d3.scale.quantize()
.domain(d3.extent(data, function(d) {return d.crashTotal;}))
.range(d3.range(9).map(function(i) {return "q" + i + "-9"}));
//min = d3.min(crashDataMap.values(), function(d) {return d.crashTotal;});
//max = d3.max(crashDataMap.values(), function(d) {return d.crashTotal;});
//console.log(quantize(crashDataMap.get(6037).crashTotal));
counties.selectAll("path")
.attr("class", function(d) {return quantize(crashDataMap.get(d.id).crashTotal);});
});
This should get me the correct coloring for my map, but my map stays white. I can confirm that by testing out quantize, I get the correct class name from my CSS file.
console.log(quantize(crashDataMap.get(1001).crashTotal)); //returns q0-9
More help is appreciated. Thanks.
EDIT2[3-6-2014] I decided to just post the entire code I have here, hoping someone could make sense out of the madness of why this doesn't work
//CSS or <style></style> bracket
svg {
background: white;
}
path {
fill: none;
stroke: #000;
stroke-width: 0.1px;
}
#counties path{
stroke: #000;
stroke-width: 0.25px;
}
#states path{
fill: none;
stroke: #000;
stroke-width: 0.5px;
}
.Blues .q0-9{fill:rgb(247,251,255)}
.Blues .q1-9{fill:rgb(222,235,247)}
.Blues .q2-9{fill:rgb(198,219,239)}
.Blues .q3-9{fill:rgb(158,202,225)}
.Blues .q4-9{fill:rgb(107,174,214)}
.Blues .q5-9{fill:rgb(66,146,198)}
.Blues .q6-9{fill:rgb(33,113,181)}
.Blues .q7-9{fill:rgb(8,81,156)}
.Blues .q8-9{fill:rgb(8,48,107)}
//Crashes.js file
var width = 960
var height = 500;
var data;
var crashDataMap;
var quantize;
var path = d3.geo.path();
var zoom = d3.behavior.zoom()
.on("zoom", zoomed);
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom)
.append("g");
var counties = svg.append("g")
.attr("id", "counties")
.attr("class", "Blues");
var states = svg.append("g")
.attr("id", "states");
d3.json("county.json", function(county) {
var countyFeatures = topojson.feature(county, county.objects.county);
counties.selectAll("path")
.data(countyFeatures.features)
.enter().append("path")
.attr("d", path);
});
d3.json("state.json", function(state) {
var stateFeatures = topojson.feature(state, state.objects.state);
states.selectAll("path")
.data(stateFeatures.features)
.enter().append("path")
.attr("d", path);
});
d3.json("Crashes.json", function(crashes) {
crashDataMap = d3.map();
crashes.forEach(function(d) {crashDataMap.set(d.id, d);});
data = crashDataMap.values();
quantize = d3.scale.quantize()
.domain(d3.extent(data, function(d) {return d.crashTotal;}))
.range(d3.range(9).map(function(i) {return "q" + i + "-9"}));
/*
for(i = 0; i < data.length; i++) {
console.log(quantize(crashDataMap.get(data[i].id).crashTotal));
}
*/
counties.selectAll("path")
.attr("class", function(d) {return quantize(crashDataMap.get(d.id).crashTotal);});
});
function zoomed() {
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
};
Take a look at where I generated the paths for counties. After .enter().append("path")
statement, if I were to enter the code .attr("class", "q8-9) It would color every county to the scheme defined as q8-9.
If I were to call counties.selectAll("path").attr("class", "q8-9") anywhere outside of the code bracket, nothing happens; the map stays white. This is bugging me as I clearly have no idea why this can happen. I can verify that the path elements are there for both county and state.
To explain what's going on in the original code:
The tutorial you linked to uses two data files, one for the maps and one for the data values. Unfortunately, it doesn't seem to include links to the actual data files used, but their both JSON. The counties have an 'id' property and that property seems to be used as the keys in the second JSON data file. I.e., that second file (data) must be of the form:
{
"1001": ".097",
"1003": ".091",
"1005": ".134",
/*...*/
}
This is different from the data structure used in the very similar Mike Bostock example, which uses a .tsv file for the unemployment data, which is then used to generate a d3.map hashmap data dictionary.
var rateById = d3.map();
queue.defer(d3.tsv, "unemployment.tsv", function(d) { rateById.set(d.id, +d.rate); })
//this is equivalent to
/*
d3.tsv("unemployment.tsv",
function(d) { rateById.set(d.id, +d.rate); },
readyFunction );
*/
//except that the readyFunction is only run when *both* data files are loaded.
//When two functions are given as parameters to d3.tsv,
//the first one is called on each row of the data.
//In this case, it adds the id and rate as a key:value pair to the hashmap
Both of these examples end up with a data structure where the id values are keys that can be used to grab the appropriate data value. In contrast, your data are in an unkeyed array, with your id values as just another property, not as a key. That is why data[d.id] was returning an error for you -- instead of grabbing a data number that matches that id, it's grabbing an element of your array at the index equivalent the id number. That either returns an object, which becomes NaN when converted to a number, or undefined, which becomes zero.
In either example, once they have the number, they then want to convert it to an integer from 0 to 8 in order to assign one of the ColorBrewer class names to the path. The Scott Becker tutorial uses a somewhat arbitrary calculation for this, the Mike Bostock example uses a quantize scale with a hard-coded domain. You say you want to figure out a domain based on your data.
To help you figure out what you need to do:
Your first step is to get your crash data into a structure where you can easily grab a data element based on its id value.
One option would be to create a d3.map object (var crashDataMap = d3.map();) and then use a forEach call on your existing data array to add each object to the map using map.set(key, value) with its id as the key.
crashDataArray.forEach( function(d){ crashDataMap.set( d.id, d) });
Then when you are setting the class on your shapes, you can use crashDataMap.get(d.id) to grab the crash data that matches the shape's id, and you can extract the correct number from that.
For dividing your data into categories, you probably want to use a quantize scale similar to Mike Bostock's example. On your original data array, you can use d3.extent with an appropriate accessor function to grab the crash totals from each entry and find the max and min for setting the domain.
This has to be simple, but it's been a while since I was using d3.js and I can't figure out a good solution.
I have a single set of data and I'm using it to create two sets of elements
circles = svg.selectAll('.highcircles')
.data(data)
.enter()
.append('circle');
and
list.selectAll('.states-list')
.data(data)
.enter()
.append('p');
I'd like to be able to have on mouseover of the <p> tags, to have the related circle animate. I can't though think of the way to link the two. Is it through a data-state attribute? Is there a better solution?
selection.filter can be used to filter down a selection based on data. You can use the datum from the <p> event target to filter down a <circle> selection like this:
var circleMatch = svg.selectAll(".highcircles")
.filter(function(d) {
return d.key === targetDatum.key; // 'key' is some datum-unique property
});
You can add "id" attributes to your circles, and then reference those ids in your mouseover function. Something like this:
circles.attr("id", function(d) { return "id" + d; })
list.on('mouseover', function(d) {
d3.select("#id" + d)
.style("fill", "yellow")
})
http://jsfiddle.net/woodedlawn/7ZqZx/
Every D3js beginner must be going through this thought, I am pretty much sure about it.
I have been around this thing for few hours now!!!!But I don't know how to use it and what is the difference between them?
function(d){return d}
function(d,i){return d and some more custom code}
for Example--->
var data = [4, 8, 15, 16, 23, 42];
Function(d):::::
chart.selectAll("div")
.data(data)
.enter().append("div")
.style("width", function(d) { return d * 10 + "px"; })
.text(function(d) { return d; });
------------------------------------------------------------------------------------
Function(d*i):::::
chart.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("y", function(d, i) { return i * 20; })
.attr("width", x)
.attr("height", 20);
Your example is a good illustrator of the difference between the two.
In the first example, only d is used. d represents the data associated with a given selection. In this case, an array of selected div elements is created, one for each element in the data array:
chart.selectAll("div")
.data(data)
.enter()
.append("div")
This not only creates an array of div elements, but associates data with each of those elements. This is done on a one-to-one basis, with each div corresponding to a single element in the data array. One is associated with '4', one with '8', and so on.
If I then go on to use .text(function(d){...}) on the array of selections, d will refer to the data associated with each selected div, so if I use the following method on my selections:
.text(function(d) { return d; });
Each of my divs will have text added, the value of which is d, or the data associated with the element.
When an array of selections is created, they are also given an index in the array. In your example, this corresponds to the position of their data in the data array. If your function requests both d and i, then i will correspond to this index. Going back to our divs, the div associated with '4' will have an index of '0', '8' will have an index of '1', and so on.
It's also important to note that the character used in the variable requested doesn't matter. The first variable in the function call is always the data, and the second is the index. If i used a method like
.text(function(cat,moose){ return( "data is: " + cat + " index is: " + moose)})
cat will correspond to the data of the selection, and moose will correspond to the index.
I hope that this example can help you. This is a complete web page where you can start playing:
<!doctype html>
<meta charset="utf-8">
<title>my first d3</title>
<body>
<script>
var data=[10,20,30,40];
var lis = d3.select("body")
.append("ul")
.selectAll("li")
.data(data)
lis.enter()
.append("li")
.text(function(d,i){ return "item n° "+i+" has value: "+d})
</script>
Basically d is the value of the data, and i is his index.
You can take a look of this example here: http://jsfiddle.net/M8nK8/
If you're talking about the callback functions you would pass to methods like .attr(), then the function is called for each item in the current selection, where the i gives you the index of the current item, but depending on what you're doing you might not care about the index. So although D3.js will always call your function with both arguments, if you don't actually need the second argument in a particular case your function need not declare it explicitly.
JavaScript lets you call any function with any number of arguments regardless of how many were explicitly included in the function declaration. If you call a function with fewer arguments than were defined the leftovers will get the value undefined. If you call a function with more arguments than were defined you can still access the additional ones from within the function by using the arguments object - or you can ignore them.
(Note: you should have a lowercase f in function().)