Hover function on javascript - javascript

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>

Related

javascript canvas not drawing line in one unusual scenario

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.

How can I evenly distribute ticks when using maxTicksLimit?

I made a line chart using Chart.js version 2.1.3.
var canvas = $('#gold_chart').get(0);
var ctx = canvas.getContext('2d');
var fillPatternGold = ctx.createLinearGradient(0, 0, 0, canvas.height);
fillPatternGold.addColorStop(0, '#fdca55');
fillPatternGold.addColorStop(1, '#ffffff');
var goldChart = new Chart(ctx, {
type: 'line',
animation: false,
data: {
labels: dates,
datasets: [{
label: '',
data: prices,
pointRadius: 0,
borderWidth: 1,
borderColor: '#a97f35',
backgroundColor: fillPatternGold
}]
},
title: {
position: 'bottom',
text: '\u7F8E\u5143 / \u76CE\u53F8'
},
options: {
legend: {
display: false
},
tooltips: {
callback: function(tooltipItem) {
return tooltipItem.yLabel;
}
},
scales: {
xAxes: [{
ticks: {
maxTicksLimit: 8
}
}]
}
}
});
The output is as follow:
As you can see, I limited the maximum count of ticks to 8 via maxTicksLimit. However, the distribution is not even. How can I make the ticks distribute evenly?
p.s. there are always 289 records in the dataset, and the data is recorded every 5 minutes. Sample values of prices variable are:
[
{"14:10", 1280.3},
{"14:15", 1280.25},
{"14:20", 1282.85}
]
I tried different values of maxTicksLimit, and the results are still not distributed evenly.
Chart.js uses an integral skipRatio (to figure out how many labels to skip). With Chart.js v2.1.x, you can write your own plugin to use a fractional skipRatio
Preview
Script
Chart.pluginService.register({
afterUpdate: function (chart) {
var xScale = chart.scales['x-axis-0'];
if (xScale.options.ticks.maxTicksLimit) {
// store the original maxTicksLimit
xScale.options.ticks._maxTicksLimit = xScale.options.ticks.maxTicksLimit;
// let chart.js draw the first and last label
xScale.options.ticks.maxTicksLimit = (xScale.ticks.length % xScale.options.ticks._maxTicksLimit === 0) ? 1 : 2;
var originalXScaleDraw = xScale.draw
xScale.draw = function () {
originalXScaleDraw.apply(this, arguments);
var xScale = chart.scales['x-axis-0'];
if (xScale.options.ticks.maxTicksLimit) {
var helpers = Chart.helpers;
var tickFontColor = helpers.getValueOrDefault(xScale.options.ticks.fontColor, Chart.defaults.global.defaultFontColor);
var tickFontSize = helpers.getValueOrDefault(xScale.options.ticks.fontSize, Chart.defaults.global.defaultFontSize);
var tickFontStyle = helpers.getValueOrDefault(xScale.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle);
var tickFontFamily = helpers.getValueOrDefault(xScale.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily);
var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
var tl = xScale.options.gridLines.tickMarkLength;
var isRotated = xScale.labelRotation !== 0;
var yTickStart = xScale.top;
var yTickEnd = xScale.top + tl;
var chartArea = chart.chartArea;
// use the saved ticks
var maxTicks = xScale.options.ticks._maxTicksLimit - 1;
var ticksPerVisibleTick = xScale.ticks.length / maxTicks;
// chart.js uses an integral skipRatio - this causes all the fractional ticks to be accounted for between the last 2 labels
// we use a fractional skipRatio
var ticksCovered = 0;
helpers.each(xScale.ticks, function (label, index) {
if (index < ticksCovered)
return;
ticksCovered += ticksPerVisibleTick;
// chart.js has already drawn these 2
if (index === 0 || index === (xScale.ticks.length - 1))
return;
// copy of chart.js code
var xLineValue = this.getPixelForTick(index);
var xLabelValue = this.getPixelForTick(index, this.options.gridLines.offsetGridLines);
if (this.options.gridLines.display) {
this.ctx.lineWidth = this.options.gridLines.lineWidth;
this.ctx.strokeStyle = this.options.gridLines.color;
xLineValue += helpers.aliasPixel(this.ctx.lineWidth);
// Draw the label area
this.ctx.beginPath();
if (this.options.gridLines.drawTicks) {
this.ctx.moveTo(xLineValue, yTickStart);
this.ctx.lineTo(xLineValue, yTickEnd);
}
// Draw the chart area
if (this.options.gridLines.drawOnChartArea) {
this.ctx.moveTo(xLineValue, chartArea.top);
this.ctx.lineTo(xLineValue, chartArea.bottom);
}
// Need to stroke in the loop because we are potentially changing line widths & colours
this.ctx.stroke();
}
if (this.options.ticks.display) {
this.ctx.save();
this.ctx.translate(xLabelValue + this.options.ticks.labelOffset, (isRotated) ? this.top + 12 : this.options.position === "top" ? this.bottom - tl : this.top + tl);
this.ctx.rotate(helpers.toRadians(this.labelRotation) * -1);
this.ctx.font = tickLabelFont;
this.ctx.textAlign = (isRotated) ? "right" : "center";
this.ctx.textBaseline = (isRotated) ? "middle" : this.options.position === "top" ? "bottom" : "top";
this.ctx.fillText(label, 0, 0);
this.ctx.restore();
}
}, xScale);
}
};
}
},
});
Fiddle - http://jsfiddle.net/bh63pe1v/
A simpler solution until this is permanently fixed by the Chart JS contributors is to include a decimal in maxTicksLimit.
For example:
maxTicksLimit: 8,
produces a huge gap at the end.
maxTicksLimit: 8.1,
Does not produce a huge gap at the end.
Depending on what you want to set your maxTicksLimit to, you need to play around with different decimals to see which one produces the best result.
Just do this:
yAxes: [{
ticks: {
stepSize: Math.round((Math.max.apply(Math, myListOfyValues) / 10)/5)*5,
beginAtZero: true,
precision: 0
}
}]
10 = the number of ticks
5 = rounds tick values to the nearest 5 - all y values will be incremented evenly
Similar will work for xAxes too.

