d3.js zoomable circle packing: change radius for specific circles - d3.js

I am using the example Zoomable Circle Packing by Mike Bostock and I try to change the radius of some circles, which are identified by the type "secteur" in the dataset.
To do that, I tried to use the following line:
circle.filter(function(d) { return d.type === "secteur"; }).attr("r", function(d) { return d.r * 1.5 ; });
The example is available here.
Even without filtering by datum type (I tried to remove the .filter part) the line seems to have no effect on any radiuses, I didn't see any changes in the console.
I do not understand why, and I would appreciate insights if any
Thank you

Your zoomTo function set's the radius again after you set it with your filter.
You call:
zoomTo([root.x, root.y, root.r * 2 + margin]);
Which does:
circle.attr("r", function(d) { return d.r * k; });

Related

How to dynamically translate an SVG group in D3.js?

I'am working on a bubble-chart using d3. Now there should be an arrow as a graphical asset below the text elements. What I wan to achieve is a dynamic positioning of the arrow-group having a defined gap between itself and the last text:
I've already tried to position it with a percentage value:
arr.append("g").attr("class", "arrow").style('transform', 'translate(-1%, 5%)');
Which does not give the effect I want to.
I also tried to insert a dynamic value based on the radius of the circles which is simply not working and I don't know why:
arr.append("g")
.attr("class", "arrow")
.attr('transform', function(d, i) { return "translate(20," + d.r / 2 + ")");});
OR
arr.append("g")
.attr("class", "arrow")
.style('transform', (d, i) => 'translate(0, ${d.r/2})');
Please find my pen here.
Thank you very much in advance!
Ok.. solved it! For everyone who is interested or having the same trouble:
My last attempt was nearly correct but I was not able to transform via .style(...). I had to use .attr(...) like this:
arr.append("g")
.attr("class", "arrow")
.attr('transform', (d, i) => translate(0, ${d.r/2})');

D3 - Stacked bar chart dual axis with line chart

I have a stackblitz here - https://stackblitz.com/edit/d3-stacked-trend-bar-positioned-months-4sqvwd?embed=1&file=src/app/bar-chart.ts&hideNavigation=1
I'm using D3 to create a stacked bar chart in Angular
I now also need to have a line graph on the same chart.
I think the best way to do this is with a dual axis.
I have the second axis working but can't get the line to work.
Can anyone point me the direction to get this working
The line function (valueline in your case) doesn't seem like its defined correctly as it's missing the accessor functions. Here are the docs for the same.
I couldn't fork your code but here's a snippet (containing the drawLine method) you can try:
private drawLine(linedata:any){
var that = this;
var valueline = d3.line().x(function(d, i) {
return that.x1(d.date);
// return that.x0(d.date) + 0.5 * that.x0.bandwidth();
}).y(function(d) {
return that.y1(d.value);
});
this.x1.domain(this.data.map((d:any)=>{
return d.date
}));
this.y1.domain(d3.extent(linedata, function(d) {
return d.value
}));
this.lineArea.append("path")
.data([linedata])
.attr("class", "line").style('stroke-width', 2)
.attr("d", valueline);
}
It works and I also have included a commented line for the x attribute which matches the way you're offsetting the bars. And another suggestion would be to use the same x0 scale as the newly defined x1 has the same domain as x0. Hope this helps.

D3 data binding [D3js in Action]

I'm new to d3.js, and am working my way through the book "D3.js in action". So far I have been able to figure out all the questions I had, but this one I can't completely answer on my own, it seems.
I post the source code from the book here, since it is available on the books website and the authors homepage. This is the bl.ocks: http://bl.ocks.org/emeeks/raw/186d62271bb3069446b5/
The basis idea of the code is to create a spreadsheet-like layout out of div elements filled with fictious twitter data. Also implemented is a sort function to sort the data by timestamp and reorder the sheet. As well as a function to reestablish the original order.
Here is the code (I left out the part where the table structure is created, except the part where the data is bound):
<html>
<...>
<body>
<div id="traditional">
</div>
</body>
<footer>
<script>
d3.json("tweets.json",function(error,data) { createSpreadsheet(data.tweets)});
function createSpreadsheet(incData) {
var keyValues = d3.keys(incData[0])
d3.select("div.table")
.selectAll("div.datarow")
.data(incData, function(d) {return d.content})
.enter()
.append("div")
.attr("class", "datarow")
.style("top", function(d,i) {return (40 + (i * 40)) + "px"});
d3.selectAll("div.datarow")
.selectAll("div.data")
.data(function(d) {return d3.entries(d)})
.enter()
.append("div")
.attr("class", "data")
.html(function (d) {return d.value})
.style("left", function(d,i,j) {return (i * 100) + "px"});
d3.select("#traditional").insert("button", ".table")
.on("click", sortSheet).html("sort")
d3.select("#traditional").insert("button", ".table")
.on("click", restoreSheet).html("restore")
function sortSheet() {
var dataset = d3.selectAll("div.datarow").data();
dataset.sort(function(a,b) {
var a = new Date(a.timestamp);
var b = new Date(b.timestamp);
return a>=b ? 1 : (a<b ? -1 : 0);
})
d3.selectAll("div.datarow")
.data(dataset, function(d) {return d.content})
.transition()
.duration(2000)
.style("top", function(d,i) {return (40 + (i * 40)) + "px"});
}
function restoreSheet() {
d3.selectAll("div.datarow")
.transition()
.duration(2000)
.style("top", function(d,i) {return (40 + (i * 40)) + "px"});
}
}
</script>
</footer>
</html>
What I don't fully understand is how sortSheet and restoreSheet work.
This part of sortSheet looks like it rebinds data, but after console logging I think it doesn't actually rebind data to the DOM. Instead it just seems to redraw the div.tablerow elements based on the array index of the sorted array.
But then what purpose does the key-function have?
And why is the transition working? How does it know which old element to put in which new position?
EDIT:
---After some more reading I now know that selectAll().data() does indeed return the update selection. Apparenty the already bound data identified by the key function is re-sorted to match the order of the keys in the new dataset? Is that correct?
So the update selection contains the existing div.datarow s, but in a new ordering. The transition() function works on the new order, drawing the newly ordered div.datarow s beginning with index 0 for the first element to determine its position on the page, to index n for the last element. The graphical transition then somehow (how? by way of the update selection?) knows where the redrawn div.datarow was before and creates the transition-effect.
Is that correct so far?---
d3.selectAll("div.datarow")
.data(dataset, function(d) {return d.content}) //why the key function?
.transition()
.duration(2000)
.style("top", function(d,i) {return (40 + (i * 40)) + "px"});
And what happens when the original order is restored? Apparently during both operations there is no actual rebinding of data, and the order of the div.datarows in the DOM does not change. So the restore function also redraws the layout based on the array index.
But what kind of selection does the .transition() work on? Is it an update? It is an update.
And why does the drawing using the index result in the old layout? Shouldn't the index of the DOM elements always be 0,1,...,n? I think it is. Apparently the old page layout is redrawn, with the DOM never having changed. But how can the transition() function create the appropriate graphical effect?
function restoreSheet() {
d3.selectAll("div.datarow")
.transition()
.duration(2000)
.style("top", function(d,i) {return (40 + (i * 40)) + "px"});
}
I have been thinking for hours about this, but I can't find the correct answer I think.
Thanks for your help!
It all becomes clear when you understand where all these functions were called: inside the json function, where the data was originally bound. When a button calls the sortSheet function, a new array of objects is made and bound to the rows. The transition simply starts with the original order and move the rows according to the new order of the objects inside the array.
And what happens when the original order is restored?
Now comes the interesting part: restoreSheet is called inside the json function and has no access to the dataset variable. So, the data restoreSheet uses is the original data. Then, a transition simply moves the rows according to the order of the objects inside the original array.
I just made a fiddle replicating this: https://jsfiddle.net/k9012vro/2/
Check the code: I have an array with the original data. Then, a button called "sort" creates a new array.
When I click "original" the rectangles move back to the original position. But there is nothing special in that function, no new data being bound:
d3.select("#button1").on("click", function(){
rects.transition()
.duration(500).attr("x", function(d, i){ return i * 30})
});
It moves all the rectangles to the original positions because this function uses the same original data.

d3 adding drag and drop to packed circles

I am trying to add the ability to drag a circle from within one parent into another using this as the starting point: https://bl.ocks.org/mbostock/7607535
I've added this code:
var drag = d3.behavior.drag()
.on("drag", function(d,i) {
d.x += d3.event.dx
d.y += d3.event.dy
d3.select(this).attr("transform", function(d,i){
return "translate(" + [ d.x,d.y ] + ")"
})
});
and then tried attaching it like this:
svg.selectAll("circle").call(drag);
or
node.call(drag);
I've two problems, one is positioning the circle correctly as I drag (which I think is because I need to save the origin) and the other one is how to select a sub circle to drag as it is assuming I want the parent circle. Ideally I'd like to be able to select any circle and be able to drop it into any other circle. Is there a way of attaching the drag behaviour so this just works or do I need to look at the data structure in order to work out the lowest level circle I could be trying to drag?
There is a lot going on in the Zoomable example and some of it is colliding with your intent. The quick fixes:
One is positioning the circle correctly as I drag (which I think is because I need to save the origin).
Positioning the circle will require an update during the ondrag event. Here is what I used:
var drag = d3.behavior.drag()
.on("drag", function(d, i) {
d.x += d3.event.dx;
d.y += d3.event.dy;
draw();
});
function draw() {
var k = diameter / (root.r * 2 + margin);
node.attr("transform", function(d) {
return "translate(" + (d.x - root.x) * k + "," + (d.y - root.y) * k + ")";
});
circle.attr("r", function(d) {
return d.r * k;
});
}
This draw function replaces the zoomTo function from the Zoomable example. Getting zoom to work with dragging is possible but it requires some extra thought.
The other one is how to select a sub circle to drag as it is assuming I want the parent circle.
The Zoomable example has this CSS:
.label,
.node--root,
.node--leaf {
pointer-events: none;
}
If you want to target the leaf nodes, you will have to remove the .node--leaf portion of the CSS rule. You can also add a new rule to turn off events for the non-leaf nodes:
.attr("class", function(d) {
return d.parent ? d.children ? "node node--middle" : "node node--leaf" : "node node--root";
})
Note the addition of node--middle.
Ideally I'd like to be able to select any circle and be able to drop it into any other circle. Is there a way of attaching the drag behaviour so this just works or do I need to look at the data structure in order to work out the lowest level circle I could be trying to drag?
You can drag it around without much trouble but the drag behavior will not automatically propagate changes into the data structure. If you want the actual hierarchy to change then there are a few extra steps required:
Detect where the leaf node ended
Change the parent of the leaf node appropriately
Recalculate the entire packing
Redraw/animate the new packing
Step 1 will be the tricky one. I do not have an example handy for helping with this. You can quickly detect the location that a drag event ended using the dragstop event — the trick will be figuring out which node is underneath that stopping point.
It's not allowing you to drag the leaf-most children nodes because there's a css style with pointer-events: none applied to those nodes, so they never trigger drag. So you need to take out some or all of these classes:
.label,
.node--root,
.node--leaf {
pointer-events: none;
}
That'll make it possible to drag the children, but it will not prevent the ability to drag the parents. If you don't want the parents to be draggable, you have to NOT .call(drag) on them. The way to do it is to call drag on a subset of all circles, using filter.
So, after creating the circles, you can do this:
circle
.filter( function(d) { return d.children ? false : true; } )
.call(drag);
That will apply drag only to things that do not have children.
You are right about the incorrect positioning during drag being caused by not knowing the origin. I'm pretty sure you need to save the original circle position on dragstart and then add d3.event.dx to that position during drag.

d3 append and enter issues

So I am having a problem when following a very simple d3 tutorial. Essentially I am attempting to add SVG circle elements by binding data to the current svg circle elements then calling enter.append As shown below:
var svg =d3.select("body").selectAll("svg");
var circle = svg.selectAll("circle");
var w=window.innerWidth;
console.log(circle);
circle.data([500,57,112,200,600,1000]);
circle.enter().append("circle")
.attr("cy",function(d) {
return (d)
})
.attr("cx", function(i){
return (i*100)
})
.attr("r", function(d) {
return Math.sqrt(d);
})
Which seems like it would add 3 new circle elements (because I already have 3 created). However, instead of getting these 3 new circle elements added, I am running into this error:
Uncaught TypeError: Object [object SVGCircleElement],[objectSVGCircleElement],[object SVGCircleElement] has no method 'enter'
I have done essentially the same thing with paragraphs, and it seems to work fine:
var p =d3.select("body").selectAll("p")
.data([4, 8, 15, 16, 23, 42])
.text(function(d) { return "I'm number " + d + "!"; });
//Enter
p.enter().append("p")
.text(function(d) { return "I'm number " + d + "!"; })
.style("color", function(d, i) {
return i % 2 ? "#000" : "#eee";
});
However as soon as I try to add SVG Elements into it, I continue to get the same error.
It seems like there should be just a syntax error or something, but I have gone through the code 5 trillion times, and can't find anything.
Any help is much appreciated, and thank you before hand for your time.
Isaac
You want to call enter on the result of circle.data instead of circle itself.
var circle = svg.selectAll("circle");
circle.data([500,57,112,200,600,1000]).enter().append("circle");
You did this correctly in your p example by storing the return of data in the p variable. Whereas in your circle example, you are storing the return of d3.select.selectAll in your circle variable.

Resources