giovedì 7 febbraio 2013

Diagrammi e altro

Da quando ci stiamo occupando di grafica abbiamo seguito il corso di ZetCode abbastanza fedelmente. Però adesso ci troviamo a una svolta: è possibile fare le cose che ci propone ma forse non sono quelle che servono, che ci piacciono. Inoltre ormai siete in grado di andare avanti da soli, siamo quasi alla fine, ne sapete quanto noi.


E allora, secondo noi, è il caso di procedere un po' per conto nostro. Prendiamo per esempio il digramma che ci propone qui e visualizziamolo (chart.py):

#!/usr/bin/python
# -*- coding: utf-8 -*-

import wx

data = ((10, 9), (20, 22), (30, 21), (40, 30), (50, 41),
(60, 53), (70, 45), (80, 20), (90, 19), (100, 22),
(110, 42), (120, 62), (130, 43), (140, 71), (150, 89),
(160, 65), (170, 126), (180, 187), (190, 128), (200, 125),
(210, 150), (220, 129), (230, 133), (240, 134), (250, 165),
(260, 132), (270, 130), (280, 159), (290, 163), (300, 94))

years = ('2010', '2011', '2012')

class LineChart(wx.Panel): 
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.SetBackgroundColour('WHITE')

        self.Bind(wx.EVT_PAINT, self.OnPaint)

    def OnPaint(self, event):
        dc = wx.PaintDC(self)
        dc.SetDeviceOrigin(40, 240)
        dc.SetAxisOrientation(True, True)
        dc.SetPen(wx.Pen('WHITE'))
        dc.DrawRectangle(1, 1, 300, 200)
        self.DrawAxis(dc)
        self.DrawGrid(dc)
        self.DrawTitle(dc)
        self.DrawData(dc)

    def DrawAxis(self, dc):
        dc.SetPen(wx.Pen('#0AB1FF'))
        font =  dc.GetFont()
        font.SetPointSize(8)
        dc.SetFont(font)
        dc.DrawLine(1, 1, 300, 1)
        dc.DrawLine(1, 1, 1, 201)

        for i in range(20, 220, 20):
            dc.DrawText(str(i), -30, i+5)
            dc.DrawLine(2, i, -5, i)

        for i in range(100, 300, 100):
            dc.DrawLine(i, 2, i, -5)

        for i in range(3):
            dc.DrawText(years[i], i*100-13, -10)

    def DrawGrid(self, dc):
        dc.SetPen(wx.Pen('#d5d5d5'))

        for i in range(20, 220, 20):
            dc.DrawLine(2, i, 300, i)

        for i in range(100, 300, 100):
            dc.DrawLine(i, 2, i, 200)

    def DrawTitle(self, dc):
        font =  dc.GetFont()
        font.SetWeight(wx.FONTWEIGHT_BOLD)
        dc.SetFont(font)
        dc.DrawText('Historical Prices', 90, 235)

    def DrawData(self, dc):
        dc.SetPen(wx.Pen('#0ab1ff'))
        for i in range(10, 310, 10):
            dc.DrawSpline(data)


class LineChartExample(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(390, 300))

        panel = wx.Panel(self, -1)
        panel.SetBackgroundColour('WHITE')

        hbox = wx.BoxSizer(wx.HORIZONTAL)
        linechart = LineChart(panel)
        hbox.Add(linechart, 1, wx.EXPAND | wx.ALL, 15)
        panel.SetSizer(hbox)

        self.Centre()
        self.Show(True)

app = wx.App()
LineChartExample(None, -1, 'A line chart')
app.MainLoop()



Tutto OK, ma un po' rigido, i dati che plotta sono inseriti nello script e se vogliamo visualizzarne altri dobbiamo modificare lo script ogni volta.

L'idea che propongo è di memorizzare i dati in un file che lo script legge (leggi.py): 

#!/usr/bin/python
# -*- coding: utf-8 -*-

fdati = 'dati.dat'
fd = open(fdati, 'r')
txt = fd.readlines()
#print txt
fd.close()

arr_x = []
arr_y = []
for line in txt:
    riga = line.strip()
    #print riga
    if len(riga) > 0:
        p = riga.find(' ')
        x = float(riga[:p])
        y = float(riga[p:])
        arr_x.append(x)
        arr_y.append(y)

Lcoppie = zip(arr_x, arr_y)
coppie = tuple(Lcoppie)
print coppie
xmax = max(arr_x)
ymax = max(arr_y)
print xmax, ymax


OK? legge un file di dati di tipo text con le coppie di valori x y, come questo (dati.dat):

Siccome sono pigro il file dati.dat l'ho creato con un script bash, di quelli per cui Bit3Lux è maestro e saprebbe fare molto meglio di me. Comnunque è questo:
#!/bin/sh
sed 's/),/\n/g;s/(//g;s/,//g;s/))//g;' dati.or > dati.tmp
sed 's/^ //g;/^$/d' dati.tmp > dati.dat
rm dati.tmp-----------------


come input usa il file dati.or che è la tupla preso da chart.py:


((10, 9), (20, 22), (30, 21), (40, 30), (50, 41),
(60, 53), (70, 45), (80, 20), (90, 19), (100, 22),
(110, 42), (120, 62), (130, 43), (140, 71), (150, 89),
(160, 65), (170, 126), (180, 187), (190, 128), (200, 125),
(210, 150), (220, 129), (230, 133), (240, 134), (250, 165),
(260, 132), (270, 130), (280, 159), (290, 163), (300, 94))

A questo punto lo script chart.py può essere modificato cambiando la riga data = ((10, 9), .... con l'istruzione di leggere il file, cioè inserire leggi.py nello script.

Anche il nome del file dei dati non deve essere fisso e inserito nello script. Considerate il seguente snippet (finp.py):

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys, os.path

if len(sys.argv) == 1:
    print "uso:", sys.argv[0], "file-dei-dati"
    sys.exit(1)
elif not os.path.isfile(sys.argv[1]):
    print "file", sys.argv[1], "non trovato"
    sys.exit(2)

print "processo", sys.argv[1]


OK, da mettere in chart.py anche questo.

Ah! un'ultima cosa ancora: per quelli abituati all'ambiente grafico si può utilizzate la funzione wx.FileDialog(). Così (opfd.py):

#!/usr/bin/python
# -*- coding: utf-8 -*-

import wx, os.path

class MyForm(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY,
                          "wx.FileDialog Tutorial")
        panel = wx.Panel(self, wx.ID_ANY)
 
        btn = wx.Button(panel, label="Open File Dialog")
        btn.Bind(wx.EVT_BUTTON, self.onOpenFile)
 
    def onOpenFile(self, event):
        dlg = wx.FileDialog(
        self, message = "Scegli un file",
        defaultDir = os.path.curdir,
        defaultFile = "*.*",
        wildcard = "File di dati (*.dat)|*.dat|" \
                   "File di testo (*.txt)|*.txt|" \
                   "Tutti i file (*.*)|*.*",
        style = wx.OPEN | wx.CHANGE_DIR)

        if dlg.ShowModal() == wx.ID_OK:
            paths = dlg.GetPaths()
            print "hai scelto:", paths
        dlg.Destroy()

if __name__ == "__main__":
    app = wx.App(False)
    frame = MyForm()
    frame.Show()
    app.MainLoop()


Da mettere anche questo in chart.py.

Ecco, avete presente l'Ikea? o da piccoli avete certamente giocato con il Lego. Vi ho fornito tutti i componenti (i mattoncini del Lego) lasciando a voi il piacere di assemblarli. Buon lavoro!

L'indice di Mission Python lo trovate qui

Nessun commento:

Posta un commento