XML.com: XML From the Inside Out
oreilly.comSafari Bookshelf.Conferences.

advertisement

The Visual Display of Quantitative XML
by Fabio Arciniegas A. | Pages: 1, 2, 3, 4

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 &gt; 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 &gt; 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.


Figure 6: Popularity Data Incorporated (right-click for source)

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.

Pages: 1, 2, 3, 4

Next Pagearrow