{source}<script type="text/javascript">
function CalcMain( myWay) {
var cx = document.getElementById("myCanvas");
var ctx= cx.getContext("2d");
 
//check if browser supports canvas
if(ctx) { ctx.clearRect(0,0,700,830);}
else { alert('This browser does not support the drawing canvas'); return; }
if (myWay==0) {
   document.getElementById("sundraw").reset();
   ctx.clearRect(0,0,700,830); return; }
if (myWay==1) {
//check that latitude is in limits
var strLat = document.getElementById("myLat").value;
var x = Number(strLat);
if (strLat=="" || isNaN(x) || Math.abs(x)<18 || Math.abs(x)>65) {
alert("Latitude must be a number between 18 and 65 N+ or S-"); }
else{DrawSundial(ctx,x); } } }
 
function DrawSundial(ctx,x){
// create javascript dial variables
var dial = new Object();
dial.name = "North American Sundial Society";
dial.radius= 280;
dial.cxo = 400;
dial.cyo = 1.0*dial.radius;
dial.gxo = 20;
dial.gyo = 2.2*dial.radius;
dial.ring1 = dial.radius * 0.90;
dial.ring2 = dial.radius * 0.88;
dial.ring3 = dial.radius * 0.70;
dial.ring4 = dial.radius * 0.65;
dial.ring5 = dial.radius * 0.63;
dial.ring6 = dial.radius * 0.60;
dial.ring7 = dial.radius * 0.58;
dial.gwidth= 0.015 * dial.radius;
dial.gwside= dial.gwidth;
dial.tab = 0.06 *dial.radius;
dial.goffset= -0.4* dial.radius;
dial.glength= 0.8* dial.radius;
dial.glabel = 0.03* dial.radius;
dial.delta = 4.0* Math.PI/180;
dial.gextend= 1.18;
dial.firstHr= 5;
dial.lastHr = 19;
dial.lat = x;
dial.sgnlat = Math.sign(x);
 
// test squares
ctx.clearRect(1,1,700,830);ctx.fillStyle = "rgb(200,0,0)";ctx.fillRect (10, 10, 55, 50);
ctx.fillStyle = "rgba(0, 0, 200, 0.5)";ctx.fillRect (30, 30, 55, 50);
 
// draw dial
drawCircle(dial.ring1,ctx,dial);
drawCircle(dial.ring2,ctx,dial);
drawCircle(dial.ring3,ctx,dial);
drawCircle(dial.ring6,ctx,dial);
drawCircle(dial.ring7,ctx,dial);
drawOctagon(dial.radius,ctx,dial);
drawLabelText(ctx,dial);
drawCompassRose(ctx, dial);
drawGnomonTab(ctx, dial);
drawNASSLogo(ctx, dial)
drawHourLines(0.0, ctx, dial);     //hours
drawHourLines(0.50, ctx, dial);   //half hours
drawHourLines(0.25, ctx, dial);   //quarter hours
drawHourLines(0.75, ctx, dial);   //quarter hours
drawNumerals(ctx, dial);
drawGnomon(ctx, dial)
}
 
function ToRad(x) { return(Math.PI/180*x);}
function ToDeg(x) { return(180/Math.PI*x);}
 
function drawCircle(radius,ctx,dial) {
ctx.beginPath();
ctx.arc(dial.cxo,dial.cyo,radius,0,Math.PI*2,true);
ctx.strokeStyle = "Black";
ctx.stroke();}
 
function drawOctagon(radius,ctx,dial) {
var xx = 0.0; var yy = 0.0; var phi = 22.5;
ctx.beginPath();
for(phi = 22.5; phi<385; phi=phi + 45) {
xx = dial.cxo + radius*Math.cos(ToRad(phi));
yy = dial.cyo + radius*Math.sin(ToRad(phi));
if (phi == 22.5) { ctx.moveTo(xx,yy); }
ctx.lineTo(xx, yy); }
ctx.closePath(); ctx.strokeStyle = "Black"; ctx.stroke();}
 
function drawLabelText(ctx,dial) {
var cy1 = dial.cyo + 0.72*dial.ring3;
var cy2 = dial.cyo + 0.5*(dial.ring2 + dial.ring3);
var txt = "Lat: " + dial.lat;
var offx2 = dial.cxo-5.4*txt.length/2;
var offx1 = dial.cxo-5.4*dial.name.length/2;
var offy1 = dial.cyo+.77*dial.radius;
var offy2 = dial.cyo+.50*dial.radius;
ctx.fillStyle = "Black";
ctx.font="12px Arial"; ctx.fillText(dial.name,offx1,offy1); ctx.fillText(txt,offx2,offy2);}
 
