Dynamic returned values in append function - javascript

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", ...);
}
});

Related

D3.js method chaining command dosn't work when I split it, it works

I am new in D3.js,
when i use this code it doesn't work,(it is part of the redraw, when running for first time it works good when calling redraw again it works unexpextedly)
var rows=tbody.selectAll('tr').data(roster);
rows.enter().append('tr');
rows.exit().remove();
rows.selectAll('td').data(function(row) { return columns.map(function(col) {
return row[col];
});}).enter().append('td').text(function(d) {return d;} );
when I break the chain down into smaller it works.
var rows=tbody.selectAll('tr').data(roster);
rows.enter().append('tr');
rows.exit().remove();
var cells = rows.selectAll("td")
.data(function(row) { return columns.map(function(col) {
return row[col];
});});
cells.enter().append("td");
cells.text(function(d) { return d; });
any reason or any rule govern this.
In the first case you are only updating the text on the new cells, not the old ones. When you chain .enter() like that, all of the following methods chained apply to the object returned by .enter() and that is the enter selection : added cells in other words.
Read this

D3: Transition between polygons

Here is a D3 project I am working on. It updates a shape of a regular polygon from n to either n-1 or n+1 angles. Pressing the buttons a,b,c,d or e will trigger this. I followed the tutorial on general update patterns and have tried to follow patterns in many other samples I have found, most notably bl.ocks.org/mbostock/3081153.
Here is the drawing function
function draw(data) {
var v = vis.selectAll("polygon")
.data(data, function(d) { return d; });
// enter selection
v.enter().append("polygon");
// update selection
v.attr("class", "update")
.transition()
.duration(750)
.attr("points",function(d) {
return d.map(function(d) { return [d.x,d.y].join(","); }).join(" ");})
.attr("stroke","black")
.attr("stroke-width",2);
// exit selection
v.exit().attr("class", "exit")
.transition()
.duration(750).remove();
};
And the updating function
function update() {
if (this.classList.contains('on')) {
d3.select(this).classed('on',false)
letters.pop(this.id);
}
else {
d3.select(this).classed('on',true)
letters.push(this.id);
}
var N = letters.length;
draw([poly(N,N,[])]);
};
My question is how and where to call an interpolation to make this go smoothly from one polygon to the next?
Thanks
Thanks Lars for looking over my question. I had figured this much but I didn't know where this would come in. I think the problem was that I was creating new polygons instead of changing the old ones (which is what happens in the updating example, old out and new in). Most people link to the California-> circle example but this one actually helped much more with less code.
By combining what I had before with this example I got this. The update function now being
function update() {
if (this.classList.contains('on')) {
d3.select(this).classed('on',false)
letters.pop(this.id);
}
else {
d3.select(this).classed('on',true)
letters.push(this.id);
}
var N = letters.length;
d3.select(".big").transition().duration(500).attr("points", mapPoints(poly(N,N,[])));};
There are obviously still many faults with this but I am happy with the animations.

function to calculate the parent size from children's size

I'm doing something like this, http://bl.ocks.org/mbostock/1283663
But I want that the parent's size was the average of children's size. I found a solution, but It works only for the last parent. My proposition is to implement a new function average
function average(d) {
if (d.children !=null){
return d3.sum(d.children, function(d) {return d.value;})/d.children.length;}
else{
return d.value;
}
}
Then I replace all occurence of
.attr("width", function(d) { return x(d.value); })
with
.attr("width", function(d) { return x(average(d)); })
But it calculate the average only for the lastest parents and their children. Have you any idea how can I correct this function to calculate the average for all parents ?
It's the third time i asked this question, but please understand me, it's very interesting for me, and I know that i will find solution if someone helps me
Thanks a lot
First of all, you don't need to invent a new function to find the average of a certain property for each element in an array of objects. You can use d3.mean for that.
You could just use a recursive function to add a new property to each node that has children, calling it something like averageChildValue.
For example, you could do this:
function recurse(node) {
// IF A NODE HAS CHILDREN...
if (node.children) {
// ADD A PROPERTY REPRESENTING THE AVG VALUE OF ITS CHILDREN
node.averageChildValue = d3.mean(node.children, function(d) {
return d.value;
});
// RECURSIVELY CALL THE FUNCTION ON THE CHILDREN
node.children.forEach(function(d) { recurse(d); });
}
}
d3.json("readme.json", function(error, root) {
// PARTITION THE DATA
partition.nodes(root);
// USE THE RECURSIVE FUNCTION TO CALCULATE THE AVERAGES
recurse(root)
// DO SOMETHING WITH YOUR NEW-AND-IMPROVED DATA
console.log(root)
});
Check your console output and each node with children should now have an extra property called averageChildValue.

