Generating SVG for Web Pages with the gridSVG Package

by Paul Murrell

2015-11-18

This document describes several different techniques for including SVG images within a web page and points out the important SVG attributes that control the final appearance of the SVG image within the web page. The document then describes how to control those attributes when generating SVG images with the 'gridSVG' package for R.

This material was developed on Linux (Ubuntu 14.04) and tested on Firefox (42.0), Chromium (45.0.2454.101) and Opera (12.16). The browser you are using to view this material describes itself as:

This document concerns the production of graphical images from R, in an SVG format, for inclusion in web pages. The document has two parts: The first section discusses the different options for including SVG images in web pages; and the second section describes how to produce SVG plots with 'gridSVG' to suit those different options. It is assumed that the reader has a working knowledge of SVG (and R).

Including SVG in web pages

This section works with a very simple SVG image and explores a variety of size specifications and a variety of embedding techniques.

The SVG fragment that forms the basis of the SVG images is shown below.

<rect x="0" y="0" width="100" height="50" fill="black"/>
<circle cx="50" cy="25" r="30" fill="gray"/>

  

This fragment draws a grey circle atop a black rectangle.

The result of including this image within a web page will depend on how the image is included (e.g., <img>, <object>, or inline) and how the image describes itself (via 'width'/'height', 'viewBox', and 'preserveAspectRatio' attributes). Various combinations of these options are explored in the following sections.

SVG within a container

The simplest way to include an SVG image within a web page is to use an <img> element.

<img src="contained.svg"/>
  

If we do this, there are two things to take into account to determine the size of the image: the size of the container element and the aspect ratio of the image.

The size of an <img> element is easy to specify, via its 'width' and 'height'.

<img width="100" height="50" src="contained.svg"/>
  

The aspect ratio of the image can be controlled by the 'viewBox' attribute (specifically, the third and fourth values of the 'viewBox') in the top-level <svg> element of the image.

<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     viewBox="0 0 100 50" version="1.1">
  

In the examples given above, the container size and the image aspect ratio match, so the result is simple to predict. However, if these do not match, we get a scaling of the image to fit the container. The images below show some examples, just varying the container size to show how the image scales. In each example, the size of the container element is displayed and also indicated by a red outline.

An SVG image within an <img> container at different container sizes:
100x50
100x100
300x100

This means that it is a good idea to generate SVG with a 'viewBox' attribute, so that the image maintains the correct aspect ratio when it is used within a container, without having to size the container to exactly match the image size or aspect ratio.

Another option for including an SVG image in a web page is to use an <object> element. Again, we can easily control the container size through 'width' and 'height' attributes.

<object width="100" height="50" 
        type="image/svg+xml" data="contained.svg"/>
  

The result is the same as when we use an <img> element for the container.

An SVG image within an <object> container at different container sizes:
100x50
100x100
300x100

One difference between using <img> and using <object> is that the former is only useful for static images. Consider the minor alteration of the SVG content, which adds an 'onclick' attribute to the <circle> element.

<rect x="0" y="0" width="100" height="50" fill="black"/>
<circle onclick="alert('ouch!')" cx="50" cy="25" r="30" fill="gray"/>

  

This modified SVG is included below using <img> on the left and using <object> on the right - clicking on the right image should generate an alert box, but clicking on the left image should do nothing.

An SVG image with 'onclick' attribute included within different containers:
<img>
<object>

Yet another alternative for including SVG in a web page is to use an <embed> element. This should work in a similar manner to an <object> element, but due to a murkier history for the <embed> element, the <object> element is recommended instead.

Finally, there is the <iframe> element. This also behaves similarly to the <object> element.

An SVG image within an <iframe> container at different container sizes:
100x50
100x100
300x100

The difference between an <iframe> element and an <object> element is that the content of an <iframe> element is like its own browser window that displays its own web page. This can lead to some different behaviour under certain conditions (more on this later).

Inline SVG

A quite different way to include an SVG image within a web page is to inline the SVG directly within the HTML code for the web page.

In this case, there is no container to provide a size for the SVG image, so we need to use the 'width' and 'height' attributes on the top-level <svg> element.

<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     width="100" height="50" viewBox="0 0 100 50" version="1.1">
  

The resulting image is again a combination of the 'width'/'height' and the aspect ratio described by the 'viewBox'.

An inline SVG image at different sizes:
100x50
100x100
300x100

This means that it is a good idea to generate SVG with 'width' and 'height' attributes if you are going to use it inline, so that it is drawn at a predictable size.

This sort of inline image can include interactivity, as for SVG that is included via an <object> element. One difference is that SVG that is included via an <object> element cannot be styled via CSS (but this is possible with inline SVG). As an example, this web page includes a CSS rule ...

    svg.styled circle { fill: yellow }
  