Why am I getting an infinite for loop using javascript?

I am trying to create a bookshelf with various books. However, when I attempt to make a new bookshelf for every 4 books, I get an error. Possibly an infinite for loop? What is going wrong? (Khan Academy Program)
An array of books.
var book = [
{
title: "The Giver",
stars: 4,
author: "Lois Lowry",//2.Author property to each book added #1
color: color(0, 120, 42),//3. Property that stores color
recommended: true
},
{
title: "NWT of the Holy Scriptures",
stars: 5,
author: "Jehovah",//2.Author property... #2
color: color(204, 204, 204),//3. Property that stores color
recommended: true
},
{
title: "The Cay",
stars: 4,
author: "Theodore Taylor",//2.Author property... #3
color: color(80, 84, 209),//3. Property that stores color
recommended: true
},
{
title: "The Golden Compass",
stars: 5,
author: "Philip Pullman",//2.Author property... #4
color: color(97, 55, 186),//3. Property that stores color
recommended: true
},
];
Draw bookshelves and books
for(var x = 0; x < book.length; x++){
//Draw books
fill(book[x].color);
rect(5 + 100 * x, 20, 90, 100);
fill(0, 0, 0);
text(book[x].title, 15 + 100 * x, 29, 70, 100);
text(book[x].author,35 + 100 * x, 76, 70, 100);
//Draw leaf for recommended books
if(book[x].recommended === true){
var leaf = getImage("avatars/leaf-red");
image(leaf, 10 + 100 * x, 85,25,25);
}
//Draw stars for star rating
for (var i = 0; i < book[x].stars; i++) {
image(getImage("cute/Star"), 17 + i * 15 + 100 * x, 96 , 15, 25);
}
//Draw bookshelf for every 4 books
for(var y = book.length;y >= 0;y - 4){
// draw shelf
fill(87, 10, 0);
rect(0, 120 + 100 * y, width, 10);
}/// <------ infinite loop?
}
for(var y = book.length;y >= 0;y - 4){
...is not actually mutating the value of y. Change it to:
for( var y = book.length; y >= 0; y -= 4 ) {

Highchart tooltip show nearest point

I have been trying to make highchart tooltip to show the nearest point incase the x-axis value aren't align in different series.
This is what I got so far
http://jsfiddle.net/Yw8hb/5/
Highcharts.wrap(Highcharts.Tooltip.prototype, 'refresh', function (proceed) {
var args = arguments,
points = args[1],
point = points[0],
chart = point.series.chart;
// Loop over all the series of the chart
Highcharts.each(chart.series, function(series) {
// This one already exist
if (series == point.series) return;
var current,
dist,
distance = Number.MAX_VALUE;
// Loop over all the points
Highcharts.each(series.points, function(p) {
// use the distance in X to determine the closest point
dist = Math.abs(p.x - point.x);
if (dist < distance) {
distance = dist;
current = p;
}
});
// Add the closest point to the array
points.push(current);
});
proceed.apply(this, [].slice.call(args, 1));
});
It seems to be working half way there however when you hover in some spot it shows duplicated series. I have spent hours trying to figure this out any help would be very appreciated.
Before insertion, check whether points array contains the current point in your refresh callback function.
// Add the closest point to the array
if(points.indexOf(current)==-1)
points.push(current);
Highcharts.wrap(Highcharts.Tooltip.prototype, 'refresh', function (proceed) {
var args = arguments,
points = args[1],
point = points[0],
chart = point.series.chart;
// Loop over all the series of the chart
Highcharts.each(chart.series, function(series) {
// This one already exist
if (series == point.series) return;
var current,
dist,
distance = Number.MAX_VALUE;
// Loop over all the points
Highcharts.each(series.points, function(p) {
// use the distance in X to determine the closest point
dist = Math.abs(p.x - point.x);
if (dist < distance) {
distance = dist;
current = p;
}
});
// Add the closest point to the array
if(points.indexOf(current)==-1)
points.push(current);
});
proceed.apply(this, [].slice.call(args, 1));
});
$('#container').highcharts({
tooltip: {
shared: true
},
xAxis: {
crosshair: {
color: '#F70000'
}
},
series: [{
data: [{
x: 0.0,
y: 1
}, {
x: 1.0,
y: 2
}, {
x: 2.0,
y: 3
}, {
x: 3.0,
y: 2
}, {
x: 4.0,
y: 1
}]
}, {
data: [{
x: 0.2,
y: 0
}, {
x: 1.2,
y: 1
}, {
x: 2.2,
y: 1
}, {
x: 3.2,
y: 1
}, {
x: 4.2,
y: 2
}]
}, {
data: [{
x: 0.2,
y: 5
}, {
x: 1.2,
y: 9
}, {
x: 2.2,
y: 4
}, {
x: 3.2,
y: 5
}, {
x: 4.2,
y: 3
}]
}]
});
#container {
min-width: 300px;
max-width: 800px;
height: 300px;
margin: 1em auto;
}
<script src="http://code.jquery.com/jquery-git.js"></script>
<script src="http://code.highcharts.com/highcharts.js"></script>
<script src="http://code.highcharts.com/modules/exporting.js"></script>
<div id="container"></div>
If you want to show visible series' in the tooltip only, change
// This one already exist
if (series == point.series) return;
to
// This one already exist
if (series == point.series || series.visible==false) return;
Thanks for you solution!!!
for constant order the tooltips
Highcharts.wrap(Highcharts.Tooltip.prototype, 'refresh', function (proceed) {
var args = arguments,
points = args[1],
point = points[0],
chart = point.series.chart;
// Loop over all the series of the chart
Highcharts.each(chart.series, function (series) {
// This one already exist
if (series == point.series || series.visible == false)
return;
var current,
dist,
distance = Number.MAX_VALUE;
// Loop over all the points
Highcharts.each(series.points, function (p) {
// use the distance in X to determine the closest point
dist = Math.abs(p.x - point.x);
if (dist < distance) {
distance = dist;
current = p;
return;
}
});
// Add the closest point to the array
if (points.indexOf(current) == -1)
points.push(current);
});
// for not changing the tooltip series order
var tt = [].slice.call(args, 1);
tt[0].sort(function (a, b) {
if (a.color < b.color)
return -1;
if (a.color > b.color)
return 1;
return 0;
});
proceed.apply(this, tt);
});
Don't forget tooltip option shared!
options = {
tooltip: {
shared: true,
....

How would I go about editing the polar clock counter clockwise?

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!

Categories

Resources