Digital humanities


Maintained by: David J. Birnbaum (djbpitt@gmail.com) [Creative Commons BY-NC-SA 3.0 Unported License] Last modified: 2020-04-29T19:45:16+0000


Regex test: answer key

The task

Your first SVG homework assignment asked you to create by hand an SVG bar chart that included the results of a Best stooge ever contest, and your second and third SVG homework assignments asked you to write XSLT to generate a bar graph of presidential election results. You may have figured out that we faked the data for the Best stooge ever contest, but for this assignment you will be using real data from Five Thirty Eight, mostly known for election predictions, but here turned to a much more useful research goal: what are the most popular Dungeons and Dragons classes? Some of the poll results can be represented in a bar graph, like the data from the stooge contest or the US presidential election. Your task for this test is to write an XSLT stylesheet to convert the XML results of the DnD character creation data (below) into an SVG bar graph. At a minimum, your graph should include the X and Y axes with labels (with whatever labels you consider appropriate), as well as bars for the percentage of votes for each class. You should upload only your XSLT to CourseWeb; we do not need either the XML or the SVG that your XSLT generates. But be sure to open your output SVG in <oXygen/> to verify that the SVG is valid, and also in a browser to ensure that it looks the way you think it should look.

In the XML below, each element contains the raw number of created characters (you have to use XPath to calculate the percentages) each class received.

<classes>
    <class type="rogue">11307</class>
    <class type="wizard">9855</class>
    <class type="bard">7804</class>
    <class type="cleric">9009</class>
    <class type="ranger">8887</class>
    <class type="druid">6328</class>
    <class type="paladin">8840</class>
    <class type="barbarian">9063</class>
    <class type="warlock">8711</class>
    <class type="monk">7892</class>
    <class type="fighter">13906</class>
    <class type="sorcerer">7587</class>
</classes>

You may find it helpful to use variables to calculate percentages and other values needed for proper placement of your SVG objects.

Solution

See the inline comments to explanation.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="3.0"
    xmlns="http://www.w3.org/2000/svg">
    <xsl:output method="xml" indent="yes"/>

    <!-- ================================================================ -->
    <!-- Stylesheet variables                                             -->
    <!-- ================================================================ -->
    <xsl:variable name="total" as="xs:double" select="sum(//class)"/>
    <xsl:variable name="barWidth" select="30" as="xs:double"/>
    <xsl:variable name="interbarSpace" select="$barWidth div 2" as="xs:double"/>
    <xsl:variable name="yScale" as="xs:double" select="3"/>
    <xsl:variable name="maxHeight" select="max(//class) div $total * 20" as="xs:double"/>
    <xsl:variable name="XLength" select="($interbarSpace + $barWidth) * (count(//class) + 1)"
        as="xs:double"/>
    <!-- Sequence of color names to color bars differently by position    -->
    <xsl:variable name="color" as="xs:string+"
        select="
            'red', 'CornflowerBlue', 'green', 'LemonChiffon', 'Lavender', 'LightCyan',
            'LightGreen', 'pink', 'grey', 'Violet', 'LemonChiffon', 'Lavender', 'LightCyan'"/>
    <!--                                                                  -->

    <!-- ================================================================ -->
    <!-- Templates                                                        -->
    <!-- ================================================================ -->
    <xsl:template match="/">
        <svg width="{$XLength}" height="{$maxHeight + 200}">
            <g transform="translate(40,{$maxHeight + 100})">
                <!-- ==================================================== -->
                <!-- Ruling lines first, so that bars overlap them        -->
                <!-- ==================================================== -->
                <!-- 10% gray line -->
                <line x1="0" y1="-{10 * $yScale}" x2="{$XLength}" y2="-{10 * $yScale}"
                    stroke="lightgray" stroke-width="1"/>
                <text x="-10" y="-{10 * $yScale}" text-anchor="middle" font-size="50%">10%</text>
                <!-- 20% gray line -->
                <line x1="0" y1="-{20 * $yScale}" x2="{$XLength}" y2="-{20 * $yScale}"
                    stroke="lightgray" stroke-width="1"/>
                <text x="-10" y="-{20 * $yScale}" text-anchor="middle" font-size="50%">20%</text>
                <!--                                                      -->
                <!-- ==================================================== -->
                <!-- Create the bars                                      -->
                <!-- ==================================================== -->
                <xsl:apply-templates select="//class">
                    <xsl:sort select="(. div $total) * 100" order="descending"/>
                </xsl:apply-templates>
                <!--                                                      -->
                <!-- ==================================================== -->
                <!-- Draw axes last, so that they overlap bars            -->
                <!-- ==================================================== -->
                <!-- x-axis -->
                <line x1="0" y1="0" x2="{$XLength}" y2="0" stroke="black" stroke-width="2"
                    stroke-linecap="square"/>
                <!-- y-axis -->
                <line x1="0" y1="0" x2="0" y2="-{20 * $yScale}" stroke="black" stroke-width="2"
                    stroke-linecap="square"/>
                <!-- x-axis label -->
                <text x="0" y="-{$maxHeight * $yScale}" font-size="65%" text-anchor="middle"
                    transform="rotate(270 -20 -{$maxHeight div 2 * $yScale})">Percentage of
                    Characters</text>
                <!-- y-axis label -->
                <text x="{$XLength div 2}" y="45" text-anchor="middle" font-size="75%"
                    >Classes</text>
                <!-- title -->
                <text x="{$XLength div 2}" y="-{$maxHeight * $yScale + 55}" text-anchor="middle"
                    >Most Popular Classes in Dungeons and Dragons</text>
            </g>
        </svg>
    </xsl:template>
    <!--                                                                  -->
    <xsl:template match="class">
        <xsl:variable name="XPos" as="xs:double"
            select="(position() - 1) * ($barWidth + $interbarSpace) + $interbarSpace"/>
        <xsl:variable name="votes" as="xs:double" select="."/>
        <xsl:variable name="votePercent" as="xs:double" select="$votes div $total * 100"/>
        <!-- ============================================================ -->
        <!-- Bars                                                         -->
        <!-- Use position() to select color                               -->
        <!-- ============================================================ -->
        <rect x="{$XPos}" y="-{$votePercent * $yScale}" height="{$votePercent * $yScale}"
            width="{$barWidth}" stroke="black"
            fill="{$color[count(current()/preceding-sibling::class) + 1]}"/>
        <!-- ============================================================ -->
        <!-- Percentage of characters                                     -->
        <!-- ============================================================ -->
        <text x="{$XPos + ($barWidth div 2)}" y="-{($votePercent * $yScale) + 5}" font-size="75%"
            text-anchor="middle">
            <xsl:value-of select="round-half-to-even(. div $total * 100)"/>
            <xsl:text>%</xsl:text>
        </text>
        <!-- ============================================================ -->
        <!-- Class name                                                   -->
        <!-- Use concat(), upper-case() to capitalize first letter        -->
        <!-- ============================================================ -->
        <text x="{$XPos + ($barWidth div 2) - 15}" y="15"
            transform="rotate(-50 {$XPos + ($barWidth div 2)} 0)" text-anchor="middle"
            font-size="50%">
            <xsl:value-of
                select="
                    concat(upper-case(substring(@type, 1, 1)),
                    substring(@type, 2))"
            />
        </text>
    </xsl:template>
</xsl:stylesheet>