I have a script that originated in D3 V3 and I'm trying to rebuild it in V5.
I'll go through the code and then the problem. This is the relevant part of code.
var height = 570
var width = 510
var svg = d3.select("#chart").append("svg")
.attr("width", width + 160)
.attr("height", height + 90)
.append("g")
.attr("transform", "translate(" + 160 + "," + 90 + ")");
// this has a count for how many are in each group
var grpcnts = {
met: { '1960': 0, '1970': 0, '2010': 0, label: "First Met", x: 1, y: 1 },
romantic: { '1960': 0, '1970': 0, '2010': 0, label: "Romantic", x: 2, y: 1 },
lived: { '1960': 0, '1970': 0, '2010': 0, label: "Live Together", x: 3, y: 1 },
married: { '1960': 0, '1970': 0, '2010': 0, label: "Married", x: 4, y: 3 },
}
var x = d3.scaleBand()
.domain([1950, 1970, 1980, 2010])
.range([0, width]);
var y = d3.scaleBand()
.domain(d3.keys(grpcnts))
.range([height, 0]);
var sched_objs = [],
curr_index = -1;
// Load data
d3.tsv("timelines.tsv")
.then(function(data) {
data.forEach(function(d) {
var day_array = d.timeline.split(",");
var activities = [];
for (var i=0; i < day_array.length; i++) {
// Duration
if (i % 2 == 1) {
activities.push({'act': day_array[i-1], 'duration': +day_array[i]});
}
}
sched_objs.push(activities);
});
// A node for each person's schedule
var nodes = sched_objs.map(function(o,i) {
var act = o[0].act;
var init_x = x(+data[i].decade) + Math.random();
var init_y = y('met') + Math.random();
var col = "#cccccc";
grpcnts[act][data[i].decade] += 1;
return {
act: act,
radius: maxRadius,
x: init_x,
y: init_y,
decade: data[i].decade,
color: color(act),
married: false,
moves: 0,
next_move_time: o[0].duration,
sched: o
}
});
}) // end tsv
This is a sample of the dataset.
"decade" "timeline"
1970 "met,4,romantic,14,lived,1,married,-99"
1970 "romantic,2,married,-99"
1970 "met,9,romantic,48,married,-99"
1970 "romantic,20,married,-99"
1970 "met,2,romantic,10,married,-99"
1970 "met,13,romantic,16,married,-99"
The problem is that the x and y fields show up as NaN.
I've added console.log statements before the return clause and the init_x and init_y values print out the right numbers.
I've tested the x() and y() functions with all the valid inputs and they return the right values. I've tested Math.random() which appears to work just fine.
None of the other fields show up as NaN which leads me to believe that the syntax for returning multiple values is right. I've also tried wrapping init_x and init_y in Number().
Unfortunately your code is full of errors, to the point that we cannot make it run without a major refactor.
However, just to answer your current specific question ("where are the NaNs coming from?"), this is the problem (line 212 in your pasteBin):
var k = 0.03 * this.alpha;
Since there is no alpha, but alpha() instead, when you use k (which is undefined) latter on...
o.x += (x(+o.decade) - o.x) * k * damper;
... you get that nice and beautiful NaN.
I'd like to emphasise again that this change will not make your code work, you have a lot of other problems to fix.
I hope I have come to the right place. I am currently doing a project on Khan academy where I need to use the % operator to create a loop of books for my bookshelf. I have only managed to get so far but I am stuck on how to get my list of books to automatically go to the next shelf after a certain number.
Apologies if I am not being clear but hopefully with the code and images I have provided, it makes sense.
Thanks in advance.
Bookshelf Project
Bookshelf Project Instructions
Project Code:
//array of objects for books
var book = [
{title: "The Giver",
author: "Jamie",
stars: 4,
color: color(156, 24, 222),
rec: true
},
{title: "Lord of the Rings",
author: "Mr Rings",
stars: 5,
color: color(222, 24, 97),
rec: true
},
{title: "Lord of the Flies",
author: "Mr Flies",
stars: 2,
color: color(222, 24, 215),
rec: false
},
{title: "Grapes of Wrath",
author: "Mr Grapes",
stars: 3,
color: color(34, 133, 19),
rec: true
}
];
// draw shelf
fill(173, 117, 33);
rect(0, 120, width, 10);
// loop of books
for (var i = 0; i < book.length; i++) {
fill(book[i].color);
rect(100 * i + 5, 20, 90, 100);
fill(240, 228, 240);
text(book[i].author, 100 * i + 10, 85, 70, 100);
text(book[i].title, 100 * i + 10, 30, 70, 100);
for (var s = 0; s < book[i].stars; s++) {
image(getImage("cute/Star"), 5 + s * 15 + i * 100, 90, 20, 30);
}
if(book[i].rec === true) {
image(getImage("creatures/Winston"), i * 100 + 75, 25, 15, 15);
} else {
image(getImage("creatures/OhNoes"), i * 100 + 75, 25, 15, 15);
}
fill(173, 117, 33);
rect(0, 120 + i * 100, width, 10);
}›
Javascript as many other programming languages uses % (Percentage sign) as an operator called Remainder, it will return the remainder after division of one number by another
Examples:
2 % 4 //-> 0
4 % 4 //-> 0
5 % 4 //-> 1
6 % 4 //-> 2
8 % 4 //-> 0
9 % 4 //-> 1
10 % 4 //-> 2
In the assignment he asks you to add more shelves down the canvas, and it's apparent that the shelve contains only four books, you can use % for determining the index of any book in each shelve inside the loop, as I mentioned in the examples above, if you're using the image index as the left-hand of the operation and the length of books as the right-hand, you will know which is the first book in the left in each shelve hence you can count its location in the canvas
(index + 1) % 4 // Add one to index because computer starts counting from 0
I do have an array of objects (book) here. My question is: How do I use the "color" object so each book has its color property? I think it has to be something like this -> book[amount].color <- but I can't make it work.
I don't know how to call the color property.
This is the array of objects:
var book = [
{title: "The Giver",
stars: 3,
author: "Lowry Loys",
color: color(0, 38, 255), //How do I make this property work in the code?
image: true},
{title: "How to win friends",
stars: 5,
author: "Dale Carnegie",
color: color(214, 255, 219),
image: false},
{title: "Principios fund. de la filosofía",
stars: 5,
author: "Georges Politzer",
color: color(115, 0, 255),
image: false}
];
This is the code
// draw shelf
fill(173, 117, 33);
rect(0, 120, width, 10);
// draw books + title + author
for (var amount = 0; amount < book.length; amount++) { //draw books
fill(214, 255, 219);
rect(154*amount, 20, 90, 100);
fill(0, 0, 0);
textSize(13);
text(book[amount].title, 5 + 158*amount, 27, 68, 100); //draw title
textSize(10);
text(book[amount].author, 5 + 155*amount, 91, 75, 100); //draw author
for (var s = 0; s < book[amount].stars; s++) { //draw stars
image(getImage("cute/Star"), 11 + s * 15 + amount * 151, 98, 15, 22);
}
for (var i = 0; i < book[amount].image; i++) { //draw stars
image(getImage("avatars/aqualine-sapling"), 9 + i * 60 + amount * 46, 42, 36, 39);
}
}
Assuming you don't have a color function, you'll have to quote those properties and then extract the color information.
In this simple example a regex grabs the color values from the (index 1) object, and then sets the background color of a temporary element.
var book=[{title:"The Giver",stars:3,author:"Lowry Loys",color:'color(0, 38, 255)',image:!0},{title:"How to win friends",stars:5,author:"Dale Carnegie",color:'color(214, 255, 219)',image:!1},{title:"Principios fund. de la filosofía",stars:5,author:"Georges Politzer",color:'color(115, 0, 255)',image:!1}]
const result = document.getElementById('result');
const color = book[1].color.match(/(\d+)/g);
result.style.backgroundColor = getColor(color);
function getColor(color) {
return `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
}
<div id="result">sdfdsfdsf</div>
The color function doesn't exist in plain javascript. I think the easiest way will be to store your colors as a HEXs in strings like below:
var book = [{
title: "The Giver",
stars: 3,
author: "Lowry Loys",
color: "#0026ff", // converted rgb(0, 38, 255)
image: true
}];
book[0].color; // "#0026ff"
You can use e.g. this tool to manually convert colors to HEX. If you don't like it just implement your own color function to convert rgb to hex (link).
Here you can also find an interesting npm package named Qix-/color:
JavaScript library for immutable color conversion and manipulation with support for CSS color strings.
Hope it helps!
Sorry for my english. I don't speak it well at all.
I'm designing a chart library for practicing with canvas. I send it a json with the data to draw and the config options. I'm inserting that json below:
function drawChart() {
let canvas = document.querySelector("#canvas")
var chart = new Chartest(canvas, {
chart: {
type: Chartest.Line,
style: {
background: "#fff",
barBackground: "#456"
},
title: "Unidades"
},
data: [{
val: 10600,
label: "Unidad 1"
}, {
val: 6000,
label: "Segunda Unidad"
}, {
val: 4000,
label: "Unidad numero tres"
}, {
val: 4005,
label: "u4"
}, {
val: 0,
label: "u5"
}, {
val: 50,
label: "u6"
}, {
val: 3400,
label: "u7"
}]
})
}
I draw a line from the current point to the predecessor point and so. Everything works fine except when I send a point with value 0. Then the line with the predecessor point doesn't draw. I'm logging the drawing point and everything seems to be correctly. Here you have the logged info and the final result where you can see, It's a missing line and I can't understand why:
Img: Logged info and final result
I'm inserting the part of my code when I draw the lines below:
for (let i = 0; i < this.config.data.length; i++) {
// line type
ctx.beginPath()
let scaledPointVal = this.config.data[i].val * scaledUnitVal
let gradient = ctx.createLinearGradient(0, absoluteGridTop - scaledPointVal, 0, absoluteGridTop)
gradient.addColorStop(0, 'rgba(10, 20, 200, .85)')
gradient.addColorStop(.7, 'rgba(10, 20, 200, .7)')
ctx.strokeStyle = gradient
ctx.lineWidth = 2
ctx.closePath()
let x = scaleSpaceWidth + spaceForEachOne * i
if (i != 0
&& !isNaN(parseFloat(this.config.data[i - 1].val)) && isFinite(this.config.data[i - 1].val)
&& !isNaN(parseFloat(this.config.data[i].val)) && isFinite(this.config.data[i].val)) {
// last and current point available
// draw current point
let pointYPos = headerHeight + chartContainerHeight - scaledPointVal
ctx.beginPath()
ctx.rect(x + spaceForEachOne / 2 - 2, pointYPos - 2, 4, 4)
ctx.fill()
/////////////////////////////////////////////////
// line from last point to current
/////////////////////////////////////////////////
ctx.closePath()
ctx.beginPath()
ctx.moveTo(this.fixPixelPos(x + spaceForEachOne / 2), this.fixPixelPos(pointYPos))
ctx.lineTo(this.fixPixelPos(x - spaceForEachOne / 2),
this.fixPixelPos(headerHeight + chartContainerHeight - this.config.data[i - 1].val * scaledUnitVal))
console.log(`from ${this.fixPixelPos(x + spaceForEachOne / 2)}, ${this.fixPixelPos(pointYPos)}. to ${this.fixPixelPos(x - spaceForEachOne / 2)}, ${this.fixPixelPos(headerHeight + chartContainerHeight - this.config.data[i - 1].val * scaledUnitVal)}`);
ctx.stroke()
ctx.closePath()
//////////////////////////////////////////////////
//////////////////////////////////////////////////
}
else if (!isNaN(parseFloat(this.config.data[i].val)) && isFinite(this.config.data[i].val)) {
// first point or last not valid or empty. just draw current point
let pointYPos = headerHeight + chartContainerHeight - scaledPointVal
ctx.beginPath()
ctx.rect(x + spaceForEachOne / 2 - 2, pointYPos - 2, 4,
4)
ctx.fill()
ctx.closePath()
}
ctx.beginPath()
ctx.font = "12.5px Barlow"
ctx.textAlign = "center"
let text = this.config.data[i].label
while (ctx.measureText(text).width > spaceForEachOne - 15)
text = text.slice(0, text.length - 1)
if (text.length < this.config.data[i].label.length)
text += "..."
ctx.fillText(text, x + spaceForEachOne / 2, headerHeight +
chartContainerHeight + 20)
}
Please help me.
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>