Werner's Blog — Opinion, Analysis, Commentary
My SVG exam clock

This is exam season at UBC. I often find myself in a classroom where there is no clock, or the clock is in a far corner where most students will be unable to see it. To compensate for the lack of time-keeping pieces in our classrooms, our internet-savvy generation relies on the web to provide a solution. There are numerous clocks that you can display, such as those on the Time and Date web site or the The Time Now web site. For example, you can find a digital clock for Vancouver that is simple and convenient and appears as yellow numbers on a black background. Another option is available at TheTimeNow.com.

I would like to display additional information for my students, and I also prefer the look of an old-fashioned analog clock to show the time. Rüdiger Appel has written a beautfiul little SVG script that shows a German railway station clock, a "Bahnhofsuhr". It is available under a free license and I have incorporated it into my own script, which is also availble under a free license (GPL) and is published below. If you like my clock, go to my exam clock to set the start and finish time of the exam, and the new window will show the analog clock along with a dial for the elapsed and remaining exam time. This is what it looks like:

My Exam Clock

The dial changes color as it approaches the end of the exam. It starts out as a solid green and turns to a yellow-green 20 minutes before the end of the exam. At the 15-minute mark it turns to yellow, and at the 10-minute mark to orange. During the last five minutes, the dial turns to red. An additional field provides instructions such as "START" at the beginning and "STOP" at the end, and "WAIT" before the exam starts. During most of the exam in progress the field shows the current time digitally. And yes, I am on a European 00-23 time scale. The most important feature is that the minutes left in the exam period are shown underneath the dial.

The SVG implementation of my exam dial creates a rectangular image of 240-by-320 pixels that is suitably magnified by a factor of two. The bulk of the code is JavaScript that updates the text and the dial. The SVG script requires some interaction with PHP for setting the start and finish time, provided as variables $hh0, $mm0, $hh1, and $mm1, for hours and minutes, respectively. The heavy lifting in the Javascript is done by the window.setInterval and window.setTimeout functions. These are convenient ways to make the dial and text dynamic, or to provide initial information on start-up. Except for the Javascript, the SVG script is very compact. The only elements that are somewhat more difficult to understand are the path elements in SVG. To make the dial work, the path elements need to be updated every second by calculating the coordinates of the two end points that are moving.

Let me know if you find my exam dial and clock useful, or if you have ideas about how to improve it.

<?xml version="1.0" encoding="utf-8"?> <!-- exam-dial.svg Copyright (c) 2014 Werner Antweiler Date: 2014-12-08 Version: 1.0 Email: werner@sauder.ubc.ca Licensed under GNU Public License terms. --> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events" version="1.1" width="480" height="640" baseProfile="full" viewBox="0 0 240 320"> <defs> <script type="text/javascript"> <![CDATA[ window.setTimeout(function() { var z = document.getElementById('today'); if (z) { var now=new Date(); var dayName=new Array("Sunday","Monday","Tuesday", "Wednesday","Thursday","Friday","Saturday"); var monName=new Array("January","February","March", "April","May","June","July","August", "September","October","November","December"); z.textContent=dayName[now.getDay()] + ", " + monName[now.getMonth()] + " " + now.getDate() + ", " +now.getFullYear(); } },60); window.setInterval(function() { var hh0 = <?php echo $hh0; ?>; var mm0 = <?php echo $mm0; ?>; var hh1 = <?php echo $hh1; ?>; var mm1 = <?php echo $mm1; ?>; var now = new Date(); var yy = now.getFullYear(); var mm = now.getMonth(); var dd = now.getDate(); var hh = now.getHours(); var mi = now.getMinutes(); var from = new Date(yy,mm,dd,hh0,mm0,0,0); var thru = new Date(yy,mm,dd,hh1,mm1,0,0); var duration= (thru-from)/60/1000; var left = (thru-now)/60/1000; var gone = (now-from)/60/1000; var hhmm = (hh<10?"0"+hh:hh)+":"+(mi<10?"0"+mi:mi); notify(Math.floor(gone),Math.ceil(duration),hhmm); if (left<0) left=0; else if (left>duration) left=duration; timeleft(Math.ceil(left)); advancedial((now-from)/(thru-from),Math.ceil(left)); }, 1000); function timeleft(minutes) { var element = document.getElementById('timeleft'); if (element) { element.textContent=minutes; if (minutes>100) size=52; else if (minutes>10) size=60; else size=72; element.setAttribute('font-size',size); } } function notify(minutes,duration,hhmm) { element = document.getElementById('what'); if (element) { if (minutes>=duration) { text="STOP"; color="red"; } else if (minutes<0) { text="WAIT"; color="orange"; } else if (minutes<=5) { text="START"; color="navy"; } else { color="navy"; text=hhmm; } element.textContent=text; element.setAttribute('fill',color); } } function advancedial(ratio,minutes) { var element = document.getElementById('dial'); if (element) { var angle=0.0; if (ratio<0.0) { angle=0.0; } else if (ratio>1.0) { angle=Math.PI; } else { angle=Math.PI*ratio; } var x0=120.0-110.0*Math.cos(angle); var y0=120.0-110.0*Math.sin(angle); var x1=120.0- 60.0*Math.cos(angle); var y1=120.0- 60.0*Math.sin(angle); var path="M10,120A110,110 0 0,1 " + x0 + "," + y0 + "L" + x1 + "," + y1 + "A60,60 0 0,0 60,120Z"; element.setAttribute('d',path); if (minutes<=5) color="red"; else if (minutes<=10) color="orange"; else if (minutes<=20) color="yellow"; else if (minutes<=30) color="lime"; else color="green"; var text='stroke:navy;stroke-width:2;fill:'+color; element.setAttribute('style',text); } } ]]> </script> </defs> <path style="fill:beige;stroke:navy;stroke-width:2;" d="M10,120A110,110 0 0,1 230,120L180,120A60,60 0 0,0 60,120Z"/> <path id="dial" style="fill:beige;stroke:navy;stroke-width:2;" d="M10,120A110,110 0 0,1 230,120L180,120A60,60 0 0,0 60,120Z"/> <text id="timeleft" x="120" y="120" font-family="Helvetica" font-size="60" fill="navy" text-anchor="middle"></text> <text id="timeleft" x="120" y="155" font-family="Helvetica" font-size="30" fill="navy" text-anchor="middle">MINUTES LEFT</text> <rect x="10" y="180" height="30" width="220" style="fill:white;stroke-width:1;stroke:navy;"/> <text x="120" y="200" font-family="Helvetica" font-size="15" fill="navy" text-anchor="middle" id="today"></text> <rect x="10" y="230" height="80" width="220" style="fill:beige;stroke-width:1;stroke:navy;"/> <text x="120" y="290" font-family="Helvetica" font-size="60" fill="navy" text-anchor="middle" id="what"></text> </svg>

Click here to access the SVG code, then save the file through your web browser.

Posted on Wednesday, December 10, 2014 at 14:00 — #Software
© 2024  Prof. Werner Antweiler, University of British Columbia.
[Sauder School of Business] [The University of British Columbia]