Coloring and Scaling SVG in a WinJS.Binding.Template Control
When creating my Windows Store application Gamer Dice, I wanted to provide a compelling visual representation of the dice the player was using that scaled well when the dice were shown at different sizes, in different colors (tints) and could show the numeric result of the roll. After thinking about a number of different approaches, the one chosen uses SVG to describe the shape of the die in a WinJS.Binding.Template control, and binds the size, color and value of the die. This post illustrates how to use this pattern in a Win8 JavaScript application. I’m going to assume working knowledge of Windows Web Applications (WWA) and Scalable Vector Graphics (SVG).
Declare the Template
In the HTML markup of the page declare the WinJS.Binding.Template control that will include the SVG fragment to be used. Blend for Visual Studio may be used to quickly add a Template Control to a page.
In the Asset panel, select the JavaScript Controls category and double-click Template control to add it. Alternatively, you can search for the Template control using the Search Assets filter in the Asset panel. Once the template is added, replace the text “Place content here” with the <svg> content you want to include as part of the template. Here’s what it would look like in Blend:
In Visual Studio the markup will look something like this:
<div data-win-control="WinJS.Binding.Template">
<div>
<svg width="328.78506" height="325.80728" >
<g transform="translate(-211.01766,-268.64704)">
<path style="fill:#ff6262;fill-opacity:1;stroke:none;"
d="m 483.39049,436.95746 …"
transform="matrix(1.4592076,0,0,1.4396496,-165.56435,-197.51494)" >
</path>
<path style="fill:#990000;fill-opacity:1;stroke:none;"
d="m 483.39049,436.95746 …"
transform="matrix(1.3314543,0,0,1.3256145,-118.29482,-148.35441)" >
</path>
<path style="fill:#ff0000;fill-opacity:1;stroke:none;"
d="m 328.54672,425.0464 …"
transform="translate(122.13283,111.14024)" >
</path>
Keep in mind that the contents of the template are not shown in the application until they are bound to something. Also, whatever SVG code is used will be duplicated over and over as the template is applied and the larger and more complex template, the more complex the resulting DOM. So care should be taken to minimize and simplify the SVG markup as much as possible. Many of the tools used to manipulate the SVG add elements to the markup that only have meaning at design time, and while this excessive SVG renders as expected in IE or WWA, there is far more markup than is necessary to get the desired effect.
Value Converters
Often in data binding, the format of the source data doesn’t match the output or doesn’t match naturally to the parts of the UI to which it is bound. A classic example is the coloring of a number as black or red depending on if it’s positive or negative. The output of the expression would be a Boolean (e.g. value >= 0) but the UI really wants to bind to a color, in this case, red or black. A value converter is a function you provide that computes the desired from the context of the data and properties being bound.
Briefly, a custom value converter takes the following arguments:
- source – the source JavaScript object for the bind operation
- sourceProperty – the name of the property to bind from the source JavaScript object
- dest – the destination DOM element for the bind operation
- destProperty – the name of the property to bind to the destination DOM element
For this example we need to two converters: One that takes a string value and applies that as the transform attribute to an element of the SVG. The other takes a color as input and updates the style.fill value of the element to “tint” or “colorize” the element.
The code below defines the two custom converters, making them available on the SVGDemo “namespace” object:
var SVGDemo;
(function (SVGDemo) {
var Converters = (function () {
function Converters() { }
Converters.svgAttr = WinJS.Binding.initializer(function (source, sourceProperty, dest, destProperty) {
dest.setAttributeNS(null, destProperty[0], source[sourceProperty[0]]);
});
Converters.tintColor = WinJS.Binding.initializer(function (source, sourceProperty, dest, destProperty) {
function getR(color) {
return parseInt(color.substring(1, 3), 16);
}
function getG(color) {
return parseInt(color.substring(3, 5), 16);
}
function getB(color) {
return parseInt(color.substring(5, 7), 16);
}
function hex(num) {
var str = num.toString(16);
if (str.length == 1) {
return '0' + str;
}
return str;
}
function scaleColor(factor, color) {
var r = Math.floor(factor * getR(color));
var g = Math.floor(factor * getG(color));
var b = Math.floor(factor * getB(color));
return "#" + hex(r) + hex(g) + hex(b);
}
var color = dest[destProperty[0]][destProperty[1]];
var tinted = scaleColor(getR(color) / 255, source[sourceProperty[0]]);
dest[destProperty[0]][destProperty[1]] = tinted;
});
})();
SVGDemo.Converters = Converters;
})(SVGDemo || (SVGDemo = {}));
The first converter, svgAttr sets an attribute on the target SVG element. In this example:
<g data-win-bind="transform:svgScale SVGDemo.Converters.svgAttr">
The transform attribute of the element is set to the .svgScale member of the object to which the template is bound. And so if the input object to the binding operation looks like this:
var starYellow = {
color: "#FFFF00",
svgScale: "scale(1.0)",
scale: 1.0
};
The svgAttr custom converter runs and the result is:
<g transform="scale(1.0)">
Custom converter tintColor is more sophisticated in that it takes both the bound object’s property into account, it also uses the original color of the targeted style property to help determine the final color. This converter uses the red component of the original style color to determine how much of the object’s source color to use. So the redder the template is, the more the source color is used; so that if the template’s color is pure red (#FF0000) then the resulting color is 100% of the source color.
So for example, we have a path with a style.fill we want to tint:
<path style="fill:#770000;fill-opacity:1;stroke:none;"
d="m 483.39049,436.95746 …"
data-win-bind="style.fill:color SVGDemo.Converters.tintColor" >
</path>
Using the same starYellow object above, the converter evaluates the red value of the source template color and calculates the resulting color:
<path style="fill:#777700;fill-opacity:1;stroke:none;"
d="m 483.39049,436.95746 …">
</path>
Note: This implementation of the tintColor converter assumes: 1) that you will be binding to style property 2) that property will be a color.
Scaling SVG in the DOM
SVG renders extremely well, but there may be issues getting it to flow properly in the document layout if you’ve scaled it like the code does above. Luckily, you have complete control over the DOM elements and if you know the size of the SVG that the template produces, you can easily calculate the size of the SVG when you render it.
var svgStarTemplateWidth = 329;
var svgStarTemplateHeight = 326;
var starTemplate = WinJS.Utilities.query("#starTemplate")[0].winControl;
function renderStar(starInfo) {
starTemplate.render(starInfo).then(function (div) {
div.style.width = (svgStarTemplateWidth * starInfo.scale) + "px";
div.style.height = (svgStarTemplateHeight * starInfo.scale) + "px";
div.style.display = "inline-block";
document.body.appendChild(div);
});
}
Doing this allows the proper layout of elements on the page:
GIT the Code
Download the code at https://github.com/gwsii/WinJSSVGTemplate
And be sure to download and enjoy your own copy of Gamer Dice for Windows 8!