Select only the X value of nodes data with D3 - javascript

I'm trying to get only the x-values of the nodes data (node.data()). At the moment I get the whole Object {x: 1, y: 0.4}. I want to compare the previous elements value with the actual. Maybe there is also a way to access the previous value with d.x instead of accessing the x-value using node.data()[i-1]?
node.append("text")
.attr("x", function(d) { return x(d.x); })
.attr("y", function(d) { return y(d.y); })
.text(function(d,i) {
console.log(node.data()[i])
var header = d3.select(this);
if(d.y < 0.7){
header.style("fill", "green");
}
return prozent(d.y); });

Well d.x would be the current x value and node.data()[i-1].x would be the previous x value, where i > 0.
The alternative is you could just use a var like so:
var lastx = undefined;
node.append("text")
.attr("x", function(d) { return x(d.x); })
.attr("y", function(d) { return y(d.y); })
.text(function(d,i) {
console.log(d.x, lastx); // do whatever you want to do comparing d.x and lastx
var header = d3.select(this);
if(d.y < 0.7){
header.style("fill", "green");
}
lastx = d.x; // make this the previous value for the next element
return prozent(d.y); });

Related

D3 V3 Stacked Bar Negative Values

I'm using a D3 V3 stacked bar like in this plunker:
http://plnkr.co/edit/rjO5vgYyeytTJjuv4emB?preview
My question is how would i render a stacked bar using this multiple measure method with some negative values, i.e. if the rect is negative then have it below the y-axis zero line and combine this with rects with positive values rendering above the y-axis zero line?
I've tried changin the rect y and height like:
//adding the rect for group chart
state.selectAll("rect")
.data(function(d) { return d.group; })
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(Math.max(0, d.y1)); })
.attr("height", function(d) { return Math.abs((y(d.y0) - y(d.y1))-y(0)); })
.style("fill", function(d) { return color(d.name); });
I've been trying but I'm a D3 noob so advice would be very welcome.
Thanks
Dom
A great idea would be to store the actual heights in the data object even if the values are negative.
This may be the solution that you need:
var y = d3.scale.linear()
.rangeRound([height/2, 0]);
state.selectAll("rect")
.data(function(d) {
return d.group;
})
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", function(d) {
if(d.y1 < 0){
return y(d.y0)
}
return y(d.y1);
})
.attr("height", function(d) {
return Math.abs(y(d.y0) - y(d.y1));
})
.style("fill", function(d) {
return color(d.name);
});
your solution didn't work but your suggestion about altering the data object was a good one, in the end I changed the data object like:
//get group and total for stack
vars.data.forEach(function (d) {
var y0 = 0;
var y0n = 0;
d.group = color.domain().map(function (name) {
return {
name: name,
yt: d[name] < 0 ? 1 : 0,
y0: y0,
y1: (y0 += +Math.max(0, d[name])),
y0n: y0n,
y1n: (y0n += +Math.min(0, d[name])),
dimension: d.dimension,
qElemNumber: d.qElemNumber,
};
});
d.total = d.group[d.group.length - 1].y1;
d.totaln = d.group[d.group.length - 1].y1n;
});
so I created y0n and y1n for negative values and yt as an indicator that shows if the value is negative or not, then you can apply the y and height like:
.attr("y", function (d) {
if (d.yt === 1) {
return y(d.y0n);
}
return y(d.y1); //NEGFIX NEEDED
})
.attr("height", function (d) {
if (d.yt === 1) {
return Math.abs(y(d.y0n) - y(d.y1n));
}
return y(d.y0) - y(d.y1); //NEGFIX NEEDED
})
hopefully thi will help anyone looking for a similar fix :)
cheers
Dom

JavaScript/d3 - Assign Attribute within Another Attribute [duplicate]

