My excel file, called thefilehere.xml
contains the following information:
<Main>
<Name>Rotunda</Name>
<Age>43</Age>
</Main>
<Main>
<Name>John</Name>
<Age>22</Age>
</Main>
As someone who has litle experience with D3 and XML files, I am trying to understand how to call the nested data from my XML file. My basic HTML is as follows:
HTML
<div id="chart"></div>
My Javascript, which does not work, hopefully makes it clear what I am trying to acplish. I want to select the main container (in this case Main
), and from there select its children (in this case Name
and Age
). Age
is to be shown via graph. Name
is to be the label on the graph.
JavaScript
d3.xml("thefilehere.xml", function(err, xml) {
d3.select("#chart")
.selectAll("div")
.data(xml.documentElement.getElementsByTagName("Main"))
.enter().append("div")
.style("width", function(d) { return d("Age").textContent * 1 + "px"; })
.text(d("Name").textContent);
});
My excel file, called thefilehere.xml
contains the following information:
<Main>
<Name>Rotunda</Name>
<Age>43</Age>
</Main>
<Main>
<Name>John</Name>
<Age>22</Age>
</Main>
As someone who has litle experience with D3 and XML files, I am trying to understand how to call the nested data from my XML file. My basic HTML is as follows:
HTML
<div id="chart"></div>
My Javascript, which does not work, hopefully makes it clear what I am trying to acplish. I want to select the main container (in this case Main
), and from there select its children (in this case Name
and Age
). Age
is to be shown via graph. Name
is to be the label on the graph.
JavaScript
d3.xml("thefilehere.xml", function(err, xml) {
d3.select("#chart")
.selectAll("div")
.data(xml.documentElement.getElementsByTagName("Main"))
.enter().append("div")
.style("width", function(d) { return d("Age").textContent * 1 + "px"; })
.text(d("Name").textContent);
});
Share
Improve this question
asked Apr 20, 2017 at 16:11
Jason ChenJason Chen
2,5876 gold badges27 silver badges47 bronze badges
3 Answers
Reset to default 5This answer adds to and builds upon the work already done by @GerardoFurtado in his answer which is perfectly fine and well-suited for many situations. However, as it is often times desirable to stay within D3 to not break its basic operating concepts, here is my D3-only—mostly, at least— solution.
Since D3 is not restricted to handling SVG DOM trees, but is capable of handling—amongst others—XML DOM just as well, you can easily use its selections on the loaded XML file. The relevant code might look like this:
d3.xml(url, (error, xml) => {
let xmlDoc = d3.select(xml.documentElement); // wrap the XML document in a D3 selection
d3.select("body")
.selectAll("div")
.data(xmlDoc.selectAll("Main").nodes()) // bind the XML's nodes as data
.enter().append("div")
.attr("class", "myDiv")
.style("width", d => d3.select(d).select("Age").text() * 5 + "px")
.text(d => d3.select(d).select("Name").text());
});
When doing d3.select(d)
within .style()
and .text()
it is important to notice, that this will actually select from the XML's DOM rather than from the HTML's DOM. Since we bound the selected elements from xmlDoc.selectAll("Main")
as data, d
in this case will refer to a <Main>
element of the XML. Having this selection at hand, you can leverage D3's capabilities by doing sub-selections as well as accessing its contents by calling .text()
and much more, if needed.
Have a look at the following snippet for a working demo:
// Set up data to simulate loading from file.
// This can be ignored for the reasoning of this answer; main logic below.
const url = URL.createObjectURL(new Blob([
`<data>
<Main>
<Name>Rotunda</Name>
<Age>43</Age>
</Main>
<Main>
<Name>John</Name>
<Age>22</Age>
</Main>
</data>`
]));
// End of setup.
// Main logic
d3.xml(url, (error, xml) => {
let xmlDoc = d3.select(xml.documentElement);
d3.select("body")
.selectAll("div")
.data(xmlDoc.selectAll("Main").nodes())
.enter().append("div")
.attr("class", "myDiv")
.style("width", d => d3.select(d).select("Age").text() * 5 + "px")
.text(d => d3.select(d).select("Name").text());
});
.myDiv{
background-color: lightblue;
margin: 4px;
}
<script src="https://d3js/d3.v4.js"></script>
Firstly, we'll change your XML structure, wrapping all your data inside a tag. Traditionally, this tag is named data
:
<data>
<main>
<name>Rotunda</name>
<age>43</age>
</main>
<main>
<name>John</name>
<age>22</age>
</main>
</data>
After that, inside your d3.xml
callback, you could simply use...
xml = xml.getElementsByTagName("main");
However, since inside <main>
you have two tags with data, it will be awkward getting that data with textContent
. This solution by Mike Bostock is way more interesting:
xml = [].map.call(xml.querySelectorAll("main"), function(d) {
return {
name: d.querySelector("name").textContent,
age: +d.querySelector("age").textContent
};
});
Now, you just need to use name
and age
in your code, which will be like this:
d3.xml("thefilehere.xml", function(err, xml) {
xml = [].map.call(xml.querySelectorAll("main"), function(d) {
return {
name: d.querySelector("name").textContent,
age: +d.querySelector("age").textContent
};
});
d3.select("body")
.selectAll("div")
.data(xml)
.enter()
.append("div")
.attr("class", "myDiv")
.style("width", function(d) {
return d.age * 5 + "px";
})
.text(function(d) {
return d.name
});
});
Since I can't use d3.xml
in the Stack snippet, here is a plunker with the working code: https://plnkr.co/edit/e9aCsHZnPiV4fBaPvAZv?p=preview
To be able to get the data with d3.xml
, you have to be serving thefilehere.xml
from a local server.
This will not work if thefilehere.xml
is simply a file in the same directory as your HTML & JS.