... that targets the circle in the SVG image that is included below. On the left, the SVG is included via <object> so the styling has no effect, but on the right the SVG is included inline so the circle fill is yellow.

An SVG image included in different ways, with styling in the parent document:
<object>
inline

A downside to using inline SVG is that the web page becomes much larger and the SVG image cannot be cached by the browser separately from the web page.

Conflict resolution and diffusion of responsibility

If we include SVG within a container, then it makes sense to specify a width and height for the container and not for the SVG. Alternatively, if we use SVG inline, then it makes sense to specify a width and height for the SVG itself. But what happens if we specify a width and height for both the container and the SVG? Conversely, what happens if neither the container nor the SVG are given a width and height?

The size of the region that the SVG image is drawn within is determined by a "negotiation" between the SVG specification of 'width' and 'height' and the container specification of 'width' and 'height'. If both the container and the SVG specify both 'width' and 'height', the container usually wins. This means that it is mostly safe to generate SVG with 'width' and 'height' attributes because it will work in most cases (within a container and inline).

However, there is an exception; when SVG is included within an <iframe> element and both the <iframe> and the SVG specify both 'width' and 'height', the SVG is sized independently of the <iframe> element, potentially leaving large amounts of whitespace or generating scroll bars (if the SVG size exceeds the <iframe> size). This effect is demonstrated below.

An SVG image within an <iframe> container, with size specified by both image and container:
100x50
100x100
300x100
75x75

This means that sometimes it is better for the SVG not to specify 'width' and 'height' (e.g., if you want to use it in an <iframe> and have it scale nicely).

If neither 'width' nor 'height' is specified by anyone, the negotiation is unresolved. This means that it is not a good idea in general to generate SVG without a 'width' and 'height' if the image is to be used inline.

Another common situation arises when only one of 'width' and 'height' is specified by the container. In this case, the aspect ratio of the image enters the "negotiation" about the image size. For example, if the container specifies only the 'width', then the height is determined by the container width and the aspect ratio of the SVG.

An SVG image within an <img> container with different height specifications:
100x50
100xauto
300xauto

Another conflict can arise if the 'width' and 'height' of the SVG does not give the same aspect ratio as the 'viewBox' of the SVG. We have already demonstrated this situation for inline SVG (the 'width' and 'height' determine the size of the image on the web page and the image scales within that according to the aspect ratio of the 'viewBox'). When the SVG is included within a container and the container needs to determine the aspect ratio of the image, the 'width'/'height' will win over the 'viewBox'. The SVG fragment below shows an example.

<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     width="100"
     height="100"
     viewBox="0 0 100 50"
     version="1.1"/>

  

The resulting images are demonstrated below. In the centre and right-hand images, the container requires the SVG aspect ratio in order to determine an appropriate height. The SVG has a 'width'/'height' aspect ratio of 1:1, even though the 'viewBox' aspect ratio is 2:1, so the result is a square image.

An SVG image with conflicting 'width'/'height' and 'viewBox' aspect ratios, within an <img> container:
100x50
100xauto
300xauto

This means that it is a good idea to generate SVG with a coherent aspect ratio for both 'width'/'height' and 'viewBox' if you are specifying both 'width'/'height' and 'viewBox'.

A final point to consider is the fact that it is sometimes recommended to specify 'width' and 'height' for the container so that the browser can determine the space required for an image before it has downloaded the image; otherwise the browser has to reflow the page after the image is loaded (which makes the page "jump" as it is being drawn).

Non-uniform scaling of SVG

In all of the examples presented so far, we have retained the aspect ratio of the SVG image, regardless of the container size. We can instead have the SVG image scale to fit the entire container size by setting the 'preserveAspectRatio' attribute on the top-level <svg> element.

<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     viewBox="0 0 100 50" preserveAspectRatio="none" version="1.1">
  

The images below show the results for different container sizes with an <img> container. The same thing happens if we use an <object> container or an inline image.

An SVG image within an <img> container, with non-uniform scaling:
100x50
100x100
300x100

The default behaviour of the SVG image scaling (centred within its container and filling as much as possible while still preserving the image aspect ratio) comes from the default value of the 'preserveAspectRatio' attribute: 'xMidYMid meet'.

Both of those components of 'preserveAspectRatio' can be modified: to control the alignment of the image within its container, e.g., 'xMinYMin' to align the image with the bottom-left of the container; and to control how the image fills the container, e.g., 'slice' to make the image fill the container even if that means some of it is sliced off. The images below shows some variations on 'preserveAspectRatio'.

