I'm having issues with creating a subpath on a path that has been transformed. It seems that the subpath is generated with the original position of the path in reference rather than the post transform location of the path. How do I get around this?
var paper = new Raphael("canvas");
var lineParams = {
fill: "#ff0",
gradient: "90-#526c7a-#64a0c1",
stroke: "#3b4449",
"stroke-width": 2
};
var line1 = paper.path("M 0 50 h 200").attr(lineParams);
line1.transform("T100,100");
if (line1 != null) {
var lineBBox = line1.getBBox()
var lineEnd = lineBBox.x2 - lineBBox.x;
var lineTipStart = lineEnd - 10;
var lineTipString = line1.getSubpath(lineTipStart, lineEnd);
var lineTip = paper.path(lineTipString);
lineTip.attr({
"stroke-width": 2,
stroke: '#FF0000'
});
}
http://jsfiddle.net/nzwthkmo/1/
Its not so much a case of getting around it, as realising what its doing, and deciding how you want to approach it.
The original path has a transform applied to it, the path is still exactly the same. So do you want the same thing (maybe your code will be expecting that), or do you want a new modified path with new baked coordinates with the transform in place.
So depending on what you will do in the future with it, you could either
a) Simply apply the same transform to the subpath...
var lineTip = paper.path(lineTipString).transform("T100,100");
or
b) create a new path where the coordinates are all permanently transformed...
var lineTip = paper.path( Raphael.transformPath(lineTipString, "T100,100") )
fiddle with both examples (one commented out).
Related
I have a long array with data that I slice with Javascript in order to display data of different date ranges in my chart. This way the backend only needs to get the data once, and I can the just slice it on the client side.
// All data
var allLabels = [
// data here
];
var allData = [
// data here
];
Then I do:
var labelsCount = allLabels.length;
var dataCount = allData.length;
var updatedLabels;
var updatedData;
if($date_range === 'last_7_days')
{
updatedLabels = allLabels.slice(labelsCount - 7);
updatedData = allData.slice(labelsCount - 7);
}
if($date_range === 'last_30_days')
{
updatedLabels = allLabels.slice(labelsCount - 30);
updatedData = allData.slice(labelsCount - 30);
}
scoreChart.data.labels = updatedLabels;
scoreChart.data.datasets[0].data = updatedData;
scoreChart.update({
duration: 1000,
easing: 'easeInOutExpo'
});
This all works as expected. When switching from 30 to 7 days the points on the right of the 7 days disappear, and the graph scales and grows nicely to the new 7 days x-axis.
The other way around, when you have the graph of 7 days and then switch to 30, produces an ugly visual effect where the first point of the graph sticks to the side, overlaps the new data points and then animates.
After the animation the graph looks as expected, it's just the animation that's ugly. It's a little tricky to explain so hopefully the screenshots help. Green arrows indicate the animation direction. I've set the animation duration to 10s so I can take this screenshot, the red circle highlights the point that starts on the right of the graph and then animates to the left.
I've also tried adding this:
scoreChart.data.labels.pop();
scoreChart.data.datasets[0].data.pop();
scoreChart.update();
and this:
scoreChart.data.labels = [];
scoreChart.data.datasets[0].data = [];
scoreChart.update();
Before the line scoreChart.data.labels = updatedLabels; but that gives the same result.
Another thing I can do is only update the labels. The result is that the chart just zooms on the timeline when changing date ranges, without the nice animation as they have in the example.
You could try to first remove all labels and the data when switching to 'last_30_days'.
if($date_range === 'last_30_days')
{
scoreChart.data.labels = [];
scoreChart.data.datasets[0].data = [];
scoreChart.update({
duration: 500,
easing: 'easeInOutExpo'
});
updatedLabels = allLabels.slice(labelsCount - 30);
updatedData = allData.slice(labelsCount - 30);
}
I've been trying to remove elements (balls) that have been added to the Physics engine, but I can't find a way to do it.
This is the code I'm using to add the molecules to the Physics Engine:
var numBodies = 15;
function _addMolecules() {
for (var i = 0; i < numBodies; i++) {
var radius = 20;
var molecule = new Surface({
size: [radius * 2, radius * 2],
properties: {
borderRadius: radius + 'px',
backgroundColor: '#'+(0x1000000+(Math.random())*0xffffff).toString(16).substr(1,6)
}
});
molecule.body = new Circle({
radius: radius,
mass: 2
});
this.pe.addBody(molecule.body);
this.molecules.push(molecule);
this.moleculeBodies.push(molecule.body);
molecule.state = new Modifier({origin: [0.5, 0.5]});
//** This is where I'm applying the gravity to the balls and also where I'm checking the position of each ball
molecule.state.transformFrom(addBodyTransform.bind(molecule.body));
this._add(molecule.state).add(molecule);
}
}
and on the addBodyTransform function I'm adding the gravity to the balls and checking their position, and for any that are outside the top part of the viewport I want to remove it completely (I'm only using walls on the left, right and bottom edges of the viewport).
function addBodyTransform() {
var pos;
for (var i = 0; i < thisObj.moleculeBodies.length; i++) {
pos = thisObj.moleculeBodies[i].getPosition();
if(pos[1]<(-windowY/2)){
//I tried this but it doesn't work
thisObj.pe.removeBody(thisObj.moleculeBodies[i]);
thisObj.moleculeBodies[i].render = function(){ return null; };
}
}
thisObj.gravity.applyForce(this);
return this.getTransform();
}
It doesn't work. I tried a couple of other things, but no luck. Whereas changing the position of the balls on the function above worked fine:
thisObj.moleculeBodies[i].setPosition([0, 0]);
Does anybody have any idea how to remove a body (a circle in this case)?
P.S.: thisObj is the variable I'm assign the "this" object to in the constructor function and thisObj.pe is the instance of the PhysicsEngine(). Hope that makes sense.
After some investigation, using the unminified source code and trying out different things, I realised that there was something weird going on in the library.
Having a look at the repository, I found out that the function _getBoundAgent is being used before it is defined, which matched with the error I was getting (you can check it here: https://travis-ci.org/Famous/physics). So it looks like it is a bug in the Famo.us source-code. Hopefully it will be fixed in the next release.
For the time being, I had to create a hack, which is basically detaching all agents (as well as gravity) from the balls that go outside the viewport and setting their (fixed) position far outside the viewport (about -2000px in both directions).
I know it is not the best approach (a dirty one indeed), but if you have the same problem and want to use it until they release a fix for that, here is what I did:
function addBodyTransform() {
var pos = this.body.getPosition();
//Check if balls are inside viewport
if(pos[1]<(-(windowY/2)-100)){
if(!this.removed){
//flagging ball so the code below is executed only once
this.removed = true;
//Set position (x and y) of the ball 2000px outside the viewport
this.body.setPosition([(-(windowX/2)-2000), (-(windowY/2)-2000)]);
}
return this.body.getTransform();
}else{
//Add gravity only if inside viewport
thisObj.gravity.applyForce(this.body);
return this.body.getTransform();
}
}
and on the _addMolecules function, I'm adding a "molecule.removed = false":
function _addMolecules() {
for (var i = 0; i < numBodies; i++) {
...
molecule.state = new Modifier({origin: [0.5, 0.5]});
//Flagging molecule so I know which ones are removed
molecule.removed = false;
molecule.state.transformFrom(addBodyTransform.bind(molecule));
this._add(molecule.state).add(molecule);
}
}
Again, I know it is not the best approach and I will be keen in hearing from someone with a better solution. :)
I'm trying to understand how sets work with transforms. Basically, I would like to have a 'container' set with all children in it, that I can move around the canvas.
I created a fiddle to show what I mean, this is a simplification of a larger drawing. http://jsfiddle.net/thibs/Hsvpf/
I've created 3 squares, red, black, blue. Each are added to a set and then they are added to a main container (set). I've added outlines to show canvas and set.
Red and black sets do not have transforms on them, but blue does. Blue remains in the 'container' set... until the container gets a transform.
Why is that? I thought that transforms were applied to all the chidlren of the set...?
Thanks in advance
Here is the fiddle code:
var paper = Raphael('holder');
var container = paper.set();
paper.rect(0, 0, '100%', '100%').attr({
stroke : 'red'
});
var rectRedSet = paper.set();
var rectRed = paper.rect(100, 10, 20, 20).attr({
'fill' : 'red',
'stroke-opacity' : 0
});
rectRedSet.push(rectRed);
container.push(rectRedSet);
var rectBlackSet = paper.set();
var rectBlack = paper.rect(150, 10, 20, 20).attr({
'fill' : 'black',
'stroke-opacity' : 0
});
rectBlackSet.push(rectBlack);
container.push(rectBlackSet);
var rectBlueSet = paper.set();
rectBlue = paper.rect(0, 0, 20, 20).attr({
'fill' : 'blue',
'stroke-opacity' : 0
});
rectBlueSet.push(rectBlue);
rectBlueSet.transform('t50,150');
container.push(rectBlueSet);
var containerBBox = container.getBBox();
paper.rect(containerBBox.x, containerBBox.y, containerBBox.width, containerBBox.height).attr({
stroke : 'black'
});
//trying to get the entire container and its children to move to 0,0
//commenting out the transform below will keep rectBlue in the container...?
container.transform('t0,0');
A "set" in Raphael is not like a "group" in SVG. A set in Raphael is just a collection of elements that you can manipulate at the same time. So when you set the transform on the container set, it is really just setting the transform on every element inside the set, overwriting any previous transform settings.
You can append or prepend to existing transformations in Raphael using "..." notation.You need to change your last line to:
container.transform("...t0,0")
But "t0,0" doesn't actually move anything anywhere. If you want to move the container so the top left corner is at 0,0, then you need to write:
container.transform('...t-' + containerBBox.x + ',-' + containerBBox.y);
I started to play a little bit with raphaeljs, however I'm having a small problem when dragging and applying a transformation to a Paper.set()
Here is my example: http://jsfiddle.net/PQZmp/2/
1) Why is the drag event added only to the marker and not the slider?
2) The transformation is supposed to be relative(i.e. translate by and not translate to), however if I drag the marker twice, the second dragging starts from the beginning and not from the end of the first.
EDIT:
After the response of Zero, I created a new JSFiddle example: http://jsfiddle.net/9b9W3/1/
1) It would be cool if this referenced the set instead of the first element of the set. Can't this be done with dragger.apply(slider)? I tried it, but only works on the first execution of the method (perhaps inside Raphael it is already being done but to the first element inside the set instead of the set)
2) According to Raphael docs the transformation should be relative to the object position (i.e. translate by and not translate to). But it is not what is happening according to the jsfiddle above (check both markers drag events).
3) So 2) above creates a third question. If a transform("t30,0") is a translation by 30px horizontally, how is the origin calculated? Based on attr("x") or getBBox().x?
The drag event is actually being added to both the marker and the slider -- but your slider has a stroke-width of 1 and no fill, so unless you catch the exact border, the click "falls through" to the canvas.
Behind that is another issue: the drag is being applied to both elements, but this in your drag handler references a specific element, not the set -- so both elements will drag independently from each other.
Lastly: the reason that each drag is starting from the initial position is because the dx, dy parameters in dragger are relative to the coordinates of the initial drag event, and your transform does not take previous transforms into account. Consider an alternative like this:
var r = new Raphael(0, 0, 400, 200);
var marker = r.path("M10,0L10,100").attr({"stroke-width": 5});
var button = r.rect(0, 0, 20, 20, 1).attr( { 'stroke-width': 2, fill: 'white' } );
var slider = r.set( marker, button );
var startx, starty;
var startDrag = function(){
var bbox = slider.getBBox();
startx = bbox.x;
starty = bbox.y;
console.log(this);
}, dragger = function(dx, dy){
slider.transform("t" + ( startx + dx ) + "," + starty );
}, endDrag = function(){
};
slider.drag(dragger, startDrag, endDrag);
To address your updates:
I believe you can specify the context in which the drag function will be executed as optional fourth, fifth, and six parameters to element.drag. I haven't tried this myself, but it looks like this should work great:
slider.drag( dragger, startDrag, endDrag, slider, slider, slider );
The transformation is relative to the object position. This works great for the first slider because its starting position is 0, but not so great for the second slider because...
...the transformation for min/max sliders should actually be relative to the scale, not the individual markers. Thus you will notice that your max slider (the red one) returns to its initial position just as you drag the mouse cursor back over the zero position. Make sense?
var position;
var rect = paper.rect(20, 20, 40, 40).attr({
cursor: "move",
fill: "#f00",
stroke: "#000"
});
t = paper.text(70,70, 'test').attr({
"font-size":16,
"font-family":
"Arial, Helvetica, sans-serif"
});
var st = paper.set();
st.push(rect, t);
rect.mySet = st;
rect.drag(onMove, onStart, onEnd);
onStart = function () {
positions = new Array();
this.mySet.forEach(function(e) {
var ox = e.attr("x");
var oy = e.attr("y");
positions.push([e, ox, oy]);
});
}
onMove = function (dx, dy) {
for (var i = 0; i < positions.length; i++) {//you can use foreach but I want to
// show that is a simple array
positions[i][0].attr({x: positions[i][1] + dx, y: positions[i][2] + dy});
}
}
onEnd = function() {}
I am hoping you might be able to help me determine if the following animation is allowed in raphael.js. I am trying to have an element fly off the page, the idea is to have it appear to fall/fly off in 3D. I am able to tell the element to rotate X degrees and slide off but its lacking the look of the element being independent of hte background. What I would like to do is be able to tell raphael to rotate the top corner "out" as it falls giving the illusion of it falling out of view as picture falling off of a wall. Is this even possible or does Raphael only operate in two dimensional space?
Raphael only deals with 2D space. To implement a 3D flip you have to fake it. Thankfully Raphael implements Scale(sx,sy,x,y) as a transform op, so you can scale about an origin to fake a 3D 'flip' rotation.
For example:
Raphael.el.flipXTransform = function (parentBbox) {
var x = this.getBBox().x;
var width = this.getBBox().width;
parentBbox = parentBbox || { width:width, x: x};
var parentWidth = parentBbox.width;
var parentX = parentBbox.x;
var originX = parentX - x + parentWidth / 2;
return 's-1,1,' + originX + ',0';
};
Raphael.el.flipX = function (duration, easing, parentBbox) {
duration = duration || 500;
easing = easing || 'easeInOut';
var scale = this.flipXTransform(parentBbox);
this.animate({ transform: '...' + scale }, duration, easing);
};
Here's a fiddle example for you to play with. The downside is this doesn't convey a perspective like a true 3D rotate does.