Snap.svg: How to use Set.bind(...)? - javascript

I can't figure out how to use the Set.bind(...) feature for Snap.svg.
Below is an example with three(3) elements in a set: 2 circles and an ellipse.
I'd like to access and change some attr's in the various elements, using bind.
A few examples of bind would be appreciated.
(Actually, at this moment, I can't see any advantage in using the Set object, rather than an array. Are there any features of the Set that can't be handled just as well with an array?)
<!DOCTYPE HTML>
<html>
<head>
<script type="text/javascript" src="http://svgDiscovery.com/_SNP/snap.svg-min.js"></script>
</head>
<body>
<svg id=mySVG width=400 height=200></svg>
<script>
var SNPsvg = Snap("#mySVG");
var circle1 = SNPsvg.circle(150,100,50).attr({fill: 'red' });
var circle2 = SNPsvg.circle(250,100,50).attr({fill: 'blue' });
var ellipse = SNPsvg.ellipse(200,100,50,20).attr({fill: 'green' });
var mySet= Snap.set(circle1,circle2,ellipse)
setTimeout(function()
{
//mySet.bind(...)
},1000)
</script>
</body>
</html>

The main reason to use a set, is that you can act on every element with a single command. For example...
mySet.animate({ transform: 's2' },1000)
jsfiddle
Which will then act on every single element with that animation.
Why would you use Set.bind ? I must admit, I've never used it, nor seen the purpose yet, but I assume there is one :).
So to the actual question, how is it used. I guess you do..
mySet.bind('x', circle1, 'cx' )
mySet.attr({ 'x': '200' })
jsfiddle
If you set attribute x, it will set attribute cx on circle1 in this case.
Or
mySet.bind('x', function( val ) { console.log( val, ' is passed' )} )
mySet.attr({ 'x': '200' })
jsfiddle
As to why though, I'm not sure :), I can see the advantage of using a set object, but not particularly with set.bind(), especially as it doesn't seem to pass 'this' to the function. I was wondering if it was something like if you set x on a set of circles and rects, you could adjust cx OR x somehow, but I don't see how that is done in any simple way, if the object that's being acted on isn't passed somehow.
I'd normally be more inclined to do something like...
mySet.forEach( function( el ) { el.attr({ fill: 'blue' }) } );

Related

Fabric.js animate sprites and text field

about month ago i asked how to quee animatations for objects in Fabric.js to which i got response from #nickvans. This is the code. Based on this I changed the code and created with HTML5 drag and drop, aplication which allows you to give to each object on canvas its own set of different commands. Basicly creating moving scene. Mine 1st question is :if it is possible to also use sprites. So instead of the triangle in the exmaple below it would be animated sprite that would also change its position. And 2nd question is it possible to somehow add text field that would follow the object during its movement? Something like those comics bubles.
Thanks in advance for any tips
function startAnimationQueue(animationQueue){
// queues up the animations for the shape
var runningDuration = 0; // variable that adds up the animationQueue durations
for (var i=0; i<animationQueue.length; i++){
var animationDefinition = animationQueue[i];
// Create a closure around the animationDefiniton so that each setTimeout gets sequential animationDefinition inputs
var fn = (function(animationDefinition){
return function(){
triangle.animate('left', animationDefinition.left, {duration: animationDefinition.duration, onChange:canvas.renderAll.bind(canvas)})
triangle.animate('top', animationDefinition.top, {duration: animationDefinition.duration, onChange:canvas.renderAll.bind(canvas)})
// Note: you can animate additional attributes here if you want, just add additional attributes to the objects in
// the animationQueue list. You could also have one of those inputs be the object to be animated in case you want
// to animate multiple objects using the same queue.
};
})
// Create the timeout that will apply the transformations sequentially
// If you want you could set the window.setTimeout to a variable that you could destroy if you need
// to interrupt the animation queue after you create it (but before it finishes)
window.setTimeout(fn(animationDefinition), runningDuration);
// set the next setTimeout duration to be .duration later than the previous one
// this makes the second animation fire after the first one completes
runningDuration += animationDefinition.duration;
}
}
document.onreadystatechange = function () {
if (document.readyState == "complete") {
// I put the canvas init stuff in here because of (I think) a failed race condition or something that caused
// your original method to fail in Chrome
window.canvas = new fabric.Canvas('scene');
window.triangle = new fabric.Triangle({
width: 30
, height: 30
, fill: 'red'
, left: 30
, top: 0
});
window.canvas.add(window.triangle);
window.canvas.renderAll();
// Create a list of animations to apply
var animationQueue = [
{"left": "+=0", "top": "+=100", "duration": 1000},
{"left": "+=55", "top": "+=0", "duration": 2000}
]
// Apply the animations in sequence using window.setTimeout
startAnimationQueue(animationQueue);
}
}
and HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
<script src="http://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.4.0/fabric.min.js"></script>
</head>
<body>
<canvas id="scene" width="400" height="400" />
</body>
</html>

