How can I remove or replace SVG content? - javascript

I have a piece of JavaScript code which creates (using D3.js) an svg element which contains a chart. I want to update the chart based on new data coming from a web service using AJAX, the problem is that each time I click on the update button, it generates a new svg, so I want to remove the old one or update its content.
Here is a snippet from the JavaScript function where I create the svg:
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
How can I remove the old svg element or at least replace its content?

Here is the solution:
d3.select("svg").remove();
This is a remove function provided by D3.js.

If you want to get rid of all children,
svg.selectAll("*").remove();
will remove all content associated with the svg.

Setting the id attribute when appending the svg element can also let d3 select so remove() later on this element by id :
var svg = d3.select("theParentElement").append("svg")
.attr("id","the_SVG_ID")
.attr("width",...
...
d3.select("#the_SVG_ID").remove();

I had two charts.
<div id="barChart"></div>
<div id="bubbleChart"></div>
This removed all charts.
d3.select("svg").remove();
This worked for removing the existing bar chart, but then I couldn't re-add the bar chart after
d3.select("#barChart").remove();
Tried this. It not only let me remove the existing bar chart, but also let me re-add a new bar chart.
d3.select("#barChart").select("svg").remove();
var svg = d3.select('#barChart')
.append('svg')
.attr('width', width + margins.left + margins.right)
.attr('height', height + margins.top + margins.bottom)
.append('g')
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')');
Not sure if this is the correct way to remove, and re-add a chart in d3. It worked in Chrome, but have not tested in IE.

I am using the SVG using D3.js and i had the same issue.
I used this code for removing the previous svg but the linear gradient inside SVG were not coming in IE
$("#container_div_id").html("");
then I wrote the below code to resolve the issue
$('container_div_id g').remove();
$('#container_div_id path').remove();
here i am removing the previous g and path inside the SVG, replacing with the new one.
Keeping my linear gradient inside SVG tags in the static content and then I called the above code, This works in IE

You could also just use jQuery to remove the contents of the div that contains your svg.
$("#container_div_id").html("");

You should use append("svg:svg"), not append("svg") so that D3 makes the element with the correct 'namespace' if you're using xhtml.

I follow the code from https://www.d3-graph-gallery.com/graph/line_basic.html and had this code:
svg.append("path")
.datum(filteredData)
.attr("fill", "none")
.attr("stroke", "blue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function(k) { return x(k.date) })
.y(function(k) { return y(k.value) })
)
So for me it turned out to be that I slap this right before the generation of new line:
d3.selectAll("path").remove()

Related

Looking for a more efficient way to remove d3 elements

I just started learning D3 and am having trouble understanding how to manipulate selections. My visualization is functioning very slowly and someone recommended to me to use Timeline to find the source of the problem. I looked over it and found that this snippet of code is most likely the culprit:
for (var i = 0; i < staticSvgLength; i++) {
var curritem = d3.select(svgChildren[0][i]);
if (curritem.attr("class") != "graphbutton") {
curritem.remove();
}
}
Here is a snapshot of the timeline that suggests to me that the problem is remove:
svgChildren elements are selected using:
var svgChildren = d3.selectAll(svgContainer[0][0].childNodes);
There are thousands of elements, most of which can be found within the following variable:
var rectangles = svgContainer.selectAll("svg")
.data(rawDataStore)
.enter()
.append("rect")
This is the graph I am trying to create:
The elements which I'm trying to prevent from being removed are the buttons in the upper left corner which are also stored in svgContainer. As a side question, is it bad practice to store the buttons in the same svg element I store the main graph?
EDIT: Here is where I define svgContainer:
var svgContainer = d3.select("body").append("svg")
.attr("Call", "svgContainer")
.attr("height", max_y)
.attr("width", max_x)
.append("g")
.attr("transform", "translate(" + margin.left + "," + (margin.top) + ")");
I select all the SVGs to be removed because when you click a button, it switches to a different graph. I don't want the two buttons to be removed, only the graphs, so I tried to exclude them from the selection by giving them a class and then removing all elements in svgContainer excluding the items that have the button class.