function drawNASSLogo(ctx, dial) {
var cxo = dial.cxo-.7*dial.ring7;
var cyo = dial.cyo+14;
ctx.fillStyle = "Black";
ctx.font="26px Arial"; ctx.fillText("NASS",cxo,cyo);
}
 
function drawCompassRose(ctx, dial) {
var xo = dial.cxo + 0.28*dial.radius;
var yo = dial.cyo;var ro = 0.075*dial.radius;
var tilt = 0;
for(tilt = 0; tilt <= 270; tilt = tilt + 90) {
var x1 = xo + 0.3*ro*Math.cos(ToRad(tilt - 45));
var y1 = yo - 0.3*ro*Math.sin(ToRad(tilt - 45));
var x2 = xo + ro*Math.cos(ToRad(tilt));
var y2 = yo - ro*Math.sin(ToRad(tilt));
ctx.font = "14px Arial";
ctx.beginPath(); ctx.moveTo(xo, yo); ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2); ctx.lineTo(xo, yo); ctx.closePath();ctx.stroke();
 
x1 = xo + 0.3*ro*Math.cos(ToRad(tilt + 45));
y1 = yo - 0.3*ro*Math.sin(ToRad(tilt + 45));
x2 = xo + ro*Math.cos(ToRad(tilt));
y2 = yo - ro*Math.sin(ToRad(tilt));
ctx.beginPath(); ctx.moveTo(xo, yo); ctx.lineTo(x1, y1); ctx.lineTo(x2, y2);
ctx.lineTo(xo, yo); ctx.closePath(); ctx.stroke();ctx.fillStyle = "#c99";ctx.fill();
 
x2 = xo + 1.35*ro*Math.cos(ToRad(tilt))-.3*ro;
y2 = yo - 1.35*ro*Math.sin(ToRad(tilt))+.27*ro;
ctx.beginPath(); ctx.fillStyle = "Black"; ctx.font = 'italic bold 12px sans-serif'; ctx.textBaseline = 'bottom';
 
if (dial.lat>0) {
if(tilt == 0) ctx.fillText("E",x2+3,y2)
;if(tilt == 90) ctx.fillText("N",x2+1,y2);
if(tilt == 180) ctx.fillText("W",x2,y2);
if(tilt == 270) ctx.fillText("S",x2,y2+2); }
 
if (dial.lat<0) {
if(tilt == 0) ctx.fillText("W",x2+3,y2);
if(tilt == 90) ctx.fillText("S",x2+1,y2);
if(tilt == 180) ctx.fillText("E",x2,y2);
if(tilt == 270) ctx.fillText("N",x2,y2+2); } }
}  
 
function drawGnomonTab(ctx, dial) {
// draw the Gnomon Rectangle Spot on the Sundial Face
var xo = dial.cxo - dial.gwidth;
var yo = dial.cyo - dial.goffset;
ctx.strokeRect(xo,yo,2*dial.gwidth,-dial.glength);
}
 
function calculateERadius(radius, theta, dial) {
// calculate effective radius for hour lines, etc.
var bo = dial.gwside * Math.sin(theta) + dial.goffset * Math.cos(theta);
var co = dial.gwside * dial.gwside + dial.goffset * dial.goffset;
var ro = -bo + Math.sqrt(Math.abs(bo*bo - co + radius*radius)); return ro;
}
 
