Countdown animation with EaselJs - javascript

I am trying to simulate a countdown animation using easeljs. I have got an example of what it should look like. http://jsfiddle.net/eguneys/AeK28/
But it looks like a hack, is there a proper/better/flexible way to do this?
To put it other way, how can i define a path, and draw that path with easeljs.
This looks ugly:
createjs.Tween.get(bar, {override: true}).to({x: 400}, 1500, createjs.linear)
.call(function() {
createjs.Tween.get(bar, {override: true}).to({y: 400}, 1500, createjs.linear)
.call(function() {
createjs.Tween.get(bar, {override: true}).to({ x: 10 }, 1500, createjs.linear)
.call(function() {
createjs.Tween.get(bar, {override: true}).to({ y: 10 }, 1500, createjs.linear);
})
});
});

You can use the TweenJS MotionGuidePlugin to tween along a path instead of having multiple tweens.
createjs.MotionGuidePlugin.install();
createjs.Tween.get(bar).to({
guide: {path: [10,10, 10,10,400,10, 400,10,400,400, 400,400,10,400, 10,400,10,10]}
}, 6000, createjs.linear);
The path array is basically the set of coordinates for a moveTo call followed by multiple curveTo calls. The coordinates will be interpolated along the path resulting from those calls.
A more modular way to to specify your path array would be to have a function generating it using a set of points you declared.
function getMotionPathFromPoints (points) {
var i, motionPath;
for (i = 0, motionPath = []; i < points.length; ++i) {
if (i === 0) {
motionPath.push(points[i].x, points[i].y);
} else {
motionPath.push(points[i - 1].x, points[i - 1].y, points[i].x, points[i].y);
}
}
return motionPath;
}
var points = [
new createjs.Point(10, 10),
new createjs.Point(400, 10),
new createjs.Point(400, 400),
new createjs.Point(10, 400),
new createjs.Point(10, 10)
];
createjs.MotionGuidePlugin.install();
createjs.Tween.get(bar).to({
guide: {path: getMotionPathFromPoints(points)}
}, 6000, createjs.linear);
FIDDLE

Related

Limit dragged line to an arc / radius of a given length