Images not Generating D3

Here is my problem. My graph currently looks like this: Which is dandy. However, I want the black squares on top to be filled with pictures. Luckily I have a CSS file that has pictures linked with classes. I also have a JSON file that contains all the class names. All those class names are assigned to the squares and I can see the picture in the inspect element on Chrome. The only issue is the pictures don't appear in the square. (Also my axises broke, but that is secondary concern). CSS, JSON
This is where I'm assigning classes and creating the rectangles.
svg.selectAll(".div")
.data(data.chartData, function(d){return d.vNm;})
.enter().append("rect")
.attr("x", function(d){
return x(d.vNm);
})
.attr("y", function(d){
return (y(d.values.reduce(function(sum, d){
return sum + d.amount;
}, 0))) - 64.5;
})
.attr("width", 43)
.attr("height", 43)
.attr("class", function(d){return d.vId;})
.style("fill", function(d) { return segColor(d.data.type); });
One approach to solve your problem is to use html elements like div for the images above the chart instead of svg elements, so you can use the full power of css.
Luckily you don't need to calculate the position of those html elements by yourself, there are some libraries that help you position the images correctly above the bars in the chart.
Check out https://popper.js.org/ for example, you can just call its API for each bar you render using d3.js:
var popper = new Popper(barElement, onPopper, {
placement: 'top'
});
SVG elements do not follow exactly the same CSS rules as typical HTML elements.
In your case, background-image doesn't work.
The least painful way to achieve the effect would be to embed an <image> tag after the <rect>:
<image width="100" height="100" xlink:href="data:image/png;base64,...">
It means that you have to modify your JSON to store the image's base64 data in there instead of CSS.

How to translate SVG and all its children

I have been playing with this fiddle : https://jsfiddle.net/thatOneGuy/8k8ggpcn/4/
Majority of it is not my code, but from line 330-345 I have added, tried to, the ability to move the SVG by a certain amount. But this is not working. I can't seem to figure out why.
I have tried using D3. So added an ID of mainSVGContainer to the SVG at the start :
svg = d3.select("#svg1")
.append("svg").attr('id', 'mainSVGContainer')
And used this to translate :
d3.select('#mainSVGContainer').style('fill','blue').attr("transform", "translate(0 "+difference +")")
Difference is an integer worked out before this call, its around 130. But this doesn't seem to work. It gets written to the DOM but doesn't look like it's affecting the SVG.
I have tried with vanilla JavaScript :
var svgContainer = document.getElementById('mainSVGContainer');
svgContainer.offsetLeft = 1000;
This doesn't work either
And I have tried with inline JS to alter the styling :
svgContainer.style.left = 1000;
Still no luck. I presumed it was due to it being an SVG element but I tried doing the same with the container of this SVG which was a div and no luck.
Any ideas ?
As JSBob and others from the following :
d3 Workaround for svg transform in chrome
d3 Nested SVG plots differently in firefox than in Chrome
I found out Chrome, along with other browsers, don't support the translation of SVG elements. So, as a work around, I appended a g element to the SVG and translated that :
Appending g :
svg = d3.select("#svg1")
.append("svg").attr("height", h)
.attr("width", w)
.append('g')
.attr('id', 'mainSVGContainer')
.attr("height", h)
.attr("width", w)
.attr("class", "graph-svg-component")
Translating g :
d3.select('#mainSVGContainer').transition().duration(1000).attr("transform", "translate(0 "+(-difference) +")")
Added the transition so you can see before and after :)
Updated fiddle : https://jsfiddle.net/thatOneGuy/8k8ggpcn/7/

Basic d3: Horizontal Bar Chart

