Related
I'm trying to make an animation inside a canvas: here, a numbered circle must be drawn and move from left to right one single time, disappearing as soon as it reaches the end of animation.
For now I managed to animate it in loop, but I need to animate at the same time (or with a set delay) multiple numbered circles, strating in different rows (changing y position) so they wont overlap.
Any idea how can I manage this? my JS code is the following:
// Single Animated Circle - Get Canvas element by Id
var canvas = document.getElementById("canvas");
// Set Canvas dimensions
canvas.width = 300;
canvas.height = 900;
// Get drawing context
var ctx = canvas.getContext("2d");
// Radius
var radius = 13;
// Starting Position
var x = radius;
var y = radius;
// Speed in x and y direction
var dx = 1;
var dy = 0;
// Generate random number
var randomNumber = Math.floor(Math.random() * 60) + 1;
if (randomNumber > 0 && randomNumber <= 10) {
ctx.strokeStyle = "#0b0bf1";
} else if (randomNumber > 10 && randomNumber <= 20) {
ctx.strokeStyle = "#f10b0b";
} else if (randomNumber > 20 && randomNumber <= 30) {
ctx.strokeStyle = "#0bf163";
} else if (randomNumber > 30 && randomNumber <= 40) {
ctx.strokeStyle = "#f1da0b";
} else if (randomNumber > 40 && randomNumber <= 50) {
ctx.strokeStyle = "#950bf1";
} else if (randomNumber > 50 && randomNumber <= 60) {
ctx.strokeStyle = "#0bf1e5";
}
function animate3() {
requestAnimationFrame(animate3);
ctx.clearRect(0, 0, 300, 900);
if (x + radius > 300 || x - radius < 0) {
x = radius;
}
x += dx;
ctx.beginPath();
ctx.arc(x, y, 12, 0, Math.PI * 2, false);
ctx.stroke();
ctx.fillText(randomNumber, x - 5, y + 3);
}
// Animate the Circle
animate3();
<canvas id="canvas"></canvas>
Here is a solution which doesn't use classes as such and separates the animation logic from the updating - which can be useful if you want more precise control over timing.
// Some helper functions
const clamp = (number, min, max) => Math.min(Math.max(number, min), max);
// Choose and remove random member of arr with equal probability
const takeRandom = arr => arr.splice(parseInt(Math.random() * arr.length), 1)[0]
// Call a function at an interval, passing the amount of time that has passed since the last call
function update(callBack, interval) {
let now = performance.now();
let last;
setInterval(function() {
last = now;
now = performance.now();
callBack((now - last) / 1000);
})
}
const settings = {
width: 300,
height: 150,
radius: 13,
gap: 5,
circles: 5,
maxSpeed: 100,
colors: ["#0b0bf1", "#f10b0b", "#0bf163", "#f1da0b", "#950bf1", "#0bf1e5"]
};
const canvas = document.getElementById("canvas");
canvas.width = settings.width;
canvas.height = settings.height;
const ctx = canvas.getContext("2d");
// Set circle properties
const circles = [...Array(settings.circles).keys()].map(i => ({
number: i + 1,
x: settings.radius,
y: settings.radius + (settings.radius * 2 + settings.gap) * i,
radius: settings.radius,
dx: settings.maxSpeed * Math.random(), // This is the speed in pixels per second
dy: 0,
color: takeRandom(settings.colors)
}));
function drawCircle(circle) {
ctx.strokeStyle = circle.color;
ctx.beginPath();
ctx.arc(circle.x, circle.y, circle.radius, 0, Math.PI * 2, false);
ctx.stroke();
ctx.fillText(circle.number, circle.x - 5, circle.y + 3);
}
function updateCircle(circle, dt) {
// Update a circle's position after dt seconds
circle.x = clamp(circle.x + circle.dx * dt, circle.radius + 1, settings.width - circle.radius - 1);
circle.y = clamp(circle.y + circle.dy * dt, circle.radius + 1, settings.height - circle.radius - 1);
}
function animate() {
ctx.clearRect(0, 0, settings.width, settings.height);
circles.forEach(drawCircle);
requestAnimationFrame(animate);
}
update(dt => circles.forEach(circle => updateCircle(circle, dt)), 50);
animate();
<canvas id="canvas" style="border: solid 1px black"></canvas>
Here I transformed your sample code into a class ...
We pass all the data as a parameter, you can see that in the constructor, I simplified a lot of your code to keep it really short, but all the same drawing you did is there in the draw function
Then all we need to do is create instances of this class and call the draw function inside that animate3 loop you already have.
You had a hardcoded value on the radius:
ctx.arc(x, y, 12, 0, Math.PI * 2, false)
I assume that was a mistake and fix it on my code
var canvas = document.getElementById("canvas");
canvas.width = canvas.height = 300;
var ctx = canvas.getContext("2d");
class Circle {
constructor(data) {
this.data = data
}
draw() {
if (this.data.x + this.data.radius > 300 || this.data.x - this.data.radius < 0) {
this.data.x = this.data.radius;
}
this.data.x += this.data.dx;
ctx.beginPath();
ctx.arc(this.data.x, this.data.y, this.data.radius, 0, Math.PI * 2, false);
ctx.stroke();
ctx.fillText(this.data.number, this.data.x - 5, this.data.y + 3);
}
}
circles = []
circles.push(new Circle({radius:13, x: 10, y: 15, dx: 1, dy: 0, number: "1"}))
circles.push(new Circle({radius:10, x: 10, y: 50, dx: 2, dy: 0, number: "2"}))
function animate3() {
requestAnimationFrame(animate3);
ctx.clearRect(0, 0, canvas.width, canvas.height);
circles.forEach(item => item.draw());
}
animate3();
<canvas id="canvas"></canvas>
Code should be easy to follow let me know if you have any questions
semi-pie progress, what i want
Hello guys. How can i implement this kind of semi-pie progress bar with highcharts?
I tried alot and my result show as below photo. i want to create dataLabels same as first photo. if there is any way, please let me know.
i use this code for datalables:
dataLabels: {
formatter: function() {
return this.y > 1 ? '<b>' + this.point.name + ': ' + this.y + '%</b>' : null;
}
my result
So I created a guideline with custome functionality how to render a dataLabel which is 'attaching' to the pie point via using the SVGRenderer feature.
See code below and the demo: https://jsfiddle.net/BlackLabel/23ybcdjm/ and the version with path: https://jsfiddle.net/BlackLabel/u843xnoe/
chart: {
events: {
render() {
let chart = this,
series = chart.series[0],
point1 = series.points[0],
point2 = series.points[1],
shape1 = point1.shapeArgs,
shape2 = point2.shapeArgs,
distance,
distance2 = 50,
PI = -Math.PI,
label1,
label2,
x1,
y1,
x2,
y2;
//check if label exist while resize
if (chart.label1) {
chart.label1.destroy()
}
if (chart.label2) {
chart.label2.destroy()
}
//set x and y positions for labels
x1 = shape1.x + (shape1.r * Math.cos(shape1.end));
y1 = shape1.y + (shape1.r * Math.sin(shape1.end));
x2 = shape2.x + shape2.r + distance2;
y2 = shape2.y + chart.plotTop;
//render label1
chart.label1 = chart.renderer.label('Actual: ' + point1.y, x1, y1)
.css({
color: '#FFFFFF'
})
.attr({
fill: 'rgba(0, 0, 0, 0.75)',
padding: 8,
zIndex: 6,
})
.add();
//render label2
chart.label2 = chart.renderer.label('Target: ' + point2.y, x2, y2)
.css({
color: '#FFFFFF'
})
.attr({
fill: 'rgba(0, 0, 0, 0.75)',
padding: 8,
zIndex: 6,
})
.add();
//find distance for label which is moving around the pie
if (shape1.end > PI && shape1.end <= 0.55 * PI) {
distance = -chart.label2.getBBox().width
} else if (shape1.end > 0.55 * PI && shape1.end <= 0.45 * PI) {
distance = -chart.label2.getBBox().width / 2
} else if (shape1.end > 0.5 * PI && shape1.end < 0.25 * PI) {
distance = chart.label2.getBBox().width / 3
} else if (shape1.end > 0.25 * PI && shape1.end <= 0) {
distance = chart.label2.getBBox().width / 2
}
label1 = chart.label1;
label2 = chart.label2;
//translate labels
label1.translate(label1.x + distance + chart.plotLeft, label1.y)
chart.label2.translate(chart.label2.x, chart.label2.y - chart.label2.getBBox().height)
}
}
},
API: https://api.highcharts.com/highcharts/chart.events.render
API: https://api.highcharts.com/class-reference/Highcharts.SVGRenderer#label
var circleA = {
x: 100,
y: 40,
radius: 20,
color: '63, 81, 181',
pathx: 220,
pathy: 150
}
var circleB = {
x: 250,
y: 50,
radius: 30,
color: '76, 175, 80',
pathx: 120,
pathy: 140
}
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var centerX = 500;
var centerY = 500;
//draw circle A
context.beginPath();
context.arc(circleA.x, circleA.y, circleA.radius, 0, 2 * Math.PI, false);
context.fillStyle = "rgba(" + circleA.color + ", 1)";
context.fill();
//draw circle A path destination
context.beginPath();
context.arc(circleA.pathx, circleA.pathy, circleA.radius, 0, 2 * Math.PI, false);
context.fillStyle = "rgba(" + circleA.color + ", 0.5)";
context.fill();
//draw line in circle A path
context.beginPath();
context.moveTo(circleA.x,circleA.y);
context.lineTo(circleA.pathx,circleA.pathy);
context.strokeStyle = "rgba(" + circleA.color + ", 1)";
context.stroke();
//draw circle B
context.beginPath();
context.arc(circleB.x, circleB.y, circleB.radius, 0, 2 * Math.PI, false);
context.fillStyle = "rgba(" + circleB.color + ", 1)";
context.fill();
//draw circle B path destination
context.beginPath();
context.arc(circleB.pathx, circleB.pathy, circleA.radius, 0, 2 * Math.PI, false);
context.fillStyle = "rgba(" + circleB.color + ", 0.5)";
context.fill();
//draw line in circle A path
context.beginPath();
context.moveTo(circleB.x,circleB.y);
context.lineTo(circleB.pathx,circleB.pathy);
context.strokeStyle = "rgba(" + circleB.color + ", 1)";
context.stroke();
//I NEED HELP HERE - i have no idea how to calculate this
function willCollide(ca_start, ca_end, cb_start, cb_end)
{
var RSum = circleA.radius + circleB.radius;
var t = 10;
var a = getPos(circleA, t);
var b = getPos(circleB, t);
var distance = (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
var sum = RSum*=2;
context.font = "20px Arial";
context.fillText('distance: ' + distance + " sum: " + sum,10,200);
}
function getPos(circle, t)
{
//position changes
var dax = (circle.pathx - circle.x);
var day = (circle.pathy - circle.y);
//normalize components
var lenA = Math.sqrt(dax * dax + day * day);
dax = dax / lenA;
day = day / lenA;
//position vs time
var ax = circleA.x + dax * t;
var ay = circleA.y + day * t;
return {
x: ax,
y: ay
}
}
willCollide(
{ x: circleA.x, y: circleA.y },
{ x: circleA.pathx, y: circleA.pathy },
circleA.radius,
{ x: circleB.x, y: circleB.y },
{ x: circleB.pathx, y: circleB.pathy },
circleB.radius
);
body {
margin: 0px;
padding: 0px;
}
<!DOCTYPE HTML>
<html>
<head>
</head>
<body>
<canvas id="myCanvas" width="578" height="200"></canvas>
</body>
</html>
I have circles running around in space with a given path on each frame.
so I can say that circle A will go to xy(10, 8) and that circle B will go to xy(5, -3).
and I need to make sure the path each circle is going is clear and that there is no other circle that will go on that path, is so, I want to give it a new path.
I've attached an image explaining the situation and the desired result on each case.
your help is much appreciated. thank you!
Position of the first circle is described as
x1 = x1_0 + vx1 * t
y1 = y1_0 + vy1 * t
where x10 is initial x-coordinate, vx1 is x-component of velocity vector, t is time.
Make similar expression for the second circle center, build expression for squared distance and find time when squared distance becomes equal to 4R^2 (for equal radii) - if solution exists at needed time interval, it is the moment of circles' collision.
In your designations (seems you have equal velocities):
RSum = circleA.radius + circleB.radius
//position changes
dax = (circleA.pathx - circleA.x)
day = (circleA.pathy - circleA.y)
//normalize components
lenA = Sqrt(dax * dax + day * day)
dax = dax / lenA
day = day / lenA
//position vs time
ax = circleA.x + dax * t
ay = circleA.y + day * t
and similar for B circle
Now make distance equation, substitute ax, bx, ay, by expressions and solve it for unknown t (quadratic equation, might have 0,1,2 roots)
(ax - bx) * (ax - bx) + (ay - by) * (ay - by) = RSum * RSum
or
(circleA.x + dax * t - circleB.x - dbx * t) * ....
I am working on a radial control similar to the HTML5 wheel of fortune example. I've modified the original here with an example of some additional functionality I require: http://jsfiddle.net/fEm9P/ When you click on the inner kinetic wedges they will shrink and expand within the larger wedges. Unfortunately when I rotate the wheel it lags behind the pointer. It's not too bad here but it's really noticeable on a mobile.
I know this is due to the fact that I'm not caching the wheel. When I do cache the wheel (uncomment lines 239-249) the inner wedges no longer respond to mouse/touch but the response on rotation is perfect. I have also tried adding the inner wedges to a separate layer and caching the main wheel only. I then rotate the inner wheel with the outer one. Doing it this way is a little better but still not viable on mobile.
Any suggestions would be greatly appreciated.
Stephen
//constants
var MAX_ANGULAR_VELOCITY = 360 * 5;
var NUM_WEDGES = 25;
var WHEEL_RADIUS = 410;
var ANGULAR_FRICTION = 0.2;
// globals
var angularVelocity = 360;
var lastRotation = 0;
var controlled = false;
var target, activeWedge, stage, layer, wheel,
pointer, pointerTween, startRotation, startX, startY;
var currentVolume, action;
function purifyColor(color) {
var randIndex = Math.round(Math.random() * 3);
color[randIndex] = 0;
return color;
}
function getRandomColor() {
var r = 100 + Math.round(Math.random() * 55);
var g = 100 + Math.round(Math.random() * 55);
var b = 100 + Math.round(Math.random() * 55);
var color = [r, g, b];
color = purifyColor(color);
color = purifyColor(color);
return color;
}
function bind() {
wheel.on('mousedown', function(evt) {
var mousePos = stage.getPointerPosition();
angularVelocity = 0;
controlled = true;
target = evt.targetNode;
startRotation = this.rotation();
startX = mousePos.x;
startY = mousePos.y;
});
// add listeners to container
document.body.addEventListener('mouseup', function() {
controlled = false;
action = null;
if(angularVelocity > MAX_ANGULAR_VELOCITY) {
angularVelocity = MAX_ANGULAR_VELOCITY;
}
else if(angularVelocity < -1 * MAX_ANGULAR_VELOCITY) {
angularVelocity = -1 * MAX_ANGULAR_VELOCITY;
}
angularVelocities = [];
}, false);
document.body.addEventListener('mousemove', function(evt) {
var mousePos = stage.getPointerPosition();
var x1, y1;
if(action == 'increase') {
x1 = (mousePos.x-(stage.getWidth() / 2));
y1 = (mousePos.y-WHEEL_RADIUS+20);
var r = Math.sqrt(x1 * x1 + y1 * y1);
if (r>500){
r=500;
} else if (r<100){
r=100;
};
currentVolume.setRadius(r);
layer.draw();
} else {
if(controlled && mousePos && target) {
x1 = mousePos.x - wheel.x();
y1 = mousePos.y - wheel.y();
var x2 = startX - wheel.x();
var y2 = startY - wheel.y();
var angle1 = Math.atan(y1 / x1) * 180 / Math.PI;
var angle2 = Math.atan(y2 / x2) * 180 / Math.PI;
var angleDiff = angle2 - angle1;
if ((x1 < 0 && x2 >=0) || (x2 < 0 && x1 >=0)) {
angleDiff += 180;
}
wheel.setRotation(startRotation - angleDiff);
}
};
}, false);
}
function getRandomReward() {
var mainDigit = Math.round(Math.random() * 9);
return mainDigit + '\n0\n0';
}
function addWedge(n) {
var s = getRandomColor();
var reward = getRandomReward();
var r = s[0];
var g = s[1];
var b = s[2];
var angle = 360 / NUM_WEDGES;
var endColor = 'rgb(' + r + ',' + g + ',' + b + ')';
r += 100;
g += 100;
b += 100;
var startColor = 'rgb(' + r + ',' + g + ',' + b + ')';
var wedge = new Kinetic.Group({
rotation: n * 360 / NUM_WEDGES,
});
var wedgeBackground = new Kinetic.Wedge({
radius: WHEEL_RADIUS,
angle: angle,
fillRadialGradientStartRadius: 0,
fillRadialGradientEndRadius: WHEEL_RADIUS,
fillRadialGradientColorStops: [0, startColor, 1, endColor],
fill: '#64e9f8',
fillPriority: 'radial-gradient',
stroke: '#ccc',
strokeWidth: 2,
rotation: (90 + angle/2) * -1
});
wedge.add(wedgeBackground);
var text = new Kinetic.Text({
text: reward,
fontFamily: 'Calibri',
fontSize: 50,
fill: 'white',
align: 'center',
stroke: 'yellow',
strokeWidth: 1,
listening: false
});
text.offsetX(text.width()/2);
text.offsetY(WHEEL_RADIUS - 15);
wedge.add(text);
volume = createVolumeControl(angle, endColor);
wedge.add(volume);
wheel.add(wedge);
}
var activeWedge;
function createVolumeControl(angle, colour){
var volume = new Kinetic.Wedge({
radius: 100,
angle: angle,
fill: colour,
stroke: '#000000',
rotation: (90 + angle/2) * -1
});
volume.on("mousedown touchstart", function() {
currentVolume = this;
action='increase';
});
return volume;
}
function animate(frame) {
// wheel
var angularVelocityChange = angularVelocity * frame.timeDiff * (1 - ANGULAR_FRICTION) / 1000;
angularVelocity -= angularVelocityChange;
if(controlled) {
angularVelocity = ((wheel.getRotation() - lastRotation) * 1000 / frame.timeDiff);
}
else {
wheel.rotate(frame.timeDiff * angularVelocity / 1000);
}
lastRotation = wheel.getRotation();
// pointer
var intersectedWedge = layer.getIntersection({x: stage.width()/2, y: 50});
if (intersectedWedge && (!activeWedge || activeWedge._id !== intersectedWedge._id)) {
pointerTween.reset();
pointerTween.play();
activeWedge = intersectedWedge;
}
}
function init() {
stage = new Kinetic.Stage({
container: 'container',
width: 578,
height: 500
});
layer = new Kinetic.Layer();
wheel = new Kinetic.Group({
x: stage.getWidth() / 2,
y: WHEEL_RADIUS + 20
});
for(var n = 0; n < NUM_WEDGES; n++) {
addWedge(n);
}
pointer = new Kinetic.Wedge({
fillRadialGradientStartPoint: 0,
fillRadialGradientStartRadius: 0,
fillRadialGradientEndPoint: 0,
fillRadialGradientEndRadius: 30,
fillRadialGradientColorStops: [0, 'white', 1, 'red'],
stroke: 'white',
strokeWidth: 2,
lineJoin: 'round',
angle: 30,
radius: 30,
x: stage.getWidth() / 2,
y: 20,
rotation: -105,
shadowColor: 'black',
shadowOffset: {x:3,y:3},
shadowBlur: 2,
shadowOpacity: 0.5
});
// add components to the stage
layer.add(wheel);
layer.add(pointer);
stage.add(layer);
pointerTween = new Kinetic.Tween({
node: pointer,
duration: 0.1,
easing: Kinetic.Easings.EaseInOut,
y: 30
});
pointerTween.finish();
var radiusPlus2 = WHEEL_RADIUS + 2;
wheel.cache({
x: -1* radiusPlus2,
y: -1* radiusPlus2,
width: radiusPlus2 * 2,
height: radiusPlus2 * 2
}).offset({
x: radiusPlus2,
y: radiusPlus2
});
layer.draw();
// bind events
bind();
var anim = new Kinetic.Animation(animate, layer);
//document.getElementById('debug').appendChild(layer.hitCanvas._canvas);
// wait one second and then spin the wheel
setTimeout(function() {
anim.start();
}, 1000);
}
init();
I made a couple of changes to the script which greatly improved the response time. The first was replacing layer.draw() with layer.batchDraw(). As the draw function was being called on each touchmove event it was making the interaction clunky. BatchDraw on the other hand will stack up draw requests internally "limit the number of redraws per second based on the maximum number of frames per second" (http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-batch-draw).
The jumping around of the canvas I seeing originally when I cached/cleared the wheel was due to the fact that I wasn't resetting the offset on the wheel when I cleared the cache.
http://jsfiddle.net/leydar/a7tkA/5
wheel.clearCache().offset({
x: 0,
y: 0
});
I hope this is of benefit to someone else. It's still not perfectly responsive but it's at least going in the right direction.
Stephen
I have a gauge / dial type level to be animated in my app. The needle and dial will automatically get updated when values change. The code I have got to works OK for small changes, but when it has bigger changes the animation does not go along the path of the semi-circle.
The code I have is like this: (the interval is just for testing purposes)
var rad = Math.PI / 180;
var getCurvePath = function(cx, cy, r, startAngle, endAngle) {
var x1 = cx + r * Math.cos(-startAngle * rad),
x2 = cx + r * Math.cos(-endAngle * rad),
y1 = cy + r * Math.sin(-startAngle * rad),
y2 = cy + r * Math.sin(-endAngle * rad);
return ["M", x1, y1, "A", r, r, 0, +(endAngle - startAngle > 180), 0, x2, y2];
};
var zeroAngle = 197, fullAngle = -15,baseAngle = 199,
r = Raphael('level');
var level = r
.path(getCurvePath(150, 150, 75, zeroAngle, baseAngle))
.attr({
'stroke': '#FF0000',
'stroke-width': '11px'
});
window.setInterval(function() {
var ratio = Math.random();
var newLevelAngle = (zeroAngle - fullAngle) * ratio;
level.animate({
'path': getCurvePath(150, 150, 75, newLevelAngle, baseAngle)
}, 1000, 'bounce');
}, 2000);
I have set up a JS Fiddle here so you can see this in action: http://jsfiddle.net/Rfd3v/1/
The accepted answer here sorted my problem out:
drawing centered arcs in raphael js
I needed to adapt a few things from #genkilabs's answer to suit the level/gauge application. Hopefully this might be helpful to someone else:
var arcCentre = { x: 100, y: 100 },
arcRadius = 50,
arcMin = 1, // stops the arc disappearing for 0 values
baseRotation = -100, // this starts the arc from a different point
circleRatio = 220/360, // I found this helpful as my arc is never a full circle
maxValue = 1000; // arbitrary
// starts the arc at the minimum value
var myArc = r.path()
.attr({
"stroke": "#f00",
"stroke-width": 14,
arc: [arcCentre.x, arcCentre.y, arcMin, 100, arcRadius],
})
.transform('r'+ baseRotation + ',' + arcCentre.x + ',' + arcCentre.y);
// set a new value
var newValue = 234; // pick your new value
// convert value to ratio of the arc to complete
var ratio = newValue / maxValue;
// set level
var newLevelValue = Math.max(arcMin, ratio * 100 * circleRatio);
myArc.animate({
arc: [arcCentre.x, arcCentre.y, newLevelValue, 100, arcRadius]
}, 750, 'bounce');