Javascript Animation Baseball Runners

Thank you in advance for you help. I am hoping someone could provide some solid examples of some Javascript or jQuery animation for running around a baseball diamond rather than starting from scratch.
So far I've found at least 1 think that gets me close however needs much control introduced. I'm looking for tracking live progress so this would be conditional based on the batters progress around the bases. So if the batter hit a double, the animation would go to 2nd base and stop. Eventually I need to add functionality to interact with the circle but that'll be another story.
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Untitled Document</title>
<script>
var context;
var x=100;
var y=200;
var dx=3;
var dy=3;
function init()
{
context= myCanvas.getContext('2d');
setInterval(draw,10);
}
function draw()
{
context.clearRect(0,0, 300,300);
context.beginPath();
context.fillStyle="#0000ff";
// Draws a circle of radius 20 at the coordinates 100,100 on the canvas
context.arc(x,y,20,0,Math.PI*2,true);
context.closePath();
context.fill();
// Boundary Logic
if( x<0 || x>300) dx=-dx;
if( y<0 || y>300) dy=-dy;
x+=dx;
y+=dy;
}
</script>
</head>
<body onLoad="init();">
<canvas id="myCanvas" width="300" height="300" > </canvas>
</body>
</html>
Lots of negative votes and I can see why, however its actually quite straightforward if you use svg and a library, eg Snap.
jsfiddle click to move between bases
Here's a run down of the process....
Firstly load an svg, just plucked one from the internet, and load it..
Snap.load( "https://upload.wikimedia.org/wikipedia/commons/8/89/Baseball_diamond_clean.svg", onSVGLoaded )
We need to create a route, simply log a mouse click to get its x,y which you can use to create a path...
s.click( setRoute );
function setRoute(ev, x,y) {
console.log('xy', ev, x, y); // a click will log coordinates so you can build path route
movePlayer(currentPath++);
if( currentPath > 3) currentPath = 0;
}
Once you've clicked on the points you want to have as the path, add them into the array....
// build our 'virtual path' and player to animate when clicked
function onSVGLoaded( frag ) {
s.append( frag );
s.click( setRoute )
paths = [
"M335,430L448,324", // these were logged from mouse click
"M453,325L337,210",
"M330,210L215,324",
"M215,325L330,436"
];
player = s.circle(335,430,10).attr({ fill: 'red' })
for( var c=0; c<paths.length; c++) {
pathList[c] = s.path(paths[c]).attr({ stroke: 'none' });
}
}
Finally, we can animate the movement...
function movePlayer( currentPath ) {
Snap.animate(0, pathList[currentPath].getTotalLength(), function( val ) {
var pt = pathList[currentPath].getPointAtLength( val );
player.attr({ cx: pt.x, cy: pt.y })
}, 2000)
}
edit: Heh, just seen how old this is, not sure why it popped up now!
Using Html5 Canvas
You can use linear interpolation to navigate your lines and you can use De Casteljau's algorithm to navigate around your Bezier Curve.
Using SVG
Svg Paths have built-in methods that give you the total length of the path: getTotalLength and also the X,Y at a specified length along the path: getPointAtLength. You can use these methods to navigate your lines and curves.

About image rotation once element with specific id is clicked