Having trouble with a simple bar chart here, can't find the problem.
See this fiddle http://jsfiddle.net/nn3yex69/
Notice two main problems:
1) The number of bars is not matching the number of elements in dataset.
2) The widths of the largest rects extend to the full size of the svg despite the desired padding in xScale.
Would really appreciate any help here, I'm stumped.
1) You are selecting all rect elements in the SVG. You already have one rect element: the background! Try changing line 36 to svg.selectAll("rect.bar").
2) There's padding on the left and the right, so on line 17 use .range([padding,w - (2*padding)]);
You were appending a 'rect' and so on the selectAll('rect'), the first element from the dataset was being overridden by the attributes of the first appended 'rect'.
Removing the following will do the trick:
svg.append("rect")
.attr("height", h)
.attr("width", w)
.attr("stroke", "black")
.attr("fill", "#ccc")
.attr("stroke-width", 2)
Updated fiddle: http://jsfiddle.net/nn3yex69/1/
I would create a div and style with css to be the containing div to hold your bars.

what is the point of calling selectAll when there are no existing nodes yet on D3.js

when i do this :
var link = svg.selectAll('.link')
.data(links)
.enter().append('path')
.attr('class', 'link')
.attr('d', diagonal)
There is no node with the .link class. So selectAll returns en empty selection. But i've found that, when you call this for the first time, you can selectAll('whaterverYouWant')
That is because D3 doesn't matter about what you select, as you provide the tag name and the classes later .append('path'), .attr(class ...).
And, if you want to select elements that already exist, i read in the doc that .enter returns a placeholder selection. But if it returns a selection of placeholders (anonymous tags with .link class ?), there is no point to append a path to a path.
When i call .append, it does what i want, i.e. append a path to svg. But i don't understand the logic behind that. (I'm glad it works though, because d3 is powerful)
So, ok i selectAll('anything') and append what i want, regardless of what i selected. But if i try this:
d3.select('#savestring-debug')
.selectAll('div')
.data(debugobjs)
.enter().append('span')
.attr('style', function(d) { return 'background:#'+d.color })
.text(function(d) { return d.aff });
This would create placeholders for divs, but i append spans. Actually spans are created but i'm still looking for my divs ;)
So, what is the principle behind selectAll >> data >> enter >> append ?
thanks
The principle behind selectAll > data > enter > append is explained pretty well by
Mike Bostock here: http://bost.ocks.org/mike/join/ where he explains the concept of the data-join. I can't speak with any authority on the right way to use selectAll, but the way I use it is to select all of the elements I am going to be modifying-appending-removing within the part of the SVG that I need to modify.
So if I'm working with "rects" in a certain area, I'll do something like this:
var svg = d3.select('#graphID')
.append("svg")
.attr("width", 300)
.attr("height", 500);
var graphGroup = self.svg.append("g");
//...Inside a render function
//just want all the "rect" elements in graphGroup
var rects = graphGroup.selectAll("rect")
.data(dataset);
//depending on dataset new rects will need to be appendend
rects.enter()
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 0)
.attr("height", 0)
//all rects are transitioned to new co-ordinates
rects.transition().duration(500)
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d);
})
.attr("width", xScale.rangeBand())
.attr("height", function(d){
return yScale(d);
})
//rects that have no data associated with them are removed
rects.exit()
.transition()
.duration(500)
.attr("x", -xScale.rangeBand())
.remove();
With the idea that I could have other rects in the SVG that do not belong to graphGroup. I just selectAll the rects in a certain area and work on them when needed.
This is a great question and a slightly odd property of D3. If you look carefully how anything is done in D3 you'll notice that everything is added by appending to what is previously created. So the logic behind having the svg.selectAll('whatever class of stuff you're going to add') is that you are kinda making a placeholder for where whatever you are about append to go. It's like the svg is a wall and you're hanging hooks on the upper ridge for you to THEN hang your paintings from. If you don't have the selectAll, I just tried this, you will still append whatever you were gonna make to the page, but it won't be appended to the svg.
The data-->enter-->append is basically saying for each element in the larger data file that you are passing into the data function, make a new element, and append this element to my selection with such and such properties (set when you use the .attr).

Categories

Resources