Outils pour utilisateurs

Outils du site


issue96:python

Last time, we discussed reading and using data from an Excel file directly. If you remember, my boss (from my “day” job) had a massive spreadsheet that if one calculation failed, it caused the entire process to abort. Well, I created a database from that spreadsheet that was easy to get a report from. However, the original spreadsheet created pretty charts and graphs that his bosses liked to see. So I undertook the task to create charts so everyone would be happy. After spending about two days digging into the existing charting/graphing packages already available for Python, most free, most of them output directly to a file, like a pdf file or some sort of a graphics (jpg, png, svg) file. What I was looking for was one that would go directly to a wxPython frame or panel so it can be displayed inside a GUI program. I found one solution, but it required so many interdependent libraries that the possibility of just giving the application on a flash drive quickly became nil. So, being the pig-headed, tenacious, never-say-die kinda guy that I am, I decided to write one on my own. The original goal was that it was to do (at least) bar-charts and maybe in the future line charts and/or other types. It also should eventually be able to do colours, but just plain black bars would suffice for the time being. It should be standalone in general so that it could be called as a library. It wasn’t supposed to be so generic that it gets complicated, just dates along the horizontal (bottom) axis, values along the vertical axis and bars that represent the daily sales for that period. In order to keep the chart somewhat neat, the dates should be angled so that they don’t overwrite each other. So, what I came up with will be presented here. Left, is a sample output of the code. Again, not fancy, not terribly pretty, but it does the job. If it needs to be prettier later on, then I can work on it down the road.

La dernière fois, nous avons présenté la lecture et l'utilisation de données directement depuis un fichier Excel. Si vous vous souvenez, mon patron (celui de mon travail « de jour ») avait une monstrueuse feuille de calcul dans laquelle, si un calcul plantait, cela mettait fin à tout le processus. Bien, j'ai créé une base de données à partir de cette feuille de calcul dont l'extraction du rapport était facile. Cependant, la feuille de calcul originale créait des jolis tableaux et graphiques que mon patron aimait voir. Aussi, j'ai complété le travail pour créer des tableaux afin que chacun soit content… Après deux jours à fouiller dans les paquets de tableaux et graphiques déjà existants pour Python, la plupart gratuits et la plupart avec sortie directe en fichier, comme en pdf ou en format graphique (jpg, png, svg).

Ce que je cherchais, c'en était un qui sortirait en panneau ou en frame wxPython, de façon à pouvoir l'afficher dans un programme d'interface graphique utilisateur (GUI). J'ai trouvé une solution, mais elle demandait tellement de bibliothèques interdépendantes que la possibilité de la mettre simplement sur une clé USB devenait rapidement nulle.

Comme je suis le genre de type entêté, tenace, qui ne s'avoue jamais vaincu, j'ai décidé d'écrire mon propre programme. Le but original était de faire (au moins) des graphiques à barres et peut-être dans le futur des graphiques linéaires et/ou d'autres types. Il devrait aussi être possible de mettre de la couleur, mais de simples barres noires devraient suffire pour le moment. Il devrait être autonome pour qu'il puisse être appelé comme une bibliothèque. Il n'était pas prévu d'être si générique qu'il deviendrait très compliqué, simplement des dates sur l'axe horizontal (en bas), des valeurs sur l'axe vertical et des barres qui représentent les ventes journalières de la période. De façon à garder le tableau plus ou moins propre, les dates seraient penchées pour éviter d'être écrites les unes sur les autres. Ainsi, ce à quoi je suis arrivé est présenté ici. A gauche, c'est un échantillon de la sortie du code.

Je me répète, rien d'affriolant, pas vraiment sexy, mais il remplit son office. S'il doit devenir plus joli par la suite, je le ferai plus tard.