For example, I need to calculate a Math.sqrt of my data for each attr, how can I calculate only one time the Math.sqrt(d)?
var circle = svgContainer.data(dataJson).append("ellipse")
.attr("cx", function(d) {
return Math.sqrt(d) + 1
})
.attr("cy", function(d) {
return Math.sqrt(d) + 2
})
.attr("rx", function(d) {
return Math.sqrt(d) + 3
})
.attr("ry", function(d) {
return Math.sqrt(d) + 4
});
Has any elegant/performative mode? I'm thinking this way:
var aux;
var circle = svgContainer.data(dataJson).append("ellipse")
.attr("cx", function(d) {
aux = Math.sqrt(d);
return aux + 1
})
.attr("cy", function(d) {
return aux + 2
})
.attr("rx", function(d) {
return aux + 3
})
.attr("ry", function(d) {
return aux + 4
});
An underestimated feature of D3 is the concept of local variables which were introduced with version 4. These variables allow you to store information on a node (that is the reason why it is called local) independent of the data which might have been bound to that node. You don't have to bloat your data to store additional information.
D3 locals allow you to define local state independent of data.
Probably the major advantage of using local variables over other approaches is the fact that it smoothly fits into the classic D3 approach; there is no need to introduce another loop whereby keeping the code clean.
Using local variables to just store a pre-calculated value is probably the simplest use case one can imagine. On the other hand, it perfectly illustrates what D3's local variables are all about: Store some complex information, which might require heavy lifting to create, locally on a node, and retrieve it for later use further on in your code.
Shamelessly copying over and adapting the code from Gerardo's answer the solution can be implemented like this:
var svg = d3.select("svg");
var data = d3.range(100, 1000, 100);
var roots = d3.local(); // This is the instance where our square roots will be stored
var ellipses = svg.selectAll(null)
.data(data)
.enter()
.append("ellipse")
.attr("fill", "gainsboro")
.attr("stroke", "darkslateblue")
.attr("cx", function(d) {
return roots.set(this, Math.sqrt(d)) * 3; // Calculate and store the square root
})
.attr("cy", function(d) {
return roots.get(this) * 3; // Retrieve the previously stored root
})
.attr("rx", function(d) {
return roots.get(this) + 3; // Retrieve the previously stored root
})
.attr("ry", function(d) {
return roots.get(this) + 4; // Retrieve the previously stored root
});
<script src="//d3js.org/d3.v4.min.js"></script>
<svg></svg>
Probably, the most idiomatic way for doing this in D3 is using selection.each, which:
Invokes the specified function for each selected element, in order, being passed the current datum (d), the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]).
So, in your case:
circle.each(function(d){
//calculates the value just once for each datum:
var squareRoot = Math.sqrt(d)
//now use that value in the DOM element, which is 'this':
d3.select(this).attr("cx", squareRoot)
.attr("cy", squareRoot)
//etc...
});
Here is a demo:
var svg = d3.select("svg");
var data = d3.range(100, 1000, 100);
var ellipses = svg.selectAll(null)
.data(data)
.enter()
.append("ellipse")
.attr("fill", "gainsboro")
.attr("stroke", "darkslateblue")
.each(function(d) {
var squareRoot = Math.sqrt(d);
d3.select(this)
.attr("cx", function(d) {
return squareRoot * 3
})
.attr("cy", function(d) {
return squareRoot * 3
})
.attr("rx", function(d) {
return squareRoot + 3
})
.attr("ry", function(d) {
return squareRoot + 4
});
})
<script src="//d3js.org/d3.v4.min.js"></script>
<svg></svg>
Another common approach in D3 codes is setting a new data property in the first attr method, and retrieving it latter:
.attr("cx", function(d) {
//set a new property here
d.squareRoot = Math.sqrt(d.value);
return d.squareRoot * 3
})
.attr("cy", function(d) {
//retrieve it here
return d.squareRoot * 3
})
//etc...
That way you also perform the calculation only once per element.
Here is the demo:
var svg = d3.select("svg");
var data = d3.range(100, 1000, 100).map(function(d) {
return {
value: d
}
});
var ellipses = svg.selectAll(null)
.data(data)
.enter()
.append("ellipse")
.attr("fill", "gainsboro")
.attr("stroke", "darkslateblue")
.attr("cx", function(d) {
d.squareRoot = Math.sqrt(d.value);
return d.squareRoot * 3
})
.attr("cy", function(d) {
return d.squareRoot * 3
})
.attr("rx", function(d) {
return d.squareRoot + 3
})
.attr("ry", function(d) {
return d.squareRoot + 4
});
<script src="//d3js.org/d3.v4.min.js"></script>
<svg></svg>
PS: by the way, your solution with var aux will not work. Try it and you'll see.

