I'm trying to build an interactive d3.js sankey chart that allows the user to do two things:
Drag each node across the x,y axes
Click an 'Update' button that randomises the values of each link and updates the sankey chart accordingly
The current status of my code is as follows:
The user is able to drag each node without issue
If the user has not dragged any nodes, the 'Update' button works without issue
However, if the user tries to click the 'Update' button after dragging a node, then the nodes and links become detached as the sankey has seemingly forgotten where the nodes were previously
My question is, is it possible to force a sankey recalculation that takes into account the 'new' position of each dragged node? If so, how would that be done?
My current code is here: https://plnkr.co/edit/F03OYn8e457NZZbD?preview
Snippet of the update() function in question
function update() {
//randomise the link values
sankeyData.links.forEach(function (link) {
link.value = Math.max(Math.ceil(Math.random() * 10), 1)
})
//rerun sankey
sankeyData = sankey(sankeyData)
//update node positions
d3.selectAll(".sankey-node")
.transition()
.duration(1000)
.attr("x", function (d) {
return d.x0;
})
.attr("y", function (d) {
return d.y0;
})
.attr("height", function (d) {
return d.y1 - d.y0;
})
//this only updates the y0 and y1 coords of each link?
sankey.update(sankeyData)
//update link positions
d3.selectAll(".sankey-link")
.transition()
.duration(1000)
.attr("d", d3.sankeyLinkHorizontal())
.style("stroke-width", function (d) {
return Math.max(1, d.width);
})
}
Related
I have two d3 visualizations, and when I click on a link on the sankey diagram (defined by an email), I want to change the radius of the corresponding node (with the same email) on the force directed network graph. No matter what I try or search on the internet I cannot find a way to adjust the attribute of one individual node without selecting all the circles.
var link = svg.append("g").selectAll(".link")
.data(graph.links)
.enter().append("path")
.attr("class", "link")
.on("click", function (d,i){
svg2.selectAll("circle").each(function (f,i){
if (f.id == d.email)
{
//CHANGE RADIUS OF F or color or anything
//svg2.select(f).attr("r",10); DOESNT WORK
//f.attr("r",10) DOESNT WORK
}
});
})
.attr("d", path)
.style("stroke-width", function(d)
{ return Math.max(0.2, d.dy); })
.sort(function(a, b) { return b.dy - a.dy; });
I have two identical charts. The graphics for them are built like so:
circles.enter().append("circle")
.attr("r", 0)
.attr("fill", function(d) { return fill_color; })
.attr("class", function(d) { return "circle_" + d.id; })
.on("mouseover", function(d, i) { build_tooltip(d, i, this); })
.on("mouseout", function(d, i) { hide_tooltip(d, i, this); });
On mouseover, it triggers the following function:
build_tooltip = function(data, i, element) {
var content = "Title: " + data.title;
show_tooltip(content, d3.event);
}
My question is: How can I make it so mousing over a circle in Chart #1 triggers the same mouseover event in Chart #2, but with unique data for each chart? Chart #2 must generate its own set of data (in this example, just a title). So, how can I make Chart #2's mouseover event fire whenever Chart #1's does?
In jQuery, this would be quite simple -- there is a literal .trigger() event. But how can I go about accomplishing the same with D3?
Have you tried using D3's dispatch? If not, see through this example for more details on how to use it.
I'm testing a d3js treemap from a blog. Please see the live jsbin here.
I want to control the filling color of each rect for each small area. I don't know where can I control the color of rect. I found the following part is setting the color.
childEnterTransition.append("rect")
.classed("background", true)
.style("fill", function(d) {
return color(d.parent.name);
});
I try to remove the fill or change the color, the filling color is not working. For example I want to change all rect fill color to FFF, it is not working at all.
childEnterTransition.append("rect")
.classed("background", true)
.style("fill", function(d) {
return '#FFF';
});
You're setting the fill colour twice three times -- once in the "enter" chain for new elements, and then again in the "update" chain for all elements, and then a third time during the zoom transition. If you're only changing one of those pieces of code, the others may be replacing your setting.
Enter code (from your bl.ocks page):
childEnterTransition.append("rect")
.classed("background", true)
.style("fill", function(d) {
return color(d.parent.name); //change this
});
Update code: You can probably delete the entire update chain and just use the zoom function to update the values to the current zoom.
childUpdateTransition.select("rect")
.attr("width", function(d) {
return Math.max(0.01, d.dx);
})
.attr("height", function(d) {
return d.dy;
})
.style("fill", function(d) {
return color(d.parent.name); //change this
});
Zoom code:
zoomTransition.select("rect")
.attr("width", function(d) {
return Math.max(0.01, (kx * d.dx));
})
.attr("height", function(d) {
return d.children ? headerHeight : Math.max(0.01, (ky * d.dy));
})
.style("fill", function(d) {
return d.children ? headerColor : color(d.parent.name); //change this
});
Also, just to nitpick: Your "enter" selection (childEnterTransition) isn't actually a transition. If it was, there would be no point to setting the colour there and then re-setting it in update, because the update transition would just cancel the earlier transition. But because it isn't a transition, setting the colour there creates a starting value for the entering elements before you transition all the elements to the current value.
While I've seen this question asked a few times, I'm having a bit trouble implementing. What I'd like to do is have the label attribute centered within each circle (as mentioned here). I believe I'd be adding the text attribute to:
canvas.selectAll('circles')
.data(nodes)
.enter()
.append('svg:circle')
.attr('cx', function (d) {
return d.x;
})
.attr('cy', function (d) {
return d.y;
})
.attr('r', function (d) {
return d.r;
})
.attr('fill', function (d) {
return d.color;
});
But am confused on why the instructions they gave in the previous example I linked to doesn't work with the setup I currently have. I believe it's the pack option that could be throwing me off (about the difference between the two), but any further examples would be a huge help. Thanks!
Update
Thanks for the answers/suggestions, I updated the Codepen with my progress (as I needed two lines of data; should have clarified) which seems to be working well. Now this is packing into a circle - at the end of the day, I'd love for this to be packed in the actual #canvas width/height (which is a rectangle). I saw this treemap example - would that be what I'm going for here?
Demo of what I have so far
Perhaps the confusion is that you can't add labels to the circle selection (because in SVG, a circle element can't contain a text element). You need to either make a g element that contains both circle and text, or a separate selection for the text, e.g.:
canvas.selectAll('text')
.data(nodes)
.enter()
.append('svg:text')
.attr('x', function (d) {
return d.x;
})
.attr('y', function (d) {
return d.y;
})
// sets the horizontal alignment to the middle
.attr('text-anchor', "middle")
// sets the vertical alignment to the middle of the line
.attr('dy', '0.35em')
.text(function(d) {
return d.label;
});
See the updated demo: http://codepen.io/anon/pen/djebv
I'm working on building a molecule creator in D3 using the original example created by Mike Bostock: http://bl.ocks.org/mbostock/3037015
I'm using a force layout just like in the example. My problem is when I add a new Node/atom dynamically, it doesn't move with the rest of the graph. I've read other questions and implemented everything that is suggested and have made sure that I'm following the proper update/join procedure that D3 requires and yet still the added carbon refuses to move with the rest of the graph.
Here is my update/create function:
function buildMolecule () {
// Update link data
link = link.data(links);
// Create new links
link.enter().append("g")
.attr("class", "link")
.each(function(d) {
d3.select(this)
.insert("line", ".node")
.style("stroke-width", function(d) { return (d.bond * 2 - 1) * 2 + "px"; });
d3.select(this)
.filter(function(d) { return d.bond > 1; }).append("line")
.attr("class", "separator");
d3.select(this)
.on("click", bondClicked);
});
// Delete removed links
link.exit().remove();
// Update node data
node = node.data(nodes);
// Create new nodes
node.enter().append("g")
.attr("class", "node")
.on("click", atomClicked)
.each(function(d) {
console.log('d:', d);
// Add node circle
d3.select(this)
.append("circle")
.attr("r", function(d) { return radius(d.size); })
.style("fill", function(d) { return color(d.atom); });
// Add atom symbol
d3.select(this)
.append("text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.atom; });
d3.select(this).call(force.drag);
});
// Delete removed nodes
node.exit().remove();
force.start();
}
JsFiddle: http://jsfiddle.net/2dPMF/1/
Any help would be greatly appreciated!
You're modifying the data structures of the nodes and links beyond adding and deleting nodes, which messes up the force layout. I'm talking about these lines in particular.
function bigBang () {
links = links.concat(linksList);
nodes = nodes.concat(nodesList);
buildMolecule();
}
The first two lines in that function are what I'm talking about. Working jsfiddle here.