Pychart est une bibliothèque Python permettant des graphiques directement en python. Un paquet Debian est disponible (nommé python-pychart) avec la dernière version 1.39 (qui date de 2006).
Debian utilise massivement des listes de diffusion pour la collaboration de ses membres. Parmi les nombreuses listes existantes, trois sont francophones :
- debian-users-fr, dédiée aux questions des utilisateurs ;
- debian-l10n-fr, dédiée aux traductions en français ;
- debian-news-french, dédiée aux communiqués officiels. Elle est exclue du graphique car elle ne représente pas vraiment l’activité des listes de diffusion.
Pychart est utilisé pour créer un graphique montrant l’évolution du volume de messages postés sur ces deux listes pendant une année glissante (soit d’avril 2012 à avril 2013).
Le graphique
Le graphique représente le nombre de messages électroniques envoyés sur les deux listes par jour.
On constate que les pics et les creux se font sur les mêmes mois. Par contre, les tendances générales sont inversées : en hausse pour la liste utilisateur, en baisse pour la liste dédiée aux traductions.
Réalisation
Le code Python est fourni à la fin de l’article. Pychart s’appuie sur de nombreuses inventions anciennes mais solidement éprouvées :
Invention de la poubelle (1884)
Toute la documentation fait des imports globaux (from pychart import *), comme beaucoup d’exemples disponibles sur le web. Pour éviter de polluer l’espace de nom, il suffit d’importer les différents sous-modules de manière classique (import pychart.sousmodule). Je n’ai pas eu besoin d’importer certains sous-modules bien qu’ils soient utilisés dans le script.
Création de postscript (1982)
Plusieurs formats de sortie sont disponibles (ps, pdf, png et svg). Par défaut, le fichier sera en PostScript.
Pour le changer vers le format png :
pychart.theme.output_format = "png"
Pour avoir une sortie en png, ghostscript doit être installé sur le système. PIL n’est pas nécessaire.
Invention de la télévision couleur (1938)
Par défaut, les graphiques seront uniquement en nuances de gris. Si on souhaite avoir plein de couleurs, il faut le déclarer de la manière suivante :
pychart.theme.use_color = True
Invention de l’écriture de travers (-3400)
Il est possible d’orienter le texte affiché (que ce soit pour les axes ou des boîtes de texte ad-hoc). Pour cela, il faut définir l’angle d’orientation (« /a » pour définir que l’on modifie l’angle, « -20 » pour baisser de 20 degrés par rapport à l’horizontale dans l’exemple ci-dessous) :
pychart.axis.X(label="Date", format="/a-20/hL%s")
La définition du formatage de la chaîne à afficher utilise le caractère « / » pour introduire un contrôle. Il est nécessaire de le doubler lorsque l’on souhaite l’afficher (pour une date par exemple).
Invention du dessin (Paléolithique)
pychart.line_plot.T(label=u"debian-user-french", #sert pour la légende
data=stats, #la structure des données à afficher
ycol=2, #la troisième colonne dans les données
line_style=_blue_line #le rendu des données
)
Invention du papier (IIième siècle avant JC)
La méthode draw() permet d’afficher les données sur la sortie standard. Il suffit simplement de la rediriger vers un fichier grâce au shell :
$ python volume.py > volume.png
Code source et références
Le code qui a permis de créer le graphique :
# -*- coding: utf-8 -*-
import pychart.area
import pychart.line_plot
import pychart.theme
pychart.theme.output_format = "png"
pychart.theme.use_color = True
pychart.theme.default_font_size = 14
pychart.theme.title = "14"
stats = (("04//2012", 12.27, 14.43),
("05//2012", 10.71, 14.42),
("06//2012", 11.77, 13.10),
("07//2012", 10.19, 8.55),
("08//2012", 11.65, 14.68),
("09//2012", 13.00, 18.33),
("10//2012", 10.32, 15.06),
("11//2012", 6.87, 14.90),
("12//2012", 5.48, 10.48),
("01//2013", 5.29, 20.58),
("02//2013", 10.25, 22.39),
("03//2013", 9.74, 24.00),
("04//2013", 8.3, 19.80))
_area = pychart.area.T(size = (700, 450),
y_grid_interval=5,
x_coord=pychart.category_coord.T(stats, 0),
x_axis=pychart.axis.X(label="Date", format="/a-20/hL%s"),
y_axis=pychart.axis.Y(label="Nombre de messages par jour"),
legend=pychart.legend.T(),
y_range=(0, None))
_red_line = pychart.line_style.T()
_red_line.width = 2
_red_line.dash = (5, 5)
_red_line.cap_style = 2
_red_line.join_style = 2
_red_line.color = pychart.color.red
_blue_line = pychart.line_style.T()
_blue_line.width = 2
_blue_line.color = pychart.color.royalblue
_l10n_plot = pychart.line_plot.T(label=u"debian-l10n-french", data=stats, line_style=_red_line)
_user_plot = pychart.line_plot.T(label=u"debian-user-french", data=stats, ycol=2, line_style=_blue_line)
_area.add_plot(_l10n_plot, _user_plot)
_area.draw()
Les moyennes journalières ont été calculées préalablement. Elles sont exclues du code source pour ne pas l’alourdir inutilement. Voici les données brutes qui ont servi aux calculs des moyennes :
date debian-l10n-french debian-user-french nbre_jours
2012/04 368 433 30
2012/05 332 447 31
2012/06 353 393 30
2012/07 316 265 31
2012/08 361 455 31
2012/09 390 550 30
2012/10 320 467 31
2012/11 206 447 30
2012/12 170 325 31
2013/01 164 638 31
2013/02 287 627 28
2013/03 302 744 31
2013/04 190 594 30
Les données ont été récupérées à partir des versions publiques des archives des listes de traductions.
La documentation de pychart est disponible à http://home.gna.org/pychart/doc/pychart.html ou dans le paquet python-pychart-doc.
Enfin, Matplotlib répond au même besoin et semble être un projet plus vivant.
Jinja2, un moteur de templates en Python, et AngularJS, un framework web côté client en Javascript, utilisent tous les deux les accolades pour indiquer une variable ou une structure à interpréter.
Par exemple {{ choucroute }} pour une variable qui a du goût.
D’où conflit.
Pour résoudre ce problème, trois possibilités :
Diviser les fichiers pour régner
Séparer l’utilisation de chaque template :
- variables Jinja uniquement dans les fichiers html fournis directement par l’application Python ;
- variables AngularJS uniquement dans les fichiers chargés dynamiquement (par exemple /partials/detail-choucroute.html).
Simple à mettre en place mais limité.
Commenter les accolades
Protéger le code AngularJS sensible en empêchant son interprétation par Jinja :
{{ '{{' }}pour ne pas interpréter des accolades ;{% raw %}pour un code plus long.
Changer les délimiteurs Jinja
Redéfinir les délimiteurs dans l’environnement de Jinja :
jinja_env = jinja2.Environment(...)
jinja_env.block_start_string = '(%'
jinja_env.block_end_string = '%)'
jinja_env.variable_start_string = '(('
jinja_env.variable_end_string = '))'
jinja_env.comment_start_string = '(#'
jinja_env.comment_end_string = '#)'
L’ensemble des caractères accolades ont été remplacés par des parenthèses pour une raison de cohérence. Limiter la redéfinition des délimiteurs à variable_start_string et variable_end_string devrait suffire.
Bien évidemment, libre à chacun de choisir d’autres délimiteurs.
À noter qu’il est possible d’obtenir un résultat similaire avec Django.
Il arrive parfois que l’on doive jongler entre différentes bases de données, chacune ayant une syntaxe légèrement différente des autres. Voici un petit résumé de ce cirque pour éviter de passer pour un clown…
Entrée des artistes : se connecter au serveur
MySQL :
$ mysql -u utilisateur_avec_les_droits -p
Il faut alors saisir le mot de passe pour accéder à l’interpréteur. Il est aussi possible de saisir le mot de passe directement après le paramètre -p mais dans ce cas, le mot de passe est lisible dans l’historique de connexion de l’utilisateur (dans le fichier .bash_history si le shell utilisé est bash).
PostgreSQL :
$ psql -U utilisateur_avec_les_droits
SQLite :
$ sqlite3 cirque.db
Pour les bases de données créées avec la version 2 de SQLite, il faut utiliser la commande sqlite et non sqlite3.
Voir les bases de données disponibles
MySQL: SHOW DATABASES;
PosgreSQL : \l
SQLite : .databases
Choisir la base de données à utiliser
Supposons qu’il existe une base nommée « cirque ».
MySQL : USE cirque;
PostgreSQL : \c cirque
sqlite : rien à faire, la base est déjà sélectionnée.
Lister les tables de la base
MySQL : SHOW TABLES;
PostgreSQL : \d
SQLite : .tables
Voir le schéma d’une table
Supposons qu’il existe une table nommée « spectacles ».
MySQL : DESC spectacles;
PostgreSQL : \d spectacles
SQLite : .schema spectacles
Aide en ligne
Si vous pensez que ce sera plus rapide que de chercher dans la première réponse trouvée par votre moteur de recherche préféré, vous pouvez utiliser l’aide en ligne de l’outil.
MySQL : help
PostgreSQL : help
SQLite : .help
Le salut des artistes
Les versions utilisées pour ce tour de piste :
MySQL : 5.1.66
PostgreSQL : 8.4.16
SQLite : 3.7.13
N’avez-vous jamais discuté des mérites de telle ou telle distribution Linux (ou BSD) avec d’autres personnes (chacun essayant de démontrer que celle qu’il utilise au quotidien est la meilleure) ? Grâce à cet article, vous n’aurez toujours pas La réponse mais vous aurez une réponse indiscutable : il s’agit de la jouer aux cartes. Les cartes sont créées avec Python Imaging Library, une bibliothèque Python de manipulation d’image. L’article explique la façon de les réaliser.

