The Visual Display of Quantitative XML
by Fabio Arciniegas A.
|
Automating The Street Labels with xsl:number
The XSLT instruction xsl:number can be used in one of
two basic ways. The first takes a pattern as its count attribute and
outputs a number for each matching node. The second takes a specific
number and formats it. The second form of xsl:number is
ideal for our purposes because we are creating the city layout based
on a fixed number of blocks, and we pass a parameter with the block
number to the row_of_blocks template. Listing 5 shows
how we use xsl:number to automate the street
labeling.
As in previous listings, I embed here the clean-cut version for the
XSLT aficionado, but a more comprehensively explained version is also
provided.
Listing 5: Marking the streets with xsl:number
(full source)
<xsl:template name="row_of_blocks">
<xsl:param name="column"/>
<xsl:param name="row"/>
<xsl:variable name="x">
<xsl:value-of select="$column *
($blockWidth + $streetWidth)"/>
</xsl:variable>
<xsl:variable name="y">
<xsl:value-of select="$row *
($blockHeight + $streetWidth)"/>
</xsl:variable>
<xsl:if test="$column > 0">
<rect x="{$x}"
y="{$y}"
rx="2"
ry="2"
width="{$blockWidth}"
height="{$blockHeight}"
fill="none"
stroke="black"/>
<!-- OUTPUT STREET NAME -->
<!-- Since we don't want to put the street name on
every intersection but only at the bottom, we must
check that we are dealing with the lowest
row before outputting the column -->
<xsl:if test="$row = $cityHeight">
<text x="{$x - 6}"
y="{$y + 32}"
style="font-family:Arial; font-size:8pt">
<xsl:number value="$column"/>
<tspan style="font-size:7pt; fill:#333333">
<xsl:if test="$column = 1">st</xsl:if>
<xsl:if test="$column = 2">nd</xsl:if>
<xsl:if test="$column = 3">rd</xsl:if>
<xsl:if test="$column > 3">th</xsl:if>
</tspan>
</text>
</xsl:if>
<xsl:call-template name="row_of_blocks">
<xsl:with-param name="column">
<xsl:value-of select="$column - 1"/>
</xsl:with-param>
<xsl:with-param name="row">
<xsl:value-of select="$row"/>
</xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:template>
After reading the listing above you might be wondering why we used
xsl:number instead of simply outputing the value of the
column; the answer is we might want to take advantage of the format
attribute of number to provide a different output sequence. This is
shown in the next listing, where we reuse the same
xsl:number element, but this time we output the street
names as the sequence "A", "B", "C",
etc.
Listing 6: Marking letter streets with xsl:number
(full
source)
<xsl:if test="$column = 0 and $row != 1">
<text x="{$x + 23}" y="{$y + 2}"
style="font-family:Arial; font-size:8pt">
<xsl:number value="$cityHeight - $row + 2"
format="A"/>
</text>
</xsl:if>
The result is nice alphabetical and numeric-labeled street signs as shown in
Figure 5 below
Figure 5: Street Signs (right click for source)
Putting a Club on the Map
In order to display the establishments on the map we will use small
PNG icons. We could also have drawn the icons in SVG, but using PNG
icons gives us the chance to point out a few important issues about
mixing raster images with SVG. We will use one PNG icon for each kind
of establishment (restaurant.png, cafe.png, and bar.png). I chose the
PNG (Portable Network Graphics) format not only because it is an
increasingly popular one (similar to GIF without the licensing
baggage), but because it is guaranteed that all compliant SVG viewers
can display it. The only other guaranteed format is JPEG.
The syntax for including an image is simple and is shown
below. Perhaps the only remarkable thing about this element is that
SVG uses XLink for its href attribute; you can safely
ignore the XLink details for now, but if you are interested you can
read more about XLink in my article "What is
XLink?".
<image xlink:href="booze.png"
x="116" y="97" width="16"
height="15"/>
Now that we have a complete city layout, we will begin using the
data on the source file to position clubs, cafes, and restaurants.
The process of figuring out the position and adding the appropriate
icon is shown in the following template.
Listing 7 Putting clubs on the Map
(complete
source)
<xsl:template match="attraction">
<xsl:variable name="x">
<xsl:value-of
select="number(abbrev_address/number) div 1000"/>
</xsl:variable>
<xsl:variable name="y">
<xsl:value-of select="translate(abbrev_address/street,
'ABCDEFGHI','123456789')"/>
</xsl:variable>
<xsl:if test="string-length(translate
(abbrev_address/street,'ABCDEFGHI','')) = 0">
<xsl:call-template name="point">
<xsl:with-param name="x">
<xsl:value-of select="$x"/>
</xsl:with-param>
<xsl:with-param name="y">
<xsl:value-of select="$cityHeight - ($y - 2)"/>
</xsl:with-param>
<xsl:with-param name="x_proportion">
<xsl:value-of select="$blockWidth +
$streetWidth"/>
</xsl:with-param>
<xsl:with-param name="y_proportion">
<xsl:value-of select="$blockHeight +
$streetWidth"/>
</xsl:with-param>
<xsl:with-param name="img">
<xsl:value-of select="concat(@type,'.png')"/>
</xsl:with-param>
<xsl:with-param name="y_skew">
<xsl:value-of select="(-16 - $streetWidth)*
($x*1000 mod 2)"/>
</xsl:with-param>
</xsl:call-template>
</xsl:if>
<!-- same for addresses on letter streets -->
</xsl:template>
There are many details included in the complete stylesheet to make
sure the representation is correct. For example, we determine which
side of the road the place should be according to whether its number
is odd or even.
How "Hot" is a Destination?
Now that we have each place on the map we want to display its
popularity using color, which will give us the opportunity to improve
our point XSLT template and also to play with SVG's
transparencies.
In theory, adding a colored halo to indicate popularity is really
simple: map each level of popularity to a color and draw a circle
around the place. In practice, this exposes two important details:
choosing the value of a parameter based on a discrete set of options
(a problem on the XSLT side) and creating transparencies (a problem on
the SVG side).
To map each rating to a color value, we use the following Listing 8 code.
Listing 8 Mapping Discrete Values
<xsl:variable name="color">
<xsl:choose>
<xsl:when test="average_rating=5">#F60C00</xsl:when>
<xsl:when test="average_rating=4">#F66E00</xsl:when>
<xsl:when test="average_rating=3">#F6B300</xsl:when>
<xsl:when test="average_rating=2">#F6ED00</xsl:when>
<xsl:when test="average_rating=1">#7BB105</xsl:when>
</xsl:choose>
</xsl:variable>
When starting with XSLT many people try something like
<xsl:if test test="average_rating = 5">
<xsl:variable name="color">#F60C00</xsl:variable>
</xsl:if>
This does not work simply because once the xsl:if is closed, the
variable goes out of scope. One to one mapping between input and the
value of a variable is a very common problem when representing visual
data, so make sure you keep the correct solution scheme (Listing 8) at
hand.
As you may have guessed, the reason we are creating a color
variable is so we can pass it to an improved point
template. The revised version of point is shown below in Listing 9,
where the style property fill-opacity is also used to make a
nice blend between the icons and the popularity circles.
Listing 9: Point template with icon and color
support
(full
source)
<xsl:template name="point">
<xsl:param name="x"/>
<xsl:param name="y"/>
<xsl:param name="x_proportion" select="1"/>
<xsl:param name="y_proportion" select="1"/>
<xsl:param name="img" select="none"/>
<xsl:param name="x_skew" select="0"/>
<xsl:param name="y_skew" select="0"/>
<xsl:param name="color" select="black"/>
<xsl:param name="size" select="13"/>
<circle style="{concat('fill:',$color,
'; fill-opacity:0.4')}"
cy="{$y * $y_proportion + $y_skew + 8}"
cx="{$x * $x_proportion + $x_skew + 8}"
r="{$size}"
/>
<xsl:if test="$img != ''">
<image xlink:href="{$img}"
x="{round($x * $x_proportion) + $x_skew}"
y="{round($y * $y_proportion) + $y_skew}"
width="16"
height="16"/>
</xsl:if>
</xsl:template>
The final result of adding the popularity information to the
graphic is shown below. Now with one glance, your users know the hot
areas of the city.
The Population Information -- Getting Dimensions Right
The population information is displayed with text and the
mouse-over effect. However, if you look at the code closely, you will
notice we could have passed a circle of radius dependent on the number
of people who attend the place. Instead, I chose to keep the circles
all of the same size and use them only for their colors. The reason
why I haven't used this area resource more dramatically is because the
average_occupants variable is a one dimensional piece of
information (a point), while a circle is a two-dimensional
representation (an area). Mixing the two would have resulted in one of
the most common errors when displaying quantitative data: a mismatch
of dimensions.
This problem is serious because your data would change linearly,
while your representation would not. For example, for 5, 10, 20
people, the areas for circles with those radii are 19.6, 78.5,
314.15. The result would be visually misleading about a place's
popularity. The moral is simple: don't lie about your data. Don't use
snazzy effects, but especially if they end up in a faulty
representation of data.
Prev [1] [2] [3] [4] Next