If you search the web for examples of d3 data visualizations, you can expect to find a host of charts, graphs, plots, and maps. But data visualizations don’t have to be limited to these examples. They can also include photos, drawings, and animated graphics. I will focus on using SVGs so we can manipulate each shape inside our graphic, and rescale them without losing image quality. D3 has the power to load, print, and manipulate SVG data, giving us limitless ways to create visualizations.
In a previous related post I described steps to create web-friendly graphics using Procreate and Adobe Illustrator. In this post, I will explain how to load those illustrations into an application. This seems like it should be straightforward; however, there are a few gotchas to be aware of such as when loading an SVG more than one time, when working with multiple SVGs and when positioning the SVG with data.
Table of contents
- Load one SVG
- Load multiple SVGs
- Summary: Using imported SVGs in the real world
Tools
- Two or more SVGs (or use mine, Maple leaf and Oak leaf)
- d3 v5
- Your favorite text editor. I’m using Atom
Set up files
Create a folder containing your design files, an empty js file, and an HTML document with this template:
<html> <head> <style></style> </head> <body> <script src="index.js"></script> </body> </html>
Load the d3.js library
Download the latest version of d3 to your folder or link directly from d3js.org. Paste the path to d3.min.js or the direct link into a script tag before your index.js script.
<html> <head> <style></style> </head> <body> <script src="https://d3js.org/d3.v5.min.js" defer></script> <script src="index.js" defer></script> </body> </html>
Get the XML data
Since SVG is an XML language, d3.xml()
can be used to access your image document. The request for the XML data will go in your index.js file.
Load one illustration, one time
Create a new div in the body of your html template. This will be the container for your SVG.
In your javascript file, select the new div with d3.select("#svg-container")
and attach the XML data to it using .node().append(data.documentElement)
.
I’ve added a max height and width to the style tag in the html document to make sure the SVG doesn’t run off the edge of the window.
index.html
<html> <head> <style> svg { max-height: calc(100vh - 10px); max-width: calc(100vw - 10px); } </style> </head> <body> <div id="svg-container"></div> <script src="https://d3js.org/d3.v5.min.js" defer></script> <script src="index.js" defer></script> </body> </html>
index.js
d3.xml("maple_illustration.svg") .then(data => { d3.select("#svg-container").node().append(data.documentElement) });
Result
Multiple instances of a single illustration
Add two or more divs to the body.
When we made the ajax request for the SVG, d3 parsed the file and created an object representation of the contents, in this case an xml dom node. We cannot simply change
d3.select("#svg-container")
to d3.selectAll("div")
and expect the illustration to print out in both divs. Instead, we must copy the object into each div using .cloneNode().
On the next line, .nodes().forEach()
executes an anonymous function, which accepts “n” as the first parameter, which in this case is a dom node.
index.html
<html> <head> <style> svg { max-height: calc(100vh - 10px); max-width: calc(100vw - 10px); } </style> </head> <body> <div></div> <div></div> <script src="https://d3js.org/d3.v5.min.js" defer></script> <script src="index.js" defer></script> </body> </html>
index.js
d3.xml("maple_illustration.svg") .then(data => { d3.selectAll("div").nodes().forEach(n => { n.append(data.documentElement.cloneNode(true)) }) });
Result
Multiple files, printed one time each
Create two divs with unique ids in the body.
Illustrator assigns classes like cls-1 to paths upon exporting. The paths in your SVGs will need to have unique class names or else their styles will overwrite each other. Unfortunately this renaming needs to be done in the text editor, although Atom has a convenient find and replace all tool (cmd/ctrl + F), which can be used to replace all of the cls-1s/2s/etc with unique names.
To access more than one document, use Promise.all
. Promise.all
is used to run a batch of asynchronous tasks in parallel; in this case, we are wrapping multiple ajax requests into one promise.
The rest of the process is very similar to the setup for loading a single illustration, with some minor changes to make sure we are selecting unique xml objects and appending to unique divs. Even though we are printing multiple illustrations, we do not need to use .cloneNode() here because the illustrations are coming from different files and only get printed once.
index.html
<html> <head> <style> svg { max-height: calc(100vh - 10px); max-width: calc(100vw - 10px); } </style> </head> <body> <div id="maple-container"></div> <div id="oak-container"></div> <script src="https://d3js.org/d3.v5.min.js" defer></script> <script src="index.js" defer></script> </body> </html>
index.js
Promise.all([ d3.xml("maple_illustration.svg"), d3.xml("oak_illustration.svg") ]) .then(([mapleIllustration, oakIllustration]) => { d3.select("#maple-container").node().append(mapleIllustration.documentElement); d3.select("#oak-container").node().append(oakIllustration.documentElement); }); </html>
Result
Multiple instances of multiple files
Create four or more divs in the body. Give some of them one class name and the rest a different class name.
This process is basically a combination of the last two examples. The steps include:
- renaming the classes in your SVG file so they are unique
- using
.cloneNode()
to copy the dom nodes - using
Promise.all
to send multiple ajax requests - using
d3.selectAll
and.forEach
to append the objects to their respective divs
index.html
<html> <head> <style> svg { max-height: calc(100vh - 10px); max-width: calc(100vw - 10px); } </style> </head> <body> <div class="maple-container"></div> <div class="oak-container"></div> <div class="maple-container"></div> <div class="oak-container"></div> <script src="https://d3js.org/d3.v5.min.js" defer></script> <script src="index.js" defer></script> </body> </html>
index.js
Promise.all([ d3.xml("maple_illustration.svg"), d3.xml("oak_illustration.svg") ]) .then(([mapleIllustration, oakIllustration]) => { d3.selectAll(".maple-container").nodes().forEach(n => { n.append(mapleIllustration.documentElement.cloneNode(true)) }); d3.selectAll(".oak-container").nodes().forEach(n => { n.append(oakIllustration.documentElement.cloneNode(true)) }); });
Result
See the full page result
Summary: Using imported SVGs in the real world
Everything I’ve shown has just been illustrations appended to fullscreen divs. Most projects will require more sophistication than that, and the possibilities of layouts are greater than I can cover here.
Take a look at this scatterplot that uses imported SVGs as symbols. Since SVGs have their own coordinate system, positioning them correctly on an axis can be tricky. The SVGs in this plot have been appended to a pattern that is used as a fill for d3.symbolSquare
, which is much easier to set coordinates for in this case. I’m sure that there are many more ways that this could have been solved that I’m not even considering.
I’ve opened the floor for suggestions on Twitter about ways to improve this plot (which Mike Bostock has contributed to generously). If you have another solution, or if you would like to discuss problems/solutions you have faced when working with imported SVGs, I would love to hear from you in a comment or tweet.
Here is a D3 scatterplot that uses imported SVGs as symbols. I haven’t found any similar examples (perhaps for good reason?) and I’d love to hear thoughts on this solution using an SVG pattern as a fill for
d3.symbolSquare.https://t.co/WROUaSGaW6— Sara DeLessio (@SaraDeLessio) July 29, 2019
Wow, finally someone shows how to do this with the current version of d3! Thanks a lot. It’s a pity that all the outdated posts about d3v2 are found first by Google.
Hi,
Thanks for your examples. I tried the first one and it’s working fine for me.
Could you please share a zoom example too? I found a lot of examples but only for D3 version 3-4 not for 5.
Thanks,
Carsten
Hi Carsten,
Thank you for the question! Unfortunately, I don’t have a zoom example to share at this time, but I’ve taken note of your request.
Sara