function drawHourLines(mark, ctx, dial) {
var ringo= 0.0;
if (mark == 0.0) { ringo = dial.ring3;}
if (mark == 0.50){ ringo = dial.ring4;}
if (mark == 0.25){ ringo = dial.ring5;}
if (mark == 0.75){ ringo = dial.ring5;}
var Hrs = 0;
for(Hrs = dial.firstHr+mark; Hrs <= dial.lastHr-mark; Hrs++) {
var HA = 15*(Hrs-12);
var theta = Math.atan(Math.tan(ToRad(HA))*Math.sin(Math.abs(ToRad(dial.lat))));
// account for gnomon width and angles before 6am and after 6pm
dial.gwside = dial.gwidth;
if(HA < 0) dial.gwside = -dial.gwidth;
if(HA < -90) { theta = theta - Math.PI; dial.gwside = dial.gwidth; }
if(HA > 90) { theta = theta - Math.PI; dial.gwside = - dial.gwidth; }
// effective radius
var r1 = calculateERadius(dial.ring6, theta, dial);
var r2 = calculateERadius(ringo, theta, dial);
// hour lines confined to rings r1 and r2
var stheta = Math.sin(theta);
var ctheta = Math.cos(theta);
var x1 = r1 * stheta + dial.gwside;
var y1 = r1 * ctheta + dial.goffset;
var x2 = r2 * stheta + dial.gwside;
var y2 = r2 * ctheta + dial.goffset;
// draw hour angles
ctx.beginPath(); ctx.moveTo(dial.cxo + x1, dial.cyo - y1);
ctx.lineTo(dial.cxo + x2, dial.cyo - y2); ctx.stroke();
// code for second hour line because of gnomon width
if(dial.gwidth > 0 && Hrs==12) {
dial.gwside = -dial.gwside;
r1 = calculateERadius(dial.ring6, theta, dial);
r2 = calculateERadius(ringo, theta, dial);
// hour lines confined to rings r1 and r2
x1 = r1 * stheta + dial.gwside;
y1 = r1 * ctheta + dial.goffset;
x2 = r2 * stheta + dial.gwside;
y2 = r2 * ctheta + dial.goffset;
// draw hour angles
ctx.beginPath(); ctx.moveTo(dial.cxo + x1, dial.cyo - y1);
ctx.lineTo(dial.cxo + x2, dial.cyo - y2); ctx.stroke(); } }
}
 
function drawNumerals(ctx, dial) {
// decide which Roman Numeral to draw
dial.gwside = dial.sgnlat*dial.gwidth;
var Hrs = 0;
for(Hrs = dial.firstHr; Hrs <= dial.lastHr; Hrs++) {
var HA = 15*(Hrs-12);
var theta = Math.atan(Math.tan(ToRad(HA))*Math.sin(ToRad(dial.lat)));
// account for gnomon width and angles before 6am and after 6pm
if(HA < 0) dial.gwside = -dial.gwside;
// morning
if(HA < -90) {
// before 6am
theta = theta - Math.PI;
dial.gwside = - dial.gwside; }
if(HA > 90) {
// after 6pm
theta = theta - Math.PI;
dial.gwside = - dial.gwside; }
// select the numeral combination
if(Hrs == 5) { drawV(theta, ctx, dial); }
if(Hrs == 6) { drawV(theta - 0.30*dial.delta, ctx, dial);
   drawI(theta + 0.60*dial.delta, ctx, dial); }
if(Hrs == 7) { drawV(theta - 0.50*dial.delta, ctx, dial);
   drawI(theta + 0.25*dial.delta, ctx, dial);
   drawI(theta + 0.75*dial.delta, ctx, dial); }
if(Hrs == 8) { drawV(theta - 0.75*dial.delta, ctx, dial);
   drawI(theta, ctx, dial);
   drawI(theta + 0.40*dial.delta, ctx, dial);
   drawI(theta + 0.80*dial.delta, ctx, dial); }
if(Hrs == 9) { drawI(theta - 0.80*dial.sgnlat*dial.delta, ctx, dial);
   drawX(theta - 0.15*dial.sgnlat*dial.delta, ctx, dial); }
if(Hrs == 10) { drawX(theta, ctx, dial); }
if(Hrs == 11) { drawX(theta - 0.50*dial.sgnlat*dial.delta, ctx, dial);
   drawI(theta + 0.10*dial.sgnlat*dial.delta, ctx, dial); }
if(Hrs == 12) { drawX(theta - 0.50*dial.delta, ctx, dial);
   drawI(theta + 0.05*dial.delta, ctx, dial);
   drawI(theta + 0.40*dial.delta, ctx, dial); }
if(Hrs == 13) { drawI(theta, ctx, dial); }
if(Hrs == 14) { drawI(theta - 0.20*dial.delta, ctx, dial);
   drawI(theta + 0.20*dial.delta, ctx, dial); }
if(Hrs == 15) { drawI(theta - 0.40*dial.delta, ctx, dial);
   drawI(theta, ctx, dial);
   drawI(theta + 0.40*dial.delta, ctx, dial); }
if(Hrs == 16) { drawI(theta - 0.40*dial.delta, ctx, dial);
   drawV(theta + 0.25*dial.delta, ctx, dial); }
if(Hrs == 17) { drawV(theta, ctx, dial); }
if(Hrs == 18) { drawV(theta - 0.25*dial.delta, ctx, dial);
   drawI(theta + 0.65*dial.delta, ctx, dial); }
if(Hrs == 19) { drawV(theta - 0.50*dial.delta, ctx, dial);
   drawI(theta + 0.70*dial.delta, ctx, dial);
   drawI(theta + 1.40*dial.delta, ctx, dial); } }
}
 