D3 updating colors in co-occurence chart

https://hansardbrowser.s3.amazonaws.com/Cooccurmatrix/index%20%2812.12.2015%207.43.36%20PM%29.html
I am currently trying to recreate the co-occurrence matrix from the D3 Les Miserables demo.
I am able to add more variables to the "order" drop down list, and I am currently trying to add the code to change the color as well.
The current setting defaults to "party" as the color option, but how can I insert the new coloroption value into the .style code and refresh?
Original grouping change code:
d3.select("#order").on("change", function() {
clearTimeout(timeout);
order(this.value);
});
function order(value) {
x.domain(orders[value]);
var t = svg.transition().duration(2500);
t.selectAll(".row")
.delay(function(d, i) { return x(i) * 4; })
.attr("transform", function(d, i) { return "translate(0," + x(i) + ")"; })
.selectAll(".cell")
.delay(function(d) { return x(d.x) * 4; })
.attr("x", function(d) { return x(d.x); });
t.selectAll(".column")
.delay(function(d, i) { return x(i) * 4; })
.attr("transform", function(d, i) { return "translate(" + x(i) + ")rotate(-90)"; });
}
My attempt to add the color change function in (sorry i am extremely new to D3 and javascript)
d3.select("#coloroption").on("change", function() {
coloroption(this.value);
});
function coloroption(value) {
var cell = d3.select(this).selectAll(".cell")
.data(row.filter(function(d) { return d.z; }))
.enter().append("rect")
.attr("class", "cell")
.attr("x", function(d) { return x(d.x); })
.attr("width", x.rangeBand())
.attr("height", x.rangeBand())
.style("fill-opacity", function(d) { return z(d.z); })
.style("fill", function(d) { return nodes[d.x].(coloroption[value]) == nodes[d.y].(coloroption[value]) ? c(nodes[d.x].(coloroption[value]) ) : null; })
//.on("mouseover", mouseover)
// .on("mouseout", mouseout);
}
Not sure if this is the right way I should handle this
This .(coloroption[value]) is not valid javascript; you need to access the properties using bracket notation. The d3 portion of this is then a simple .selectAll with a style change:
d3.select("#coloroption").on("change", function() {
// get selected value from dropdown
var opt = this.options[this.selectedIndex].value;
svg.selectAll(".cell")
.style("fill", function(d){
// get it by bracket notation
return nodes[d.x][opt] == nodes[d.y][opt] ? c(nodes[d.x][opt]) : null;
})
});
Example here.

Moving fixed nodes in d3 force layout

