removing all svg elements in D3 save some specified - d3.js

const svg=d3.append("svg").attr(...)
const g1=svg.append("g").attr("id","g1").attr(...)
var v1=g1.append(...)
... // many other nested append operations
const g2=g1.append("g").attr("id","g2").attr(...)
var v2=g2.append(...)
... // many other nested append operations
Then how can I remove all elements from svg object apart groups g1 and g2?
Something similar to:
svg.selectAll("*:not('#g1'):not('#g2')").remove();
that does not work.

I would apply a class to all elements that you want to keep, e.g.:
const svg=d3.append("svg")
.attr(...)
const g1=svg.append("g")
.attr(...)
.classed('doKeep', true)
var v1=g1.append(...)
// After adding many nested elements to g1
g1.selectAll('*')
.classed('doKeep', true)
...
const g2=g1.append("g")
.attr(...)
.classed('doKeep', true)
var v2=g2.append(...)
...
// After adding many nested elements to g1
g2.selectAll('*')
.classed('doKeep', true)
Then you can select all elements in you SVG that do not have that class, with a :not() CSS selector as such:
svg.selectAll(':not(.doKeep)').remove()
The inverse would also work, where you mark all elements you want to delete, and then delete them after selecting them. But giving the phrasing of your question, this is the most accurate approach.
Hope this helps!
Edit: Updated to reflect updated question that specifies many nested elements.

Related

Adjusting small multiples sparklines

I have an heatmap that show some data and a sparkline for each line of the heatmap.
If the user click on a row label, then the data are ordered in decreasing order, so each rect is placed in the right position.
Viceversa, if the user click on a column label.
Each react is placed in the right way but I'm not able to place the sparkline.
Here the code.
When the user click on a row label, also the path inside the svg containing the sparkline should be updated.
And then, when the user click on a column label, the svg containing the sparkline should be placed in the correct line.
To place the svg in the right place, I try to use the x and y attributes of svg. They are updated but the svg doesn't change its position. Why?
Here is a piece of code related to that:
var t = svg.transition().duration(1000);
var values = [];
var sorted;
sorted = d3.range(numRegions).sort(function(a, b) {
if(sortOrder) {
return values[b] - values[a];
}
else {
return values[a] - values[b];
}
});
t.selectAll('.rowLabel')
.attr('y', function(d, k) {
return sorted.indexOf(k) * cellSize;
});
Also, I don't know how to change the path of every sparkline svg. I could take the data and order them manually, but this is only good for the row on which the user has clicked and not for all the others.
How can I do?
The vertical and horizontal re-positioning/redrawing of those sparklines require different approaches:
Vertical adjustment
For this solution I'm using selection.sort, which:
Returns a new selection that contains a copy of each group in this selection sorted according to the compare function. After sorting, re-inserts elements to match the resulting order.
So, first, we set our selection:
var sortedSVG = d3.selectAll(".data-svg")
Then, since selection.sort deals with data, we bind the datum, which is the index of the SVG regarding your sorted array:
.datum(function(d){
return sorted.indexOf(+this.dataset.r)
})
Finally, we compare them in ascending order:
.sort(function(a,b){
return d3.ascending(a,b)
});
Have in mind that the change is immediate, not a slow and nice transition. This is because the elements are re-positioned in the DOM, and the new structure is painted immediately. For having a slow transition, you'll have to deal with HTML and CSS inside the container div (which may be worth a new specific question).
Horizontal adjustment
The issue here is getting all the relevant data from the selection:
var sel = d3.selectAll('rect[data-r=\'' + k + '\']')
.each(function() {
arr.push({value:+d3.select(this).attr('data-value'),
pos: +d3.select(this).attr('data-c')});
});
And sorting it according to data-c. After that, we map the result to a simple array:
var result = arr.sort(function(a,b){
return sorted.indexOf(a.pos) - sorted.indexOf(b.pos)
}).map(function(d){
return d.value
});
Conclusion
Here is the updated Plunker: http://next.plnkr.co/edit/85fIXWxmX0l42cHx or http://plnkr.co/edit/85fIXWxmX0l42cHx
PS: You'll need to re-position the circles as well.

In D3, how do you conditionally nest SVG elements (in the context of 'enter')?

