Klasse zum Zeichnen eines Graphen

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
Benutzeravatar
Mr_Snede
User
Beiträge: 387
Registriert: Sonntag 8. Februar 2004, 16:02
Wohnort: D-Dorf, Bo

Diese Klasse kann eine Liste von Werten (bei mir sind es Kontostände) als Graph auf einem Canvas* zeichnen.
Die Besonderheiten sind:
- Es können Ränder für (zukünftige) Skalen gelassen werden
- Bei Negativwerten kann eine NullLinie gezeichnet werden
- Bei der Zeichenfläche in Tkinter (canvas) wird die Eben von Links oben aufgespannt deswegen wird der Graph vertikal um die Canvashöhe nach unten verschoben und einmal umgeklappt.

* Ich will die Klasse so allgemein halten, wie möglich.
Denn an meinem Kontolister, in dessen Rahmen diese Klasse entstanden ist, will ich die wichtigsten GUI's (tkinter, GTK2, QT, WX) ausprobieren.

Meine Fragen:
1. Die Zeilen 148 und 149 habe ich aufgeteilt, damit ich unter den 80 Zeichen pro Zeile bleibe. Wie hättet ihr euch entschieden?

2. Wie hättet ihr den Test / Demo Abschnitt implementiert? Als eigene Klasse, als Funktion der Klasse Graf oder so wie hier ab Zeile 181?

Cu Sebastian

Edit:
Typos

Code: Alles auswählen

#!/usr/bin/python
# -*- encoding: latin-1 -*-

# todo:
# - Errorhandling if init values are not set
# - direkt auf (Klassenvariablen zugreifen oder beim Aufruf übergeben?)
# - in 2 Klassen aufteilen:
# - - graph-core (simple_graph)
# - - graph: inherits from graph-core plus borders, scales ()
# - check border_bottom < canvas_height --> gen_values_2_draw()
# - check border_left < canvas_width --> gen_values_2_draw()


# done:
# 004 - new Logic: regenerate all values when one is changed
# 003 -> it's working :-)




################-------#############
##              imports           ##
################-------#############

# following imports are called when running stand alone (demo mode)
#import Tkinter
#import time

class Graph:
   ''' todo: write this docstring '''
   def __init__(self, values = [1,2,3]):
      ''' All these values representing pixel on a drawing area(Tkinter canvas)
      Right now the funktion "upside_down" is called by default,
      later there may bee a choice
       - border_bottom (free space to draw a time(horizontal) scale,type=float)
       - border_left (free space to draw an y scale, type = float)
       - canvas_height (type = float)
       - canvas_width (type = float)
       - graph_upside_down (type = bool) # currently unused!!!!!!!!!!!!!!!!
       - step_width (horizontal distance between two y values, type = float)
       - values (y input values; type = List of floats)
       - values_2_draw (y output values; type = List of floats)
       - zero_line (
       - - min(values_2_draw) <  0: y value(type=float) to draw a line over y=0
       - - min(values_2_draw) >= 0: zero_line = None)'''
      self.border_bottom = 10.0
      self.border_left = 10.0
      self.canvas_height = 100.0
      self.canvas_width = 200.0
      self.graph_upside_down = True # rename to do_upside_down ?
      self.step_width = 1.0
      self.values = values
      self.values_2_draw = None
      self.zero_line = None

      self.gen_values_2_draw()



################-------#############
##              setters           ##
################-------#############
   def set_borders(self, border_left, border_bottom):
      ''' todo: write this docstring '''
      # todo: make shure values are saved as floats
      self.border_bottom = border_bottom
      self.border_left = border_left
      self.gen_values_2_draw()

   def set_canvas_dimensions(self, canvas_height, canvas_width):
      ''' todo: write this docstring '''
      # todo: make shure values are saved as floats
      # obsolete?
      self.canvas_height = canvas_height
      self.canvas_width = canvas_width
      self.gen_values_2_draw()

   def set_canvas_height(self, new_height):
      ''' todo: write this docstring'''
      self.canvas_height = new_height
      self.gen_values_2_draw()

   def set_canvas_width(self, new_width):
      ''' todo: write this docstring'''
      self.canvas_width = new_width
      self.gen_values_2_draw()

   def set_values(self, values):
      ''' todo: write this docstring '''
      # todo: make shure values are saved as floats
      self.values = values
      self.gen_values_2_draw()

   def set_upside_down(self, upside_down_bool):
      ''' todo: write this docstring '''
      # todo: make shure value is bool
      self.graph_upside_down = upside_down_bool
      self.gen_values_2_draw()



################-------#############
##              getters           ##
################-------#############
   def get_border_left(self):
      ''' todo: write this docstring
       - return type: float'''
      return self.border_left
   def get_canvas_height(self):
      ''' todo: write this docstring
       - return type: float'''
      return self.canvas_height
   def get_canvas_width(self):
      ''' todo: write this docstring
       - return type: float'''
      return self.canvas_width

   def get_step_width(self):
      ''' todo: write this docstring '''
      return self.step_width


   def get_values_2_draw(self):
      ''' todo: write this docstring
       - return type: list of floats'''
      return self.values_2_draw