Chaque carte possède un ensemble de caractéristiques, la plus forte remporte le pli. Ce principe de jeu existe depuis les années 70 (Ace_Trumps, Super Top Ass). L’ensemble des 26 cartes créées est visible ici.
L’objectif de l’article est de montrer comment on peut concevoir la création de la carte, pas le détail des paramètres de chaque fonction. Pour cela, la documentation et de nombreuses explications foisonnent déjà sur le web. C’est pourquoi, par exemple, les calculs de dimensionnement ne seront pas expliqués.
Prérequis : installer PIL (ou Pillow)
Deux possibilités :
- utiliser le système de paquet de votre distribution (python-imaging pour Debian) ;
- utiliser Pypi : PIL n’est pas disponible, il faut installer Pillow. Cela ne change pas la façon d’utiliser la bibliothèque :
from PIL import Image, ImageDraw, ImageFont
Image sert à instancier une image, ImageDraw à la modifier, ImageFont à choisir une police de caractère.
Créer une carte
La base consiste à instancier un objet image sur lequel les modifications vont être apportées. Une fois les modifications réalisées, on enregistre le résultat sur le disque :
def draw_card(distrib):
img = Image.open(BACKGROUND_PATH)
#ajout des textes, images, etc.
img.save(distrib["img_name"] + ".png")
La carte est une superposition de couches, qui sont toutes fusionnées en une seule image. On a donc un fond sur lequel les autres éléments sont superposés.
Parmi les éléments remarquables :
Textes dans un cartouche
Le titre et les caractéristiques des distributions sont faits de la même manière. Il n’existe pas d’effet avec PIL pour dessiner automatiquement une bordure. Elle est réalisée en plaçant deux rectangles l’un sur l’autre, celui de dessous étant plus large et haut.

def draw_title(img, title):
"""
pour dessiner le titre avec le fond et la bordure autour
img est l'image PIL
title est la chaine de caracteres a afficher ("Distribution")
"""
draw = ImageDraw.Draw(img)
draw_cartouche(draw, 25, 55)
font = ImageFont.truetype(FONT_PATH, TITLE_FONT_SIZE)
width, height = draw.textsize(title, font=font)
x = center(width)
draw.text((x, 29), title, font=font, fill=TEXT_COLOR)
def draw_cartouche(draw, upper_height, lower_height):
"""pour dessiner les deux rectangles"""
draw.rectangle(((MARGIN_LEFT, upper_height), (CARD_WIDTH - MARGIN_RIGHT, lower_height)),
fill="#8e6f32")
BORDER = 5
draw.rectangle(((MARGIN_LEFT + BORDER, upper_height + BORDER), (CARD_WIDTH - MARGIN_RIGHT - BORDER, lower_height - BORDER)),
fill="#e9b654")
draw.textsize() permet de connaître les dimensions que prendrait la chaîne passée en paramètre. Cela permet de faire un calcul (dans la fonction center(), omise dans l’extrait ci-dessus) pour centrer le texte.
Le logo de la distribution

L’affichage du logo suit la même logique en intercalant une image (nommée supernova.png) entre le fond et le logo de la distribution. Pour avoir un joli rendu , on décale simplement en hauteur l’image (y) car les deux images n’ont pas la même taille.
def draw_logo(img, filename):
"""on colle supernova
puis le fichier correspondant au parametre filename"""
image_path = SRC_IMGS_DIR + "supernova.png"
nova = Image.open(image_path)
nova_width, nova_height = nova.size
x = center(nova_width)
y = 60
img.paste(nova.convert("RGBA"),
(x, y, x + nova_width, y + nova_height),
mask=nova.convert('RGBA'))
image_path = SRC_IMGS_DIR + filename + ".png"
distro = Image.open(image_path)
distro_width, distro_height = distro.size
x = center(distro_width)
y = 90
img.paste(distro.convert("RGBA"),
(x, y, x + distro_width, y + distro_height),
mask=distro.convert('RGBA'))
L’exercice de factorisation de cette fonction est laissé aux lecteurs qui s’ennuient et qui ne sont pas partis faire autre chose (ce que je ne comprend pas d’ailleurs).
Le numéro de la carte
Le numéro de la carte en bas à gauche est affiché de biais. Si les images peuvent subir une rotation, les textes ne sont affichés qu’horizontalement. La solution est donc de coller le texte dans une image intermédiaire. Cette image subira une rotation puis sera collée sur l’image finale de la même manière que précédemment :
def draw_card_index(img, number):
"""pour dessiner 'number' de biais"""
HEIGHT = WIDTH = 19
num_img = Image.new("RGBA",
(WIDTH, HEIGHT),
(0, 0, 0, 0))
num_draw = ImageDraw.Draw(num_img)
font = ImageFont.truetype(SANS_PATH, TEXT_FONT_SIZE)
num_draw.text((0, 0), number, font=font, fill="brown")
n = num_img.rotate(-45)
X, Y = 7, 378
img.paste(n, (X, Y, X + WIDTH, Y + HEIGHT), mask=n)
À noter que la rotation faite ici prend peu de précaution et les nombres à deux chiffres sont légèrement tronqués. Le paramètre expand peut être ajouté à rotate() pour éviter la perte, mais l’image est automatiquement agrandie.
Code source et ressources
La documentation de PIL est précieuse.
Un tutoriel dont certains effets ont été réutilisés pour les cartes.
Les données sur les cartes proviennent principalement de Distrowatch. Les logos des distributions sont aussi ceux affichés sur Distrowatch. « first stable » représente la première version stable avec le nom actuel de la distribution ou ce qui pourrait être considéré comme équivalent. « based on » indique le nom de la distribution parente. La valeur « Indep. » signifie qu’elle n’est basée sur aucune autre distribution. Si elle est suivie d’une étoile, cela signifie qu’elle est maintenant indépendante mais a été basée sur une autre distribution par le passé.
Le fond de carte et l’effet supernova ont été réalisés avec The Gimp.
L’archive contenant le code source du script au cas où quelqu’un voudrait le réutiliser, ainsi que les logos et les cartes générées est fournie au format .tar.xz. Le code a été écrit dans un but de démo. Il est bien perfectible…
Paybox est une solution de paiement électronique que nous avons utilisé plusieurs fois.
Pour se connecter avec le service Paybox (en version 5.08), il est nécessaire de produire une empreinte HMAC des données du serveur que le client va envoyer vers le serveur Paybox pour payer. L’objectif est d’empêcher toute falsification car elle serait détectée grâce à l’empreinte qui ne correspondrait plus.
La documentation fournit une implémentation d’exemple en PHP :
<?php
$key =
"0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
$msg = "FORMULAIRE";
$binkey = pack("H*", $key);
$hmac = strtoupper(hash_hmac('sha512', $msg, $binkey));
?>
Voici l’équivalent en Python :
import binascii import hashlib import hmac PRIVATE_KEY = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" msg = "FORMULAIRE" binary_key = binascii.unhexlify(PRIVATE_KEY) hmac = hmac.new(binary_key, msg, hashlib.sha512).hexdigest().upper()
Les deux codes fournissent le même résultat pour des couples (clé privée, message) identiques. Charge à vous de mettre les bonnes valeurs.
