Draw a clock
Task: draw a clock. More specific:
- Draw a time keeping device. It can be a stopwatch, hourglass, sundial, a mouth counting "one thousand and one", anything. Only showing the seconds is required, e.g. a watch with just a second hand will suffice. However, it must clearly change every second, and the change must cycle every so often (one minute, 30 seconds, etc.) It must be drawn; printing a string of numbers to your terminal doesn't qualify. Both text-based and graphical drawing are OK.
- The clock is unlikely to be used to control space flights, so it needs not be hyper-accurate, but it should be usable, meaning if one can read the seconds off the clock, it must agree with the system clock.
- A clock is rarely (never?) a major application: don't be a CPU hog and poll the system timer every microsecond, use a proper timer/signal/event from your system or language instead. For a bad example, many OpenGL programs update the framebuffer in a busy loop even if no redraw is needed, which is very undesirable for this task.
- A clock is rarely (never?) a major application: try to keep your code simple and to the point. Don't write something too elaborate or convoluted, instead do whatever is natural, concise and clear in your language.
Key points: animate simple object; timed event; polling system resources; code clarity.
AutoHotkey
requires the GDI+ Library from http://www.autohotkey.com/forum/viewtopic.php?t=32238 this code from http://www.autohotkey.com/forum/viewtopic.php?p=231836#231836 draws a very nice clock with GDI+ <lang AHK>; gdi+ ahk analogue clock example written by derRaphael
- Parts based on examples from Tic's GDI+ Tutorials and of course on his GDIP.ahk
- This code has been licensed under the terms of EUPL 1.0
- SingleInstance, Force
- NoEnv
SetBatchLines, -1
- Uncomment if Gdip.ahk is not in your standard library
- Include, Gdip.ahk
If !pToken := Gdip_Startup() {
MsgBox, 48, gdiplus error!, Gdiplus failed to start. Please ensure you have gdiplus on your system ExitApp
} OnExit, Exit
SysGet, MonitorPrimary, MonitorPrimary SysGet, WA, MonitorWorkArea, %MonitorPrimary% WAWidth := WARight-WALeft WAHeight := WABottom-WATop
Gui, 1: -Caption +E0x80000 +LastFound +AlwaysOnTop +ToolWindow +OwnDialogs Gui, 1: Show, NA hwnd1 := WinExist()
ClockDiameter := 180 Width := Height := ClockDiameter + 2 ; make width and height slightly bigger to avoid cut away edges CenterX := CenterY := floor(ClockDiameter/2) ; Center x
- Prepare our pGraphic so we have a 'canvas' to work upon
hbm := CreateDIBSection(Width, Height), hdc := CreateCompatibleDC() obm := SelectObject(hdc, hbm), G := Gdip_GraphicsFromHDC(hdc) Gdip_SetSmoothingMode(G, 4)
- Draw outer circle
Diameter := ClockDiameter pBrush := Gdip_BrushCreateSolid(0x66008000) Gdip_FillEllipse(G, pBrush, CenterX-(Diameter//2), CenterY-(Diameter//2),Diameter, Diameter) Gdip_DeleteBrush(pBrush)
- Draw inner circle
Diameter := ceil(ClockDiameter - ClockDiameter*0.08) ; inner circle is 8 % smaller than clock's diameter pBrush := Gdip_BrushCreateSolid(0x80008000) Gdip_FillEllipse(G, pBrush, CenterX-(Diameter//2), CenterY-(Diameter//2),Diameter, Diameter) Gdip_DeleteBrush(pBrush)
- Draw Second Marks
R1 := Diameter//2-1 ; outer position R2 := Diameter//2-1-ceil(Diameter//2*0.05) ; inner position Items := 60 ; we have 60 seconds pPen := Gdip_CreatePen(0xff00a000, floor((ClockDiameter/100)*1.2)) ; 1.2 % of total diameter is our pen width GoSub, DrawClockMarks Gdip_DeletePen(pPen)
- Draw Hour Marks
R1 := Diameter//2-1 ; outer position R2 := Diameter//2-1-ceil(Diameter//2*0.1) ; inner position Items := 12 ; we have 12 hours pPen := Gdip_CreatePen(0xc0008000, ceil((ClockDiameter//100)*2.3)) ; 2.3 % of total diameter is our pen width GoSub, DrawClockMarks Gdip_DeletePen(pPen) ; The OnMessage will let us drag the clock OnMessage(0x201, "WM_LBUTTONDOWN") UpdateLayeredWindow(hwnd1, hdc, WALeft+((WAWidth-Width)//2), WATop+((WAHeight-Height)//2), Width, Height) SetTimer, sec, 1000
sec:
- prepare to empty previously drawn stuff
Gdip_SetSmoothingMode(G, 1) ; turn off aliasing Gdip_SetCompositingMode(G, 1) ; set to overdraw
- delete previous graphic and redraw background
Diameter := ceil(ClockDiameter - ClockDiameter*0.18) ; 18 % less than clock's outer diameter ; delete whatever has been drawn here pBrush := Gdip_BrushCreateSolid(0x00000000) ; fully transparent brush 'eraser' Gdip_FillEllipse(G, pBrush, CenterX-(Diameter//2), CenterY-(Diameter//2),Diameter, Diameter) Gdip_DeleteBrush(pBrush) Gdip_SetCompositingMode(G, 0) ; switch off overdraw pBrush := Gdip_BrushCreateSolid(0x66008000) Gdip_FillEllipse(G, pBrush, CenterX-(Diameter//2), CenterY-(Diameter//2),Diameter, Diameter) Gdip_DeleteBrush(pBrush) pBrush := Gdip_BrushCreateSolid(0x80008000) Gdip_FillEllipse(G, pBrush, CenterX-(Diameter//2), CenterY-(Diameter//2),Diameter, Diameter) Gdip_DeleteBrush(pBrush)
- Draw HoursPointer
Gdip_SetSmoothingMode(G, 4) ; turn on antialiasing t := A_Hour*360//12 + (A_Min*360//60)//12 +90 R1 := ClockDiameter//2-ceil((ClockDiameter//2)*0.5) ; outer position pPen := Gdip_CreatePen(0xa0008000, floor((ClockDiameter/100)*3.5)) Gdip_DrawLine(G, pPen, CenterX, CenterY , ceil(CenterX - (R1 * Cos(t * Atan(1) * 4 / 180))) , ceil(CenterY - (R1 * Sin(t * Atan(1) * 4 / 180)))) Gdip_DeletePen(pPen)
- Draw MinutesPointer
t := A_Min*360//60+90 R1 := ClockDiameter//2-ceil((ClockDiameter//2)*0.25) ; outer position pPen := Gdip_CreatePen(0xa0008000, floor((ClockDiameter/100)*2.7)) Gdip_DrawLine(G, pPen, CenterX, CenterY , ceil(CenterX - (R1 * Cos(t * Atan(1) * 4 / 180))) , ceil(CenterY - (R1 * Sin(t * Atan(1) * 4 / 180)))) Gdip_DeletePen(pPen)
- Draw SecondsPointer
t := A_Sec*360//60+90 R1 := ClockDiameter//2-ceil((ClockDiameter//2)*0.2) ; outer position pPen := Gdip_CreatePen(0xa000FF00, floor((ClockDiameter/100)*1.2)) Gdip_DrawLine(G, pPen, CenterX, CenterY , ceil(CenterX - (R1 * Cos(t * Atan(1) * 4 / 180))) , ceil(CenterY - (R1 * Sin(t * Atan(1) * 4 / 180)))) Gdip_DeletePen(pPen) UpdateLayeredWindow(hwnd1, hdc) ;, xPos, yPos, ClockDiameter, ClockDiameter)
return
DrawClockMarks:
Loop, % Items Gdip_DrawLine(G, pPen , CenterX - ceil(R1 * Cos(((a_index-1)*360//Items) * Atan(1) * 4 / 180)) , CenterY - ceil(R1 * Sin(((a_index-1)*360//Items) * Atan(1) * 4 / 180)) , CenterX - ceil(R2 * Cos(((a_index-1)*360//Items) * Atan(1) * 4 / 180)) , CenterY - ceil(R2 * Sin(((a_index-1)*360//Items) * Atan(1) * 4 / 180)) )
return
WM_LBUTTONDOWN() {
PostMessage, 0xA1, 2 return
}
esc:: Exit:
SelectObject(hdc, obm) DeleteObject(hbm) DeleteDC(hdc) Gdip_DeleteGraphics(G) Gdip_Shutdown(pToken) ExitApp
Return</lang>
C
Draws a crude clock in terminal. C99, compiled with gcc -std=c99
.
<lang C>#include <stdio.h>
- include <stdlib.h>
- include <math.h>
- include <time.h>
- include <sys/time.h>
- define PI 3.14159265
char * shades = " .:-*ca&#%@";
/* distance of (x, y) from line segment (0, 0)->(x0, y0) */ double dist(double x, double y, double x0, double y0) { double l = (x * x0 + y * y0) / (x0 * x0 + y0 * y0);
if (l > 1) { x -= x0; y -= y0; } else if (l >= 0) { x -= l * x0; y -= l * y0; } return sqrt(x * x + y * y); }
enum { sec = 0, min, hur }; // for subscripts
void draw(int size) {
- define for_i for(int i = 0; i < size; i++)
- define for_j for(int j = 0; j < size * 2; j++)
double angle, cx = size / 2.; double sx[3], sy[3], sw[3]; double fade[] = { 1, .35, .35 }; /* opacity of each arm */ struct timeval tv; struct tm *t;
/* set width of each arm */ sw[sec] = size * .02; sw[min] = size * .03; sw[hur] = size * .05;
every_second: gettimeofday(&tv, 0); t = localtime(&tv.tv_sec);
angle = t->tm_sec * PI / 30; sy[sec] = -cx * cos(angle); sx[sec] = cx * sin(angle);
angle = (t->tm_min + t->tm_sec / 60.) / 30 * PI; sy[min] = -cx * cos(angle) * .8; sx[min] = cx * sin(angle) * .8;
angle = (t->tm_hour + t->tm_min / 60.) / 6 * PI; sy[hur] = -cx * cos(angle) * .6; sx[hur] = cx * sin(angle) * .6;
printf("\033[s"); /* save cursor position */ for_i { printf("\033[%d;0H", i); /* goto row i, col 0 */ double y = i - cx; for_j { double x = (j - 2 * cx) / 2;
int pix = 0; /* calcs how far the "pixel" is from each arm and set * shade, with some anti-aliasing. It's ghetto, but much * easier than a real scanline conversion. */ for (int k = hur; k >= sec; k--) { double d = dist(x, y, sx[k], sy[k]); if (d < sw[k] - .5) pix = 10 * fade[k]; else if (d < sw[k] + .5) pix = (5 + (sw[k] - d) * 10) * fade[k]; } putchar(shades[pix]); } } printf("\033[u"); /* restore cursor pos so you can bg the job -- value unclear */
fflush(stdout); sleep(1); /* sleep 1 can at times miss a second, but will catch up next update */ goto every_second; }
int main(int argc, char *argv[]) { int s; if (argc <= 1 || (s = atoi(argv[1])) <= 0) s = 20; draw(s); }</lang>
GUISS
<lang guiss>Start,Programs,Accessories,Analogue Clock</lang>
JavaScript
Tested on Gecko. Put the following in a <script> tag somewhere, and call init_clock()
after body load.
<lang JavaScript>var sec_old = 0;
function update_clock() {
var t = new Date();
var arms = [t.getHours(), t.getMinutes(), t.getSeconds()];
if (arms[2] == sec_old) return;
sec_old = arms[2];
var c = document.getElementById('clock'); var ctx = c.getContext('2d'); ctx.fillStyle = "rgb(0,200,200)"; ctx.fillRect(0, 0, c.width, c.height); ctx.fillStyle = "white"; ctx.fillRect(3, 3, c.width - 6, c.height - 6); ctx.lineCap = 'round';
var orig = { x: c.width / 2, y: c.height / 2 }; arms[1] += arms[2] / 60; arms[0] += arms[1] / 60; draw_arm(ctx, orig, arms[0] * 30, c.width/2.5 - 15, c.width / 20, "green"); draw_arm(ctx, orig, arms[1] * 6, c.width/2.2 - 10, c.width / 30, "navy"); draw_arm(ctx, orig, arms[2] * 6, c.width/2.0 - 6, c.width / 100, "maroon"); }
function draw_arm(ctx, orig, deg, len, w, style) { ctx.save(); ctx.lineWidth = w; ctx.lineCap = 'round'; ctx.translate(orig.x, orig.y); ctx.rotate((deg - 90) * Math.PI / 180); ctx.strokeStyle = style; ctx.beginPath(); ctx.moveTo(-len / 10, 0); ctx.lineTo(len, 0); ctx.stroke(); ctx.restore(); }
function init_clock() { var clock = document.createElement('canvas'); clock.width = 100; clock.height = 100; clock.id = "clock"; document.body.appendChild(clock);
window.setInterval(update_clock, 200); }</lang>
Liberty BASIC
LB has a timer to call a routine at regular intervals. The example is a cut-down version of the full clock supplied with LB as an example. <lang lb>
WindowWidth =120 WindowHeight =144 nomainwin
open "Clock" for graphics_nsb_nf as #clock #clock "trapclose [exit]" #clock "fill white" for angle =0 to 330 step 30 #clock "up ; home ; north ; turn "; angle #clock "go 40 ; down ; go 5" next angle
#clock "flush"
timer 1000, [display] wait
[display] ' called only when seconds have changed
time$ =time$() seconds =val( right$( time$, 2)) ' delete the last drawn segment, if there is one if segId >2 then #clock "delsegment "; segId -1 ' center the turtle #clock "up ; home ; down ; north" ' erase each hand if its position has changed if oldSeconds <>seconds then #clock, "size 1 ; color white ; turn "; oldSeconds *6 ; " ; go 38 ; home ; color black ; north" : oldSeconds =seconds ' redraw all three hands, second hand first #clock "size 1 ; turn "; seconds * 6 ; " ; go 38" ' flush to end segment, then get the next segment id # #clock "flush" #clock "segment" input #clock, segId
wait
[exit]
close #clock
end
</lang>
PicoLisp
This is an animated ASCII drawing of the "Berlin-Uhr", a clock built to display the time according to the principles of set theory, which is installed in Berlin since 1975. See www.surveyor.in-berlin.de/berlin/uhr/indexe.html and www.cs.utah.edu/~hatch/berlin_uhr.html. <lang PicoLisp>(de draw Lst
(for L Lst (for X L (cond ((num? X) (space X)) ((sym? X) (prin X)) (T (do (car X) (prin (cdr X)))) ) ) (prinl) ) )
(de bigBox (N)
(do 2 (prin "|") (for I 4 (prin (if (> I N) " |" " ======== |")) ) (prinl) ) )
(call 'clear) # Clear screen (call "tput" "civis") # Set cursor invisible
(push '*Bye '(call "tput" "cnorm")) # Set cursor visible on exit
(loop
(call "tput" "cup" 0 0) # Cursor to top left (let Time (time (time)) (draw (20 (5 . _)) (19 / 5 \\)) (if (onOff (NIL)) (draw (18 / 7 \\) (18 \\ 7 /)) (draw (18 / 2 (3 . "#") 2 \\) (18 \\ 2 (3 . "#") 2 /)) ) (draw (19 \\ (5 . _) /) (+ (10 . -) + (10 . -) + (10 . -) + (10 . -) +) ) (bigBox (/ (car Time) 5)) (draw (+ (10 . -) + (10 . -) + (10 . -) + (10 . -) +)) (bigBox (% (car Time) 5)) (draw (+ (43 . -) +)) (do 2 (prin "|") (for I `(range 5 55 5) (prin (cond ((> I (cadr Time)) " |") ((=0 (% I 3)) " # |") (T " = |") ) ) ) (prinl) ) (draw (+ (43 . -) +)) (bigBox (% (cadr Time) 5)) (draw (+ (10 . -) + (10 . -) + (10 . -) + (10 . -) +)) ) (wait 1000) )</lang>
The six '#' characters in the "circle" on top toggle on/off every second. This is the display at 17:46:
_____ / \ / ### \ \ ### / \_____/ +----------+----------+----------+----------+ | ======== | ======== | ======== | | | ======== | ======== | ======== | | +----------+----------+----------+----------+ | ======== | ======== | | | | ======== | ======== | | | +-------------------------------------------+ | = | = | # | = | = | # | = | = | # | | | | = | = | # | = | = | # | = | = | # | | | +-------------------------------------------+ | ======== | | | | | ======== | | | | +----------+----------+----------+----------+
Tcl
<lang tcl>package require Tcl 8.5 package require Tk
- GUI code
pack [canvas .c -width 200 -height 200] .c create oval 20 20 180 180 -width 10 -fill {} -outline grey70 .c create line 0 0 1 1 -tags hour -width 6 -cap round -fill black .c create line 0 0 1 1 -tags minute -width 4 -cap round -fill black .c create line 0 0 1 1 -tags second -width 2 -cap round -fill grey30 proc updateClock t {
scan [clock format $t -format "%H %M %S"] "%d%d%d" h m s # On an analog clock, the hour and minute hands move gradually set m [expr {$m + $s/60.0}] set h [expr {($h % 12 + $m/60.0) * 5}] foreach tag {hour minute second} value [list $h $m $s] len {50 80 80} {
.c coords $tag 100 100 \ [expr {100 + $len*sin($value/30.0*3.14159)}] \ [expr {100 - $len*cos($value/30.0*3.14159)}]
}
}
- Timer code, accurate to within a quarter second
set time 0 proc ticker {} {
global time set t [clock seconds] after 250 ticker if {$t != $time} {
set time $t updateClock $t
}
} ticker</lang> Note that though this code does poll the system timer approximately four times a second, this is a cheap operation; the GUI update (the relatively expensive part) only happens once a second. The amount of system processing power consumed by this code isn't noticeable on my system; it vanishes with respect to the other processing normally happening.