I am new to D3.js and want to make a treemap like this: http://bl.ocks.org/mbostock/4063582#index.html
What I am going to do is to insert new div into parent nodes.
...
d3.json("flare.json", function(error, root) {
var node = div.datum(root).selectAll(".node")
.data(treemap.nodes)
.enter().append("div")
// parent nodes have class name "first"
.attr("class", function(d) { return d.children ? "node first" : "node"; })
.call(position)
.style("background", function(d) { return d.children ? color(d.name) : null; })
.text(function(d) { return d.children ? null : d.name; });
// my code
var parents = d3.selectAll(".first")
.data(treemap.value(function(d) { return d.size; }).nodes) // ISSUE
.enter().append("div")
.text(function(d) { return d.name; });
d3.selectAll("input").on("change", function change() {
var value = this.value === "count"
? function() { return 1; }
: function(d) { return d.size; };
node
.data(treemap.value(value).nodes)
.transition()
.duration(1500)
.call(position);
});
});
...
But this doesn't work correctly.
HOW CAN I UPDATE ONLY THE PAREENT NODES BY ADDING NEW DIV TO THEM?
Could anybody help me please. Thanks a lot...
Related
I have a situation where I want users to browse 3 different data sets using circle pack layout. I am using Bostock's Zoomable circle packing.
I have added 3 options additionally which loads data and creates new nodes. Here chartid is passed from these elements.
function changeDataSet(chartid)
{
console.log(chartid);
//console.log(nodes);
//add new chart data depending on the selected option
if(chartid === "plevels")
{
root = JSON.parse(newKmap_slevels);
focus = root;
nodes = pack.nodes(root);
}
else if (chartid === "pduration")
{
root = JSON.parse(newKmap_sduration);
focus = root;
nodes = pack.nodes(root);
}
else
{
root = JSON.parse(newKmap_stype);
focus = root;
nodes = pack.nodes(root);
}
refresh();
}
Then I have the refresh function, but I am not understnading how to remove existting nodes, and add new ones based on the new data set. Showing some transition animations would be nice also.
Currently I am trying to remove and recreate the initial elements, but the chart goes blank when I do that.
var refresh = function() {
//var nodes = pack.nodes(root);
var duration = 10;
console.log(nodes);
d3.select("#conf_knowledge_map").selectAll("g")
.remove();
svg
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
circle = svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("class", function(d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; })
.style("fill", function(d) { return d.children ? color(d.depth) : null; })
.on("click", function(d) {
if (focus !== d) zoom(d), d3.event.stopPropagation();
});
text = svg.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("class", "label")
.style("fill-opacity", function(d) { return d.parent === root ? 1 : 0; })
.style("display", function(d) { return d.parent === root ? "inline" : "none"; })
.text(function(d) {
//console.log(d);
if( d.size )
{
return d.name + ":" + d.size;
}
else
return d.name;
});
}
So my question is how can I remove and then create new nodes on click?
UPDATE
I was able to remove all nodes and add the new nodes based on the new data, but now on click to zoom the layout is all messed up. The transform function is not applied to the new nodes somehow.
var refresh = function() {
svg.selectAll(".node").remove();
svg.selectAll(".label").remove();
var nodes = pack.nodes(root);
focus = root;
circle = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("class", function(d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; })
.attr("r", function(d) { return d.r;})
.attr("cx", function(d) {
console.log(d);
// if(d.depth === 0)
// return 0;
// else
// return d.x - (diameter/2);
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
// .attr("transform", "translate(" + "x" + "," + "y" + ")")
.style("fill", function(d) { return d.children ? color(d.depth) : null; })
.on("click", function(d) {
// d.x = d.x - (diameter/2);
// d.y = d.y - (diameter/2);
if (focus !== d) zoom(d), d3.event.stopPropagation();
});
text = svg.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("class", "label")
.attr("x", function(d) {return d.x - (diameter/2);})
.attr("y", function(d) {return d.y - (diameter/2);})
.style("fill-opacity", function(d) { return d.parent === root ? 1 : 0; })
.style("display", function(d) { return d.parent === root ? "inline" : "none"; })
// .attr("transform", "translate(" + "x" + "," + "y" + ")")
.text(function(d) {
//console.log(d);
if( d.size )
{
return d.name + ":" + d.size;
}
else
return d.name;
});
}
How can I transform the new nodes to be in place and for the zoom to work properly?
UPDATE 2
Now the transform is working properly after attaching 'g' element to the circles, and showing all the nodes and text correctly. The only problem now is that the zoom does not work when I click on the circles!!
circle = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("class", function(d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; })
.attr("r", function(d) { return d.r;})
.attr("cx", function(d) {
if(d.depth === 0)
return 0;
else
return d.x - (diameter/2);
})
.attr("cy", function(d) {
if(d.depth === 0)
return 0;
else
return d.y - (diameter/2);
})
.style("fill", function(d) { return d.children ? color(d.depth) : null;})
.on("click", function(d) {
if (focus !== d) zoom(d); d3.event.stopPropagation();
})
.append("g")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
;
How to make the zoom work??
It so happens that I was making the mistake of simultaneously creating three independent circle pack layouts, with mixed statements one after the other. This was problematic as when you have to select svg sections, different elements were all getting selected and wrongly attached with different events.
So I decided to separate the three implementations and create the three layouts one after the other, only after each one finished. This way I kept the section selection and attaching events etc, separate and then load them as and when required.
I don't really know svg and I want to select the text of this element (and just this element).
var text = svg.selectAll("text")
.data(nodes)
.enter()
.append("text")
.attr("class", "label")
.style("fill-opacity", function(d) { return d.parent === rootZC ? 1 : 0; })
.style("display", function(d) { return d.parent === rootZC ? "inline" : "none"; })
.text(function(d) { return d.name; });
To be clearer, I have some other text in my code and I want to apply a transition on this part and not on the text above.
I apply my transition like this
transition.selectAll("text")
but don't know. (work but remove the other text elements)
Then, how to select well my text? (I tried svg.text but that doesn't work).
Many thanks in advance for your help.
If you want to select this text you create here :
var text = svg.selectAll("text")
.data(nodes)
.enter()
.append("text")
.attr("class", "label")
.style("fill-opacity", function(d) { return d.parent === rootZC ? 1 : 0; })
.style("display", function(d) { return d.parent === rootZC ? "inline" : "none"; })
.text(function(d) { return d.name; });
I would just add a unique class for this text (for example sampleClass) :
.attr('class','label sampleClass'); //adding to the class you already have
Then you can select all elements like so :
var sampleClass = d3.selectAll('.sampleClass');
Then apply the translation like so :
sampleClass.attr('translate', 'transform('+sampleX+','+samplyY')';
So your code would look like :
var text = svg.selectAll("text")
.data(nodes)
.enter()
.append("text")
.attr("class", "label sampleClass")
.style("fill-opacity", function(d) { return d.parent === rootZC ? 1 : 0; })
.style("display", function(d) { return d.parent === rootZC ? "inline" : "none"; })
.text(function(d) { return d.name; });
var sampleClass = d3.selectAll('.sampleClass');
sampleClass.attr('translate', 'transform('+sampleX+','+samplyY')';
I am trying to modify this force directed graph, and in particular what I'd like to do is to draw text on each graph edge. I tried the following:
// add new links
path.enter().append('svg:path')
.attr('class', 'link')
.classed('selected', function(d) { return d === selected_link; })
.style('marker-start', function(d) { return d.left ? 'url(#start-arrow)' : ''; })
.style('marker-end', function(d) { return d.right ? 'url(#end-arrow)' : ''; })
.on('mousedown', function(d) {
if(d3.event.ctrlKey) return;
// select link
mousedown_link = d;
if(mousedown_link === selected_link) selected_link = null;
else selected_link = mousedown_link;
selected_node = null;
restart();
})
.append('svg:text').attr('x', 0).attr('y', 4)
.attr('class', 'id').text(function(d) { console.log(d.id); return d.id; })
Only the last line is my modification. I also added an id property to the link objects so the code I'm working with is not exactly what I've linked to. The log statement is printing as expected but no text or error messages appear. How can I append text right in the middle of each link?
You will need to use the svg group <g>, that will let you append elements inside.
var new_paths = path.enter().append('g'),
links = new_paths.append('svg:path').attr('class', 'link');
Be free to add whatever style or behaviour to links as you wish.
Then, for the labels, you will append again to the group:
var labels = new_paths.append('svg:text').attr('class', 'label');
And again you can add whatever you want to labels.
When you define the force, you will need to select the links and labels, wich will be something like this:
force.on("tick", function() {
links.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; });
labels.attr("transform",function(d){
return
"translate("+
(0.5*(d.source.x+d.target.x))+
","+
(0.5*(d.source.y+d.target.y))+
")";
});
});
Here is a demo fiddle http://jsfiddle.net/k2oyef30/
Update
Sorry, I didn't follow your example before. Here is your particular solution.
Change path to be defined as:
// handles to link and node element groups
var path = svg.append('svg:g').selectAll('.path'),
circle = svg.append('svg:g').selectAll('g');
Inside function tick() { you have to select the .links or the .id
path.select('.link').attr('d', function(d) {
and manage the new element:
path.select('.id').attr("transform",function(d){
return "translate("+
(0.5*(d.source.x+d.target.x))+
","+
(0.5*(d.source.y+d.target.y))+
")";
});
Inside function restart() { you have to create a <g> element, for example with class path, and append the elements into it.
// add new links
var newPaths = path.enter().append('svg:g').attr('class','path');
newPaths.append('svg:path')
.attr('class', 'link')
.classed('selected', function(d) { return d === selected_link; })
.style('marker-start', function(d) { return d.left ? 'url(#start-arrow)' : ''; })
.style('marker-end', function(d) { return d.right ? 'url(#end-arrow)' : ''; })
.on('mousedown', function(d) {
if(d3.event.ctrlKey) return;
// select link
mousedown_link = d;
if(mousedown_link === selected_link) selected_link = null;
else selected_link = mousedown_link;
selected_node = null;
restart();
});
newPaths.append('svg:text').attr('x', 0).attr('y', 4)
.attr('class', 'id').text(function(d) { return d.id; });
The problem with the example definition was that it already selected the <path> element making you unable to select the new <g class="path">
I would like to display text in leaf when parent is focused like this: http://i60.tinypic.com/25qxdzt.jpg. Here is my code which display text only when last parent is focused:
var text = svg.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("class", "label")
.style("fill-opacity", function(d) { return d.parent === root ? 1 : 0; })
.style("display", function(d) { return d.children ? null : "none"; })
.text(function(d) { return d.name; });
I am a bit new to D3 and still have some problems with understanding it.
I am using this tutorial Zoomable Circle Packing:
However, I don't know how to load multiple data sets.
For example I need something like (you can see on jsfiddle) but when the button is pressed, a different .JSON file is loaded (the names in both files are the same, but values are different).
The solution might be "Thinking with Joins" by mbostock but I really dont know how to use it.
Any help would be appreciated.
You can use function to call loading of json file like this:
var callJson = function (json) {
d3.json(json, function(error, root) {
if (error) return console.error(error);
svg.selectAll("circle").remove();
svg.selectAll("text").remove();
var focus = root,
nodes = pack.nodes(root),
view;
var circle = svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("class", function(d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; })
.style("fill", function(d) { return d.children ? color(d.depth) : null; })
.on("click", function(d) { if (focus !== d) zoom(d), d3.event.stopPropagation(); });
var text = svg.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("class", "label")
.style("fill-opacity", function(d) { return d.parent === root ? 1 : 0; })
.style("display", function(d) { return d.parent === root ? null : "none"; })
.text(function(d) { return d.name; });
var node = svg.selectAll("circle,text");
d3.select("body")
.style("background", color(-1))
.on("click", function() { zoom(root); });
zoomTo([root.x, root.y, root.r * 2 + margin]);
function zoom(d) {
var focus0 = focus; focus = d;
var transition = d3.transition()
.duration(d3.event.altKey ? 7500 : 750)
.tween("zoom", function(d) {
var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2 + margin]);
return function(t) { zoomTo(i(t)); };
});
transition.selectAll("text")
.filter(function(d) { return d.parent === focus || this.style.display === "inline"; })
.style("fill-opacity", function(d) { return d.parent === focus ? 1 : 0; })
.each("start", function(d) { if (d.parent === focus) this.style.display = "inline"; })
.each("end", function(d) { if (d.parent !== focus) this.style.display = "none"; });
}
function zoomTo(v) {
var k = diameter / v[2]; view = v;
node.attr("transform", function(d) { return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")"; });
circle.attr("r", function(d) { return d.r * k; });
}
});
};
... and then you call it with callJson("flare.json");
Here is working example with multiple json files - http://bl.ocks.org/chule/74e95deeadd353e42034