Logo and elements from ul once clicked rotates image. By default image is already rotated by certain degrees, then on each click image rotates to necessary value.
So far I was using the following:
$("#objRotates").css('opacity','.2');
var value = 0;
var prev_value = 0;
$( "li" ).click(function() {
var text=$(this).text();
if(text==="text1"){value=0;}
if(text==="text2"){value=33;}
if(text==="text3"){value=66;}
if(prev_value != value){
$("#objRotates").animate({opacity:'1'});
$("#objRotates").rotate({
animateTo:value,
easing: $.easing.easeInOutExpo,
center: ["25px", "150px"],
callback: function(){$("#objRotates").animate({opacity:'0.2'});}
});
}
prev_value = value;
});
Above code is the one that was used before, where images start position was 0 and its animation was triggered from link text.
Using jqueryRotate.js examples(here)
How do I change the code, so that images start position is certain degrees and animation starts if element with specific ID is clicked?
Give at least clue..Cause for now, looking at my old code, I am lost. Thanks in advance.
SIMPLIFIED FIDDLE
Ok, so I've created a couple of samples for you to check out. The first one is very basic and I've simplified the code a little to make it easier to understand. This one just uses completely static values and a static elementId for the event, which I'm pretty sure answers your question based on your response to my comment yesterday. http://jsfiddle.net/x9ja7/594/
$("#elementId").click(function () {
var startingAngle = 45;
var endingAngle = 90;
var elementToRotate = "img";
$(elementToRotate).rotate({
angle: startingAngle,
animateTo: endingAngle
});
});
But I wanted to give another example as well that would be dynamic and repeatable for multiple elements. With the code above, you would have to copy/paste the same code over and over again if you want to perform this animation by clicking different elements. Here's an alternative. In this example, you set all of your parameters in the data attributes in the clickable element, then the function is completely repeatable, you only have to write it once. Less code = everyone happy! Here's the example: http://jsfiddle.net/x9ja7/595/
//#region Default starting angles
$("#image1").rotate({ angle: 90 });
$("#image2").rotate({ angle: 20 });
//#endregion
$(".rotateAction").click(function () {
//#region Optional parameter - used in the optional callback function
var $self = $(this);
//#endregion
var startingAngle = Number($(this).attr("data-startingangle"));
var endingAngle = Number($(this).attr("data-endingangle"));
var elementToRotate = $(this).attr("data-elementtorotate");
//#region If the current angle is the ending angle, reverse the animation - this can be removed if you want, I thought it may be cool to show some of the things you can do with this.
var currentAngle = $(elementToRotate).getRotateAngle();
if ( currentAngle[0] === endingAngle) {
startingAngle = Number($(this).attr("data-endingangle"));
endingAngle = Number($(this).attr("data-startingangle"));
}
//#endregion
$(elementToRotate).rotate({
angle: startingAngle,
animateTo: endingAngle
//#region This is optional - uncommenting this code would make the animation single-use only
//, callback: function () { $self.off().removeClass("clickable"); }
//#endregion
});
});
Hope this helps. If you need any other assistance, please let me know.

Reusing SVG.js code for various SVG's

I'm getting to grips with SVG.js
I have a pattern effect that I've created and would like to use in a number of SVG's.
I can create it in one SVG with the following code...
$( document ).ready(function() {
var draw = SVG('geo').size(1200, 1700);
// 100 lines of js creating geometric pattern, effectively this...
var rect = draw.polygon(coordinates).fill('#fff').stroke({ width: 1, color:'#fff'}).opacity(0)
});
This creates an SVG with ID geo. But I'd like to use this code again to generate various SVG's, ideally with different options (colour etc).
SVG('geo') refers to a particular SVG, how do I make it so I can apply this to any SVG I want on the page?
Hope that made sense
You can define a function that does this repeatedly. Something like the following:
function create_svg(dom_id, width, height, coord) {
var draw = SVG(dom_id).size(width, height);
var rect = draw.polygon(coord)
.fill('#fff')
.stroke({
width: 1,
color: '#fff'
})
.opacity(0);
}
$(function() {
create_svg('geo', 1200, 1700, coordinates);
create_svg('geo2', 1000, 1500, other_coordinates);
)};
If you need to use the created SVGs further, later on in the code, you could make the create_svg function return the created SVG object to a variable in your document.ready function.

How to invoke "click" event programmatically in d3?