function drawI(eta, ctx, dial) {
// draw numeral I
var ringOut = dial.ring2 - dial.glabel;
var ringIn = dial.ring3 + dial.glabel;
// find radius to outer ring using eta is hour angle
var ro = calculateERadius(ringOut, eta, dial);
var xo = ro * Math.sin(eta) + dial.gwside;
var yo = ro * Math.cos(eta) + dial.goffset;
// find radius to inner ring using eta is hour angle
var ri = calculateERadius(ringIn, eta, dial);
var xi = ri * Math.sin(eta) + dial.gwside;
var yi = ri * Math.cos(eta) + dial.goffset;
ctx.beginPath(); ctx.moveTo(dial.cxo + xo, dial.cyo - yo); ctx.lineTo(dial.cxo + xi, dial.cyo - yi); ctx.stroke();
}
 
function drawV(eta, ctx, dial) {
// draw numeral V
var ringOut = dial.ring2 - dial.glabel;
var ringIn = dial.ring3 + dial.glabel;
// find radius to inner ring using eta is hour angle
var ringo = calculateERadius(ringIn, eta, dial);
// determine scaled angle step
var delta = 0.4*dial.delta*ringIn/ringo;
// find radius to outer ring using adjusted hour angle
phi = eta + delta;
var ro = calculateERadius(ringOut, phi, dial) ;
var xo = ro * Math.sin(phi) + dial.gwside;
var yo = ro * Math.cos(phi) + dial.goffset;
// find radius to inner ring using adjusted hour angle
phi = eta;
var ri = calculateERadius( ringIn, phi, dial);
var xi = ri * Math.sin(phi) + dial.gwside;
var yi = ri * Math.cos(phi) + dial.goffset;
// find radius to outer ring using theta is hour angle
phi = eta - delta;
var ro = calculateERadius(ringOut, phi, dial);
var x3 = ro * Math.sin(phi) + dial.gwside;
var y3 = ro * Math.cos(phi) + dial.goffset; ctx.beginPath();
ctx.moveTo(dial.cxo + xo, dial.cyo - yo); ctx.lineTo(dial.cxo + xi, dial.cyo - yi);
ctx.lineTo(dial.cxo + x3, dial.cyo - y3); ctx.stroke();
} 
 
function drawX(eta,ctx, dial) {
// draw numeral X
var ringOut = dial.ring2 - dial.glabel;
var ringIn = dial.ring3 + dial.glabel;
// find radius to inner ring using eta is hour angle
var ringo = calculateERadius(ringIn, eta, dial);
// determine scaled angle step
var delta = 0.4*dial.delta*ringIn/ringo;
// find radius to outer ring using adjusted hour angle var
var phi = eta + delta;
var ro = calculateERadius(ringOut, phi, dial);
var xo = ro * Math.sin(phi) + dial.gwside;
var yo = ro * Math.cos(phi) + dial.goffset;
// find radius to inner ring using adjusted hour angle
phi = eta - delta;
var ri = calculateERadius(ringIn, phi, dial);
var xi = ri * Math.sin(phi) + dial.gwside;
var yi = ri * Math.cos(phi) + dial.goffset;
ctx.beginPath(); ctx.moveTo(dial.cxo + xo, dial.cyo - yo);
ctx.lineTo(dial.cxo + xi, dial.cyo - yi); ctx.stroke();
// find radius to outer ring using adjusted hour angle
phi = eta - delta;
ro = calculateERadius(ringOut, phi, dial);
xo = ro * Math.sin(phi) + dial.gwside;
yo = ro * Math.cos(phi) + dial.goffset;
// find radius to inner ring using adjusted hour angle
phi = eta + delta;
ri = calculateERadius(ringIn, phi, dial);
xi = ri * Math.sin(phi) + dial.gwside;
yi = ri * Math.cos(phi) + dial.goffset;
ctx.beginPath(); ctx.moveTo(dial.cxo + xo, dial.cyo - yo); ctx.lineTo(dial.cxo + xi, dial.cyo - yi); ctx.stroke();
}
 