Specifically, given this example: https://bl.ocks.org/mbostock/4062045
How would I nest circles inside of the others? And have those be able to link to other (parent) circles?
The key for me was to use what was returned from 'enter' for appending the children and parent SVG objects separately. e.g.:
var enter = svg...enter()
var node = enter.append("rect")...
var children = enter.append("rect")...
(see the link in the question for the entire code context)
To conditionally add children, you can use a filter as follows:
var children = enter.filter(...).append("rect")...

.append() to different selection depending on condition?

I have a similar issue as in Updating SVG Element Z-Index With D3
My preferred solution would be to have two groups within my svg as described in the answer by notan3xit.
But I have one data set, and a boolean flag on one of the data properties determines to which group the svg element belongs.
svg.selectAll("path")
.data(d)
.enter()
.if(d.property).appendToGroup1()
.else.appendToGroup2()
This obviously doesn't work, but something like this.
I only want to iterate through the data once, and during runtime append the generated svg elements to the according groups.
Any ideas how to go about achieving that with d3.js?
Try
.each(function(d){
var toAppend = $(this);
if (!!d.property){
toAppend.appendtoGroup1();
} else {
toAppend.appendToGroup2();
}
})//...
The general idea would be to pass d into a callback, and apply the right append method inside that callback.
It should be cleaner to first save reference to what you want to append to, in an enclosing scope so you can easily call .append() on it.
Try this:
var selectedPath=svg.selectAll("path").data(d);
svg.selectAll("path").each(function(d,i){
if(d3.select(this).attr("property's name"))
selectedPath.append("some data to group 1")
else
selectedPath.append("some data to group 2")
});
If I had your code better able to help.

D3: can create a subset of elements, but not update them

I have a dataset, one element of which is another dataset (let's call it "dataset2"), of a variable length.
I create a complex plot:
var group = svg.selectAll('g').data(dataset)
var groupEnter = group.enter().append('g')
/* Add new elements */
groupEnter.append('line')
...
groupEnter.append('polygon')
...
groupEnter.append('text')
...
/* Update elements */
group.select('line')
...
group.select('polygon')
...
group.select('text')
...
So far so good.
Now, I want to add polygons representing "dataset2":
var subGroup = group.selectAll('.someClass').data(function(d) { return d['dataset2'] })
var subGroupEnter = subGroup.enter().append('polygon')
.attr('class', 'someClass')
...
This seems to work; polygons are created.
However, I can't update them:
subGroup.selectAll('.someClass')
...
This has no effect.
If I ran
subGroup.selectAll('.someClass')
.attr('...', function(d) { console.log(d); return ... })
there is no output.
What am I doing wrong?
That's because you're not actually adding someClass to the subgroup (at least in the fragments you show)
Maybe
var subGroupEnter = subGroup.enter().append('polygon').classed('someClass', true)
Or something similar (hard to be more specific with just the fragments above)

Sub-selection based on function

I have a selection of elements that I'm trying to filter down based on a particular style value (I want just the ones with opacity=1). I'm looking at the documentation for selection.filter along with selection.select and selection.selectAll as well but I'm confused about the correct usage with a function argument.
"select" indicates that it selects the first matching element (as expected) but then the example in the filter documentation shows it being used with a function to select the "odd" elements while maintaining the index.
"selectAll" indicates that you can return an array of elements, but that the function argument is invoked one-by-one in the usual way for each element in the original selection. I'm having difficulty imagining a use case for this.
I guess what I'm wondering is whether there are any tutorials or examples around that discuss the correct usage of these functions?
Thanks,
scott
If you want to reduce a selection to a subset of selected elements, use filter. If you want to select descendent elements, use select or selectAll.
Most often, filter is used to filter elements based on data or index. However, you can access the selected element as this within the filter function. Thus, if you have some elements selected, and you want to reduce that selection to only those elements with an opacity of 1, you can say:
var opaque = selection.filter(function() {
return this.style.opacity == 1;
});
To be safe, you might prefer to look at the computed style rather than the element's style properties. This way, if the opacity is inherited from a stylesheet, you'll get the correct value; otherwise, when a style is inherited this.style.opacity will be the empty string.
var opaque = selection.filter(function() {
return window.getComputedStyle(this, null).getPropertyValue("opacity") == 1;
});
Or equivalently, select the node and use selection.style:
var opaque = selection.filter(function() {
return d3.select(this).style("opacity") == 1;
});
You might find it easier if you filter by data or by class, instead of by computed style property. For example, if you set a class on your nodes, you can filter a selection by class instead:
var opaque = selection.filter(".opaque");

Resources