Formula Controlled Graphics on Salesforce - Part I: SVG and Visualforce
Recently I needed to quickly find a way to generate some simple images in response to a service going off line. The service basically took some URL parameters, and returned a simple 'avatar' that simply consisted of some initials and a background colour. The output looked like this:
We used the service by generating the URL for each contact by using a formula field, calculating the background colour parameter based on a picklist value, and the initials simply using LEFT(FirstName, 1)
and RIGHT(LastName, 1)
. My first thought was to simply stand up a similar service on Heroku, as that wouldn't require an update to our managed package to build something with CSS, but on the other hand it involved another micro service to manage, so I wondered what I might be able to do on platform. It quickly occurred to me that SVG (Scalable Vector Graphics) images are composed using XML, and Visualforce can be used to generate all sorts of content if you exclude the headers etc. and specify the correct content type, so I started to experiment. Clearly the first port of call was to see whether the idea was feasible at all, so I simply aimed to generate a coloured box with some text. SVG markup can range from very simple to excessively complicated, though in the latter case you can pretty much guarantee that it's been generated by an vector design package and not by hand. In Part I of this post we'll look at how to get started with generating an SVG using Visualforce, and then in Part II we'll move on to controlling it via a Formula field, for easy control and updates.
A Simple SVG Example
<svg version="1.1" baseProfile="full" width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="#336699"/>
<text x="10" y="20" fill="white">Hello, World! </text>
</svg>
This source generates the following image:
As you can see, SVG markup is pretty easy to parse. It's plain to see here there's a wrapping svg
tag, which includes attributes for the width and the height of the image. Nested inside of that are two other simple elements, a rect
with a width, height and fill colour (specified in hex, but other familiar web formats will work too, try blue
or rgb(0, 0, 255)
in place of #336699
for example) and a text
element that includes coordinates for it's position (specified from the top left, which has long been a standard for computer graphics) and a colour to apply.
Serving it Using Visualforce
Visualforce by default generates HTML pages. You've probably seen before that the <apex:page>
element has an attribute which can be used to render it as a PDF (literally just renderas="pdf"
, but you may not have realised that you can return other content types as well. I've used it to generate XML in the past, as well as CSV files and JSON data structures for our main product. SVG is based on XML, so by setting the correct content type we're good to go. The following example illustrates how to do this and includes the SVG image we've defined above.
<apex:page contentType="image/svg+xml; charset=UTF-8">
<svg version="1.1" baseProfile="full" width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="#336699"/>
<text x="10" y="20" fill="white">Hello, World! </text>
</svg>
</apex:page>
Let's just call out the important part here:
contentType="image/svg+xml; charset=UTF-8"
This tells the Visualforce server what content is being rendered, and it sets the Content-Type header for the response (if that doesn't make much sense, it can be summarised by saying that we're telling the user's browser that it's not receving a web page, but an SVG in XML format). I'm including the charset specification from the start even thought it can be omitted, because I got stung by this trying to display non-ascii characters during some tests: UTF-8 is not the default so if you want to display Chinese characters for example, you'll need this.
Now all you need to do is navigate to your page (instance + '/apex/<<pagename >>') to see this image being generated by the platform. In my current scratch org, I've saved this page with the name SVG and I can reach it like so (note this link won't work unless you're authenticated against my scratch org, it's just for the sake of an example):
https://fun-customer-5258-dev-ed--prxc.visualforce.com/apex/svg
Visiting this URL results in me seeing the exact same image I included earlier in this post.
Going Dynamic
So how do we make this more flexible? Well one quick and easy way would be to leverage a StandardController, allowing us to pull values from a record when the ID is passed via the URL. For instance, this page:
<apex:page standardController="Account" contentType="image/svg+xml; charset=UTF-8">
<svg version="1.1" baseProfile="full" width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="#336699"/>
<text x="10" y="20" fill="white">
<apex:outputText value="{!Account.Name}"/>
</text>
</svg>
</apex:page>
Would replace the old "Hello, World!" text with the name of an account that we specify via ID, so this URL:
https://fun-customer-5258-dev-ed--prxc.visualforce.com/apex/svg?id=0010m00000NPxXHAA1
generates this image:
If you look at the source of this page you'll see the SVG XML in the content: I'm not saving and inserting these images, but dropping the XML generated by my Visualforce page straight into this post. This image isn't particularly great however, so let's work towards my original use case for now, and turn this into something that can be used as an avatar of sorts: the account's initials (yes I am using PersonAccounts here, you may want to use the Contact
controller and a contact ID), centred, and with a colour coded background.
First off, we'll centre the text. I do not profess to be an expert in SVGs, in fact I'm far from it and know very little about them, but it didn't take much searching last week (because the MDN is an awesome resource) to achieve this quickly. Straight off the bat we can use a couple attributes to centre the text around it's origin point, that is, the point defined by the x
and y
attributes. By setting dominant-baseline
to central
we'll centre it vertically around it's origin, and by text-anchor
to middle
we'll centre it horizontally. So now we get this:
Not great, but by setting the coordinates for the text to be the mid point of the image (coordinates 100,100 as the image is 200x200) it's looking a bit better:
Next, we can throw in a couple of useful Visualforce functions to get the initials we're after, if you'll excuse my lack of error handling and the like:
<apex:page standardController="Account" contentType="image/svg+xml; charset=UTF-8">
<svg version="1.1" baseProfile="full" width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="#336699"/>
<text x="100" y="100" fill="white" dominant-baseline="central" text-anchor="middle">
<apex:outputText value="{!LEFT(Account.FirstName, 1){!LEFT(Account.LastName, 1)}"/>
</text>
</svg>
</apex:page>
Next we'll work on the style a bit more to make the font bigger, since the initials are now seemingly lost at sea, and we'll switch to a sans-serif font. SVG uses styling akin to CSS, so we'll pull out the fill="white"
and specify that along with the font changes inside of some style tags, defining a class that'll specify on the text element:
<apex:page standardController="Account" contentType="image/svg+xml; charset=UTF-8">
<svg version="1.1" baseProfile="full" width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<style>
.initials
{
font: 100px sans-serif;
fill: white;
}
</style>
<rect width="100%" height="100%" fill="#336699"/>
<text x="100" y="100" class="initials" dominant-baseline="central" text-anchor="middle">
<apex:outputText value="{!LEFT(Account.FirstName, 1)}{!LEFT(Account.LastName, 1)}"/>
</text>
</svg>
</apex:page>
At this point we're looking pretty good, the image is square and not circle because we've used <rect>
. There is a <circle>
element available in SVG, but I was using this image in a page styled by it's own CSS. By making the image square, the page including this image can keep it square, or make it circle by using the CSS border-radius
property and setting it to 50%. Generating a square image gives the most flexibility to the consumer of it.
We'll look at one final example of adding some dynamic content to this image before calling an end to Part I: modifying the background colour of the image based on the Account Rating field. For this we can leverage the Visualforce CASE
function, which you'll likely be familiar with if you've ever used a formula field. The changed line is that for the <rect>
and it now looks like this:
<rect width="100%" height="100%" fill="{!CASE(Account.Rating, 'Hot', 'Red', 'Warm', 'Yellow', 'Cold', 'LightBlue', 'Grey')}"/>
So if the Rating is "Hot" we get a red background, "Warm" then yellow, "Cold" a light blue and finally grey if the account has no rating. The four variants of the image now look like this (I reduced the size here just for display purposes):
That's the end of Part I, and hopefully it's provided a good launching point for some experimentation, and Part II should follow on shortly. Visualforce is old, and Lightning Web Components are the snazzy new way to build user interfaces on the Salesforce platform these days, but it think you'll agree that it still has some great use cases going for it.