I'm stuck with this in the past 2 weeks. I've been browsing all StackOverflow questions about updating the pack layout and similars and I read the d3js documentation about it and the update pattern, but none of them solve this problem and I really can't append a new element to the pack layout without redrawing everything.
Demo: http://jsfiddle.net/v74qp3a7/1/
var diameter = 300;
// The layout I'm using now
var pack = d3.layout.pack()
.size([diameter - 4, diameter - 4])
.value(function(d) { return 1 });
// The basic container
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(2,2)");
// My initial data source
var data = {
name: "Languages",
children: [{
name: "Functional",
children: [
{ name: "OCaml" },
{ name: "Haskell" },
{ name: "Erlang" }
]
}, {
name: "Imperative",
children: [
{ name: "BASIC" },
{ name: "Clipper" }
]
}]
};
// The node basis
var node = svg.datum(data).selectAll(".node")
.data(pack.nodes)
.enter().append("circle")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.attr("r", function(d) {
return d.r;
});
function addToRoot(obj) {
// Modify the current data object
data.children.push(obj);
// I try to modify by entering the node and applying a transition
svg.datum(data).selectAll(".node")
.data(pack.nodes)
.enter().append("circle")
.transition()
.duration(500)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.attr("r", function(d) {
return d.r;
});
}
d3.select(self.frameElement).style("height", diameter + "px");
I read about enter and exit, but I didn't figure out how I can update the data object (not the data properties, that is pretty simple). I've been trying to use the three update patterns and got no results.
How can I add a simple element and update the graphical interface without redrawing everything when I call addToRoot({ name: "foo" })?
First, your selector relies on a class that you're never assigning to the circles in question. Hence, your selection will always be empty and simply add everything again. Second, you need to handle the update selection as well, not just the enter selection.
var sel = svg.datum(data).selectAll(".node")
.data(pack.nodes);
sel.enter().append("circle")
.classed("node", true);
sel
.transition()
.duration(500)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.attr("r", function(d) {
return d.r;
});
Complete demo here.
Related
I have this chart:
The symbol names, '$TSLA' and '$AAPL' are not appended to the line, they are simply placed there with the appropriate x and y values. I would like to append them to the line, such that if the line ended at a different position, the symbol name would appear next to it. Like in this example.
Here is the code:
var svg = d3.select('#graph')
.append('svg')
.attr('width', w)
.attr('height', h);
svg.append('path')
.datum(data)
.attr('class', 'stock')
.attr('stroke', '#157145') ....
//I have included the above code because it is what's different from the link-
//my lines are appended to the variable 'svg'. I am, however, selecting the correct class
//in the below code:
....
var sec = d3.selectAll(".stock")
.data(kvals) //this data follows the same pattern as the examples'
.enter().append("g")
.attr("class", "stock");
sec.append("text")
.datum(function(d){
return {
name: d.name,
value: d.values[d.values.length-1]
};
})
.attr("transform", function(d){
console.log(xScale(d.value.date)); //not displayed in console.
return "translate(" + xScale(d.value.date) + "," + yScale(d.value.stock) +")"
})
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d){
return d.name;
});
Thank you for your help. If you need all the code, I can paste it.
Here's the code I used to add the labels. First part (knames) is what I used to plot the line too. (there might be an easier way of doing this):
var knames = d3.scaleOrdinal();
knames.domain(Object.keys(data[0]).filter(function(key){ //only get non-sma values
return !key.match(/([_])|(d)|(band)\w+/g)
}));
var kvals = knames.domain().map(function(name){
return {
name: name,
values: data.map(function(d){
return {
date: d.date,
stock: d[name]
};
})
};
});
var sec = svg.selectAll("stock")
.data(kvals)
.enter().append("g")
.attr("class", "stock");
sec.append("text")
.datum(function(d){
return {
name: d.name,
value: d.values[d.values.length-1]
};
})
.attr("transform", function(d){
return "translate(" + xScale(d.value.date) + "," + yScale(d.value.stock) +")"
})
.attr("x", 7)
.attr("dy", ".3em")
.style("fill", function(d) {
return accent(d.name);
})
.style('font-family', 'Helvetica')
.style('font-size', '11px')
.style('letter-spacing', '1px')
.style('text-transform', 'uppercase')
.text(function(d){
return d.name;
});
I'm trying to use a visualization as a selector on a D3 costum chart. I'm following the SDK documentation Here, and I can't make my example work.
Basicly I star by declaring "me" var and enable the "use as filter" option.
var me = this;
this.addUseAsFilterMenuItem();
Then, when appending de svg element, I add the clear and end selecion methods:
var g = d3.select(this.domNode).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.on("click", function(d) {
if (event.target.classList.contains('bar')) {
me.clearSelections();
me.endSelections();
return true;
} else {
return true;
}
});
When getting data I use the hasSelection attribute:
var data = this.dataInterface.getRawData(mstrmojo.models.template.DataInterface.ENUM_RAW_DATA_FORMAT.TREE, {
hasSelection: true
}).children;
And when adding the "applyselection" method on my bars:
g.selectAll(".bar")
.data(data)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", function(d) {
return x(d.name);
})
.attr("y", function(d) {
return y(d.value);
})
.attr("height", function(d) {
return height - y(d.value);
})
.attr("width", x.rangeBand())
.style("fill", function(d) {
})
.on("click", function(d) {
me.applySelection(d.selection);
});
But it does not work. I manage to console d.selection on the bar click event, i it's undifined.
Can some one please give me a hand on this?
Thank you.
I was able to find out what was wrong with my code, after spending many many hours on this. The selection method must be called like this:
.on("click", function(d, i) {
me.applySelection(data[i].attributeSelector);
return true;
});
Sorry for the silly question, I'm just a poor d3 newbie...
I have the following demo bubble diagram on JSFiddle: what I am trying to achieve is to increase the radius of the circles whenever I click over them and, after that, to adjust the pack layout accordingly.
This is the code in JSFiddle for your convenience:
text {
font: 20px sans-serif;
}
<!DOCTYPE html>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
var root = {
"name": "Root",
"children": [
{
"name": "Leaf One",
"children": null,
"size": 1
},
{
"name": "Leaf Two",
"children": null,
"size": 1
},
{
"name": "Leaf Three",
"children": null,
"size": 1
}
],
"size": 1
};
var diameter = 400,
format = d3.format(",d"),
color = d3.scale.category20c();
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.padding(1.5);
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.attr("class", "bubble");
// not needed in JSfiddle, data is hard-coded:
// d3.json("data.json", function(error, root) {
// if (error) throw error;
var node = svg.selectAll(".node")
.data(bubble.nodes(classes(root))
.filter(function(d) { return !d.children; }))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("title")
.text(function(d) { return d.className + ": " + format(d.value); });
node.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return color(d.packageName); });
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.className.substring(0, d.r / 3); });
node.on("click", function(e, i){
var circle = svg.select("circle");
circle.attr("value", function(d) {
d.value += 1;
return d.value;
});
circle.attr("r", function(d) {
d.r += 1.0;
return d.r;
});
});
// });
// Returns a flattened hierarchy containing all leaf nodes under the root.
function classes(root) {
var classes = [];
function recurse(name, node) {
if (node.children) node.children.forEach(function(child) { recurse(node.name, child); });
else classes.push({packageName: name, className: node.name, value: node.size});
}
recurse(null, root);
return {children: classes};
}
d3.select(self.frameElement).style("height", diameter + "px");
</script>
I tried to do that via the node.on("click", ...) method but I got somehow stuck, as the modified circle is always the first one: what is the best way to select the circle I clicked?
Besides, how can I force the d3 pack layout to refresh after I modify the circle radius?
first, you have to remember that in event handlers the this is bound to the current DOM element (in out case - the <g>). so you can select clicked <g> by d3.select(this), and then select the circle within:
var circle = d3.select(this).select("circle");
(by just doing svg.select("circle") you select the first circle in the DOM, which always happens to be the same one)
in order to refresh the layout you have to update the underlying data, recalculate layout, recompute data join, and update values:
// store the underlying data in a value
var classedRoot = classes(root);
// ...your node creating code here
node.on("click", function(d) {
// update the data in classedRoot
d.value += 1;
// recalculate layout
var data = bubble.nodes(classedRoot);
// recompute data join
node.data(data, function(d) { return d.className; }) // use key function
// update values
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.select("circle")
.attr("r", function(d) { return d.r });
});
note the use of key function here to properly bind updated data with the circles already present. the value you use must be unique for all circles. i used className here, but if it's not unique, you'll have to use some kind of Id.
here's updated jsFiddle
I'm having some problems while updating a pack layout that is composed by a circle inside a g element, where the translation is controled by g and the radius is controled by the circle:
<g transform="translate(1,1)"><circle r="1"></circle></g>
The update was possible with the translation controlled by the circle, but I'm having the wrong x, y and r with a g as a container, as in demo.
var diameter = 300;
function translate(x, y) {
return "translate(" + x + "," + y + ")";
}
// The layout I'm using now
var pack = d3.layout.pack()
.size([diameter - 4, diameter - 4])
.value(function(d) { return 1; });
// The basic container
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(2,2)");
// My initial data source
var data = {
name: "Languages",
children: [{
name: "Functional",
children: [
{ name: "OCaml" },
{ name: "Haskell" },
{ name: "Erlang" }
]
}, {
name: "Imperative",
children: [
{ name: "BASIC" },
{ name: "Clipper" }
]
}]
};
(window.update = function() {
// Modify the current data object
data.children.push({
name: "NEW ELEMENT " + Math.floor(Math.random() * 100)
});
// Select *ALL* elements
var selection = svg.datum(data).selectAll(".node")
.data(pack.nodes);
// Select *ONLY NEW* nodes and work on them
selection
.enter()
.append("g")
.classed("node", true)
.append("circle")
.style("fill", "black");
// Recompute *ALL* elements
// Here the results aren't consistent, I always get the wrong translation
// and radius, therefore, the elements are badly positioned
selection
.transition()
.duration(500)
.attr("transform", function(d) {
return translate(d.x, d.y);
})
.selectAll("circle")
.attr("r", function(d) {
return d.r;
});
})();
// Set the height
d3.select(self.frameElement).style("height", diameter + "px");
I see I'm selecting the correct elements, but why I'm having the wrong results whenever I call update?
Well, the code is mostly correct, but selectAll("circle") will select all the circle elements and apply the radius of the inner element for all of them. We need to select just the inner circle, therefore, select("circle") solves the question (demo).
I am trying to implement an extended version of the Grouped Bar Chart. Everything works fine, except updating the plot. In a regular bar chart, I would do something like this:
function draw(data) {
// Join the data with the selection
var bars = svg.selectAll(".bar")
.data(data);
// Create new bars if needed
bars.enter().append("rect")
...
// Update existing bars if needed
bars.transition()
...
// Remove bars if needed
bars.exit().remove();
}
Works like a charm. Now i tried the same with my groups:
var groups = chart.selectAll(".group")
.data(data);
groups.enter().append("g")
.attr("class", "group")
.attr("transform", function (d) {
return "translate(" + 0 + "," + y(d.name) + ")";
});
var bars = groups.selectAll(".bar").data(function (d) {
return d.values;
});
bars.enter().append("rect")
...
groups.transition().duration(750).attr("transform", function (d) {
return "translate(" + 0 + "," + y(d.name) + ")";
});
groups.exit().remove();
But: it only appends more and more groups on every update, no transitions or exits.
I am aware of this question: D3: update data with multiple elements in a group. However, I think did the setup just as described in the answer, without success.
EDIT: I am still trying to figure this out. I updated the fiddles.
Here is the working example without groups: http://jsfiddle.net/w2q0kjgd/2/
This is the not working example with groups: http://jsfiddle.net/o3fpaz2d/9/
I know it has been almost a month since the original posting, I tried to do something else in between and then have a look at this problem again, sometimes this helps. But I just cannot figure it out...
Please update the group before you select all the bars..
var groups = chart.selectAll(".group")
.data(data);
groups.enter()...
groups.transition().duration(750).attr("transform", function (d) {
return "translate(" + 0 + "," + y(d.name) + ")";
});
var bars = groups.selectAll(".bar").data(function (d) {
return d.values;
});
//add bars
//update bars
bars.exit().remove();
groups.exit().remove();
I was also playing around with the same block and was trying to figure it out. In the end, this solution worked for me:
var bars = g
.selectAll("g")
.data(data, function(d) {return d ? d.format_date : this.id; });
var enter = bars
.enter().append("g");
bars = enter.merge(bars)
.attr("transform", function(d) { return "translate(" + x0(d.format_date) + ",0)"; });
var rect = bars
.selectAll("rect")
.data(function(d) { return keys.map(function(key) { return {key: key, value: d[key]}; }); })
rect
.enter().append("rect").merge(rect)
.attr("x", function(d) { return x1(d.key); })
.attr("y", function(d) { return y(d.value); })
.attr("width", x1.bandwidth())
.attr("height", function(d) { return height - y(d.value); })
.attr("fill", function(d) { return z(d.key); });
I think the trick here is that you need to update and merge it twice. One is for your x0 axes overall group and the other is for the bars within that group. Hope this also works out for you.