Select/detect the changed elements in d3.js?

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.

D3.js - Retrieving DOM subset given data subset

I'm using d3.js to create a large number of svg:ellipse elements (~5000). After the initial rendering some of the data items can be updated via the backend (I'll know which ones) and I want to change the color of those ellipses (for example).
Is there a fast way to recover the DOM element or elements associated with a data item or items? Other than the obvious technique if recomputing a join over the full set of DOM elements with the subset of data?
var myData = [{ id: 'item1'}, { id: 'item2' }, ... { id: 'item5000' }];
var create = d3.selectAll('ellipse).data(myData, function(d) { return d.id; });
create.enter().append('ellipse').each(function(d) {
// initialize ellipse
});
// later on
// this works, but it seems like it would have to iterate over all 5000 elements
var subset = myData.slice(1200, 1210); // just an example
var updateElements = d3.selectAll('ellipse').data(subset, function(d) { return d.id; });
updateElements.each(function(d) {
// this was O(5000) to do the join, I _think_
// change color or otherwise update
});
I'm rendering updates multiple times per second (as fast as possible, really) and it seems like O(5000) to update a handful of elements is a lot.
I was thinking of something like this:
create.enter().append('ellipse').each(function(d) {
d.__dom = this;
// continue with initialization
});
// later on
// pull the dom nodes back out
var subset = myData.slice(1200, 1210).map(function(d) { return d.__dom; });
d3.selectAll(subset).each(function(d) {
// now it should be O(subset.length)
});
This works. But it seems like this would be a common pattern, so I'm wondering if there is a standard way to solve this problem? I actually want to use my data in multiple renderings, so I would need to be more clever so they don't trip over each other.
Basically, I know that d3 provides a map from DOM -> data via domElement.__data__. Is there a fast and easy way to compute the reverse map, other than caching the values myself manually?
I need to get from data -> DOM.
As long as you keep the d3 selection reference alive (create in your example), D3 is using a map to map the data keys to DOM nodes in the update so it's actually O(log n).
We can do some testing with the D3 update /data operator method vs a loop method over the subset:
var d3UpdateMethod = function() {
svg.selectAll("ellipse").data(subset, keyFunc)
.attr("style", "fill:green");
}
var loopMethod = function() {
for (var i=0; i < subset.length; i++) {
svg.selectAll(".di" + i)
.attr("style", "fill:green");
}
}
var timedTest = function(f) {
var sumTime=0;
for (var i=0; i < 10; i++) {
var startTime = Date.now();
f();
sumTime += (Date.now() - startTime);
}
return sumTime / 10;
};
var nextY = 100;
var log = function(text) {
svg.append("text")
.attr("x", width/2)
.attr("y", nextY+=100)
.attr("text-anchor", "middle")
.attr("style", "fill:red")
.text(text);
};
log("d3UpdateMethod time:" + timedTest(d3UpdateMethod));
log("loopMethod time:" + timedTest(loopMethod));
I also created a fiddle to demonstrate what I understand you're trying to do here.
Another method to make it easy to track the nodes that are in your subset is by adding a CSS class to the subset. For example:
var ellipse = svg.selectAll("ellipse").data(data, keyFunc).enter()
.append("ellipse")
.attr("class", function (d) {
var cl = "di" + d.i;
if (d.i % 10 == 0)
cl+= " subset"; //<< add css class for those nodes to be updated later
return cl;
})
...
Note how the "subset" class would be added only to those nodes that you know are in your subset to be updated later. You can then select them later for an update with the following:
svg.selectAll("ellipse.subset").attr("style", "fill:yellow");
I updated the fiddle to include this test too and it's nearly as fast as the directMethod.

Categories

Resources