From here: http://raphaeljs.com/polar-clock.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Raphaël · Polar Clock</title>
<link rel="stylesheet" href="demo.css" media="screen">
<link rel="stylesheet" href="demo-print.css" media="print">
<script src="raphael.js"></script>
<script>
window.onload = function () {
var r = Raphael("holder", 600, 600),
R = 200,
init = true,
param = {stroke: "#fff", "stroke-width": 30},
hash = document.location.hash,
marksAttr = {fill: hash || "#444", stroke: "none"},
html = [
document.getElementById("h"),
document.getElementById("m"),
document.getElementById("s"),
document.getElementById("d"),
document.getElementById("mnth"),
document.getElementById("ampm")
];
// Custom Attribute
r.customAttributes.arc = function (value, total, R) {
var alpha = 360 / total * value,
a = (90 - alpha) * Math.PI / 180,
x = 300 + R * Math.cos(a),
y = 300 - R * Math.sin(a),
color = "hsb(".concat(Math.round(R) / 200, ",", value / total, ", .75)"),
path;
if (total == value) {
path = [["M", 300, 300 - R], ["A", R, R, 0, 1, 1, 299.99, 300 - R]];
} else {
path = [["M", 300, 300 - R], ["A", R, R, 0, +(alpha > 180), 1, x, y]];
}
return {path: path, stroke: color};
};
drawMarks(R, 60);
var sec = r.path().attr(param).attr({arc: [0, 60, R]});
R -= 40;
drawMarks(R, 60);
var min = r.path().attr(param).attr({arc: [0, 60, R]});
R -= 40;
drawMarks(R, 12);
var hor = r.path().attr(param).attr({arc: [0, 12, R]});
R -= 40;
drawMarks(R, 31);
var day = r.path().attr(param).attr({arc: [0, 31, R]});
R -= 40;
drawMarks(R, 12);
var mon = r.path().attr(param).attr({arc: [0, 12, R]});
var pm = r.circle(300, 300, 16).attr({stroke: "none", fill: Raphael.hsb2rgb(15 / 200, 1, .75).hex});
html[5].style.color = Raphael.hsb2rgb(15 / 200, 1, .75).hex;
function updateVal(value, total, R, hand, id) {
if (total == 31) { // month
var d = new Date;
d.setDate(1);
d.setMonth(d.getMonth() + 1);
d.setDate(-1);
total = d.getDate();
}
var color = "hsb(".concat(Math.round(R) / 200, ",", value / total, ", .75)");
if (init) {
hand.animate({arc: [value, total, R]}, 900, ">");
} else {
if (!value || value == total) {
value = total;
hand.animate({arc: [value, total, R]}, 750, "bounce", function () {
hand.attr({arc: [0, total, R]});
});
} else {
hand.animate({arc: [value, total, R]}, 750, "elastic");
}
}
html[id].innerHTML = (value < 10 ? "0" : "") + value;
html[id].style.color = Raphael.getRGB(color).hex;
}
function drawMarks(R, total) {
if (total == 31) { // month
var d = new Date;
d.setDate(1);
d.setMonth(d.getMonth() + 1);
d.setDate(-1);
total = d.getDate();
}
var color = "hsb(".concat(Math.round(R) / 200, ", 1, .75)"),
out = r.set();
for (var value = 0; value < total; value++) {
var alpha = 360 / total * value,
a = (90 - alpha) * Math.PI / 180,
x = 300 + R * Math.cos(a),
y = 300 - R * Math.sin(a);
out.push(r.circle(x, y, 2).attr(marksAttr));
}
return out;
}
(function () {
var d = new Date,
am = (d.getHours() < 12),
h = d.getHours() % 12 || 12;
updateVal(d.getSeconds(), 60, 200, sec, 2);
updateVal(d.getMinutes(), 60, 160, min, 1);
updateVal(h, 12, 120, hor, 0);
updateVal(d.getDate(), 31, 80, day, 3);
updateVal(d.getMonth() + 1, 12, 40, mon, 4);
pm[(am ? "hide" : "show")]();
html[5].innerHTML = am ? "AM" : "PM";
setTimeout(arguments.callee, 1000);
init = false;
})();
};
</script>
<style media="screen">
#holder {
height: 600px;
margin: -300px 0 0 -300px;
width: 600px;
}
#time {
text-align: center;
font: 100 3em "Helvetica Neue", Helvetica, Arial, sans-serif;
}
</style>
</head>
<body>
<div id="holder"></div>
<div id="time">
<span id="h"></span>:<span id="m"></span>:<span id="s"></span> <span id="ampm"></span> · <span id="d"></span>/<span id="mnth"></span>
</div>
<p id="copy">Demo of Raphaël—JavaScript Vector Library</p>
</body>
</html>
How would I go about getting only the minutes and turn it counter clockwise?
Here is a little image of what I'm taking about: http://i.imgur.com/Pvmkvs7.png
Would there also be a way to edit he color easily or edit the size with CSS?
To begin with, download everything so that you can edit it. You'll need the html source, the stylesheets, and the script file (raphael.js). Then we can get to work.
Let's take each issue one by one.
To flip the clock, we could either edit the polar math in the onload function (which makes us have to think), or, we could use the css3 transform property (and it's browser specific aliases) with a scaleX. I find the latter to be much more approachable and intuitive.
#holder svg //apply to any elements of type svg within the element with the id 'holder'
{
transform:scaleX(-1);
-webkit-transform:scaleX(-1);//for chrome and safari
-ms-transform:scaleX(-1);//IE 9
-ms-filter: "FlipH";//IE 8 (not sure if it also applies to 7)
}
Next, you want to display only the minutes. For this, we need to delete the code that draws the other arcs. There are two places that reference these arcs; once each in the onload (i.e. "var sec = r.path().blahblahblah") and once during the update (the calls to updateval()). Comment out all of these lines except for the ones referring to min, since that's the minutes arc that you want to keep. Then test and see if it works.
If you want to get rid of the little dots around the circle, you can also comment out every call to drawmarks. Your choice; while you're testing, it might make sense to not generate those little dots. They make scrolling through the svg source a PITA.
Hope that helps!
Related
I'm looking for a very basic implementation of the Z-buffer, ideally in JS. I am trying to take a look at a very simple code, for example two polygons overlapping, one hiding the other.
I can't find such basic example, while I can find a couple of "well-above my current level and understanding" samples.
Is there a resource you could recommend to get started?
Thank you for your help and recommendations!
A Z-Buffer(also known as depth buffer) is nothing more than a 2D pixel array(think image). Instead of RGB it only stores a single value in each pixel, the distance from the current viewpoint:
// one value for each pixel in our screen
const depthBuffer = new Array(screenWidth * screenHeight);
It augments the color buffer that contains the actual image you present to the user:
// create buffer for color output
const numChannels = 3; // R G B
const colorBuffer = new Array(screenWidth * screenHeight * numChannels);
For every pixel of a shape you draw you check the Z-Buffer to see if there's anything closer to the camera that occludes the current pixel, if so you don't draw it. This way you can draw things in any order and they're still properly occluded on a per pixel level.
Z-Buffering may not only be used in 3D but also in 2D to achieve draw order-independence. Lets say we want to draw a few boxes, this will be our box class:
class Box {
/** #member {Object} position of the box storing x,y,z coordinates */
position;
/** #member {Object} size of the box storing width and height */
size;
/** #member {Object} color of the box given in RGB */
color;
constructor (props) {
this.position = props.position;
this.size = props.size;
this.color = props.color;
}
/**
* Check if given point is in box
* #param {Number} px coordinate of the point
* #param {Number} py coordinate of the point
* #return {Boolean} point in box
*/
pointInBox (px,py) {
return this.position.x < px && this.position.x + this.size.width > px
&& this.position.y < py && this.position.y + this.size.height > py;
}
}
With this class we can now create a few boxes and draw them:
const boxes = [
new Box({
position: { x: 50, y: 50, z: 10 },
size: { width: 50, height: 20 },
color: { r: 255, g: 0, b:0 }
}),
// green box
new Box({
position: { x: 80, y: 30, z: 5 },
size: { width: 10, height: 50 },
color: { r: 0, g: 255, b:0 }
}),
// blue
new Box({
position: { x: 60, y: 55, z: 8 },
size: { width: 50, height: 10 },
color: { r: 0, g: 0, b: 255 }
})
];
With our shapes specified we can now draw them:
for(const box of boxes) {
for(let x = 0; x < screenWidth; x++) {
for(let y = 0; y < screenHeight; y++) {
// check if our pixel is within the box
if (box.pointInBox(x,y)) {
// check if this pixel of our box is covered by something else
// compare depth value in depthbuffer against box position
// this is commonly referred to as "depth-test"
if (depthBuffer[x + y * screenWidth] < box.position.z) {
// something is already closer to the viewpoint than our current primitive, don't draw this pixel:
continue;
}
// we passed the depth test, put our current depth value in the z-buffer
depthBuffer[x + y * screenWidth] = box.position.z;
// put the color in the color buffer, channel by channel
colorBuffer[(x + y * screenWidth)*numChannels + 0] = box.color.r;
colorBuffer[(x + y * screenWidth)*numChannels + 1] = box.color.g;
colorBuffer[(x + y * screenWidth)*numChannels + 2] = box.color.b;
}
}
}
}
Note that this code is exemplary so it's overly verbose and inefficient for the sake of laying out the concept.
const ctx = document.getElementById("output").getContext('2d');
const screenWidth = 200;
const screenHeight = 200;
// one value for each pixel in our screen
const depthBuffer = new Array(screenWidth * screenHeight);
// create buffer for color output
const numChannels = 3; // R G B
const colorBuffer = new Array(screenWidth * screenHeight * numChannels);
/**
* Represents a 2D box
* #class
*/
class Box {
/** #member {Object} position of the box storing x,y,z coordinates */
position;
/** #member {Object} size of the box storing width and height */
size;
/** #member {Object} color of the box given in RGB */
color;
constructor (props) {
this.position = props.position;
this.size = props.size;
this.color = props.color;
}
/**
* Check if given point is in box
* #param {Number} px coordinate of the point
* #param {Number} py coordinate of the point
* #return {Boolean} point in box
*/
pointInBox (px,py) {
return this.position.x < px && this.position.x + this.size.width > px
&& this.position.y < py && this.position.y + this.size.height > py;
}
}
const boxes = [
// red box
new Box({
position: { x: 50, y: 50, z: 10 },
size: { width: 150, height: 50 },
color: { r: 255, g: 0, b:0 }
}),
// green box
new Box({
position: { x: 80, y: 30, z: 5 },
size: { width: 10, height: 150 },
color: { r: 0, g: 255, b:0 }
}),
// blue
new Box({
position: { x: 70, y: 70, z: 8 },
size: { width: 50, height: 40 },
color: { r: 0, g: 0, b: 255 }
})
];
const varyZ = document.getElementById('varyz');
varyZ.onchange = draw;
function draw () {
// clear depth buffer of previous frame
depthBuffer.fill(10);
for(const box of boxes) {
for(let x = 0; x < screenWidth; x++) {
for(let y = 0; y < screenHeight; y++) {
// check if our pixel is within the box
if (box.pointInBox(x,y)) {
// check if this pixel of our box is covered by something else
// compare depth value in depthbuffer against box position
if (depthBuffer[x + y * screenWidth] < box.position.z) {
// something is already closer to the viewpoint that our current primitive, don't draw this pixel:
if (!varyZ.checked) continue;
if (depthBuffer[x + y * screenWidth] < box.position.z + Math.sin((x+y))*Math.cos(x)*5) continue;
}
// we passed the depth test, put our current depth value in the z-buffer
depthBuffer[x + y * screenWidth] = box.position.z;
// put the color in the color buffer, channel by channel
colorBuffer[(x + y * screenWidth)*numChannels + 0] = box.color.r;
colorBuffer[(x + y * screenWidth)*numChannels + 1] = box.color.g;
colorBuffer[(x + y * screenWidth)*numChannels + 2] = box.color.b;
}
}
}
}
// convert to rgba for presentation
const oBuffer = new Uint8ClampedArray(screenWidth*screenHeight*4);
for (let i=0,o=0; i < colorBuffer.length; i+=3,o+=4) {
oBuffer[o]=colorBuffer[i];
oBuffer[o+1]=colorBuffer[i+1];
oBuffer[o+2]=colorBuffer[i+2];
oBuffer[o+3]=255;
}
ctx.putImageData(new ImageData(oBuffer, screenWidth, screenHeight),0,0);
}
document.getElementById('redz').oninput = e=>{boxes[0].position.z=parseInt(e.target.value,10);draw()};
document.getElementById('greenz').oninput = e=>{boxes[1].position.z=parseInt(e.target.value,10);draw()};
document.getElementById('bluez').oninput = e=>{boxes[2].position.z=parseInt(e.target.value,10);draw()};
draw();
canvas {
border:1px solid black;
float:left;
margin-right: 2rem;
}
label {display:block;}
label span {
display:inline-block;
width: 100px;
}
<canvas width="200" height="200" id="output"></canvas>
<label><span>Red Z</span>
<input type="range" min="0" max="10" value="10" id="redz"/>
</label>
<label><span>Green Z</span>
<input type="range" min="0" max="10" value="5" id="greenz"/>
</label>
<label><span>Blue Z</span>
<input type="range" min="0" max="10" value="8" id="bluez"/>
</label>
<label><span>Vary Z Per Pixel</span>
<input type="checkbox" id="varyz"/>
</label>
I want to create the array below with a for loop as its large
var centres = {
1979: { x: width * 1 / 41, y: height / 2 },
1980: { x: width * 2 / 41, y: height / 2 },
1981: { x: width * 3 / 41, y: height / 2 },
...
}
and then access it as follows:
function nodeYearPos(d) {
return yearCenters[d.year].x;
}
I have the following code, but its only setting the year...
var yearCenters = Array.from(new Array(2020-1919+1), (x, i) => i + 1919);
for (year = 1919; year <= 2020; year++) {
coords = getCentres(year); // this returns an object in the form {x : x, y : y}
yearCenters[year] = coords;
}
you can do as gorak commented but with the getCenters function
var yearCenters = Object.fromEntries(Array.from(new Array(2020-1919+1), (x, i) => [i + 1919, getCenters(i + 1919)]));
or you can also try
var yearCenters = {};
for (year = 1919; year <= 2020; year++) {
coords = getCenters(year);
yearCenters[year] = coords;
}
When you try to fetch by year in yearCenters array (e.g. yearCenters[year]) this won't work since the year is not the index in the array.
I would suggest you first convert the array into a JS object so that indexing on it works with years.
See below snippet -
// Create obejct from array
var yearCenters = Object.fromEntries(Array.from(new Array(2020-1919+1), (x, i) => [i + 1919, null]))
// This loop remains same
for (year = 1919; year <= 2020; year++) {
coords = getCentres(year); // this returns an object in the form {x : x, y : y}
yearCenters[year] = coords;
}
// Mock function
function getCentres(year) {
return {
x: Math.random() * 100,
y: Math.random() * 100
}
}
console.log(yearCenters)
I have a slider that has the following raw snap points:
[-100, -200, -300, -400, -500, -600]
And I would like to convert the sliding value to match the following snap points:
[0, 5, 10, 25, 50, 100]
A raw value in [-100, -200) should be mapped to a value in [0, 5)
A raw value in [-200, -300) should be mapped to a value in [5, 10)
A raw value in [-300, -400) should be mapped to a value in [10, 25)
And so on ..
How can I achieve that?
Edit: added my attempt (different raw values though)
// sliderValue is an integer obtained from the slider
const base = -70
const offset = -80
const limits = [
base + offset * 0, // -70
base + offset * 1, // -150
base + offset * 2, // -230
base + offset * 3, // -310
base + offset * 4, // -390
base + offset * 5, // -470
]
const points = [0, 5, 10, 25, 50, 100]
// I can't even begin to make sense of this
// don't know I came up with it, but it works ¯\_(ツ)_/¯
if (sliderValue <= limits[4]) {
percentage = scaleValue(sliderValue, limits[4], limits[5], 50, 100)
} else if (sliderValue <= limits[3]) {
percentage = scaleValue(sliderValue, limits[3], limits[4], 25, 50)
} else if (sliderValue <= limits[2]) {
percentage = scaleValue(sliderValue, limits[2], limits[3], 10, 25)
} else if (sliderValue <= limits[1]) {
percentage = scaleValue(sliderValue, limits[1], limits[2], 5, 10)
} else if (sliderValue <= limits[0]) {
percentage = scaleValue(sliderValue, limits[0], limits[1], 0, 5)
}
console.log(percentage)
// ..
function scaleValue(num, in_min, in_max, out_min, out_max) {
return ((num - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min
}
You could take a function with a look up for the section. Then build the new value, based on the four values as a linear function.
function getValue(x) {
var a = [-100, -200, -300, -400, -500, -600],
b = [0, 5, 10, 25, 50, 100],
i = a.findIndex((v, i, a) => v >= x && x >= a[i + 1]);
return [x, (x -a[i])* (b[i + 1] - b[i]) / (a[i + 1] - a[i]) +b[i]].join(' ');
}
console.log([-100, -150, -200, -250, -300, -350, -400, -450, -500, -550, -600].map(getValue));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Simple linear equation: add 100, divide by 20, then negate.
UPDATE: Due to early-morning eye bleariness, I misread the question. (Sorry!) The general method for mapping linear relations to each other is to figure out the offset of the two sets and the scale factor.
I can't find a smooth relationship between the example points you gave, so I'm not sure how to find a single equation that would neatly and continuously map the points to each other. It looks like your solution (you said it works) might be the best: figure out which range each value maps to, and scale correspondingly.
You can just map the values:
var mapping = {
"-100": 0,
"-200": 5,
"-300": 10,
"-400": 25,
"-500": 50,
"-600": 100
}
function map_values(array){
return [mapping[array[0]], mapping[array[1]]];
}
var input = [-200,-300];
console.log(map_values(input));
I have two vectors, the Y-aligned is fixed whereby the X-aligned is allowed to rotate. These vectors are connected together through two fixed-length segments. Given the angle between the two vectors (82.74) and the length of all segments, how can I get the angle of the two jointed segments (24.62 and 22.61)?
What is given: the magnitude of the vectors, and the angle between the X-axis and OG:
var magOG = 3,
magOE = 4,
magGH = 3,
magEH = 2,
angleGamma = 90;
This is my starting point: angleGamma = 90 - then, I will have following vectors:
var vOG = new vec2(-3,0),
vOE = new vec2(0,-4);
From here on, I am trying to get angleAlphaand angleBeta for values of angleGamma less than 90 degrees.
MAGNITUDE OF THE CONSTRAINED SEGMENTS:
Segments HG and HE must meet following conditions:
/
| OG*OG+ OE*OE = (HG + HE)*(HG + HE)
>
| OG - HG = OE - HE
\
which will lead to following two solutions (as pointed out in the accepted answer - bilateration):
Solution 1:
========================================================
HG = 0.5*(-Math.sqrt(OG*OG + OE*OE) + OG - OE)
HE = 0.5*(-Math.sqrt(OG*OG + OE*OE) - OG + OE)
Solution 2:
========================================================
HG = 0.5*(Math.sqrt(OG*OG + OE*OE) + OG - OE)
HE = 0.5*(Math.sqrt(OG*OG + OE*OE) - OG + OE)
SCRATCHPAD:
Here is a playground with the complete solution. The visualization library used here is the great JSXGraph. Thanks to the Center for Mobile Learning with Digital Technology of the Bayreuth University.
Credits for the circle intersection function: 01AutoMonkey in the accepted answer to this question: A JavaScript function that returns the x,y points of intersection between two circles?
function deg2rad(deg) {
return deg * Math.PI / 180;
}
function rad2deg(rad) {
return rad * 180 / Math.PI;
}
function lessThanEpsilon(x) {
return (Math.abs(x) < 0.00000000001);
}
function angleBetween(point1, point2) {
var x1 = point1.X(), y1 = point1.Y(), x2 = point2.X(), y2 = point2.Y();
var dy = y2 - y1, dx = x2 - x1;
var t = -Math.atan2(dx, dy); /* range (PI, -PI] */
return rad2deg(t); /* range (180, -180] */
}
function circleIntersection(circle1, circle2) {
var r1 = circle1.radius, cx1 = circle1.center.X(), cy1 = circle1.center.Y();
var r2 = circle2.radius, cx2 = circle2.center.X(), cy2 = circle2.center.Y();
var a, dx, dy, d, h, h2, rx, ry, x2, y2;
/* dx and dy are the vertical and horizontal distances between the circle centers. */
dx = cx2 - cx1;
dy = cy2 - cy1;
/* angle between circle centers */
var theta = Math.atan2(dy,dx);
/* vertical and horizontal components of the line connecting the circle centers */
var xs1 = r1*Math.cos(theta), ys1 = r1*Math.sin(theta), xs2 = r2*Math.cos(theta), ys2 = r2*Math.sin(theta);
/* intersection points of the line connecting the circle centers */
var sxA = cx1 + xs1, syA = cy1 + ys1, sxL = cx2 - xs2, syL = cy2 - ys2;
/* Determine the straight-line distance between the centers. */
d = Math.sqrt((dy*dy) + (dx*dx));
/* Check for solvability. */
if (d > (r1 + r2)) {
/* no solution. circles do not intersect. */
return [[sxA,syA], [sxL,syL]];
}
thetaA = -Math.PI - Math.atan2(cx1,cy1); /* Swap X-Y and re-orient to -Y */
xA = +r1*Math.sin(thetaA);
yA = -r1*Math.cos(thetaA);
ixA = cx1 - xA;
iyA = cy1 - yA;
thetaL = Math.atan(cx2/cy2);
xL = -r2*Math.sin(thetaL);
yL = -r2*Math.cos(thetaL);
ixL = cx2 - xL;
iyL = cy2 - yL;
if(d === 0 && r1 === r2) {
/* infinite solutions. circles are overlapping */
return [[ixA,iyA], [ixL,iyL]];
}
if (d < Math.abs(r1 - r2)) {
/* no solution. one circle is contained in the other */
return [[ixA,iyA], [ixL,iyL]];
}
/* 'point 2' is the point where the line through the circle intersection points crosses the line between the circle centers. */
/* Determine the distance from point 0 to point 2. */
a = ((r1*r1) - (r2*r2) + (d*d)) / (2.0 * d);
/* Determine the coordinates of point 2. */
x2 = cx1 + (dx * a/d);
y2 = cy1 + (dy * a/d);
/* Determine the distance from point 2 to either of the intersection points. */
h2 = r1*r1 - a*a;
h = lessThanEpsilon(h2) ? 0 : Math.sqrt(h2);
/* Now determine the offsets of the intersection points from point 2. */
rx = -dy * (h/d);
ry = +dx * (h/d);
/* Determine the absolute intersection points. */
var xi = x2 + rx, yi = y2 + ry;
var xi_prime = x2 - rx, yi_prime = y2 - ry;
return [[xi, yi], [xi_prime, yi_prime]];
}
function plot() {
var cases = [
{a: 1.1, l: 1.9, f: 0.3073},
{a: 1.0, l: 1.7, f: 0.3229}
];
var testCase = 1;
var magA = cases[testCase].a, magL = cases[testCase].l;
var maxS = Math.sqrt(magA*magA+magL*magL), magS1 = maxS * cases[testCase].f, magS2 = maxS - magS1;
var origin = [0,0], board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-5.0, 5.0, 5.0, -5.0], axis: true});
var drawAs = {dashed: {dash: 3, strokeWidth: 0.5, strokeColor: '#888888'} };
board.suspendUpdate();
var leftArm = board.create('slider', [[-4.5, 3], [-1.5, 3], [0, -64, -180]]);
var leftLeg = board.create('slider', [[-4.5, 2], [-1.5, 2], [0, -12, -30]]);
var rightArm = board.create('slider', [[0.5, 3], [3.5, 3], [0, 64, 180]]);
var rightLeg = board.create('slider', [[0.5, 2], [3.5, 2], [0, 12, 30]]);
var lh = board.create('point', [
function() { return +magA * Math.sin(deg2rad(leftArm.Value())); },
function() { return -magA * Math.cos(deg2rad(leftArm.Value())); }
], {size: 3, name: 'lh'});
var LA = board.create('line', [origin, lh], {straightFirst: false, straightLast: false, lastArrow: true});
var cLS1 = board.create('circle', [function() { return [lh.X(), lh.Y()]; }, function() { return magS1; }], drawAs.dashed);
var lf = board.create('point', [
function() { return +magL * Math.sin(deg2rad(leftLeg.Value())); },
function() { return -magL * Math.cos(deg2rad(leftLeg.Value())); }
], {size: 3, name: 'lf'});
var LL = board.create('line', [origin, lf], {straightFirst: false, straightLast: false, lastArrow: true});
var cLS2 = board.create('circle', [function() { return [lf.X(), lf.Y()]; }, function() { return magS2; }], drawAs.dashed);
var lx1 = board.create('point', [
function() { return circleIntersection(cLS1, cLS2)[0][0]; },
function() { return circleIntersection(cLS1, cLS2)[0][1]; }
], {size: 3, face:'x', name: 'lx1'});
var lx2 = board.create('point', [
function() { return circleIntersection(cLS1, cLS2)[1][0]; },
function() { return circleIntersection(cLS1, cLS2)[1][1]; }
], {size: 3, face:'x', name: 'lx2'});
/* Angle between lh, lx1 shall be between 0 and -180 */
var angleLAJ = board.create('text', [-3.7, 0.5, function(){ return angleBetween(lh, lx1).toFixed(2); }]);
/* Angle between lf, lx1 shall be between 0 and 180 */
var angleLLJ = board.create('text', [-2.7, 0.5, function(){ return angleBetween(lf, lx1).toFixed(2); }]);
var rh = board.create('point', [
function() { return +magA * Math.sin(deg2rad(rightArm.Value())); },
function() { return -magA * Math.cos(deg2rad(rightArm.Value())); }
], {size: 3, name: 'rh'});
var RA = board.create('line', [origin, rh], {straightFirst: false, straightLast: false, lastArrow: true});
var cRS1 = board.create('circle', [function() { return [rh.X(), rh.Y()]; }, function() { return magS1; }], drawAs.dashed);
var rf = board.create('point', [
function() { return +magL * Math.sin(deg2rad(rightLeg.Value())); },
function() { return -magL * Math.cos(deg2rad(rightLeg.Value())); }
], {size: 3, name: 'rf'});
var RL = board.create('line', [origin, rf], {straightFirst: false, straightLast: false, lastArrow: true});
var cRS2 = board.create('circle', [function() { return [rf.X(), rf.Y()]; }, function() { return magS2; }], drawAs.dashed);
var rx1 = board.create('point', [
function() { return circleIntersection(cRS1, cRS2)[1][0]; },
function() { return circleIntersection(cRS1, cRS2)[1][1]; }
], {size: 3, face:'x', name: 'rx1'});
var rx2 = board.create('point', [
function() { return circleIntersection(cRS1, cRS2)[0][0]; },
function() { return circleIntersection(cRS1, cRS2)[0][1]; }
], {size: 3, face:'x', name: 'rx2'});
var angleRAJ = board.create('text', [+1.3, 0.5, function(){ return angleBetween(rh, rx1).toFixed(2); }]);
var angleRLJ = board.create('text', [+2.3, 0.5, function(){ return angleBetween(rf, rx1).toFixed(2); }]);
board.unsuspendUpdate();
}
plot();
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/jsxgraph/0.99.7/jsxgraph.css" />
<link rel="stylesheet" href="style.css">
<script type="text/javascript" charset="UTF-8" src="//cdnjs.cloudflare.com/ajax/libs/jsxgraph/0.99.7/jsxgraphcore.js"></script>
</head>
<body>
<div id="jxgbox" class="jxgbox" style="width:580px; height:580px;"></div>
</body>
</html>
According to your sketch, the coordinates of E and G are:
E = (0, -magOE)
G = magOG * ( -sin(gamma), -cos(gamma) )
Then, calculating the position of H is a trilateration problem. Actually, it is just bilateration because you are missing a third distance. Hence, you will get two possible positions for H.
First, let us define a new coordinate system, where E lies at the origin and G lies on the x-axis. The x-axis direction in our original coordinate system is then:
x = (G - E) / ||G - E||
The y-axis is:
y = ( x.y, -x.x )
The coordinates of E and G in this new coordinate system are:
E* = (0, 0)
G* = (0, ||G - E||)
Now, we can easily find the coordinates of H in this coordinate system, up to the ambiguity mentioned earlier. I will abbreviate ||G - E|| = d like in the notation used in the Wikipedia article:
H.x* = (magGH * magGH - magEH * magEH + d * d) / (2 * d)
H.y* = +- sqrt(magGH * magGH - H.x* * H.x*)
Hence, we have two solutions for H.y, one positive and one negative.
Finally, we just need to transform H back into our original coordinate system:
H = x * H.x* + y * H.y* - (0, magOE)
Given the coordinates of H, calculating the angles is pretty straightforward:
alpha = arccos((H.x - G.x) / ||H - G||)
beta = arccos((H.y - E.y) / ||H - E||)
Example
Taking the values from your example
magOG = 3
magOE = 4
magGH = 3
magEH = 2
angleGamma = 82.74°
we first get:
E = (0, -4)
G = 3 * ( -sin(82.74°), -cos(82.74°) )
= (-2.976, -0.379)
Our coordinate system:
x = (-0.635, 0.773)
y = ( 0.773, 0.635)
In this coordinate system:
E* = (0, 0)
G* = (0, 4.687)
Then, the coordinates of H in our auxiliary coordinate system are:
H* = (2.877, +- 0.851)
I will only focus on the positive value for H*.y because this is the point that you marked in your sketch.
Transform back to original coordinate system:
H = (-1.169, -1.237)
And finally calculate the angles:
alpha = 25.41°
beta = 22.94°
The slight differences to your values are probably caused by rounding errors (either in my calculations or in yours).
I found a javascript clock on internet, good to learn an make some test, changing the skin, size, etc.
At this point, i would like to know the way to change the skin on hover (regular for black on the sample). This is too much for my primitive knowledge ))))
Some help? Thanks
Codepen samp
/**
* CoolClock 2.1.4
* Copyright 2010, Simon Baird
* Released under the BSD License.
*
* Display an analog clock using canvas.
* http://randomibis.com/coolclock/
*
*/
// Constructor for CoolClock objects
window.CoolClock = function(options) {
return this.init(options);
}
CoolClock.config = {
tickDelay: 1000,
longTickDelay: 15000,
defaultRadius: 85,
renderRadius: 100,
showSecs: true,
showAmPm: true,
skins:{
regular: {
outerBorder: { lineWidth: 6, radius:90, color: "orange", alpha: 1 },
smallIndicator: { lineWidth: 2, startAt: 80, endAt: 93, color: "orange", alpha: 1 },
largeIndicator: { lineWidth: 6, startAt: 70, endAt: 93, color: "orange", alpha: 1 },
hourHand: { lineWidth: 8, startAt: -2, endAt: 45, color: "black", alpha: 1 },
minuteHand: { lineWidth: 7, startAt: -1, endAt: 68, color: "black", alpha: 1 },
secondHand: { lineWidth: 1, startAt: -20, endAt: 85, color: "orange", alpha: 1 },
secondDecoration: { lineWidth: 2, startAt: 0, radius: 3, fillColor: "orange", color: "red", alpha: 1 }
},
black: {
outerBorder: { lineWidth: 6, radius:90, color: "black", alpha: 1 },
smallIndicator: { lineWidth: 2, startAt: 80, endAt: 93, color: "black", alpha: 1 },
largeIndicator: { lineWidth: 6, startAt: 70, endAt: 93, color: "black", alpha: 1 },
hourHand: { lineWidth: 8, startAt: -2, endAt: 45, color: "black", alpha: 1 },
minuteHand: { lineWidth: 7, startAt: -1, endAt: 68, color: "black", alpha: 1 },
secondHand: { lineWidth: 1, startAt: -20, endAt: 85, color: "black", alpha: 1 },
secondDecoration: { lineWidth: 2, startAt: 0, radius: 3, fillColor: "black", color: "red", alpha: 1 }
},
},
// Test for IE so we can nurse excanvas in a couple of places
isIE: !!document.all,
// Will store (a reference to) each clock here, indexed by the id of the canvas element
clockTracker: {},
// For giving a unique id to coolclock canvases with no id
noIdCount: 0
};
// Define the CoolClock object's methods
CoolClock.prototype = {
// Initialise using the parameters parsed from the colon delimited class
init: function(options) {
// Parse and store the options
this.canvasId = options.canvasId;
this.skinId = options.skinId || CoolClock.config.defaultSkin;
this.displayRadius = options.displayRadius || CoolClock.config.defaultRadius;
this.showSecondHand = typeof options.showSecondHand == "boolean" ? options.showSecondHand : true;
this.gmtOffset = (options.gmtOffset != null && options.gmtOffset != '') ? parseFloat(options.gmtOffset) : null;
this.showDigital = typeof options.showDigital == "boolean" ? options.showDigital : false;
this.logClock = typeof options.logClock == "boolean" ? options.logClock : false;
this.logClockRev = typeof options.logClock == "boolean" ? options.logClockRev : false;
this.tickDelay = CoolClock.config[ this.showSecondHand ? "tickDelay" : "longTickDelay" ];
// Get the canvas element
this.canvas = document.getElementById(this.canvasId);
// Make the canvas the requested size. It's always square.
this.canvas.setAttribute("width",this.displayRadius*2);
this.canvas.setAttribute("height",this.displayRadius*2);
this.canvas.style.width = this.displayRadius*2 + "px";
this.canvas.style.height = this.displayRadius*2 + "px";
// Explain me please...?
this.renderRadius = CoolClock.config.renderRadius;
this.scale = this.displayRadius / this.renderRadius;
// Initialise canvas context
this.ctx = this.canvas.getContext("2d");
this.ctx.scale(this.scale,this.scale);
// Keep track of this object
CoolClock.config.clockTracker[this.canvasId] = this;
// Start the clock going
this.tick();
return this;
},
// Draw a circle at point x,y with params as defined in skin
fullCircleAt: function(x,y,skin) {
this.ctx.save();
this.ctx.globalAlpha = skin.alpha;
this.ctx.lineWidth = skin.lineWidth;
if (!CoolClock.config.isIE) {
this.ctx.beginPath();
}
if (CoolClock.config.isIE) {
// excanvas doesn't scale line width so we will do it here
this.ctx.lineWidth = this.ctx.lineWidth * this.scale;
}
this.ctx.arc(x, y, skin.radius, 0, 2*Math.PI, false);
if (CoolClock.config.isIE) {
// excanvas doesn't close the circle so let's fill in the tiny gap
this.ctx.arc(x, y, skin.radius, -0.1, 0.1, false);
}
if (skin.fillColor) {
this.ctx.fillStyle = skin.fillColor
this.ctx.fill();
}
else {
// XXX why not stroke and fill
this.ctx.strokeStyle = skin.color;
this.ctx.stroke();
}
this.ctx.restore();
},
// Draw some text centered vertically and horizontally
drawTextAt: function(theText,x,y) {
this.ctx.save();
this.ctx.font = '15px sans-serif';
var tSize = this.ctx.measureText(theText);
if (!tSize.height) tSize.height = 15; // no height in firefox.. :(
this.ctx.fillText(theText,x - tSize.width/2,y - tSize.height/2);
this.ctx.restore();
},
lpad2: function(num) {
return (num < 10 ? '0' : '') + num;
},
tickAngle: function(second) {
// Log algorithm by David Bradshaw
var tweak = 3; // If it's lower the one second mark looks wrong (?)
if (this.logClock) {
return second == 0 ? 0 : (Math.log(second*tweak) / Math.log(60*tweak));
}
else if (this.logClockRev) {
// Flip the seconds then flip the angle (trickiness)
second = (60 - second) % 60;
return 1.0 - (second == 0 ? 0 : (Math.log(second*tweak) / Math.log(60*tweak)));
}
else {
return second/60.0;
}
},
timeText: function(hour,min,sec) {
var c = CoolClock.config;
return '' +
(c.showAmPm ? ((hour%12)==0 ? 12 : (hour%12)) : hour) + ':' +
this.lpad2(min) +
(c.showSecs ? ':' + this.lpad2(sec) : '') +
(c.showAmPm ? (hour < 12 ? ' am' : ' pm') : '')
;
},
// Draw a radial line by rotating then drawing a straight line
// Ha ha, I think I've accidentally used Taus, (see http://tauday.com/)
radialLineAtAngle: function(angleFraction,skin) {
this.ctx.save();
this.ctx.translate(this.renderRadius,this.renderRadius);
this.ctx.rotate(Math.PI * (2.0 * angleFraction - 0.5));
this.ctx.globalAlpha = skin.alpha;
this.ctx.strokeStyle = skin.color;
this.ctx.lineWidth = skin.lineWidth;
if (CoolClock.config.isIE)
// excanvas doesn't scale line width so we will do it here
this.ctx.lineWidth = this.ctx.lineWidth * this.scale;
if (skin.radius) {
this.fullCircleAt(skin.startAt,0,skin)
}
else {
this.ctx.beginPath();
this.ctx.moveTo(skin.startAt,0)
this.ctx.lineTo(skin.endAt,0);
this.ctx.stroke();
}
this.ctx.restore();
},
render: function(hour,min,sec) {
// Get the skin
var skin = CoolClock.config.skins[this.skinId];
if (!skin) skin = CoolClock.config.skins[CoolClock.config.defaultSkin];
// Clear
this.ctx.clearRect(0,0,this.renderRadius*2,this.renderRadius*2);
// Draw the outer edge of the clock
if (skin.outerBorder)
this.fullCircleAt(this.renderRadius,this.renderRadius,skin.outerBorder);
// Draw the tick marks. Every 5th one is a big one
for (var i=0;i<60;i++) {
(i%5) && skin.smallIndicator && this.radialLineAtAngle(this.tickAngle(i),skin.smallIndicator);
!(i%5) && skin.largeIndicator && this.radialLineAtAngle(this.tickAngle(i),skin.largeIndicator);
}
// Write the time
if (this.showDigital) {
this.drawTextAt(
this.timeText(hour,min,sec),
this.renderRadius,
this.renderRadius+this.renderRadius/2
);
}
// Draw the hands
if (skin.hourHand)
this.radialLineAtAngle(this.tickAngle(((hour%12)*5 + min/12.0)),skin.hourHand);
if (skin.minuteHand)
this.radialLineAtAngle(this.tickAngle((min + sec/60.0)),skin.minuteHand);
if (this.showSecondHand && skin.secondHand)
this.radialLineAtAngle(this.tickAngle(sec),skin.secondHand);
// Second hand decoration doesn't render right in IE so lets turn it off
if (!CoolClock.config.isIE && this.showSecondHand && skin.secondDecoration)
this.radialLineAtAngle(this.tickAngle(sec),skin.secondDecoration);
},
// Check the time and display the clock
refreshDisplay: function() {
var now = new Date();
if (this.gmtOffset != null) {
// Use GMT + gmtOffset
var offsetNow = new Date(now.valueOf() + (this.gmtOffset * 1000 * 60 * 60));
this.render(offsetNow.getUTCHours(),offsetNow.getUTCMinutes(),offsetNow.getUTCSeconds());
}
else {
// Use local time
this.render(now.getHours(),now.getMinutes(),now.getSeconds());
}
},
// Set timeout to trigger a tick in the future
nextTick: function() {
setTimeout("CoolClock.config.clockTracker['"+this.canvasId+"'].tick()",this.tickDelay);
},
// Check the canvas element hasn't been removed
stillHere: function() {
return document.getElementById(this.canvasId) != null;
},
// Main tick handler. Refresh the clock then setup the next tick
tick: function() {
if (this.stillHere()) {
this.refreshDisplay()
this.nextTick();
}
}
};
// Find all canvas elements that have the CoolClock class and turns them into clocks
CoolClock.findAndCreateClocks = function() {
// (Let's not use a jQuery selector here so it's easier to use frameworks other than jQuery)
var canvases = document.getElementsByTagName("canvas");
for (var i=0;i<canvases.length;i++) {
// Pull out the fields from the class. Example "CoolClock:chunkySwissOnBlack:1000"
var fields = canvases[i].className.split(" ")[0].split(":");
if (fields[0] == "CoolClock") {
if (!canvases[i].id) {
// If there's no id on this canvas element then give it one
canvases[i].id = '_coolclock_auto_id_' + CoolClock.config.noIdCount++;
}
// Create a clock object for this element
new CoolClock({
canvasId: canvases[i].id,
skinId: fields[1],
displayRadius: fields[2],
showSecondHand: fields[3]!='noSeconds',
gmtOffset: fields[4],
showDigital: fields[5]=='showDigital',
logClock: fields[6]=='logClock',
logClockRev: fields[6]=='logClockRev'
});
}
}
};
if (window.jQuery) jQuery(document).ready(CoolClock.findAndCreateClocks);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="c1" class="CoolClock:regular:125"></canvas>
CoolClock works by specifying classes on the canvas element, so you can change these classes on hover using jQuery:
$(document).ready(function(){
$('#c1').hover(function() {
$(this).toggleClass('CoolClock:regular:125 CoolClock:black:125');
CoolClock.findAndCreateClocks();
}, function() {
$(this).toggleClass('CoolClock:regular:125 CoolClock:black:125');
CoolClock.findAndCreateClocks();
});
});
You'll notice that since there's no refresh function available, the findAndCreateClocks() function must be recalled on hover in and out.
Updated snippet follows:
/**
* CoolClock 2.1.4
* Copyright 2010, Simon Baird
* Released under the BSD License.
*
* Display an analog clock using canvas.
* http://randomibis.com/coolclock/
*
*/
// Constructor for CoolClock objects
window.CoolClock = function(options) {
return this.init(options);
}
CoolClock.config = {
tickDelay: 1000,
longTickDelay: 15000,
defaultRadius: 85,
renderRadius: 100,
showSecs: true,
showAmPm: true,
skins: {
regular: {
outerBorder: {
lineWidth: 6,
radius: 90,
color: "orange",
alpha: 1
},
smallIndicator: {
lineWidth: 2,
startAt: 80,
endAt: 93,
color: "orange",
alpha: 1
},
largeIndicator: {
lineWidth: 6,
startAt: 70,
endAt: 93,
color: "orange",
alpha: 1
},
hourHand: {
lineWidth: 8,
startAt: -2,
endAt: 45,
color: "black",
alpha: 1
},
minuteHand: {
lineWidth: 7,
startAt: -1,
endAt: 68,
color: "black",
alpha: 1
},
secondHand: {
lineWidth: 1,
startAt: -20,
endAt: 85,
color: "orange",
alpha: 1
},
secondDecoration: {
lineWidth: 2,
startAt: 0,
radius: 3,
fillColor: "orange",
color: "red",
alpha: 1
}
},
black: {
outerBorder: {
lineWidth: 6,
radius: 90,
color: "black",
alpha: 1
},
smallIndicator: {
lineWidth: 2,
startAt: 80,
endAt: 93,
color: "black",
alpha: 1
},
largeIndicator: {
lineWidth: 6,
startAt: 70,
endAt: 93,
color: "black",
alpha: 1
},
hourHand: {
lineWidth: 8,
startAt: -2,
endAt: 45,
color: "black",
alpha: 1
},
minuteHand: {
lineWidth: 7,
startAt: -1,
endAt: 68,
color: "black",
alpha: 1
},
secondHand: {
lineWidth: 1,
startAt: -20,
endAt: 85,
color: "black",
alpha: 1
},
secondDecoration: {
lineWidth: 2,
startAt: 0,
radius: 3,
fillColor: "black",
color: "red",
alpha: 1
}
},
},
// Test for IE so we can nurse excanvas in a couple of places
isIE: !!document.all,
// Will store (a reference to) each clock here, indexed by the id of the canvas element
clockTracker: {},
// For giving a unique id to coolclock canvases with no id
noIdCount: 0
};
// Define the CoolClock object's methods
CoolClock.prototype = {
// Initialise using the parameters parsed from the colon delimited class
init: function(options) {
// Parse and store the options
this.canvasId = options.canvasId;
this.skinId = options.skinId || CoolClock.config.defaultSkin;
this.displayRadius = options.displayRadius || CoolClock.config.defaultRadius;
this.showSecondHand = typeof options.showSecondHand == "boolean" ? options.showSecondHand : true;
this.gmtOffset = (options.gmtOffset != null && options.gmtOffset != '') ? parseFloat(options.gmtOffset) : null;
this.showDigital = typeof options.showDigital == "boolean" ? options.showDigital : false;
this.logClock = typeof options.logClock == "boolean" ? options.logClock : false;
this.logClockRev = typeof options.logClock == "boolean" ? options.logClockRev : false;
this.tickDelay = CoolClock.config[this.showSecondHand ? "tickDelay" : "longTickDelay"];
// Get the canvas element
this.canvas = document.getElementById(this.canvasId);
// Make the canvas the requested size. It's always square.
this.canvas.setAttribute("width", this.displayRadius * 2);
this.canvas.setAttribute("height", this.displayRadius * 2);
this.canvas.style.width = this.displayRadius * 2 + "px";
this.canvas.style.height = this.displayRadius * 2 + "px";
// Explain me please...?
this.renderRadius = CoolClock.config.renderRadius;
this.scale = this.displayRadius / this.renderRadius;
// Initialise canvas context
this.ctx = this.canvas.getContext("2d");
this.ctx.scale(this.scale, this.scale);
// Keep track of this object
CoolClock.config.clockTracker[this.canvasId] = this;
// Start the clock going
this.tick();
return this;
},
// Draw a circle at point x,y with params as defined in skin
fullCircleAt: function(x, y, skin) {
this.ctx.save();
this.ctx.globalAlpha = skin.alpha;
this.ctx.lineWidth = skin.lineWidth;
if (!CoolClock.config.isIE) {
this.ctx.beginPath();
}
if (CoolClock.config.isIE) {
// excanvas doesn't scale line width so we will do it here
this.ctx.lineWidth = this.ctx.lineWidth * this.scale;
}
this.ctx.arc(x, y, skin.radius, 0, 2 * Math.PI, false);
if (CoolClock.config.isIE) {
// excanvas doesn't close the circle so let's fill in the tiny gap
this.ctx.arc(x, y, skin.radius, -0.1, 0.1, false);
}
if (skin.fillColor) {
this.ctx.fillStyle = skin.fillColor
this.ctx.fill();
} else {
// XXX why not stroke and fill
this.ctx.strokeStyle = skin.color;
this.ctx.stroke();
}
this.ctx.restore();
},
// Draw some text centered vertically and horizontally
drawTextAt: function(theText, x, y) {
this.ctx.save();
this.ctx.font = '15px sans-serif';
var tSize = this.ctx.measureText(theText);
if (!tSize.height) tSize.height = 15; // no height in firefox.. :(
this.ctx.fillText(theText, x - tSize.width / 2, y - tSize.height / 2);
this.ctx.restore();
},
lpad2: function(num) {
return (num < 10 ? '0' : '') + num;
},
tickAngle: function(second) {
// Log algorithm by David Bradshaw
var tweak = 3; // If it's lower the one second mark looks wrong (?)
if (this.logClock) {
return second == 0 ? 0 : (Math.log(second * tweak) / Math.log(60 * tweak));
} else if (this.logClockRev) {
// Flip the seconds then flip the angle (trickiness)
second = (60 - second) % 60;
return 1.0 - (second == 0 ? 0 : (Math.log(second * tweak) / Math.log(60 * tweak)));
} else {
return second / 60.0;
}
},
timeText: function(hour, min, sec) {
var c = CoolClock.config;
return '' +
(c.showAmPm ? ((hour % 12) == 0 ? 12 : (hour % 12)) : hour) + ':' +
this.lpad2(min) +
(c.showSecs ? ':' + this.lpad2(sec) : '') +
(c.showAmPm ? (hour < 12 ? ' am' : ' pm') : '');
},
// Draw a radial line by rotating then drawing a straight line
// Ha ha, I think I've accidentally used Taus, (see http://tauday.com/)
radialLineAtAngle: function(angleFraction, skin) {
this.ctx.save();
this.ctx.translate(this.renderRadius, this.renderRadius);
this.ctx.rotate(Math.PI * (2.0 * angleFraction - 0.5));
this.ctx.globalAlpha = skin.alpha;
this.ctx.strokeStyle = skin.color;
this.ctx.lineWidth = skin.lineWidth;
if (CoolClock.config.isIE)
// excanvas doesn't scale line width so we will do it here
this.ctx.lineWidth = this.ctx.lineWidth * this.scale;
if (skin.radius) {
this.fullCircleAt(skin.startAt, 0, skin)
} else {
this.ctx.beginPath();
this.ctx.moveTo(skin.startAt, 0)
this.ctx.lineTo(skin.endAt, 0);
this.ctx.stroke();
}
this.ctx.restore();
},
render: function(hour, min, sec) {
// Get the skin
var skin = CoolClock.config.skins[this.skinId];
if (!skin) skin = CoolClock.config.skins[CoolClock.config.defaultSkin];
// Clear
this.ctx.clearRect(0, 0, this.renderRadius * 2, this.renderRadius * 2);
// Draw the outer edge of the clock
if (skin.outerBorder)
this.fullCircleAt(this.renderRadius, this.renderRadius, skin.outerBorder);
// Draw the tick marks. Every 5th one is a big one
for (var i = 0; i < 60; i++) {
(i % 5) && skin.smallIndicator && this.radialLineAtAngle(this.tickAngle(i), skin.smallIndicator);
!(i % 5) && skin.largeIndicator && this.radialLineAtAngle(this.tickAngle(i), skin.largeIndicator);
}
// Write the time
if (this.showDigital) {
this.drawTextAt(
this.timeText(hour, min, sec),
this.renderRadius,
this.renderRadius + this.renderRadius / 2
);
}
// Draw the hands
if (skin.hourHand)
this.radialLineAtAngle(this.tickAngle(((hour % 12) * 5 + min / 12.0)), skin.hourHand);
if (skin.minuteHand)
this.radialLineAtAngle(this.tickAngle((min + sec / 60.0)), skin.minuteHand);
if (this.showSecondHand && skin.secondHand)
this.radialLineAtAngle(this.tickAngle(sec), skin.secondHand);
// Second hand decoration doesn't render right in IE so lets turn it off
if (!CoolClock.config.isIE && this.showSecondHand && skin.secondDecoration)
this.radialLineAtAngle(this.tickAngle(sec), skin.secondDecoration);
},
// Check the time and display the clock
refreshDisplay: function() {
var now = new Date();
if (this.gmtOffset != null) {
// Use GMT + gmtOffset
var offsetNow = new Date(now.valueOf() + (this.gmtOffset * 1000 * 60 * 60));
this.render(offsetNow.getUTCHours(), offsetNow.getUTCMinutes(), offsetNow.getUTCSeconds());
} else {
// Use local time
this.render(now.getHours(), now.getMinutes(), now.getSeconds());
}
},
// Set timeout to trigger a tick in the future
nextTick: function() {
setTimeout("CoolClock.config.clockTracker['" + this.canvasId + "'].tick()", this.tickDelay);
},
// Check the canvas element hasn't been removed
stillHere: function() {
return document.getElementById(this.canvasId) != null;
},
// Main tick handler. Refresh the clock then setup the next tick
tick: function() {
if (this.stillHere()) {
this.refreshDisplay()
this.nextTick();
}
}
};
// Find all canvas elements that have the CoolClock class and turns them into clocks
CoolClock.findAndCreateClocks = function() {
// (Let's not use a jQuery selector here so it's easier to use frameworks other than jQuery)
var canvases = document.getElementsByTagName("canvas");
for (var i = 0; i < canvases.length; i++) {
// Pull out the fields from the class. Example "CoolClock:chunkySwissOnBlack:1000"
var fields = canvases[i].className.split(" ")[0].split(":");
if (fields[0] == "CoolClock") {
if (!canvases[i].id) {
// If there's no id on this canvas element then give it one
canvases[i].id = '_coolclock_auto_id_' + CoolClock.config.noIdCount++;
}
// Create a clock object for this element
new CoolClock({
canvasId: canvases[i].id,
skinId: fields[1],
displayRadius: fields[2],
showSecondHand: fields[3] != 'noSeconds',
gmtOffset: fields[4],
showDigital: fields[5] == 'showDigital',
logClock: fields[6] == 'logClock',
logClockRev: fields[6] == 'logClockRev'
});
}
}
};
$(document).ready(CoolClock.findAndCreateClocks);
$(document).ready(function(){
$('#c1').hover(function() {
$(this).toggleClass('CoolClock:regular:125 CoolClock:black:125');
CoolClock.findAndCreateClocks();
}, function() {
$(this).toggleClass('CoolClock:regular:125 CoolClock:black:125');
CoolClock.findAndCreateClocks();
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="c1" class="CoolClock:regular:125"></canvas>