I'm trying like that (also at https://gist.github.com/1703994):
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.js?1.27.2"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.time.js?1.27.2"></script>
<script type="text/javascript" src="js-libs/jquery-1.7.js"></script>
<style>
<!--
#test {
width: 400px;
height: 500px;
}
-->
</style>
</head>
<body>
<script type="text/javascript">
$(function() {
var w = 600,
h = 350;
var vis = d3.select("#test").append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
var g = vis.selectAll("g")
.data([ { x:1 , y: 2} ])
.enter().append("svg:g");
g.append("svg:path")
.attr("fill", "red")
.attr("stroke", "red")
.attr("stroke-width", "10")
.attr("d", "M 100 350 l 150 -300")
g.select("path")
.on("click", function() { console.log("Hello"); });
// XXX: how to execute click programmaticaly?
})
</script>
<div id="test"></div>
</body>
</html>
But doesn't work
I think we may use https://github.com/mbostock/d3/wiki/Internals#wiki-dispatch_on
But how to do it?
not sure why, but there appears to be a discrepancy with the way jQuery and d3 handle events that causes a jQuery induced click event $("#some-d3-element").click() to not dispatch to the d3 element.
a workaround:
jQuery.fn.d3Click = function () {
this.each(function (i, e) {
var evt = new MouseEvent("click");
e.dispatchEvent(evt);
});
};
and then call it:
$("#some-d3-element").d3Click();
Simply call the .on method as a getter for the registered value (i.e. your handler function), then call the result of that:
g.select("path").on("click")();
It gets a little more complicated if your handler uses the bound data and/or event fields, or if you've got multiple event listeners bound (e.g "click.thing1" and "click.thing2"). In that case, you're probably best off just firing a fake event using the standard DOM methods:
var e = document.createEvent('UIEvents');
e.initUIEvent('click', true, true, /* ... */);
g.select("path").node().dispatchEvent(e);
With D3 v4 you will likely want this:
d3.select('#some-id').dispatch('click');
Ref.: https://github.com/d3/d3-selection#selection_dispatch
This works. I'm using pie charts, so I'm selecting all the "selected" pie slices, and for each of them, retrieving the attached "click" callback (that I have attached in another portion of code not included here, using d3's .on() method) and then invoking with the expected parameters in the correct context.
d3.selectAll("g.selected").each(function(d, i) {
var onClickFunc = d3.select(this).on("click");
onClickFunc.apply(this, [d, i]);
});
This answer might be somewhat unrelated - but hopefully useful to someone searching for how to invoke a click event of a SVG element - since jQuery $(mySvgElement).trigger("click") won't work.
This is how you would programmatically trigger/invoke/raise a click event for a SVG element:
var ev = document.createEvent("SVGEvents");
ev.initEvent("click",true,true);
var target = $("svg>g>path[fill='#0011cc']").get(0); // get the SVG element here
target.dispatchEvent(ev); // like $(target).trigger('click') - but working!
I came this thread looking for a d3 mousemove event for angular unit testing.
#natevw answer
g.select("path").on("click")();
helped a lot on mouseover event. But, applying that to mousemove was giving an e.source null error.
The work around was to set the d3 event programmatically.
d3.event = document.createEvent('MouseEvent');
d3.event.initMouseEvent("mousemove");
d3.select(elm[0]).select("rect").on("mousemove")();
Hope this helps.
You can go super manual by getting the mouse event and passing it the arguments that d3 would otherwise provide for you. This gives you a fairly clean way to do it while still using d3 constructs. For a single element use the following:
var path = g.select('path');
path.on('click').call(path.node(), path.datum());
For multiple elements, you can trigger each one in turn:
g.selectAll('path').each(function(d, i) {
d3.select(this).on('click').apply(this, arguments);
});
The latter can also be used for a single element if your selector is specific enough, or if you use .select() instead of .selectAll() to only return the first element.
#handler answer did not work for me entirely. It would click the svg element but additional simulated events would not register. This is what worked for me:
function eventFire(el, etype){
if (el.fireEvent) {
el.fireEvent('on' + etype);
} else {
var evObj = document.createEvent('Events');
evObj.initEvent(etype, true, false);
el.dispatchEvent(evObj);
}
}
Usage:
eventFire(document.getElementById(element_id), 'click');
This is how I do it.
g.selectAll("path").on("click", function(d, i){
my_function(d, i);
});
I've found the the callbacks work with anonymous functions. So for the code above, any path that is clicked will call my_function and pass in the current datum d and index i of the path that was clicked.
I find next workaround:
d3.selectAll("path").each(function(d, i) {
onClickFunc.apply(this, [d, i]);
});
Where d is data and i is index this data
Try g.select("path").trigger("click")

Categories

Resources