I'm currently using Phaser 3, although my question isn't technically restricted to that framework, as it's more of a general JS/canvas/maths question, but:
I have a line drawn with graphics(). It’s anchored at one end, and the other end is draggable. I made a quick demo and so far, so good - you can see what I have already on CodePen.
Dragging the marker around and redrawing the line is no problem, but what I’d like is for that line to have a maximum length of 100, so even if you’re still dragging beyond that point, the line would still follow the mouse, but not get any longer than 100. Dragging inside that maximum radius, the line would shrink as normal.
I’ve put together a visual that hopefully explains it:
The issue is that I suspect this is VERY MATHS and I am very, very weak with maths. Could anyone explain like I’m five what I need to do to my code to achieve this?
Edit: Adding code in a snippet here, as requested:
var config = {
type: Phaser.AUTO,
width: 800,
height: 400,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: {
preload: preload,
create: create,
update: update
}
};
var path;
var curve;
var graphics;
var game = new Phaser.Game(config);
function preload() {
this.load.spritesheet('dragcircle', 'https://labs.phaser.io/assets/sprites/dragcircle.png', { frameWidth: 16 });
}
function create() {
graphics = this.add.graphics();
path = { t: 0, vec: new Phaser.Math.Vector2() };
curve = new Phaser.Curves.Line([ 400, 390, 300, 230 ]);
var point0 = this.add.image(curve.p0.x, curve.p0.y, 'dragcircle', 0);
var point1 = this.add.image(curve.p1.x, curve.p1.y, 'dragcircle', 0).setInteractive();
point1.setData('vector', curve.p1);
this.input.setDraggable(point1);
this.input.on('drag', function (pointer, gameObject, dragX, dragY) {
gameObject.x = dragX;
gameObject.y = dragY;
gameObject.data.get('vector').set(dragX, dragY);
});
this.input.on('dragend', function (pointer, gameObject) {
let distance = Phaser.Math.Distance.Between(curve.p0.x, curve.p0.y, curve.p1.x, curve.p1.y);
console.log(distance);
});
}
function update() {
graphics.clear();
graphics.lineStyle(2, 0xffffff, 1);
curve.draw(graphics);
curve.getPoint(path.t, path.vec);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/phaser/3.55.2/phaser.min.js"></script>
You are right, you would need some math, but phaser has many helper functions, that will do the heavy lifting.
The main idea is, of this solution is
define a maxLength
get the the new point on drag, and create a real Phaser Vector2
here is some math is needed, to create the vector, just calculate destination point minus origin point
new Phaser.Math.Vector2(pointer.x - point0.x, pointer.y - point0.y) (origin point being the starting point of the desired vector, and destination point being the mouse pointer)
calculate the length of the created vector and compare it with the maxLength
if too long adjust the vector, with the handy function setLength (link to the documentation, this is where you would have needed math, but thankfully Phaser does it for us)
set the new coordinates for point1 and the curve endpoint
Here a quick demo (based on your code):
var config = {
type: Phaser.AUTO,
width: 500,
height: 170,
scene: {
preload: preload,
create: create,
update: update
}
};
var curve;
var graphics;
var game = new Phaser.Game(config);
function preload() {
this.load.spritesheet('dragcircle', 'https://labs.phaser.io/assets/sprites/dragcircle.png', { frameWidth: 16 });
}
function create() {
graphics = this.add.graphics();
curve = new Phaser.Curves.Line([ config.width/2, config.height - 20, config.width/2, 10 ]);
// define a length, could be a global constant
let maxLength = curve.p0.y - curve.p1.y;
var point0 = this.add.image(curve.p0.x, curve.p0.y, 'dragcircle', 0);
var point1 = this.add.image(curve.p1.x, curve.p1.y, 'dragcircle', 0).setInteractive();
this.input.setDraggable(point1);
// Just add for Debug Info
this.add.circle(curve.p0.x, curve.p0.y, maxLength)
.setStrokeStyle(1, 0xffffff, .5)
this.input.on('drag', function (pointer) {
let vector = new Phaser.Math.Vector2(pointer.x - point0.x, pointer.y - point0.y);
let distance = Phaser.Math.Distance.Between( point0.x, point0.y, pointer.x, pointer.y);
if(distance > maxLength){
vector.setLength(maxLength);
}
point1.x = point0.x + vector.x;
point1.y = point0.y + vector.y;
curve.p1.x = point1.x;
curve.p1.y = point1.y;
});
// NOT REALLY NEEDED
/*this.input.on('dragend', function (pointer, gameObject) {
let distance = Phaser.Math.Distance.Between(curve.p0.x, curve.p0.y, curve.p1.x, curve.p1.y);
console.log(distance);
});*/
}
function update() {
graphics.clear();
graphics.lineStyle(2, 0xffffff, 1);
curve.draw(graphics);
}
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
Optional - Code Version using Phaser.GameObjects.Line:
This uses less code, and thanks to the Line GameObject (link to Documentation), you can directly use the vector to update the line, and also don't need the update function, graphics and so.
const config = {
type: Phaser.CANVAS,
width: 500,
height: 160,
scene: {
create
}
};
const game = new Phaser.Game(config);
const MAX_LINE_LENGTH = 100;
function create() {
let points = [ {x: config.width/2, y: config.height - 20}, {x: config.width/2, y: config.height - 120} ];
let point0 = this.add.circle(points[0].x, points[0].y, 6)
.setStrokeStyle(4, 0xff0000);
let point1 = this.add.circle(points[1].x, points[1].y, 6)
.setStrokeStyle(4, 0xff0000)
.setInteractive();
this.input.setDraggable(point1);
// Just add for Debug Info
this.add.circle(point0.x, point0.y, MAX_LINE_LENGTH)
.setStrokeStyle(1, 0xffffff, .5);
let line = this.add.line(points[0].x, points[0].y, 0, 0, 0, -100, 0x00ff00)
.setOrigin(0);
this.input.on('drag', function (pointer) {
let vector = new Phaser.Math.Vector2(pointer.x - point0.x, pointer.y - point0.y);
let distance = Phaser.Math.Distance.Between( point0.x, point0.y, pointer.x, pointer.y);
if(distance > MAX_LINE_LENGTH){
vector.setLength(MAX_LINE_LENGTH);
}
point1.x = point0.x + vector.x;
point1.y = point0.y + vector.y;
line.setTo(0, 0, vector.x, vector.y);
});
}
<script src="//cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>

Randomly spawn an object when enemies die Phaser 3

I would like to randomly spawn a sprite when an enemy dies.
Example: There is a 1 in 5 chance that when an enemy dies, it drops an object (sprites that increase your HP).
Any idea how this can be done?
I did some research, but I didn't find much.
For randomness in a Phaser application, I would use the Phaser's Math helper function Between (here is the link to the documentation).
It creates a random number (whole number) from the first number to the last one (including the last number, perfect for dice).
So for 1 in 5, you just need to select one number from the interval like 5and compare it with a call to the Between function. And only if it matches, you drop/create the sprite.
Just like this:
if(Phaser.Math.Between(1, 5) === 5){
// .. drop "loot" / health-object
}
Here a small Demo:
(In this demo something could be dropped or not, depending on your luck. 20% is pretty low)
document.body.style = 'margin:0;';
var config = {
type: Phaser.AUTO,
width: 536,
height: 183,
scene: {
create
},
banner: false
};
function create () {
this.add.text(10, 10, 'Click to red Boxes')
let graphics = this.make.graphics({x: 0, y: 0, add: false});
graphics.fillStyle(0xFF0000);
graphics.fillRect(0, 0, 20, 20);
graphics.generateTexture('enemy', 20, 20)
let enemiesGroup = this.add.group({
defaultKey: 'enemy',
maxSize: 10
});
let maxEnemiesToShow = 10
for(let idx = 0; idx < maxEnemiesToShow; idx++){
// here the function is used to spawn enemies randomly on screen
const x = Phaser.Math.Between(20, config.width - 20);
const y = Phaser.Math.Between(40, config.height /2 );
let enemy = enemiesGroup.get(x, y);
enemy.setInteractive()
.on('pointerdown', () => {
// 1 in 5
if(Phaser.Math.Between(1, 5) === 5){
// Drop object
this.add.rectangle(enemy.x, enemy.y, 10, 10, 0xFFFF00);
}
enemy.destroy();
})
}
}
new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
Bonus (because I find this Phaser function especially useful):
If you want to select different loot/outcome in phaser you, could even let phaser select from a selected Array, with the function Phaser.Math.RNG.pick(...) (link to documentation)
Bonus Demo:
document.body.style = 'margin:0;';
var config = {
type: Phaser.AUTO,
width: 536,
height: 183,
scene: {
create
},
banner: false
};
function create () {
this.add.text(10, 10, 'Click to red Boxes')
let graphics = this.make.graphics({x: 0, y: 0, add: false});
graphics.fillStyle(0xFF0000);
graphics.fillRect(0, 0, 20, 20);
graphics.generateTexture('enemy', 20, 20)
let enemiesGroup = this.add.group({
defaultKey: 'enemy',
maxSize: 10
});
let maxEnemiesToShow = 10
for(let idx = 0; idx < maxEnemiesToShow; idx++){
const x = Phaser.Math.Between(20, config.width - 20);
const y = Phaser.Math.Between(40, config.height /2 );
let enemy = enemiesGroup.get(x, y);
let loot = [0x00ff00, 0xffff00, 0x0000ff, 0x0, 0x0];
enemy
.setInteractive()
.on('pointerdown', () => {
// Select Colro from an Array of possibilities
let color = Phaser.Math.RND.pick(loot);
// only drop item if color is not black
if(color > 0){
this.add.rectangle(enemy.x, enemy.y, 10, 10, color);
}
enemy.destroy();
})
}
}
new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
Phaser Random functions, have the added bonus that you can create your own RandomDataGenerator with a specific seed if you want, that the random numbers, that are created, are generated in the same sequence. Great for testing and so.
For a 1/5 chance, you can use JavaScript's Math.random.
Math.random() will return a float between 0 and 1.
To not hard code this, you can use a function like the following which will return true or false given an odds (in your case 1/5)
function rollRandom(odds) {
return Math.random() < odds;
}
console.log(rollRandom(1/5))

PhysicsJS - how to create a body which rotates about a fixed point when struck?

I've been experimenting with the excellent PhysicsJS library, especially constraints and collisions. What I'm trying to achieve is a polygon which can spin about a fixed rotation point. As a real world example, imagine a wide shelf nailed to a wall with just one long nail through the very center, so that it spins round maybe half or one revolution when something off-center drops on it from above.
Here is an example fiddle: http://jsfiddle.net/2HRGW/41/
and the code:
Physics(function (world) {
var renderer = Physics.renderer('canvas', {
el: 'viewport',
width: 500,
height: 400
});
world.add(renderer);
var nail = Physics.body('circle', {
x: 250,
y: 200,
radius: 5,
mass: 1,
fixed: true
});
world.add(nail);
var shelf = Physics.body('convex-polygon', {
x: 250,
y: 200,
vertices: [{
x: -100,
y: -10
}, {
x: 100,
y: -10
}, {
x: 100,
y: 10
}, {
x: -100,
y: 10
}],
mass: 100,
restitution: 0.5
});
world.add(shelf);
var ball = Physics.body('circle', {
x: 175,
y: 50,
radius: 20,
mass: 10
});
world.add(ball);
world.add(Physics.integrator('verlet', {
drag: 0.003
}));
var verletConstraints = Physics.behavior('verlet-constraints', {
iterations: 2
});
verletConstraints.distanceConstraint(shelf, nail, 1);
world.add(verletConstraints);
world.add(Physics.behavior('constant-acceleration'));
world.add(Physics.behavior('body-collision-detection'));
world.add(Physics.behavior('body-impulse-response'));
world.add(Physics.behavior('sweep-prune'));
world.add(Physics.behavior('verlet-constraints'));
var bounds = Physics.aabb(0, 0, 500, 400);
world.add(Physics.behavior('edge-collision-detection', {
aabb: bounds,
restitution: 0.01
}));
Physics.util.ticker.subscribe(function (time, dt) {
world.step(time);
});
world.render();
Physics.util.ticker.start();
world.subscribe('step', function () {
world.render();
});
});
I define a fixed circle for the nail and a non-fixed polygon for the shelf and add a distance constraint linking the polygon to the circle. As you can see, there are 2 problems. Firstly, the shelf immediately drops down slightly until its top edge is flush with the top of the nail, rather than remaining evenly positioned around the nail. Secondly, when the ball drops onto the shelf, the shelf goes crazy spinning round endlessly, despite having mass and various restitution settings tried. Adjusting its position slightly, sometimes it can even detach completely and fly off.
Am I on the right track using constraints in this way, or is there a simpler solution?
As other users have mentioned, this is a current limitation of PhysicsJS. It's being worked out:
scoping of collisions https://github.com/wellcaffeinated/PhysicsJS/issues/30
constraints https://github.com/wellcaffeinated/PhysicsJS/issues/5
In the mean time, instead of patching the library, why not create a custom pin constraint behavior. For a simple pin constraint behavior that pins the body centroid to a target position, it's quite easy. Here's a jsFiddle of your example with a custom pin constraint manager defined at the beginning.
http://jsfiddle.net/wellcaffeinated/2HRGW/50/
// create a behavior to handle pin constraints
Physics.behavior('pin-constraints', function( parent ){
return {
init: function( opts ){
parent.init.call( this, opts );
this.pins = [];
},
add: function( body, targetPos ){
this.pins.push({
body: body,
target: Physics.vector( targetPos )
});
},
behave: function( data ){
var pins = this.pins
,pin
;
for (var i = 0, l = pins.length; i < l; i++){
pin = pins[ i ];
// move body to correct position
pin.body.state.pos.clone( pin.target );
}
}
};
});
Your overall implementation is correct.
The only thing you got wrong is the mass of the convex-polygon, 100 is a wild value.
A mass of around 0.2 should be enough for that object.

How to add click to shaped in loop?

I'm passing text arrays to my circleCreate function, which creates a wedge for each text. What I'm trying to do is add a click event to each wedge, so when the user clicks on a wedge, it throws an alert with each wedges text.
But it's not working. Only the outer circle is alerting text. And it always says the same text. Both inner circles alert undefined.
http://jsfiddle.net/Yushell/9f7JN/
var layer = new Kinetic.Layer();
function circleCreate(vangle, vradius, vcolor, vtext) {
startAngle = 0;
endAngle = 0;
for (var i = 0; i < vangle.length; i++) {
// WEDGE
startAngle = endAngle;
endAngle = startAngle + vangle[i];
var wedge = new Kinetic.Wedge({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
radius: vradius,
angleDeg: vangle[i],
fill: vcolor,
stroke: 'black',
strokeWidth: 1,
rotationDeg: startAngle
});
/* CLICK NOT WORKING
wedge.on('click', function() {
alert(vtext[i]);
});*/
layer.add(wedge);
}
stage.add(layer);
}
This is a typical problem you'll run into with asynchronous JavaScript code such as event handlers. The for loop in your circleCreate() function uses a variable i which it increments for each wedge. This is fine where you use i to create the wedge:
angleDeg: vangle[i],
But it fails where you use it inside the click event handler:
alert(vtext[i]);
Why is that?
When you create the wedge using the new Kinetic.Wedge() call, this is done directly inside the loop. This code runs synchronously; it uses the value of i as it exists at the very moment that this particular iteration of the loop is run.
But the click event handler doesn't run at that time. It may not run at all, if you never click. When you do click a wedge, its event handler is called at that time, long after the original loop has finished running.
So, what is the value of i when the event handler does run? It's whatever value the code left in it when it ran originally. This for loop exits when i equals vangle.length—so in other words, i is past the end of the array, and therefore vangle[i] is undefined.
You can fix this easily with a closure, by simply calling a function for each loop iteration:
var layer = new Kinetic.Layer();
function circleCreate(vangle, vradius, vcolor, vtext) {
startAngle = 0;
endAngle = 0;
for (var i = 0; i < vangle.length; i++) {
addWedge( i );
}
stage.add(layer);
function addWedge( i ) {
startAngle = endAngle;
endAngle = startAngle + vangle[i];
var wedge = new Kinetic.Wedge({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
radius: vradius,
angleDeg: vangle[i],
fill: vcolor,
stroke: 'black',
strokeWidth: 1,
rotationDeg: startAngle
});
wedge.on('click', function() {
alert(vtext[i]);
});
layer.add(wedge);
}
}
What happens now is that calling the addWedge() function captures the value of i individually for each loop iteration. As you know, every function can have its own local variables/parameters, and the i inside addWedge() is local to that function—and specifically, local to each individual invocation of that function. (Note that because addWedge() is a function of its own, the i inside that function is not the same as the i in the outer circleCreate() function. If this is confusing, it's fine to give it a different name.)
Updated fiddle
A better way
This said, I recommend a different approach to structuring your data. As I was reading your code, the angle and text arrays caught my eye:
var anglesParents = [120, 120, 120];
var parentTextArray = ['Parent1', 'Parent2', 'Parent3'];
There are similar but lengthier pairs of arrays for children and grandchildren.
You use the values from these arrays with the vtext[i] and vangle[i] references in circleCreate().
In general, unless there's a specific reason to use parallel arrays like this, your code will become cleaner if you combine them into a single array of objects:
[
{ angle: 120, text: 'Parent1' },
{ angle: 120, text: 'Parent2' },
{ angle: 120, text: 'Parent3' }
]
For your nested cirles, we can take this a step further and combine all three rings into a single large array of objects that describes the entire set of nested rings. Where you have these arrays:
var anglesParents = [120, 120, 120];
var anglesChildren = [120, 60, 60, 60, 60];
var anglesGrandchildren = [
33.33, 20, 23.33, 43.33, 22.10, 25.26,
12.63, 28, 32, 33, 27, 36, 14.4, 9.6
];
var grandchildrenTextArray = [
'GrandCHild1', 'GrandCHild2', 'GrandCHild3', 'GrandCHild4',
'GrandCHild5', 'GrandCHild6', 'GrandCHild7', 'GrandCHild8',
'GrandCHild9', 'GrandCHild10', 'GrandCHild11', 'GrandCHild12',
'GrandCHild13', 'GrandCHild14', 'GrandCHild15', 'GrandCHild16'
];
var childrenTextArray = [
'Child1', 'Child2', 'Child3', 'Child4', 'Child5'
];
var parentTextArray = ['Parent1', 'Parent2', 'Parent3'];
It would be:
var rings = [
{
radius: 200,
color: 'grey',
slices: [
{ angle: 33.33, text: 'GrandChild1' },
{ angle: 20, text: 'GrandChild2' },
{ angle: 23.33, text: 'GrandChild3' },
{ angle: 43.33, text: 'GrandChild4' },
{ angle: 22.10, text: 'GrandChild5' },
{ angle: 25.26, text: 'GrandChild6' },
{ angle: 12.63, text: 'GrandChild7' },
{ angle: 28, text: 'GrandChild8' },
{ angle: 32, text: 'GrandChild9' },
{ angle: 33, text: 'GrandChild10' },
{ angle: 27, text: 'GrandChild10' },
{ angle: 36, text: 'GrandChild12' },
{ angle: 14.4, text: 'GrandChild13' },
{ angle: 9.6, text: 'GrandChild14' }
]
},
{
radius: 150,
color: 'darkgrey',
slices: [
{ angle: 120, text: 'Child1' },
{ angle: 60, text: 'Child2' },
{ angle: 60, text: 'Child3' },
{ angle: 60, text: 'Child4' },
{ angle: 60, text: 'Child5' }
]
},
{
radius: 100,
color: 'lightgrey',
slices: [
{ angle: 120, text: 'Parent1' },
{ angle: 120, text: 'Parent2' },
{ angle: 120, text: 'Parent3' }
]
}
];
Now this is longer than the original, what with all the angle: and text: property names, but that stuff compresses out very nicely with the gzip compression that servers and browsers use.
More importantly, it helps simplify and clarify the code and avoid errors. Did you happen to notice that your anglesGrandchildren and grandchildrenTextArray are not the same length? :-)
Using a single array of objects instead of parallel arrays prevents an error like that.
To use this data, remove the circleCreate() function and these calls to it:
circleCreate(anglesGrandchildren, 200, "grey", grandchildrenTextArray);
circleCreate(anglesChildren, 150, "darkgrey", childrenTextArray);
circleCreate(anglesParents, 100, "lightgrey", parentTextArray);
and replace them with:
function createRings( rings ) {
var startAngle = 0, endAngle = 0,
x = stage.getWidth() / 2,
y = stage.getHeight() / 2;
rings.forEach( function( ring ) {
ring.slices.forEach( function( slice ) {
startAngle = endAngle;
endAngle = startAngle + slice.angle;
var wedge = new Kinetic.Wedge({
x: x,
y: y,
radius: ring.radius,
angleDeg: slice.angle,
fill: ring.color,
stroke: 'black',
strokeWidth: 1,
rotationDeg: startAngle
});
wedge.on('click', function() {
alert(slice.text);
});
layer.add(wedge);
});
});
stage.add(layer);
}
createRings( rings );
Now this code isn't really any shorter than the original, but some of the details are more clear: slice.angle and slice.text show clearly that the angle and text belong to the same slice object, where with the original vangle[i] and vtext[i] we're left hoping that the vangle and vtext arrays are the correct matching arrays and are properly lined up with each other.
I also used .forEach() instead of a for loop; since you're using Canvas we know you are on a modern browser. One nice thing is that forEach() uses a function call, so it automatically gives you a closure.
Also, I moved the calculations of x and y outside the loop since they are the same for every wedge.
Here's the latest fiddle with this updated code and data.
because each anonymous function you define as an event handler with each loop iteration will share the same scope, each function will reference the same var (i) as the array address for the text you are trying to display. Because your are redefining the var i with each loop, you will always see the last text message in your message array displayed on each click event because the last value assigned to i will have been the length of your array.
here is the solution:
var layer = new Kinetic.Layer();
function circleCreate(vangle, vradius, vcolor, vtext) {
startAngle = 0;
endAngle = 0;
for (var i = 0; i < vangle.length; i++) {
// WEDGE
startAngle = endAngle;
endAngle = startAngle + vangle[i];
var wedge = new Kinetic.Wedge({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
radius: vradius,
angleDeg: vangle[i],
fill: vcolor,
stroke: 'black',
strokeWidth: 1,
rotationDeg: startAngle
});
(function(index) {
wedge.on('click', function() {
alert(vtext[i]);
});
})(i)
layer.add(wedge);
}
stage.add(layer);
}
Your problem is with your loop index. Try this:
(function(j) {
wedge.on('click', function() {
alert(vtext[j]);
});
})(i);
See here
The problem is that when your click handler gets called, i has the value that it had at the end of your loop, so vtext[i] is obviously undefined. By wrapping it in a closure, you can save the value of the loop index at the time the loop ran for your click handler.

Raphael, animation of points

I have a function which displays lines (x and y coordinates) based on the time information. The x and y coordinates specify the position of the drawn points whereas time represents the timestamps (in milliseconds) of the respective points.
Currently, there is a function which displays line as below
<script type="text/javascript" src="https://raw.github.com/DmitryBaranovskiy/raphael/master/raphael-min.js"></script>
<script type="text/javascript">
function drawLine(points) {
var paths = ['M ' + points[0].x + ' ' + points[0].y];
for (var i = 1; i < points.length; i++) {
var p = points[i];
paths.push(paths[i - 1] + ' L ' + p.x + ' ' + p.y);
}
var paper = new Raphael(document.getElementById('canvas_container'), 500, 500);
var line = paper.path(paths[0]);
var next = 1;
function animate() {
if (paths[next]) {
duration = points[next].t - points[next - 1].t
line.animate({ path: paths[next] }, duration, 'linear', animate);
next++;
}
}
animate();
}
</script>
And the function can be called using associative arrays as follows:
drawLine([
{ x: 0, y: 0, t: 0 },
{ x: 100, y: 230, t: 1520 },
{ x: 210, y: 290, t: 3850 },
{ x: 150, y: 200, t: 5060 },
]);
The question is, how can I modify this function to display points and not the lines?
You can add a drawPoint method, which will take an object with x and y properties
function drawPoint(point) {
paper.circle(point.x, point.y, 5).attr('fill', 'red');
};
Then call it from your animate function, before the points[next] comparison
drawPoint(points[next - 1]);
Here's the JSFiddle http://jsfiddle.net/jaimem/2krgN/
If you don't want the lines, then you don't need paths
function drawPoints(points){
var paper = new Raphael('canvas_container', 500, 500),
idx = 0;
function animate(){
if(points[idx]){
var currP = points[idx],
prevP = points[idx - 1],
d = currP.t - (prevP ? prevP.t : 0 );
paper.circle(currP.x, currP.y, 1)
.attr('fill', 'red')
.animate({r:5}, d, animate);
idx++
}
}
animate();
}
The recursive animate callback might be a little difficult to understand/read, so might just want to use a setTimeout. Also you can pass a string with the id of an element to the Raphael constructor and the library will find the DOM node for you.
JS Fiddle: http://jsfiddle.net/jaimem/Q5G5y/2/

Categories