#!/usr/bin/env python import Tkinter import string import math COLORMAP = { 'black' : ( '#000', '#888' ), 'gray' : ( '#888', '#ccc' ), 'red' : ( '#c00', '#f88' ), 'green' : ( '#080', '#4f4' ), 'blue' : ( '#00c', '#88f' ) } SMALLFONT = ( 'Helvetica', -10 ) LARGEFONT = ( 'Helvetica', -12 ) FIXEDFONT = ( 'Courier New', -12 ) class Point: def __init__( self, x, y, name='' ): self.x, self.y, self.name = x, y, name class Group: def __init__( self, points, name='', color='black' ): self.points, self.name, self.color = points, name, color self.calculate() def calculate( self ): n = float( len( self.points ) ) x = sum( [ point.x for point in self.points ] ) / n y = sum( [ point.y for point in self.points ] ) / n self.mean = Point( x, y ) self.xVar = sum( [ ( point.x - x ) ** 2 for point in self.points ] ) / n self.yVar = sum( [ ( point.y - y ) ** 2 for point in self.points ] ) / n self.cov = sum( [ ( point.x - x ) * ( point.y - y ) for point in self.points ] ) / n self.pearson = self.cov / math.sqrt( self.xVar * self.yVar ) class FigureFrame( Tkinter.Frame ): def __init__( self, parent ): Tkinter.Frame.__init__( self, parent ) self.groups = [] self.d = 1 label = Tkinter.Label( self, text='Merkmalsraum X1 x X2', font=LARGEFONT ) label.pack( fill=Tkinter.X ) frame = Tkinter.Frame( self ) self.yAxisCanvas = Tkinter.Canvas( frame, highlightthickness=0, bd=0, width=96 ) self.yAxisCanvas.pack( side=Tkinter.LEFT, fill=Tkinter.Y ) self.plotCanvas = Tkinter.Canvas( frame, highlightthickness=0, bd=1, relief=Tkinter.SOLID, bg='#ffffff' ) self.plotCanvas.pack( expand=True, fill=Tkinter.BOTH ) frame.pack( expand=True, fill=Tkinter.BOTH ) frame = Tkinter.Frame( self ) canvas = Tkinter.Canvas( frame, highlightthickness=0, bd=0, width=96, height=24 ) canvas.create_text( 76, 0, anchor=Tkinter.NE, text='x2', font=LARGEFONT, fill='#048' ) canvas.create_text( 96, 24, anchor=Tkinter.SE, text='x1', font=LARGEFONT, fill='#048' ) canvas.pack( side=Tkinter.LEFT ) self.xAxisCanvas = Tkinter.Canvas( frame, highlightthickness=0, bd=0, height=24 ) self.xAxisCanvas.pack( fill=Tkinter.X ) frame.pack( fill=Tkinter.X ) self.plotCanvas.bind( '', self.handleResize ) def handleResize( self, event ): self.redraw() def redraw( self ): self.calculateViewRange() self.calculateAxisTicks() self.redrawXAxis() self.redrawYAxis() self.redrawGrid() self.redrawData() def getCanvasX( self, x ): width = self.plotCanvas.winfo_width() return ( x - self.xMin ) / ( self.xMax - self.xMin ) * width def getCanvasY( self, y ): height = self.plotCanvas.winfo_height() return ( self.yMax - y ) / ( self.yMax - self.yMin ) * height def calculateViewRange( self ): X, Y = [], [] for group in self.groups: for point in group.points: X.append( point.x ) Y.append( point.y ) if X and Y: xMin, xMax, yMin, yMax = min( X ), max( X ), min( Y ), max( Y ) if xMin == xMax: if not xMin: self.xMin, self.xMax = -1, 1 else: self.xMin = min( 0, 2 * xMin ) self.xMax = min( 0, 2 * xMax ) else: self.xMin = xMin - 0.25 * ( xMax - xMin ) self.xMax = xMax + 0.25 * ( xMax - xMin ) if yMin == yMax: if not yMin: self.yMin, self.yMax = -1, 1 else: self.yMin = min( 0, 2 * yMin ) self.yMax = min( 0, 2 * yMax ) else: self.yMin = yMin - 0.25 * ( yMax - yMin ) self.yMax = yMax + 0.25 * ( yMax - yMin ) else: self.xMin, self.xMax, self.yMin, self.yMax = -1, 1, -1, 1 def calculateAxisTicks( self ): width = self.plotCanvas.winfo_width() tick = ( self.xMax - self.xMin ) / float( width ) * 64 self.xTick = 2 ** math.ceil( math.log( tick, 2 ) ) self.xStart = self.xMin - self.xMin % self.xTick + self.xTick height = self.plotCanvas.winfo_height() tick = ( self.yMax - self.yMin ) / float( height ) * 24 self.yTick = 2 ** math.ceil( math.log( tick, 2 ) ) self.yStart = self.yMin - self.yMin % self.yTick + self.yTick def redrawXAxis( self ): for item in self.xAxisCanvas.find_all(): self.xAxisCanvas.delete( item ) tick = self.xTick / 10 for i in range( 1, int( ( self.xStart - self.xMin ) / tick ) ): px = self.getCanvasX( self.xStart - i * tick ) self.xAxisCanvas.create_line( px, 0, px, 4 ) x, i = self.xStart, 0 while True: x = i * self.xTick + self.xStart if x >= self.xMax: break px = self.getCanvasX( x ) self.xAxisCanvas.create_line( px, 0, px, 8 ) self.xAxisCanvas.create_text( px, 8, anchor=Tkinter.N, text=str( x ), font=SMALLFONT ) for j in range( 1, 10 ): px = self.getCanvasX( x + j * tick ) self.xAxisCanvas.create_line( px, 0, px, 4 ) i += 1 def redrawYAxis( self ): for item in self.yAxisCanvas.find_all(): self.yAxisCanvas.delete( item ) tick = self.yTick / 5 for i in range( 1, int( ( self.yStart - self.yMin ) / tick ) ): py = self.getCanvasY( self.yStart - i * tick ) self.yAxisCanvas.create_line( 92, py, 96, py ) y, i = self.yStart, 0 while True: y = i * self.yTick + self.yStart if y >= self.yMax: break py = self.getCanvasY( y ) self.yAxisCanvas.create_line( 88, py, 96, py ) self.yAxisCanvas.create_text( 88, py, anchor=Tkinter.E, text=str( y ), font=SMALLFONT ) for j in range( 1, 5 ): py = self.getCanvasY( y + j * tick ) self.yAxisCanvas.create_line( 92, py, 96, py ) i += 1 def redrawGrid( self ): for item in self.plotCanvas.find_all(): self.plotCanvas.delete( item ) height, i = self.plotCanvas.winfo_height(), 0 while True: x = i * self.xTick + self.xStart if x >= self.xMax: break px = self.getCanvasX( x ) self.plotCanvas.create_line( px, 0, px, height, fill='#ccc', stipple='gray50' ) i += 1 width, i = self.plotCanvas.winfo_width(), 0 while True: y = i * self.yTick + self.yStart if y >= self.yMax: break py = self.getCanvasY( y ) self.plotCanvas.create_line( 0, py, width, py, fill='#ccc', stipple='gray50' ) i += 1 def redrawData( self ): self.redrawMeanDistances() for group in self.groups: self.redrawPoints( group ) self.redrawVariance( group ) self.redrawMean( group ) self.redrawCommonVariance() self.redrawCommonMean() def redrawMeanDistances( self ): for i in range( 1, len( self.groups ) ): group0 = self.groups[ i - 1 ] px0 = self.getCanvasX( group0.mean.x ) py0 = self.getCanvasY( group0.mean.y ) for group in self.groups[ i : ]: px = self.getCanvasX( group.mean.x ) py = self.getCanvasY( group.mean.y ) self.plotCanvas.create_line( px0, py0, px, py, arrow=Tkinter.BOTH, fill=COLORMAP[ 'black' ][ 1 ] ) def redrawPoints( self, group ): for point in group.points: px, py = self.getCanvasX( point.x ), self.getCanvasY( point.y ) self.plotCanvas.create_oval( px - 3, py - 3, px + 3, py + 3, outline=COLORMAP[ group.color ][ 0 ], fill=COLORMAP[ group.color ][ 1 ] ) self.plotCanvas.create_text( px, py - 5, anchor=Tkinter.S, text=point.name, fill='#888', font=SMALLFONT ) def redrawMean( self, group ): px0, py0 = self.getCanvasX( 0 ), self.getCanvasY( 0 ) px = self.getCanvasX( group.mean.x ) py = self.getCanvasY( group.mean.y ) length = math.sqrt( group.mean.x ** 2 + group.mean.y ** 2 ) self.plotCanvas.create_line( px0, py0, px, py, fill=COLORMAP[ group.color ][ 1 ], arrow=Tkinter.BOTH ) self.plotCanvas.create_text( px, py + 20, anchor=Tkinter.N, fill=COLORMAP[ group.color ][ 0 ], text=( '|m|=' + str( length ) ) ) self.plotCanvas.create_oval( px - 3, py - 3, px + 3, py + 3, outline=COLORMAP[ group.color ][ 0 ], fill=COLORMAP[ group.color ][ 1 ] ) self.plotCanvas.create_oval( px - 5, py - 5, px + 5, py + 5, outline=COLORMAP[ group.color ][ 0 ] ) self.plotCanvas.create_text( px - 1, py + 6, anchor=Tkinter.N, text=( 'm_%s' % group.name ), font=SMALLFONT ) def redrawVariance( self, group ): self.drawVariance( group.mean, group.xVar, group.yVar, group.cov, group.name, group.color ) def redrawCommonMean( self ): if not self.groups: return n = float( len( self.groups ) ) x = sum( [ group.mean.x for group in self.groups ] ) / n y = sum( [ group.mean.y for group in self.groups ] ) / n px, py = self.getCanvasX( x ), self.getCanvasY( y ) self.plotCanvas.create_oval( px - 3, py - 3, px + 3, py + 3, outline='#000', fill='#000' ) self.plotCanvas.create_oval( px - 5, py - 5, px + 5, py + 5, outline='#000' ) self.plotCanvas.create_text( px - 1, py + 6, anchor=Tkinter.N, text='m_gem.', font=SMALLFONT ) def redrawCommonVariance( self ): if not self.groups: return X, Y = [], [] for group in self.groups: X += [ point.x for point in group.points ] Y += [ point.y for point in group.points ] if not X or not Y: return n = float( len( X ) ) xMean, yMean = sum( X ) / n, sum( Y ) / n X, Y = map( lambda x: x - xMean, X ), map( lambda y: y - yMean, Y ) xVar = sum( map( lambda x: x ** 2, X ) ) / n yVar = sum( map( lambda y: y ** 2, Y ) ) / n if not xVar or not yVar: return cov = sum( map( lambda x, y: x * y, X, Y ) ) / n self.drawVariance( Point( xMean, yMean ), xVar, yVar, cov, 'gem.', 'black' ) ## def drawVariance( self, mean, xVar, yVar, cov, name, color ): ## coords = [] ## for i in range( 64 ): ## angle = 2 * math.pi * i / 64 ## rx, ry = math.cos( angle ), math.sin( angle ) ## x = mean.x + rx * xVar + ry * cov ## y = mean.y + rx * cov + ry * yVar ## px = self.getCanvasX( x ) ## py = self.getCanvasY( y ) ## coords += [ px, py ] ## oval = self.plotCanvas.create_polygon( *coords ) ## self.plotCanvas.itemconfigure( oval, smooth=True, fill='', ## outline=COLORMAP[ color ][ 1 ] ) ## px0, py0 = self.getCanvasX( mean.x ), self.getCanvasY( mean.y ) ## px1 = self.getCanvasX( mean.x + math.sqrt( xVar ) ) ## py1 = self.getCanvasY( mean.y + math.sqrt( yVar ) ) ## px2 = self.getCanvasX( mean.x - math.sqrt( xVar ) ) ## py2 = self.getCanvasY( mean.y - math.sqrt( yVar ) ) ## self.plotCanvas.create_line( px1, py0, px2, py0, arrow=Tkinter.BOTH, ## fill=COLORMAP[ color ][ 1 ], ## stipple='gray50', width=2 ) ## self.plotCanvas.create_text( px1, py0, anchor=Tkinter.W, ## fill=COLORMAP[ color ][ 1 ], ## text=( 'stdev_%s' % name ), ## font=SMALLFONT ) ## self.plotCanvas.create_line( px0, py1, px0, py2, arrow=Tkinter.BOTH, ## fill=COLORMAP[ color ][ 1 ], ## stipple='gray50', width=2 ) ## self.plotCanvas.create_text( px0, py1, anchor=Tkinter.S, ## fill=COLORMAP[ color ][ 1 ], ## text=( 'stdev_%s' % name ), ## font=SMALLFONT ) def drawVariance( self, mean, xVar, yVar, cov, name, color ): det = xVar * yVar - cov ** 2 if det == 0: return s00, s11 = yVar / det, xVar / det s01 = s10 = -cov / det x = xMax = self.d / math.sqrt( s00 ) points = [] while True: a = -( s01 + s10 ) * x / ( 2 * s11 ) b = a ** 2 - ( s00 * x ** 2 - self.d ** 2 ) / s11 if b < 0.0: break points.append( ( x, a + math.sqrt( b ) ) ) x += xMax / 64.0 points.reverse() x = xMax while True: x -= xMax / 64.0 a = -( s01 + s10 ) * x / ( 2 * s11 ) b = a ** 2 - ( s00 * x ** 2 - self.d ** 2 ) / s11 if b < 0.0: break points.append( ( x, a + math.sqrt( b ) ) ) points += [ ( -x, -y ) for ( x, y ) in points ] coords = [] for x, y in points: px = self.getCanvasX( mean.x + x ) py = self.getCanvasY( mean.y + y ) coords += [ px, py ] oval = self.plotCanvas.create_polygon( *coords ) self.plotCanvas.itemconfigure( oval, smooth=True, fill='', outline=COLORMAP[ color ][ 1 ] ) px0, py0 = self.getCanvasX( mean.x ), self.getCanvasY( mean.y ) px1 = self.getCanvasX( mean.x + math.sqrt( xVar ) ) py1 = self.getCanvasY( mean.y + math.sqrt( yVar ) ) px2 = self.getCanvasX( mean.x - math.sqrt( xVar ) ) py2 = self.getCanvasY( mean.y - math.sqrt( yVar ) ) self.plotCanvas.create_line( px1, py0, px2, py0, arrow=Tkinter.BOTH, fill=COLORMAP[ color ][ 1 ], stipple='gray50', width=2 ) self.plotCanvas.create_text( px1, py0, anchor=Tkinter.W, fill=COLORMAP[ color ][ 1 ], text=( 'stdev_%s' % name ), font=SMALLFONT ) self.plotCanvas.create_line( px0, py1, px0, py2, arrow=Tkinter.BOTH, fill=COLORMAP[ color ][ 1 ], stipple='gray50', width=2 ) self.plotCanvas.create_text( px0, py1, anchor=Tkinter.S, fill=COLORMAP[ color ][ 1 ], text=( 'stdev_%s' % name ), font=SMALLFONT ) class GroupsFrame( Tkinter.Frame ): def __init__( self, parent ): Tkinter.Frame.__init__( self, parent ) self.groupFrames = [] frame = Tkinter.Frame( self ) label = Tkinter.Label( frame, text='Klassenzahl:', font=LARGEFONT ) label.pack( side=Tkinter.LEFT, fill=Tkinter.Y ) self.countVar = Tkinter.StringVar() self.countVar.set( '0' ) entry = Tkinter.Entry( frame, highlightthickness=0, width=2, textvariable=self.countVar, font=FIXEDFONT ) entry.pack( expand=True, fill=Tkinter.X ) frame.pack( fill=Tkinter.X ) button = Tkinter.Button( self, highlightthickness=0, font=LARGEFONT, text='setzen', command=self.handleButton ) button.pack( fill=Tkinter.X ) def handleButton( self ): self.update() def update( self ): oldCount = len( self.groupFrames ) try: newCount = int( self.countVar.get().strip() ) except: newCount = oldCount if 0 < newCount <= 4 and newCount != oldCount: if newCount < oldCount: for frame in self.groupFrames[ newCount : ]: frame.destroy() self.groupFrames = self.groupFrames[ : newCount ] else: for i in range( newCount - oldCount ): groupFrame = GroupFrame( self ) groupFrame.pack( expand=True, fill=Tkinter.BOTH ) self.groupFrames.append( groupFrame ) self.countVar.set( str( len( self.groupFrames ) ) ) def getGroups( self ): return [ groupFrame.getGroup() for groupFrame in self.groupFrames ] def setGroups( self, groups ): self.countVar.set( str( len( groups ) ) ) self.update() for i in range( len( self.groupFrames ) ): self.groupFrames[ i ].setGroup( groups[ i ] ) class GroupFrame( Tkinter.Frame ): def __init__( self, parent ): Tkinter.Frame.__init__( self, parent, bd=1, relief=Tkinter.RAISED ) frame = Tkinter.Frame( self ) label = Tkinter.Label( frame, text='Klasse:', font=LARGEFONT ) label.pack( side=Tkinter.LEFT, fill=Tkinter.Y ) self.nameVar = Tkinter.StringVar() entry = Tkinter.Entry( frame, highlightthickness=0, font=FIXEDFONT, textvariable=self.nameVar ) entry.pack( expand=True, fill=Tkinter.X ) frame.pack( fill=Tkinter.X ) frame = Tkinter.Frame( self ) label = Tkinter.Label( frame, text=' Name ', font=SMALLFONT ) label.pack( side=Tkinter.LEFT, expand=True, fill=Tkinter.X ) label = Tkinter.Label( frame, text=' x1 ', font=SMALLFONT ) label.pack( side=Tkinter.LEFT, expand=True, fill=Tkinter.X ) label = Tkinter.Label( frame, text=' x2 ', font=SMALLFONT ) label.pack( side=Tkinter.LEFT, expand=True, fill=Tkinter.X ) frame.pack( fill=Tkinter.X ) frame = Tkinter.Frame( self ) self.dataText = Tkinter.Text( frame, highlightthickness=0, width=16, height=1, font=FIXEDFONT ) self.dataText.pack( side=Tkinter.LEFT, expand=True, fill=Tkinter.BOTH ) scrollbar = Tkinter.Scrollbar( frame, orient=Tkinter.VERTICAL, command=self.dataText.yview ) scrollbar.pack( side=Tkinter.RIGHT, fill=Tkinter.Y ) self.dataText[ 'yscrollcommand' ] = scrollbar.set frame.pack( expand=True, fill=Tkinter.BOTH ) frame = Tkinter.Frame( self ) self.colorVar = Tkinter.StringVar() self.colorVar.set( 'gray' ) radioButton = Tkinter.Radiobutton( frame, highlightthickness=0, variable=self.colorVar, value='gray', text='grau', font=LARGEFONT ) radioButton.pack( side=Tkinter.LEFT ) radioButton = Tkinter.Radiobutton( frame, highlightthickness=0, variable=self.colorVar, value='red', text='rot', font=LARGEFONT ) radioButton.pack( side=Tkinter.LEFT ) radioButton = Tkinter.Radiobutton( frame, highlightthickness=0, variable=self.colorVar, value='green', text='gruen', font=LARGEFONT ) radioButton.pack( side=Tkinter.LEFT ) radioButton = Tkinter.Radiobutton( frame, highlightthickness=0, variable=self.colorVar, value='blue', text='blau', font=LARGEFONT ) radioButton.pack( side=Tkinter.LEFT ) frame.pack( fill=Tkinter.X ) self.xVarLabel = Tkinter.Label( self, text='var(X1)', font=SMALLFONT ) self.xVarLabel.pack( fill=Tkinter.X ) self.yVarLabel = Tkinter.Label( self, text='var(X2)', font=SMALLFONT ) self.yVarLabel.pack( fill=Tkinter.X ) self.covLabel = Tkinter.Label( self, text='cov(X1,X2)', font=SMALLFONT ) self.covLabel.pack( fill=Tkinter.X ) self.pearsonLabel = Tkinter.Label( self, text='PKK(X1,X2)', font=SMALLFONT ) self.pearsonLabel.pack( fill=Tkinter.Y ) def getGroup( self ): content = self.dataText.get( '1.0', Tkinter.END ) lines, points = content.split( '\n' ), [] for line in lines: words = line.strip().split() if len( words ) == 2 or len( words ) == 3: try: x, y = float( words[ -2 ] ), float( words[ -1 ] ) except: continue name = '' if len( words ) > 2: name = words[ 0 ] points.append( Point( x, y, name ) ) content = '' for point in points: content += '%s\t%.3f\t%.3f\n' % ( point.name, point.x, point.y ) self.dataText.delete( '1.0', Tkinter.END ) self.dataText.insert( '1.0', content ) group = Group( points, self.nameVar.get().strip(), self.colorVar.get() ) self.xVarLabel.configure( text=( 'var(X1) = %f' % group.xVar ) ) self.yVarLabel.configure( text=( 'var(X2) = %f' % group.yVar ) ) self.covLabel.configure( text=( 'cov(X1,X2) = %f' % group.cov ) ) self.pearsonLabel.configure( text=( 'pearson(X1,X2) = %f' % \ group.pearson ) ) return group def setGroup( self, group ): self.nameVar.set( group.name ) self.colorVar.set( group.color ) content = '' for point in group.points: content += '%s\t%.3f\t%.3f\n' % ( point.name, point.x, point.y ) self.dataText.delete( '1.0', Tkinter.END ) self.dataText.insert( '1.0', content ) class Application: def __init__( self ): self.window = Tkinter.Tk() self.window.title( 'Deskriptive Statistik' ) self.groupsFrame = GroupsFrame( self.window ) self.groupsFrame.pack( side=Tkinter.LEFT, fill=Tkinter.Y, padx=4, pady=4 ) frame = Tkinter.Frame( self.window ) self.figureFrame = FigureFrame( frame ) self.figureFrame.pack( expand=True, fill=Tkinter.BOTH ) frame2 = Tkinter.Frame( frame ) label = Tkinter.Label( frame2, text='d=', font=LARGEFONT ) label.pack( side=Tkinter.LEFT ) self.dVar = Tkinter.StringVar() self.dVar.set( '1.0' ) entry = Tkinter.Entry( frame2, highlightthickness=0, width=8, textvariable=self.dVar, font=FIXEDFONT ) entry.pack( side=Tkinter.LEFT ) label = Tkinter.Label( frame2, text=' Pfad:', font=LARGEFONT ) label.pack( side=Tkinter.LEFT ) self.pathVar = Tkinter.StringVar() self.pathVar.set( 'descrStat.ps' ) entry = Tkinter.Entry( frame2, highlightthickness=0, textvariable=self.pathVar, font=FIXEDFONT ) entry.pack( expand=True, side=Tkinter.LEFT, fill=Tkinter.X ) button = Tkinter.Button( frame2, highlightthickness=0, text='PS exportieren', font=LARGEFONT, command=self.handleExportButton ) button.pack( side=Tkinter.LEFT, padx=8 ) button = Tkinter.Button( frame2, highlightthickness=0, text='aktualisieren', font=LARGEFONT, command=self.handleUpdateButton, padx=8, pady=8 ) button.pack( side=Tkinter.RIGHT ) frame2.pack( fill=Tkinter.X ) frame.pack( expand=True, fill=Tkinter.BOTH, padx=4, pady=4 ) groups = [ Group( [ Point( 3.0, 3.5, 'v2' ), Point( 5.0, 3.0, 'v4' ), Point( 3.0, 2.0, 'v6' ) ], 'echt', 'red' ), Group( [ Point( 1.0, 1.0, 'v1' ), Point( 3.0, 0.5, 'v3' ), Point( 1.0, 3.0, 'v5' ) ], 'unecht', 'green' ) ] self.groupsFrame.setGroups( groups ) self.update() def handleExportButton( self ): path = self.pathVar.get() try: self.figureFrame.plotCanvas.postscript( file=path ) except: pass def handleUpdateButton( self ): self.update() def update( self ): try: d = float( self.dVar.get() ) if d > 0.0: self.figureFrame.d = d except: pass self.dVar.set( '%.3f' % self.figureFrame.d ) try: self.figureFrame.groups = self.groupsFrame.getGroups() self.figureFrame.redraw() except: pass def run( self ): self.window.mainloop() application = Application() application.run()