An SVG image within an <img> container, scaled to fit, with different alignment:
xMidYMid meet
xMidYMin meet
xMidYMax meet
An SVG image within an <img> container, scaled to fill, with different alignment:
xMidYMid slice
xMinYMid slice
xMaxYMid slice

Generating SVG with gridSVG

The 'gridSVG' package can be used to export a graphical image produced by R to an SVG format. The following code demonstrates how this works for a 'lattice' plot (the resulting image is included below the code).

library(lattice)
library(gridSVG)
gridsvg("plot.svg")
xyplot(mpg ~ disp, mtcars)
dev.off()

By default, the SVG generated by 'gridSVG' (and by the built-in svg() device in R) has 'width', 'height', and 'viewBox' all specified. The 'width' and 'height' arguments to the gridsvg() function specify the size of the image in inches and there is also a 'res' argument to specify the resolution (dpi). The <svg> element from the above plot is shown below (the value 504 comes from the default device size, 7 inches, multiplied by the default resolution, 72).

<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     width="504px"
     height="504px"
     viewBox="0 0 504 504"
     version="1.1"/>

  

Following the discussion in the previous section, this default should work well for including the plot in a web page using any of <img>, <object>, or inline. The plot above uses <img>, with no width or height specification, and the images below are produced using <object>, with width and height specified to show that the plot will scale nicely (these images are obviously too small for effective viewing - the point of these examples is to demonstrate scaling, not legibility).

An SVG R plot within an <object> container, at different sizes:
200x200
200xauto
300x200

However, if we try to use an <iframe> and do not use exactly the same dimensions as the plot, we will get an ugly result.

From version 1.5-0 of the 'gridSVG' package, the gridsvg() function includes a 'rootAttrs' argument that allows us to have finer control over the attributes within the root <svg> element. As a simple demonstration, it is now possible to specify the 'preserveAspectRatio' attribute for an SVG plot. Although it is unlikely that we would want to distort an SVG image of a plot by setting 'preserveAspectRatio' to "none", it is worth keeping in mind that R graphics can be used to produce more than just plots, and there are some 'preserveAspectRatio' settings that are useful even with plots. In the case below, we use 'preserveAspectRatio' to make sure that the SVG image is left-aligned within its container, regardless of the container size. The relevant R code is shown first, followed by the resulting <svg> element, then finally the resulting image.

gridsvg("plot-left.svg",
        rootAttrs=list(preserveAspectRatio="xMinYMid meet"))
xyplot(mpg ~ disp, mtcars)
dev.off()
<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     preserveAspectRatio="xMinYMid meet"
     width="504px"
     height="504px"
     viewBox="0 0 504 504"
     version="1.1"/>

  
An SVG R plot within an <object> container, at different sizes, with left-alignment:
200x200
200xauto
300x200

In order to produce an SVG image that will scale nicely when it is included within a <iframe> element, we need to not set the 'width' and 'height' attributes in the SVG image. The following R code shows how to do that (and the resulting SVG is shown below the R code - note the absence of 'width' and 'height' attributes - then the SVG image is included within an <iframe> element below that).

gridsvg("plot-iframe.svg", rootAttrs=list(width=NULL, height=NULL))
xyplot(mpg ~ disp, mtcars)
dev.off()
<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     viewBox="0 0 504 504"
     version="1.1"/>

  

This new 'gridSVG' feature is of course just a convenience. Setting attributes on the top-level <svg> element of an SVG document (including one generated by the built-in svg() graphics device) could also be achieved with the 'XML' package, though it would require a little more effort.

Summary

An SVG image can be included in a web page in a variety of ways. The final appearance of the SVG within the web page depends on settings for the 'width', 'height', 'viewBox', and 'preserveAspectRatio' attributes of the top-level <svg> element in the SVG code.

An SVG image that is produced by the 'gridSVG' package has useful default settings that allow the image to be included within a web page under most conditions. The new 'rootAttrs' argument to the gridsvg() function provides additional flexibility so that we can produce an SVG image that will work no matter how we choose to include the SVG in a web page.

A note on versions

The statements made in this document about the behaviour of HTML elements and SVG elements are based on HTML 5 and SVG 1.1. We assume a modern browser and we have ignored browser-specific quirks. In other words, this document is written with reference to the HTML and SVG standards themselves rather than any specific implementation of the standards.

Resources

SVG
Scalable Vector Graphics (SVG) 1.1 (Second Edition)
An SVG Primer for Today's Browsers
W3C Test (of SVG embedding in HTML)
Using SVG
How to Scale SVG
Using SVG with HTML 5 Tutorial
HTML
HTML5: A vocabulary and associated APIs for HTML and XHTML
CSS
Cascading Style Sheets Level 2 Revision 2 (CSS 2.2) Specification
gridSVG
Information about the 'gridSVG' package