I am using D3's forced layout to display a graph. Now, I require that the nodes change their positions when any node is clicked.
I looked up other related StackOverflow questions but that didn't help me.
The code for render is as follows :
var render = function(graph){
/* var loading = svg.append("text")
.attr("x", width / 2)
.attr("y", height / 2)
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text("Simulating. One moment pleaseā€¦");*/
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links);
//Enter phase for links.
link.enter().append("line");
//Update phase for links
link
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.selectAll(".node")
.data(graph.nodes,function(d){return d.name;});
//Enter phase for nodes
var node_g = node.enter()
.append("g")
.attr("class","node")
.on("dblclick",nodeClick)
.call(force.drag);
//Update phase for nodes
node_g.append("text")
.attr("class","NodeLabel")
.text(function(d){
return d.name;
});
var nodeCirlce = node_g.append("circle");
nodeCirlce
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
node_g.append("title")
.text(function(d) { return d.name; });
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node_g.attr("transform",function(d){
return "translate("+ d.x+","+ d.y+")";
});
//TODO : Add attr change for node text as well.
});
And the code for the node click handler looks like this :
var nodeClick = function(d,i){
//Translate the graph to center around the selected node.
var x_trans = x_cent - d.x;
var y_trans = y_cent - d.y;
var nodes = oldGraph.nodes;
for(var i=0;i<nodes.length;i++){
var node = nodes[i];
node.x = node.x + 1000;
node.y = node.y + 1000;
node.fixed = true;
}
//oldGraph.nodes = updateNodes(nodes,oldGraph.links);
render(oldGraph);
//setTimeout(function(){layout("json/HUMAN-1g.json");},000);
};
However, the node positions don't get updated.
After changing the data, you need to run the code that updates the positions in the DOM. This is exactly what you have in the tick event handler function:
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node_g.attr("transform",function(d){
return "translate("+ d.x+","+ d.y+")";
I recommend pulling this out into a separate function and then setting it as the tick handler function and calling it from your nodeClick() function. To be clear, you don't need to call render() from the nodeClick() function.

Bug in d3.js Stacked chart morphing

I've created a stacked chart animation/update app. However there appears to be NaN values being passed into the y and height variables. I am unsure as to what is wrong. If you toggle the data the charts eventually fill up.
jsFiddle
but the problem may occur first in setting the yaxis
svg.select("g.y")
.transition()
.duration(500)
.call(methods.yAxis);
It looks like something goes wrong in the bar rect enter/exit code.
//_morph bars
var bar = stacks.selectAll("rect")
.data(function(d) {
return d.blocks;
});
// Enter
bar.enter()
.append("rect")
.attr("class", "bar")
.attr("y", function(d) { return methods.y(d.y1); })
.attr("width", methods.x.rangeBand())
.style("fill", function(d) { return methods.color(d.name); });
// Update
bar
.attr("y", methods.height)
.attr("height", initialHeight)
.attr("width", methods.x.rangeBand())
.transition()
.duration(500)
.attr("x", function(d) { return methods.x(d.Label); })
.attr("width", methods.x.rangeBand())
.attr("y", function(d) { return methods.y(d.y1); })
.attr("height", function(d) { return methods.y(d.y0) - methods.y(d.y1); })
// Exit
bar.exit()
.transition()
.duration(250)
.attr("y", function(d) { return methods.y(d.y1); })
.attr("height", function(d) { methods.y(d.y0) - methods.y(d.y1); })
.remove();
//__morph bars
I've managed to narrow down the problem to the setDBlock function.
It appears if another chart has the same set of data, it takes on additional object parameters inside the dblock obj.
http://jsfiddle.net/XnngU/44/
I'm not sure at this stage as to how to clean it up. But I have isolated this via a legend and a function.
setDBlocks: function(incomingdata){
var data = incomingdata.slice(0);
methods.color.domain(d3.keys(data[0]).filter(function(key) { return key !== "Label"; }));
data.forEach(function(d) {
console.log("D", d);
var y0 = 0;
if(d["blocks"] == undefined){
d.blocks = methods.color.domain().map(function(name) {
var val = d[name];
if(isNaN(val)){
val = 0;
}
return {name: name, values: val, y0: y0, y1: y0 += +val};
});
}
d.total = d.blocks[d.blocks.length - 1].y1;
});
}
I've fixed the anomaly by deleting data in the update function. I'm not sure why though the data is not unique - it looks like if the data is the same - as the last chart - it gets modified accordingly and used again for its next chart. Is there a better way of cleaning this up - I've tried to keep objects unique and clean by cloning/splicing but maybe that is contributing towards the problem.
delete d.blocks;
delete d.total;
http://jsfiddle.net/XnngU/53/
update: function(data){
methods.el = this;
var selector = methods.el["selector"];
data.forEach(function(d) {
delete d.blocks;
delete d.total;
});
methods.animateBars(selector, data);
}

Categories