{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.