The first thing I had to do was pull out my wxPython documentation to remember the graphic commands. In able to draw graphics, we use the a “dc” or Device Context. It’s sort of like a blank canvas that we can draw points, lines, and text to. wxPython offers 9 different types of dc objects and I chose the wx.PaintDC which works from the OnPaintEvent. We will use some very basic commands to do our drawing and painting. These are: dc.DrawLine dc.SetPen dc.SetFont dc.DrawText dc.DrawRectangle dc.DrawRotatedText dc.GetFullTextExtent Those are the only wxPython routines we will use, though there are many others that would make our program much prettier. We will combine these commands into our own “logical” routines like, DrawBars, DrawAxis, DrawValues, and so on. While I could have done it in one or two large routines, I wanted to break them out into routines that make sense for the teaching moment. So let’s get started looking into the code. Create a file called mygraph.py. I couldn’t come up with anything pithy, since PyChart, PyGraph and the like are all taken. Maybe if I had a bit more time, I’d come up with something, but that’s not important. Let’s get started. First, we’ll do the imports as we always do. #!/usr/bin/python # mygraph.py import wx from datetime import date, datetime, time import time import math

La première chose que j'ai eu à faire, c'était de récupérer ma documentation sur wxPython pour me rappeler des commandes des graphes. Pour être en mesure de dessiner des graphes, nous utilisons « dc » ou Device Content (contenu de l'élément). C'est une espèce de canevas vierge dans lequel nous pouvons dessiner des lignes, des points et du texte. wxPython offre 9 types différents d'objets dc et j'ai choisi wx.PaintDC qui fonctionne à partir de OnPaintEvent. Nous utiliserons quelques commandes très simples pour dessiner et peindre. Ce sont :

dc.DrawLine dc.SetPen dc.SetFont dc.DrawText dc.DrawRectangle dc.DrawRotatedText dc.GetFullTextExtent

Ce sont les seules routines wxPython que nous utiliserons, bien qu'il y en ait plein d'autres qui rendraient notre programme beaucoup plus joli. Nous combinerons ces commandes dans nos propres routines « logiques » comme Drawbars, DrawAxis, DrawValues et ainsi de suite. Bien que j'aie pu faire une ou deux grosses routines, je voulais les découper en routines qui aient un sens pour la formation. Allez, commençons à regarder le code. Créez un fichier nommé mongraphe.py. Je n'ai rien trouvé de plus parlant, car PyChart, PyGraph et équivalents sont déjà tous pris. Peut-être que si j'avais eu un peu plus de temps, j'aurai trouvé autre chose, mais ce n'est pas important. Démarrons. D'abord faisons les imports comme nous le faisons toujours.

#!/usr/bin/python

# mongraphe.py

import wx

from datetime import date, datetime, time

import time

import math

Obviously, we need to import he wxPython library and the math library will help us with some of the calculations. The datetime and time libraries are used to do the date calculations for the horizontal axis labels. Something to keep in mind as we go from here…When you think about drawing on a context, the upper left corner of the container window (our dc) is X=0, Y=0. X is the horizontal axis and Y is the vertical axis. The closer we get the lower right corner, both numbers go higher. In our program, we will actually start by drawing a box that defines our charting area which starts at upper left X=10, Y=10 and end with lower right at X=800, Y=700. However, before we get to that part, we have to define a class to handle the routines and the init routine. Hopefully you remember these from earlier sessions. Top right is the class definition and the __init__ routine. Our class is called Line and we will be creating a wxFrame to do our drawing. This could also be a panel within a frame or any number of other options. My choice was to have a Frame pop up with our chart data on it. When the class is first instantiated, the __init__ routine is called with the name of the parent object, the id of that object, the title of the frame (in the title bar), the data that we want to chart and finally the title of the chart itself. Next we create the wx.Frame object that is 1024×768 pixels in size. Next we bind the paint event (which is called everything the frame is created, moved, covered, uncovered, etc.) to our event routine OnPaint. Remember, since this is inside of a class we use the “self.” to say the routine belongs to the class not somewhere else. We set some variables (BoxWidth, BoxHeight, ChartTitle, data) for use later. After we set self.data to an empty list, we call a routine called SetData to find our data scale, which we will discuss further down. Finally, we set the frame to be centered in the screen and call the Show routine. This will automatically call the OnPaint routine since we are creating the Frame.

Évidemment, nous avons besoin d'importer la bibliothèque wxPython et celle des maths nous aidera pour certains calculs. Les bibliothèques de date et de temps sont utilisées pour les étiquettes de l'axe horizontal.

Quelque chose à garder à l'esprit à partir de maintenant… Quand vous pensez à un dessin dans un contexte, le coin en haut à gauche de la fenêtre conteneur (notre dc) est x=0, y=0. X est l'axe horizontal et Y, l'axe vertical. Plus nous sommes près de l'angle en bas à droite, plus les deux nombres deviennent grands. Dans notre programme, nous commencerons par dessiner une boîte qui définit la zone de notre graphe, qui commence en haut à gauche à X=10, Y=10 et finit à X=800, Y=700. Cependant, avant de passer à cela, nous devons définir une classe pour manipuler les routines et la routine __init__. J'espère que vous vous souvenez des sessions précédentes.

En haut à droite, vous trouvez la définition de classe et la routine __init__.

Notre classe s'appelle Ligne et nous créerons une « wxFrame » pour faire notre dessin. Ce pourrait être un panneau dans une frame ou toute autre option. Mon choix a été d'avoir une fenêtre surgissante dans le graphe avec les données dedans. La première fois que la classe est instanciée, la routine __init__ est appelée avec le nom de l'objet parent, l'identifiant de cet objet, le titre de la fenêtre (dans la barre de titre) les données que l'on veut mettre en graphe et enfin le titre du graphe lui-même. Ensuite, nous créons l'objet wx.frame qui a une taille de 1024×768 pixels. Puis nous relions l'événement paint (qui est appelé quand la frame est créée, déplacée, couverte, découverte…) à notre routine d'événement OnPaint. Souvenez-vous, comme c'est à l'intérieur d'une classe, nous utilisons « self. » pour dire que la routine appartient à la classe et pas à une autre. Nous déclarons les variables (LargeurBoite, HauteurBoite, TitreGraphe, donnees) pour les utiliser plus tard. Après avoir défini self.donnees comme une liste vide, nous appelons une routine appelée ReglerDonnees pour trouver l'échelle de nos données, dont nous parlerons plus tard. Enfin, nous déclarons que la fenêtre est centrée sur l'écran et nous appelons la routine Afficher. La routine OnPaint est appelée automatiquement parce que nous créons une Frame.

Next (above) we will write a routine that will create a box that shows the area that we want to constrict our graph to. This is not a clipping or constraining box, it is simply to draw the eye to what we want the user to look at. Not really difficult. We will be using the DrawLine function several times throughout the program. Next we will create a routine that will draw the X (horizontal) and Y (vertical) axis lines on the screen. We again pass the dc of the frame into the routine. Since we just discussed the DrawLine method, there’s nothing very out of the ordinary here. We are drawing a line 580 pixels down the Frame that starts at X=60 and ends at X=700. Then we draw a line that starts at X=60 Y=580 and goes up to X=60 Y=80. This one is drawn from the bottom up, but we could have drawn it from the top down. Next we will deal with the DrawTitle routine. Once again, we pass the dc of the frame as well as the text we want to draw. During this process, think of drawing text rather than printing text. It’s a very minor thing, but it will help. This routine is longer than most of the others, but part of that is the comments I put in. The first two lines set the font and the pen style that we will be using. In the first line (SetFont), we define the font to be the “default” font, 20 points, not italic and bold. Next we set the colour of the pen to black and the width to be 20. Now we need to figure out the width of the text that we will be drawing so we know how to center it in the box. We get this information by calling the GetFullTextExtent with the text that we will be drawing using the font, font size, pen width and so on that we just defined. The tuple that is returned contains Width, Height, Decent (how far down letters like “g” and “y” will go below the base line) and any leading space. For our purposes, all we are concerned with the width. If you remember, we defined the width of the box back in the init function as 790. To find the center of our text within our box we take the box width minus the width of the text and then divide it by 2. That will be the X value we use to draw our text. Finally, we reset the pen size and colour. Rather than use some default values we pick out of nowhere, we could have called the dc.GetPen function before we started, but when I started the project, I didn’t think about it.

Ensuite (ci-dessus), nous écrirons une routine qui créera une boîte qui affiche la zone dans laquelle le graphe sera confiné. Ce n'est pas une boîte découpante ou contraignante, c'est simplement pour attirer l’œil de l'utilisateur sur ce que nous voulons qu'il regarde.

Pas vraiment difficile. Nous utiliserons la fonction Drawline plusieurs fois tout au long de ce programme. Ensuite, nous créerons une routine qui tracera les lignes d'axes X (horizontal) et Y (vertical) à l'écran. Nous passons à nouveau le dc de la fenêtre à la routine.

Pour ce qui est de la méthode DrawLine dont nous venons de parler, il n'y a rien d'extraordinaire. Nous dessinons une ligne de 580 pixels qui descend le long du Frame, commençant à X=60 et terminant à X=700. Ensuite nous traçons une ligne qui part de X=60, Y=580 et va jusqu'à X=60, Y=80. Cette ligne est tirée de bas en haut, mais vous pourriez la tracer de haut en bas.

Ensuite, nous nous occuperons de la routine DessineTitre. Une fois encore, nous passons le dc de la fenêtre ainsi que le texte que nous voulons dessiner. Durant le processus, pensez que l'on dessine du texte plutôt que l'afficher. Ce n'est pas grand chose, mais ça aide.

Cette routine est plus longue que la plupart des autres, mais c'est dû en partie aux commentaires que j'ai mis. Les deux premières lignes initialisent la police et le style d'écriture que nous utiliserons. Dans la première ligne (SetFont), nous définissons la police qui sera celle par défaut, 20 points, pas italique et grasse. Ensuite, nous déclarons noire la couleur du crayon et la largeur à 20. Maintenant nous devons estimer la largeur du texte pour le centrer dans la boîte. Nous obtenons cette information en appelant GetFullTextExtent avec le texte que nous voulons dessiner, en donnant la police et sa taille, la largeur du trait et tout ce que nous venons de définir. Le tuple qui est retourné contient Width, Height, Decent (largeur, hauteur, décalage - jusqu'à quel point des lettres comme « g » ou « y » passeront sous la ligne de base) et toute espace initiale. Pour nos besoins, seule la largeur nous importe. Si vous vous souvenez, nous avons défini une largeur de boîte de 790 dans la fonction __init__. Pour trouver le centre de notre texte dans la boîte, nous prenons la largeur de la boîte moins la largeur du texte et nous divisons par 2. Ce sera la valeur X à utiliser pour tracer le texte. Enfin, nous réinitialisons la taille du crayon et la couleur. Plutôt que d'utiliser des valeurs par défaut prises on ne sait où, nous aurions pu appeler la fonction dc.GetPen avant de commencer, mais quand j'ai commencé le projet, je n'y ai pas pensé.

Our next routine will draw the tic lines along the horizontal axis at the bottom of the chart. We want them to be equidistant along the line. We pass (as usual) the dc and a value I called dcount which is the number of dates we want to show. Since the number of days in any given month can range from 28 to 31, I wanted to be a bit dynamic. We simply use a for loop to count the number of lines to draw, which one to draw and where. If you have been carefully paying attention, we will start the lines at position 85 and it will be 20 pixels high and they will be 20 pixels apart. When we get around to drawing the dates into the chart, we want to draw the text on an angle. That way, the text doesn’t draw over itself and, well let’s admit it, looks cool. For this we will use the DrawRotatedText function. The function takes the text that we want to have drawn, the X and Y location as a starting point and the angle we want the text to be drawn. In this case, we want the text to be rotated anti-clockwise by 45 degrees which we enter as “-45”. We will set the font and pen parameters each time the text is drawn. We’ll deal with the actual draw date function in a little bit. We will also want to draw the values along the vertical axis showing tics along the way. If we had the same range of data each time, it would be very easy to do. However, reality shows that the data range of our chart could vary from run to run. One time, the highest value could be 300. The next time it could be 3000. How could we create a generic routine that would account for this? I will try to explain my mindset here. You might have wondered why I chose the value of 500 for the vertical axis when we drew the line from 80 to 580 (or actually 580 to 80). I chose to use a 500 pixel “view port” to contain our values. That way, we can create a scaling value based on an offset of 500.

Notre prochaine routine dessinera les traits d'échelle le long de l'axe horizontal en bas du graphe. Nous les voulons équidistants tout le long de la ligne. Nous passons (comme d'habitude) dc et une valeur que j'ai appelé dcount qui est le nombre de dates que nous voulons afficher. Comme le nombre de jours d'un mois varie entre 28 et 31, j'ai voulu que ce soit un peu dynamique. Nous utilisons simplement une boucle for pour compter le nombre de lignes à tracer, lesquelles tracer et où. Si vous avez été très attentifs, nous démarrerons les lignes à la position 85, elles auront 20 pixels de haut et seront espacées de 20 pixels.

Quand nous passons au tracé des dates sur le graphe, nous voulons les dessiner en biais. De cette manière, les textes ne se chevaucheront pas et, avouons-le, ce sera plus chouette. Pour cela, nous utiliserons la fonction DessineTexteRot. La fonction prend le texte que nous voulons voir dessiné, la position en X et Y comme point de départ et l'angle que nous choisissons pour le tracé. Dans le cas présent, nous voulons un texte tourné de 45 degrés en rotation anti-horaire, ce qui s'écrit « -45 ». Nous réglerons les paramètres de la police et du crayon à chaque tracé du texte. Nous parlerons de la véritable fonction de dessin de date un peu plus tard.

Nous voudrons aussi tracer les valeurs le long de l'axe vertical, avec des traits d'échelle tout le long. Si nous avions chaque fois la même étendue des données, ce serait facile à faire. Cependant, la réalité montre que la plage des données de notre graphe peut varier d'un mois sur l'autre. Une fois, la valeur la plus haute peut être 300. La fois suivante, cela pourrait être 3 000. Comment créer une routine générique qui en tient compte ? Je vais essayer ici de vous expliquer mon raisonnement.

Vous pourriez vous demander pourquoi j'ai choisi une valeur de 500 pour l'axe vertical si je trace une ligne de 80 à 580 (en réalité de 580 à 80). J'ai choisi d'utiliser une « profondeur visuelle » de 500 pixels pour contenir les valeurs. De cette façon, vous pouvez créer un facteur d'échelle basé sur un module de 500.

Let’s say that for a given run that our highest value is 395. We can simply draw a bar that is 395 pixels high to represent that value. The next run, our highest value is 2,345. If we try to draw the bar to its full height, it would disappear off the top of the chart. In order to show the value, we can round the value to the nearest 500, which would be 2500 and then set that as the top value of our axis. We then can scale the value by dividing 2500 by 500 which gives us a “scaling factor” of 4. Now if we take our data values and divide each one by our scaling factor, we can then plot the values that they will fit within our graph. So (shown top right) we need to find the highest value within our data and round that up to the nearest 500. So 375 would be 500, 3750 would be 5000 and so on. Next, we need to decide what kind of data we are going to use. You will see further down the program that I provide two different types of data in lists. One assumes that the date range we will use, along the X axis, is data for October, but you can easily follow that code (shown in a few moments) and change it to whatever month you wish. The second data list, is more generic and provides both a date and a value as a list of tuples. This allows for data to be passed for any time period. The date is a string and the value is either an integer or a float. The SetData function will look at the first value within the data list and to determine if it is a tuple. If it is, we assume that the data structure of the list is the second option, if not, it is the first. If it is a tuple, we create two lists, one for the dates and one for the values. We then walk the list splitting the data between the two lists. Once we have that done, we then find the highest value (max(self.ValList)) and send it the roundup function (shown above) so we can get our scaling value. If the data isn’t in tuples, then we clear BOTH lists and do the same steps as above.

Disons que pour un calcul donné notre valeur maximum sera 395. Nous pourrions simplement tracer une barre de 395 pixels de haut pour représenter la valeur. Au calcul suivant, ce maximum est de 2 345. Si nous essayons de tracer la barre à sa pleine hauteur, ça dépassera le haut du graphe. De façon à montrer cette valeur, je dois l'arrondir au 500 le plus près au-dessus, c'est-à-dire 2 500, à prendre comme valeur la plus haute de l'axe. Nous pouvons alors mettre à l'échelle en divisant 2 500 par 500 soit un facteur d'échelle de 5. Maintenant, si nous prenons nos données et que nous divisons chacune par le facteur d'échelle, nous pouvons tracer les valeurs, qui tiendront dans le graphe.

Aussi (voir en haut à droite), nous avons besoin de trouver la valeur la plus haute dans nos données et de l'arrondir au multiple de 500 supérieur le plus proche. Ainsi, pour 375, ce sera 500 ; pour 3 750, ce sera 4 000 et ainsi de suite.

Ensuite, nous devons décider quel type de données nous allons utiliser. Nous verrons plus loin dans le programme que je fournis deux types différents de données dans les listes. L'un assure que les plages de dates que nous utiliserons, le long de l'axe des X, sont les données pour octobre, mais vous pouvez facilement suivre le code (montré dans un petit instant) et changer pour le mois que vous voulez. La seconde liste de données est plus générique et fournit à la fois une date et une valeur comme une liste de tuples. Ceci permet de passer des données de n'importe quelle période. La date est une chaîne et la valeur est soit un entier, soit en virgule flottante. La fonction ReglerDonnees regarde la première valeur de la liste de données et détermine si c'est un tuple. Si c'est le cas, nous supposons que la structure de la liste correspond à la seconde option, sinon, c'est la première.

Si c'est un tuple, nous créons deux listes, une pour les dates et une pour les valeurs. Ensuite, nous parcourons la liste en la séparant en deux listes. Une fois cela fait, nous trouvons la plus haute valeur (max(Self.ListeValeurs)) et nous lançons la fonction d'arrondi (voir ci-dessus) pour déterminer notre facteur d'échelle. Si les données ne sont pas en tuples, nous effaçons les DEUX listes et faisons les mêmes étapes qu'au-dessus.

Now that we have our scale value we can draw our tics and the values that will represent our vertical axis. We again use a for loop, this time from 580 to 30 with a step of -50 to work our way up the line and draw a 10 pixel line. Next we set the font (just in case it gets changed somehow) and draw the value of each of our values. Now we get into the routines that will create the date tics along the X axis if we choose to have a simple list of data without including the dates. We have two support routines, one called DateToStamp and the other Timestamp2Date (Yes, I got lazy when I wrote this one). Rather than going through a bunch of complicated DateTime routines to determine the number of days in any given month, I’m going to use a start date and an end date, convert both of those to Unix timestamps to get the proper day of month within the sequence. I’ve shown you the DateToStamp routine before and the Timestamp2Date simply reverses the process. The next routine takes the start date and end date, as we discussed a moment ago, converts them to Unix timestamps, then adds 86400 (the number of ticks in a 24 hour period) to make sure we get the last date in the sequence, then uses another for loop to draw the rotated text where we want it. We are now at the OnPaint event handler that calls all the helper routines we dealt with so far. Remember, by using the PaintDC, every time the frame is moved, re-sized, covered or uncovered, the OnPaint event handler is called, thereby assuring our graph will be persistent.

Maintenant que nous avons notre facteur d'échelle, nous pouvons tracer les traits d'échelle et les valeurs qui vont représenter l'axe vertical. Nous utilisons à nouveau une boucle for, cette fois-ci de 580 à 30 par pas de -50 le long de la ligne, en traçant des traits de 10 pixels. Après, nous configurons la police (juste au cas où elle aurait changé) et nous dessinons chaque valeur.

Maintenant, regardons les routines qui créeront les traits d'échelle pour les dates le long de l'axe des X si nous choisissons d'avoir une simple liste de données sans inclure les dates. Nous avons deux routines de renfort, une appelée DateToStamp et l'autre Timestamp2Date (Oui, j'étais un peu fainéant quand j'ai écrit celle-ci.) Plutôt que de passer par un paquet de routines DateTime compliquées pour déterminer le nombre de jours d'un mois donné, je vais utiliser une date de début et une date de fin et convertir les deux en horodatage Unix pour obtenir le bon jour du mois dans la séquence. Je vous ai montré la routine DateToStamp précédemment et la routine Timestamp2Date exécute le processus inverse.

La routine suivante prend les dates de début et de fin, comme présenté auparavant, les convertit en horodatage Unix, puis ajoute 86 400 (le nombre de traits dans une période de 24 heures) pour être sûr d'avoir la dernière valeur de la séquence, puis utilise une autre boucle for pour dessiner le texte en biais où nous le voulons.

Nous arrivons maintenant au gestionnaire d'événements OnPaint qui appelle toute les routines utilitaires que nous devons gérer. Souvenez-vous, en utilisant la routine PaintDC, à chaque fois que la fenêtre est bougée, redimensionnée, couverte ou découverte, le gestionnaire d'événement OnPaint est appelé, assurant de ce fait que notre graphe sera persistant.

First (shown on the next page, top left) we get an instance of our dc, and then we call the DrawBox, DrawAxis, DrawTitle, and the DrawDateTicks routines. We then determine if the DateList list (created in the SetData routine called from init routine) is empty or if it has dates for us to draw. If so, we call the DrawDates routine with the proper values. We then call the DrawValues routine and finally the DrawBars routine. Now you should understand why I broke everything down into little bitty chunks. The last thing we have to look at is the runtime routine. You probably remember that the ’if name == “main”’ runs if we are calling the program as a standalone rather than as a library. The next two lines are the dummy data that I used to test the program. You could comment out the first one and run it with the second data line which is the one that uses the tuple. The last three lines will instantiate the wxPython routines, then the Line class and finally call the app.MainLoop wxPython routine to get the frame to run. So there it is. Our own graphing/charting program and library. I’ve put the full code up on pastebin at http://pastebin.com/m2feeh5P. Until next time, have fun coding.

D'abord (voir en haut à gauche de la page suivante), nous obtenons une instance de notre dc, puis nous appelons les routines DessineBoite, DessineAxe, DessineTitre et DessineBarresDates. Ensuite, nous déterminons si la ListeDates (créée dans la routine ReglerDonnees appelée par la routine __init__) est vide ou si des dates peuvent en être extraites. Si c'est le cas, nous appelons la routine DessineDates avec les bonnes valeurs. Puis nous appelons la routine DessineValeurs et, enfin, la routine DessineBarres. Maintenant, vous devriez comprendre pourquoi j'ai découpé le sujet en tout petits bouts.

La dernière chose que nous avons à regarder est la routine d'exécution. Vous vous souvenez probablement que le « if __name__ == “__main__” » fonctionne si nous appelons le programme seul plutôt que comme une bibliothèque. Les deux lignes suivantes sont les données fictives que j'ai utilisées pour tester le programme. Vous pouvez commenter la première et lancer le programme avec la seconde ligne qui est celle qui utilise le tuple. Les trois dernières lignes instancieront les routines wxPython, puis la classe Ligne et enfin appellera la routine wxPython app.MainLoop pour lancer la fenêtre.

Et voilà notre programme et notre bibliothèque personnalisés de graphe/tableau. J'ai mis le code complet sur pastebin à : http://pastebin.com/fJ00bhud.

Jusqu'à la prochaine fois, amusez-vous bien à coder.

code encadré orangé page 17 haut

class Ligne(wx.Frame):

  def __init__(self, parent, id, TitreFenetre, DonneesEntrantes, TitreGraphe):
      wx.Frame.__init__(self, parent, id, TitreFenetre, size=(1024, 768))
      self.Bind(wx.EVT_PAINT, self.OnPaint)
      self.LargeurBoite = 790
      self.HauteurBoite = 690
      self.TitreGraphe = TitreGraphe
      self.donnees = []
      self.ReglerDonnees(DonneesEntrantes)
      self.Centre()
      self.Show(True)

page 17 milieu

modifier uniquement la 1ère ligne du code :

  def DessineBoite(self,dc):

puis traduire le texte en noir (et laisser la dernière ligne en orange) :

C'est plutôt simple. On passe le dc de la fenêtre puis on dessine 4 lignes. Les paramètres de la fonction DrawLine sont :

page 17 bas

  def DessineAxe(self,dc):
      # Horizontal
      dc.DrawLine(60,580,700,580)
      # Vertical
      dc.DrawLine(60,580,60,80)

page 18 haut

  def DessineTitre(self,dc,txt):
      dc.SetFont(wx.Font(20,wx.DEFAULT,wx.NORMAL,wx.BOLD))
      dc.SetPen(wx.Pen(wx.NamedColour('black'),20))
      # Recupere la longueur du texte a dessiner
      vals = dc.GetFullTextExtent(txt)
      # Retourne (Largeur,hauteur,Decalage,espacementInitial)
      # Recupere la position gauche (x) pour centrer le texte
      txtleft = (self.LargeurBoite-vals[0])/2
      dc.DrawText(txt,txtleft,30)
      # Raz taille et couleur du stylo
      dc.SetPen(wx.Pen(wx.NamedColour('black'),2))

page 18 milieu

modifier juste la 1ère ligne :

  def DessineBarresDates(self,dc,dcount):

page 18 bas

modifier juste la 1ère ligne :

  def DessineTexteRot(self,dc,txt,x,y):

page 19 haut

  #==================================
  # Arrondi au 500 le plus proche
  #==================================
  def arrondi(self,x):
      return int(math.ceil(x/500.0))*500

page 19 milieu

  def ReglerDonnees(self,DonneesAUtiliser):
      if type(DonneesAUtiliser[1]) is tuple:
          self.ListeDates=[]
          self.ListeValeurs=[]
          for l in DonneesAUtiliser:
              self.ListeDates.append(l[0])
              self.ListeValeurs.append(l[1])
          self.ValeurMax = self.arrondi(max(self.ListeValeurs))
          self.ValeurEchelle = self.ValeurMax/500
      else:
          self.ListeValeurs=[]
          self.ListeDates=[]
          for l in DonneesAUtiliser:
              self.ListeValeurs.append(l)
          self.ValeurMax = self.arrondi(max(self.ListeValeurs))
          self.ValeurEchelle = self.ValeurMax/500

page 19 bas

modifier juste la 1ère ligne :

  def DessineValeurs(self,dc):

et la dernière :

          c2 = c2 + (50 * self.ValeurEchelle)

page 20 haut

modifier juste la 1ère ligne :

  def DessineBarres(self,dc):

page 20 milieu

  #==================================
  #  Convertit dd/mm/yy en timestamp unix
  #==================================
  def DateToStamp(self,x):
      x = x+" 00:00:00"
      return(time.mktime(time.strptime(x, "%d/%m/%Y %H:%M:%S")))
  #==================================
  #  Convertit un horodatage unix en dd/mm/yy
  #==================================
  def Timestamp2Date(self,tstmp):
      return datetime.fromtimestamp(int(tstmp)).strftime('%d/%m/%Y')

page 20 bas

  #==================================
  #  Dessine les dates en biais
  #==================================
  def DessineDates(self,dc,startdate,enddate):
      sd = int(self.DateToStamp(startdate))
      ed = int(self.DateToStamp(enddate))
      ed = ed + 86400
      stp = 1
      for cntr in range(sd,ed,86400):
          dt = self.Timestamp2Date(cntr)
          self.DessineTexteRot(dc,dt,65+(stp*20),600)
          stp = stp + 1

page 21 milieu

  #==================================
  #  Routine principale
  #==================================
  def OnPaint(self,event):
      dc = wx.PaintDC(self)
      self.DessineBoite(dc)
      self.DessineAxe(dc)
      self.DessineTitre(dc,self.TitreGraphe)
      # Barres de dates et dates
      self.DessineBarresDates(dc,31)
      leng = len(self.ListeDates)
      if leng > 0:
          sd = self.ListeDates[0]
          ed = self.ListeDates[4]
          self.DessineDates(dc,sd,ed)
      else:
          self.DessineDates(dc,"02/01/2015","03/01/2015")
      # Barres de valeurs - Dessine 10 barres 
      self.DessineValeurs(dc)
      # Enfin on dessine les barres de donnees
      self.DessineBarres(dc)

page 21 bas

modifier « data » en « donnees » sur les 2 premières lignes, et remplacer la ligne :

  Ligne(None, -1, 'Bar Chart',donnees,"Ventes mensuelles - Colorado Springs")
issue96/python.txt · Dernière modification : 2015/05/24 11:39 de fredphil91