############-------------###########
##          div functions         ##
############-------------###########
   def gen_values_2_draw(self):
      ''' hidden function?
      when a set_* Function is called "gen_values_2_draw" updates:
       - self.values_2_draw (list of floats) and
       - self.zero_line (bool)
       - self.step_width (float)'''
      drawable_height = self.canvas_height - 1.0 * self.border_bottom
      max_distanz = (max(self.values) - min(self.values))
      k_factor = drawable_height / max_distanz
      new_values = [round(i * k_factor,1) for i in self.values]
      self.zero_line = None  # make shure it is really None -- still needet?
      if min(new_values) < 0:
         min_value = min(new_values)
         self.zero_line = 0 - min_value
         new_values = [i - min_value for i in new_values]
      self.values_2_draw = self.upside_down(new_values, self.canvas_height)
      self.step_width = ( float(self.canvas_width - self.border_left)
                        / len(self.values) )



   def upside_down(self, values, canvas_height):
      """ hidden function?
      bla"""
      return_values = [canvas_height - i - self.border_bottom for i in values]
      if self.zero_line:
         self.zero_line = canvas_height - self.zero_line - self.border_bottom
      else:
         self.zero_line = None
      return return_values


###############-------##############
##             testing            ##
###############-------##############
   def print_all_values(self):
      '''Just for testing purposes.
         It simply prints all values'''
      print "\nself.values: ", self.values
      print "self.values_2_draw: ", self.values_2_draw
      print "self.zero_line: ", self.zero_line
      print "self.border_bottom: ", self.border_bottom
      print "self.border_left: ", self.border_left
      print "self.canvas_height: ", self.canvas_height
      print "self.canvas_width: ", self.canvas_width
      print "self.graph_upside_down: ", self.graph_upside_down
      print "self.step_width: ", self.step_width


if __name__ == '__main__':
   print "\n Demo in main gestartet\n"
   import Tkinter
   import time

   rect_height = 102
   rect_width = 109
   werte = [1, 30, -40, 30, 50, 130, 90]

   g = Graph(werte)
   g.print_all_values()
   g.set_canvas_dimensions(rect_height, rect_width)
   g.print_all_values()

   hauptfenster = Tkinter.Tk()
   canvas = Tkinter.Canvas(hauptfenster,
                           width = rect_width,
                           height = rect_height,
                           bg = "white")
   canvas.pack()

   if g.zero_line:
      canvas.create_line(  0, g.zero_line,
                           rect_width, g.zero_line,
                           width = 1,
                           fill = "red")

   x_start = g.get_border_left()
   for y in g.get_values_2_draw():
      canvas.create_line(  x_start, y, x_start, y + 5,
                           width = 2)
      x_start += g.get_step_width()
      canvas.update()
      time.sleep(0.1) # "animated" drawing :-)

   hauptfenster.mainloop()

   g.set_values([1, 30, -40, 30, 50, -130, 90])
   g.print_all_values()

   print '\nProgramm ganz zu Ende\n'
Zuletzt geändert von Mr_Snede am Freitag 2. Juni 2006, 10:35, insgesamt 3-mal geändert.
Dakayras
User
Beiträge: 3
Registriert: Donnerstag 1. Juni 2006, 14:38
Kontaktdaten:

Sers erstmal,

also ich habe mir mal fix den Script angeguggt.

Im großen und ganzen muss ich erst mal fett bedanken, weil mir der Script bie der Realisierung von meinem Projekt doch sehr geholfen hat. So rein prinzipiell erst mal.

Ich hätte den Werten auf jedenfall Variablen zugeordnet, damit der Graf auch wirklich möglichst viel kann.

Dann kann der "User" die Variablen selber mit Werten füllen.

Weil so muss man ja im Quellcode rum basteln.

Ist dann halt die Frage ob man dem "User" dann eine festgesetzt Anzahl von Werten erlaubt, oder ob er die Anzahl auch noch selber einstellen kann.

Wäre dann halt ein kleiner Dialog.

-------------------------------------------------------------------------------------
Aber ansonsten finde ich es cool. Nette Arbeit.

MfG
Dak
Benutzeravatar
Mr_Snede
User
Beiträge: 387
Registriert: Sonntag 8. Februar 2004, 16:02
Wohnort: D-Dorf, Bo

Dakayras hat geschrieben:Sers erstmal,

also ich habe mir mal fix den Script angeguggt.

Im großen und ganzen muss ich erst mal fett bedanken, weil mir der Script bie der Realisierung von meinem Projekt doch sehr geholfen hat. So rein prinzipiell erst mal.
Das freut mich.
Dakayras hat geschrieben:Ich hätte den Werten auf jedenfall Variablen zugeordnet, damit der Graf auch wirklich möglichst viel kann.

Dann kann der "User" die Variablen selber mit Werten füllen.

Weil so muss man ja im Quellcode rum basteln.

Ist dann halt die Frage ob man dem "User" dann eine festgesetzt Anzahl von Werten erlaubt, oder ob er die Anzahl auch noch selber einstellen kann.

Wäre dann halt ein kleiner Dialog.
Diese Klasse soll nur die Daten aufbereiten, woher die Eingangswerte kommen und wo/wie sie weiterverwendet werden ist ihr egal.

Genau sowas musst du selbst implementieren. Der Teil ab Zeile 181 ist als Demo gedacht.
Normalerweise wird der vorliegende Quelltext von deinem "Steuerprogramm" importiert.
Dann wird beim Erzeugen einer Instanz von der Klasse Graph (In der Demo in Zeile 190: g = Graph(werte) ) ein Schwung von Werten übergeben. Diese Werte können durchaus vorher vom Benutzer erfragt werden.
Sollen die Werte später mal geändert werden, so benutzt du set_values(mit deinen neuen Werten) und zeichnest den Grafen neu.
Dakayras hat geschrieben: Aber ansonsten finde ich es cool. Nette Arbeit.
Wenn ich dir sage, wie lange ich daran schon herumbastel fällste vom Stuhl ;-) Da Steckt also einiges an Vorarbeit drin. Bin auch ein wenig stolz, dass ich es mir komplett selbst erarbeitet habe ohne irgendwo nachfragen zu müssen. Denn so gut programmieren kann ich wirklich nicht - aber es wird von Monat zu Monat besser :-)

Cu Sebastian
Antworten