function drawGnomon(ctx, dial) {
// draw the double-faced gnomon with base tabs
var gxo = dial.gxo;
var gyo = dial.gyo;
var arc = Math.abs(dial.lat);
var slat = Math.sin(ToRad(arc));
var clat = Math.cos(ToRad(arc));
var tlat = Math.tan(ToRad(arc));
var sarc = Math.sin(ToRad(arc));
var carc = Math.cos(ToRad(arc));
var gbase = dial.glength;
var tip = .05*gbase;
var edge = dial.gextend*gbase/clat;
var r1 = dial.glength * (0.08 + 0.03 * slat);
var r2 = 1.75*r1;
// draw style edge
ctx.beginPath(); ctx.moveTo(gxo + edge, gyo);
ctx.lineTo(gxo, gyo); ctx.stroke();
 
// draw base #1 and #2
var x1 = gxo + gbase*clat;
var y1 = gyo - gbase*slat;
var x2 = gxo;
var y2 = gyo;
var x3 = gxo + gbase*clat;
var y3 = gyo + gbase*slat;
ctx.beginPath(); ctx.moveTo(x1,y1); ctx.lineTo(x2,y2);
ctx.lineTo(x3,y3); ctx.stroke();
 
// draw tab #1
x1 = gxo - dial.tab*slat;
y1 = gyo - dial.tab*clat;
x2 = x1 + gbase*clat;
y2 = y1 - gbase*slat;
x3 = x2 + dial.tab*slat;
y3 = y2 + dial.tab*clat;
ctx.beginPath(); ctx.moveTo(gxo,gyo); ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2); ctx.lineTo(x3, y3); ctx.stroke();
 
// draw tab #2
x1 = gxo - dial.tab*slat;
y1 = gyo + dial.tab*clat;
x2 = x1 + gbase*clat;
y2 = y1 + gbase*slat;
x3 = x2 + dial.tab*slat;
y3 = y2 - dial.tab*clat;
ctx.beginPath(); ctx.moveTo(gxo,gyo); ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2); ctx.lineTo(x3, y3); ctx.stroke();
 
// draw tip on edge
x1 = gxo + edge;
y1 = gyo - tip;
x2 = gxo + edge;
y2 = gyo + tip; ctx.beginPath();
ctx.moveTo(x1, y1); ctx.lineTo(x1, y2); ctx.stroke();
 
// draw arcs at edge
x1 = gxo + edge - r1;
y1 = gyo - tip;
x2 = gxo + edge - r1;
y2 = gyo + tip;
ctx.beginPath(); ctx.arc(x1,y1,r1, ToRad(0), ToRad(-90), true);ctx.stroke();
ctx.beginPath(); ctx.arc(x2,y2,r1, ToRad(0), ToRad( 90), false); ctx.stroke();
 
// draw arc on base #1
x1 = gxo + (gbase-r2)*clat;
y1 = gyo - (gbase-r2)*slat;
ctx.beginPath(); ctx.arc(x1,y1,r2,ToRad(90-arc),ToRad(-arc), true); ctx.stroke();
 
// draw Bezier Curve #1
x1 = gxo + (gbase-r2)*clat + r2*slat;
y1 = gyo - (gbase-r2)*slat + r2*clat;
x2 = gxo + 1.1*r2/slat + (tip+r1)/tlat;
y2 = gyo - (tip + r1); x3 = gxo + edge - r1;
y3 = gyo - (tip + r1);
ctx.stroke(); ctx.beginPath(); ctx.bezierCurveTo(x1,y1,x2,y2,x3,y3); ctx.stroke();
 
// draw arc on base #2
x1 = gxo + (gbase-r2)*clat;
y1 = gyo + (gbase-r2)*slat;
ctx.beginPath(); ctx.arc(x1,y1,r2,ToRad(arc-90),ToRad(arc), false); ctx.stroke();
 
// draw Bezier Curve #2
x1 = gxo + (gbase-r2)*clat + r2*slat;
y1 = gyo + (gbase-r2)*slat - r2*clat;
x2 = gxo + 1.1*r2/slat + (tip+r1)/tlat;
y2 = gyo + (tip + r1);
x3 = gxo + edge - r1;
y3 = gyo + (tip + r1);
ctx.beginPath(); ctx.bezierCurveTo(x1,y1,x2,y2,x3,y3); ctx.stroke();
}
</script>
{/source}

This page requires a browser that supports JAVASCRIPT and CANVAS such as Firefox, Chrome, and Safari. Early versions of Internet Explorer (IE) do not support canvas. Make sure that your browser has JAVASCRIPT enabled. For example in Firefox use Tools/Options/Content and check Enable Javscript.

To draw your sundial below, convert your latitude into a decimal number for entry. Example: 38° 45' N would be entered as 38.75  Then click the draw button to see the resulting angles for your dial.

When your sundial is drawn, print out the webpage and paste the dial face onto thin cardboard such as from an old cereal box.  Fold the gnomon in half and paste it together.  Be careful to first fold out the bottom tabs.  Apply paste to the tabs and stick onto the base.  Make sure that the high end of the gnomon face toward noon ( XII ).

Enter Your Latitude: