Welcome, guest | Sign In | My Account | Store | Cart

A very simple tkinter analog clock

Python, 137 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# clock.py  By Anton Vredegoor (anton.vredegoor@gmail.com) 
# last edit: july 2009,
# license: GPL
# enjoy!

"""
A very simple  clock.

The program transforms worldcoordinates into screencoordinates 
and vice versa according to an algorithm found in: "Programming 
principles in computer graphics" by Leendert Ammeraal.

"""

from Tkinter import *
from time import localtime
from datetime import timedelta,datetime
from math import sin, cos, pi
import sys, types, os

_inidle = type(sys.stdin) == types.InstanceType and \
	  sys.stdin.__class__.__name__ == 'PyShell'

class transformer:

    def __init__(self, world, viewport):
        self.world = world 
        self.viewport = viewport

    def point(self, x, y):
        x_min, y_min, x_max, y_max = self.world
        X_min, Y_min, X_max, Y_max = self.viewport
        f_x = float(X_max-X_min) /float(x_max-x_min) 
        f_y = float(Y_max-Y_min) / float(y_max-y_min) 
        f = min(f_x,f_y)
        x_c = 0.5 * (x_min + x_max)
        y_c = 0.5 * (y_min + y_max)
        X_c = 0.5 * (X_min + X_max)
        Y_c = 0.5 * (Y_min + Y_max)
        c_1 = X_c - f * x_c
        c_2 = Y_c - f * y_c
        X = f * x + c_1
        Y = f * y + c_2
        return X , Y

    def twopoints(self,x1,y1,x2,y2):
        return self.point(x1,y1),self.point(x2,y2)

class clock:

    def __init__(self,root,deltahours = 0):
        self.world       = [-1,-1,1,1]
        self.bgcolor     = '#000000'
        self.circlecolor = '#808080'
        self.timecolor   = '#ffffff'
        self.circlesize  = 0.09
        self._ALL        = 'all'
        self.pad         = 25
        self.root        = root
        WIDTH, HEIGHT = 400, 400
        root.bind("<Escape>", lambda _ : root.destroy())
        self.delta = timedelta(hours = deltahours)  
        self.canvas = Canvas(root, 
                width       = WIDTH,
                height      = HEIGHT,
                background  = self.bgcolor)
        viewport = (self.pad,self.pad,WIDTH-self.pad,HEIGHT-self.pad)
        self.T = transformer(self.world,viewport)
        self.root.title('Clock')
        self.canvas.bind("<Configure>",self.configure())
        self.canvas.pack(fill=BOTH, expand=YES)
        self.poll()
 
    def configure(self):
        self.redraw()
    
    def redraw(self):
        sc = self.canvas
        sc.delete(self._ALL)
        width = sc.winfo_width()
        height =sc.winfo_height()
        sc.create_rectangle([[0,0],[width,height]],
                fill = self.bgcolor, tag = self._ALL)
        viewport = (self.pad,self.pad,width-self.pad,height-self.pad)
        self.T = transformer(self.world,viewport)
        self.paintgrafics()

    def paintgrafics(self):
        start = -pi/2
        step = pi/6
        for i in range(12):
            angle =  start+i*step
            x, y = cos(angle),sin(angle)
            self.paintcircle(x,y)
        self.painthms()
        self.paintcircle(0,0)
    
    def painthms(self):
        T = datetime.timetuple(datetime.utcnow()-self.delta)
        x,x,x,h,m,s,x,x,x = T
        self.root.title('%02i:%02i:%02i' %(h,m,s))
        angle = -pi/2 + (pi/6)*h + (pi/6)*(m/60.0)
        x, y = cos(angle)*.60,sin(angle)*.60   
        scl = self.canvas.create_line
        scl(apply(self.T.twopoints,[0,0,x,y]), fill = self.timecolor, 
                    tag =self._ALL, width = 6)
        angle = -pi/2 + (pi/30)*m + (pi/30)*(s/60.0)
        x, y = cos(angle)*.80,sin(angle)*.80   
        scl(apply(self.T.twopoints,[0,0,x,y]), fill = self.timecolor, 
                    tag =self._ALL, width = 3)
        angle = -pi/2 + (pi/30)*s
        x, y = cos(angle)*.95,sin(angle)*.95   
        scl(apply(self.T.twopoints, [0,0,x,y]), fill = self.timecolor,
                    tag =self._ALL, arrow = 'last')
    
    def paintcircle(self,x,y):
        ss = self.circlesize / 2.0
        mybbox = [-ss+x,-ss+y,ss+x,ss+y]
        sco = self.canvas.create_oval
        sco(apply(self.T.twopoints,mybbox), fill = self.circlecolor,
                    tag =self._ALL)
   
    def poll(self):
        self.configure()
        self.root.after(200,self.poll)

def main():
    root= Tk()
    # deltahours: how far are you from utc?
    # someone should automatize that, but I sometimes want to display
    # time as if I am in another timezone ...
    clock(root,deltahours = -2)
    if not _inidle:
        root.mainloop()

if __name__=='__main__':
  main()

If one has a wide screen, the sides look so empty, and I like analog clocks. One issue is one has to manually edit the code to set the time zone, but that is a feature, not a bug!

7 comments

James Mills 14 years, 9 months ago  # | flag

Hi. Great clock :) I believe there might be a bug or two though as it showed the wrong time on my PC.

--JamesMills (prologic)

Anton Vredegoor (author) 14 years, 9 months ago  # | flag

@James Mills: Was the time off by a round number of hours? If so, you could try adjusting the deltahours knob.

a 13 years, 12 months ago  # | flag

apply() is deprecated. apply(self.T.twopoints,[0,0,x,y] should be changed to self.T.twopoints(0,0,x,y) apply(self.T.twopoints,mybbox) should be changed to self.T.twopoints(*mybbox)

Michael Sweeney 11 years ago  # | flag

Really nice piece of coding.

Martin Miller 10 years, 7 months ago  # | flag

I think your handling of the delta time argument is backwards because timezone offsets values are measured relative to UTC and computed as localtime - UTCtime, which mean you should to add the delta offset to utc to obtain the local value -- not subtract it as done in line 99.

(It's also worth noting that UTC doesn't change with the seasons, and so doesn't observe a daylight saving or summer Time mode that many local times do. This means the deltahours value for a local time won't vary either.)

In conclusion, it might be easiest to just obtain the local time directly and ignore the deltahours argument by replacing line 99 with:

T = datetime.timetuple(datetime.now())

Of course the would preclude being able to configure the clock to display some non-local time...

Otherwise, I agree with @Michael, nice work!

Martin Miller 10 years, 7 months ago  # | flag

Correction: What I said about deltahours not needing to vary due to daylight saving / summer time being in effect or not was incorrect -- it may depending on the date and timezone, since UTC itself doesn't observe it.

Yosef Ashenafi 9 years, 11 months ago  # | flag

when i ran with geany i got the following error how can i fix it. AttributeError: 'module' object has no attribute 'InstanceType'