Réaliser un histogramme en SVG (XSLT 1.0)
Soit le fichier xml suivant (effectifs.xml), présentant les effectifs
d'une startup répartis par année d'entrée dans l'entreprise
:
<?xml version="1.0" encoding="ISO-8859-1"?>
<effectifs>
<classe>
<annee_arrivee>1997</annee_arrivee>
<nb_personnes>13</nb_personnes>
</classe>
<classe>
<annee_arrivee>1998</annee_arrivee>
<nb_personnes>20</nb_personnes>
</classe>
<classe>
<annee_arrivee>1999</annee_arrivee>
<nb_personnes>8</nb_personnes>
</classe>
<classe>
<annee_arrivee>2000</annee_arrivee>
<nb_personnes>5</nb_personnes>
</classe>
<classe>
<annee_arrivee>2001</annee_arrivee>
<nb_personnes>2</nb_personnes>
</classe>
</effectifs>
On se propose de représenter ces effectifs sous forme d'un diagramme
à barres, avec en abscisses les années d'ancienneté
(de 0 à 5 ans) et en ordonnées le pourcentage du personnel
ayant dépassé l'ancienneté considérée.
Voir le diagramme en SVG
(plug-in requis)
Au moyen de l'élément SVG nous allons déclarer
que notre image occupe tout l'écran et que dans notre espace utilisateur
les abscisses iront de 0 à 1500 et les ordonnées de 0 à
1000 (unités utilisateur)
<svg width="100%" height="100%" viewBox="0 0 1500
1000">
La position de l'origine de notre système de coordonnées,
la largeur des barres, le coefficient multiplicateur à appliquer
aux ordonnées, l'espacement des barres vont être déclarés
au tout début de la feuille de style, au moyen de paramètres
dont la valeur sera exprimée en "unités utilisateur".
<xsl:param name="Ox" select="200" />
<xsl:param name="Oy" select="800" />
<xsl:param name="largeur_barre" select="200" />
<xsl:param name="pas_y" select="5" />
<xsl:param name="intervalle" select="30" />
Notre modèle de plus haut niveau sera donc de la forme suivante
:
<xsl:template match="/">
<svg width="100%" height="100%" viewBox="0 0 1500 1000">
<title>Ancienneté à MarkupMyWeb</title>
<xsl:apply-templates/>
</svg>
</xsl:template>
Un second modèle va se contenter de mettre un titre à la
page, de sélectionner l'ensemble des noeuds classe, de les
classer par ordre décroissant de millésimes, puis de passer
(récursivement) la main à un troisième modèle
chargé de traiter spécifiquement ces noeuds classe
<xsl:template match="anciennetes">
<text style="font-size:60; font-family:'Comic Sans MS'; color:teal;
text-anchor:middle;" x="750" y="100">
Répartition de l'ancienneté à <tspan style="color:red;
font-style:italic">MarkupMyWeb</tspan>
</text>
<g style="filter:url(#MyFilter)">
<xsl:apply-templates>
<xsl:sort select="annee_arrivee" data-type="number" order="descending"/>
</xsl:apply-templates>
</g>
</xsl:template>
Le troisième modèle fait l'essentiel du travail. Il détermine
la couleur de la barre en fonction de la position du noeud courant dans
l'ensemble des noeuds sélectionnés. Il calcule le pourcentage
des personnes entrées dans la société pendant l'année
courante par rapport au cumul des personnes entrées pendant l'année
courante et toutes les années précédentes. Et il appelle
le modèle nommé barre chargé de générer
la barre, en lui transmettant cette valeur, une légende à
inscrire en-dessous de la barre, la position horizontale de la barre et
sa couleur.
<xsl:template match="classe">
<xsl:variable name="couleur">
<xsl:choose>
<xsl:when test="position() mod 5 = 1">red</xsl:when>
<xsl:when test="position() mod 5 = 2">blue</xsl:when>
<xsl:when test="position() mod 5 = 3">green</xsl:when>
<xsl:when test="position() mod 5 = 4">purple</xsl:when>
<xsl:otherwise>orange</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="cumul">
<xsl:choose>
<xsl:when test="preceding-sibling::*"><xsl:value-of select="sum(preceding-sibling::*/nb_personnes)"
/></xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:call-template name="barre">
<xsl:with-param name="valeur" select="round((nb_personnes + $cumul)
div $total * 100)" />
<xsl:with-param name="legende">> <xsl:value-of select="2001 -
annee_arrivee"/> année<xsl:if test="(2001 - annee_arrivee) >
1">s</xsl:if>
</xsl:with-param>
<xsl:with-param name="posx" select="$Ox + (position() - 1)*($largeur_barre
+ $intervalle)" />
<xsl:with-param name="couleur" select="$couleur"/>
</xsl:call-template>
</xsl:template>
Note. On a tenu compte, dans l'évaluation
de la variable cumul, du fait que pour l'année la plus ancienne,
l'ensemble de noeuds preceding-sibling::*
est vide : sum(preceding-sibling::*/nb_personnes)
ne rend pas alors la valeur 0 mais la valeur null. Un test était
donc nécessaire pour traiter convenablement ce cas particulier.
Le modèle nommé barre va dessiner le rectangle
correspondant à la barre et le positionner par translation à
l'endroit convenable. Il va porter la légende en dessous de la barre,
et inscrire au-dessus de la barrre la valeur correspondante suivie
de l'unité convenable (% en l'occurence) -- elle-même définie
comme paramètre au tout début de la feuille de style.
<xsl:template name="barre">
<xsl:param name="valeur"/>
<xsl:param name="posx"/>
<xsl:param name="legende"/>
<xsl:param name="couleur"/>
<xsl:variable name="hauteur" select="$valeur * $pas_y"/>
<g transform="translate({$posx}, {$Oy - $hauteur})" style="font-size:
30">
<text x="{$largeur_barre div 2}" y="-20" style="text-anchor:middle">
<xsl:value-of select="concat($valeur,' ',$unite)"/>
</text>
<rect width="{$largeur_barre}" height="{$hauteur}" style="fill:{$couleur}"/>
<text x="{$largeur_barre div 2}" y="{$hauteur + 40}" style="text-anchor:middle">
<xsl:value-of select="$legende"/>
</text>
</g>
</xsl:template>
Voir/Télécharger les fichiers de l'exemple
:
Afficher l'histogramme "à la volée"
Attention : l'exemple présenté ci-dessus
-
permet de fabriquer, par transformation XSLT "hors ligne", un fichier SVG
"en dur" et de l'afficher ultérieurement ;
-
ne permet pas, du moins avec le plug-in
SVG d'Adobe dans sa version actuelle (2.0), d'afficher le SVG
"à la volée" en lisant directement dans le butineur le fichier
XML associé à sa feuille de style XSLT (voir
le résultat).
Existe-t-il une solution à ce problème ? Oui, et elle nous
est fournie par Chris Bayes sur son site XML/XSL
Portal (rubrique Inline SVG (IE5/Adobe SVG). Chris nous propose
de :
-
enchâsser le SVG généré dans une page HTML ;
-
différentier le SVG par la référence à son
espace de nommage ;
-
donner un attribut id à l'élément
SVG de plus haut niveau <svg:svg>
;
-
associer le page HTML à un script (inlinesvg_2.js dans sa version
actuelle).
Et le tour est joué !
Voir/Télécharger les fichiers correspondants
:
Contribuez ! Faites-nous profiter
de vos exemples les plus significatifs.
Retour à la page d'accueil.