Point coordinate translate to specific Surface in Famo.us - javascript

We have a pretty complex web app built in meteor. The UI is mainly in nested HTML elements. Now we are trying to rewrite the UI with Famo.us so we can have better performance as well as adding great animation effects. One feature in our app is, when user drag on top of an element A, we need to draw a new element B based on the precise position of the mouse events in B. That is, we need to calculate the coordinate of a point in any elements, even the element has complex transforms. We were using the 'webkitConvertPointFromPageToNode' function in webkit browsers(we only support webkit.) to do the job. Does Famo.us has a similar function so I can calculate a point coordinate in a specific Surface? Or do you have any suggestions on how to accomplish such features with current API?
Thanks

Given that the transforms in Famo.us are all backed by absolute positioning, finding the coordinates in any given surface is pretty straightforward. In the Event object you can grab the offsetX and offsetY of the target surface.
Check out this example..
Hope it helps!
var Engine = require('famous/core/Engine');
var Surface = require('famous/core/Surface');
var StateModifier = require('famous/modifiers/StateModifier');
var Transform = require('famous/core/Transform');
var context = Engine.createContext();
var surface = new Surface({
size:[200,200],
properties: {
backgroundColor:'green',
color:'white',
textAlign:'center',
lineHeight:'200px'
}
})
surface.on('mousemove',function(e){
surface.setContent("x: "+e.offsetX+", y: "+e.offsetY);
})
surface.state = new StateModifier({
transform: Transform.translate(100,100,0)
})
context.add(surface.state).add(surface);

I have found the right way to do this.
First, I dug into the problem mentioned in my comment that the offsetX/offsetY value is actually based on the child surfaces. Because offsetX/offsetY values are generated by DOM's MouseEvent and copied into famo.us with no modification. DOM doesn't provide the coordinate of the mouse point on the 'currentTarget'. It only provide the value for 'target', which is the element the event occurs. So we can only use the clientX/clientY coordinate in the viewport, then calculate the coordinate of that point on the target element. No official API to do the calculation either. Only webkit provide the 'webkitConvertPointFromPageToNode' api to do it because the layout engine knows all about the position and transforms on a specific element.
But then I realise that with Famo.us, we know the transforms of each surface! In the render tree, all the modifiers on the path from root context to a RenderNode form the transform for that node and the nodes below. We can multiply them to get one transform matrix M. Then we can do a coordinate system transformation to calculate the point's right coordinate in the node's local coordinate system.
But Famo.us doesn't have direct API to get all the modifiers for a node, I did it myself in my code. I would suggest Famo.us to add a 'parent' reference on each RenderNode, then we can get them easily for any node.

It took me a while but this work for me:
var myX=event.clientX;
var myY=event.clientY;
for(var i=0;i<event.path.length;i++)
{
if(event.path[i].style===undefined)
continue;
var matrix=event.path[i].style.transform;
var matrixPattern = /^\w*\((((\d+)|(\d*\.\d+)),\s*)*((\d+)|(\d*\.\d+))\)/i;
if (matrixPattern.test(matrix)) {
var matrixCopy = matrix.replace(/^\w*\(/, '').replace(')', '');
myX-=matrixCopy.split(/\s*,\s*/)[12];
myY-=matrixCopy.split(/\s*,\s*/)[13];
}
}
Tested with align and size modifier

Related

Get "absolute" CSS transformation matrix

I am implementing a set of custom elements that will be used like this:
<om-root>
...
<om-node id="node1">
...
<om-node id="node2">
...
</om-node>
...
</om-node>
...
<om-root>
That is, my <om-node> elements will be mixed in with arbitrary HTML, which may have positioning and/or CSS transform applied.
The purpose of these <om-node> elements is to apply CSS affine transformations to their content based on various conditions. But regardless of its position in the hierarchy, each om-node computes a transformation relative to the root node.
I can't just apply the computed transformation to the node, because the browser will combine that with the transformations of all its ancestor elements: if I rotate node1 by 30 degrees, then node2 will also be rotated by 30 degrees before its own transformation is applied.
Ideally, what I want is something that works like Element.getClientRects(), but returns a matrix rather than just a bounding box. Then I could do some math to compensate for the difference between the coordinate systems of the <om-node> and <om-root> elements.
This question is similar to mine, but doesn't have a useful answer. The question mentions using getComputedStyle(), but that doesn't do what is claimed – getComputedStyle(elt).transform returns a transformation relative to the element's containing block, not the viewport. Plus, the result doesn't include the effects of "traditional" CSS positioning (in fact it doesn't have a value at all for traditionally-positioned elements).
So:
Is there a robust way to get the transformation matrix for an element relative to the viewport?
The layout engine obviously has this info, and I'd prefer not to do a complicated (and expensive) tree-walking process every time anything changes.
Having thought some more about the question, it occurred to me that, in fact, you can solve the problem using getBoundingClientRect().
Of course, getBoundingClientRect() on its own does not tell you how an element has been transformed, because the same bounding box describes many possible transformations:
However, if we add three child elements, with a known size and position relative to the parent, then we can figure out more information by comparing each of their bounding boxes. The figure below shows where I have placed these three "gauge" elements (which in practice are empty and invisible):
The vectors u̅ and v̅ are orthogonal unit vectors of the parent element's untransformed coordinate system. After the element has been transformed by various CSS positioning and transform properties, we first need to find the transformed unit vectors u̅' and v̅'. We can do that by comparing the bounding boxes of the three gauge elements – the diagram below shows the process with two different example transformations:
the vector from box 1 to box 2 is equivalent to u̅'
the vector from box 1 to box 3 is equivalent to v̅'
the midpoint between [the top left of box 3] and [the bottom right of box 2] gives us point P: this is the transformed position of the parent element's origin
From these three values u̅', v̅' and P we can directly construct a 2D affine transformation matrix T:
This matrix T represents all the transformations affecting the parent element – not just CSS transform rules, but also "traditional" positioning, the effects of margins and borders, etc. And, because it's calculated from getBoundingClientRect(), it is always relative to the viewport – you can compare any two elements directly, regardless of their relationship within the DOM hierarchy.
Note: all this assumes we are only dealing with 2D affine transformations, such as transform:rotate(30deg) or left:120px. Dealing with 3D CSS transforms would be more complicated, and is left as an exercise for the reader.
Putting the above into code form:
class WonderDiv extends HTMLElement {
constructor () {
super();
this.gauges = [null, null, null];
}
connectedCallback () {
this.style.display = "block";
this.style.position = "absolute";
}
createGaugeElement (i) {
let g = document.createElement("div");
// applying the critical properties via a style
// attribute makes them harder to override by accident
g.style = "display:block; position:absolute;"
+ "margin:0px; width:100px; height:100px;"
+ "left:" + ( ((i+1)%2) ? "-100px;" : "0px;")
+ "top:" + ( (i<2) ? "-100px;" : "0px;");
this.appendChild(g);
this.gauges[i] = g;
return g;
}
getClientTransform () {
let r = [];
let i;
for (i=0; i<3; i++) {
// this try/catch block creates the gauge elements
// dynamically if they are missing, so (1) they aren't
// created where they aren't needed, and (2) they are
// restored automatically if someone else removes them.
try { r[i] = this.gauges[i].getBoundingClientRect(); }
catch { r[i] = this.createGaugeElement(i).getBoundingClientRect(); }
}
// note the factor of 100 here - we've used 100px divs
// instead of 1px divs, on a hunch that might be safer
return DOMMatrixReadOnly.fromFloat64Array(new Float64Array([
(r[1].left - r[0].left) / 100,
(r[1].top - r[0].top) / 100,
(r[2].left - r[0].left) / 100,
(r[2].top - r[0].top) / 100,
(r[1].right + r[2].left) /2,
(r[1].top + r[2].bottom) /2
]));
}
}
customElements.define("wonder-div", WonderDiv);
– the custom <wonder-div> element extends <div> to have a getClientTransform() method, which works like getClientBoundingRect() except that it returns a DOMMatrix instead of a DOMRect.
CSS Transformations are actually relatively heavy operations and do come with some gotchas.. (they TRANSFORM elements) so you may not be able to avoid traversing the nodes without implementing an intelligent state system, for example, storing all your objects + transformation in your javascript class..
That said, one easy workaround for small use cases is to disable transform on all the parent elements using something like 'inline' but this is not suitable for all cases..
<div id="outside">
<div id="inside">Absolute</div>
</div>
document.getElementById('outside').style.display = "inline";
The more robust approach is to retrieve and parse the computedStyles dynamically ...
function getTranslateXY(element) {
const style = window.getComputedStyle(element)
const matrix = new DOMMatrixReadOnly(style.transform)
return {
translateX: matrix.m41,
translateY: matrix.m42
}
}
Then you can dynamically set new transformations on any node by adding/subtracting from the current transformation state.

How to get the position X and Y from a polygon element SVG With javascript? [duplicate]

I'm using the SVG located at http://upload.wikimedia.org/wikipedia/commons/3/32/Blank_US_Map.svg in a project and interacting with it with d3.js. I'd like to create a click to zoom effect like http://bl.ocks.org/2206590, however that example relies on path data stored in a JSON object to calculate the centroid. Is there any way to load path data in d3 from an existing SVG to get the centroid?
My (hackish) attempt so far:
function get_centroid(sel){
var coords = d3.select(sel).attr('d');
coords = coords.replace(/ *[LC] */g,'],[').replace(/ *M */g,'[[[').replace(/ *z */g,']]]').replace(/ /g,'],[');
return d3.geo.path().centroid({
"type":"Feature",
"geometry":{"type":"Polygon","coordinates":JSON.parse(coords)}
});
}
This seems to work on some states, such as Missouri, but others like Washington fail because my SVG data parsing is so rudimentary. Does d3 support something like this natively?
The D3 functions all seem to assume you're starting with GeoJSON. However, I don't actually think you need the centroid for this - what you really need is the bounding box, and fortunately this is available directly from the SVG DOM interface:
function getBoundingBoxCenter (selection) {
// get the DOM element from a D3 selection
// you could also use "this" inside .each()
var element = selection.node();
// use the native SVG interface to get the bounding box
var bbox = element.getBBox();
// return the center of the bounding box
return [bbox.x + bbox.width/2, bbox.y + bbox.height/2];
}
This is actually slightly better than the true centroid for the purpose of zooming, as it avoids some projection issues you might otherwise run into.
The accepted answer was working great for me until I tested in Edge. I can't comment since I don't have enough karma or whatever but was using this solution and found an issue with Microsoft Edge, which does not use x or y, just top/left/bottom/right, etc.
So the above code should be:
function getBoundingBoxCenter (selection) {
// get the DOM element from a D3 selection
// you could also use "this" inside .each()
var element = selection.node();
// use the native SVG interface to get the bounding box
var bbox = element.getBBox();
// return the center of the bounding box
return [bbox.left + bbox.width/2, bbox.top + bbox.height/2];
}
From here
The solution is to use the .datum() method on the selection.
var element = d3.select("#element");
var centroid = path.centroid(element.datum());

Layout DirectedGraph (dagre) only on a subset of nodes

I'm looking for a way to layout only a subset of nodes of a directed graph with JointJS / Rappid diagramming library.
I need some "fixed" nodes in the graph and layout the "others", assuming that they can be connected between each other or with some of the fixed nodes (the graph is already added into the paper).
Since the joint.layout.DirectedGraph.layout API must be used on a graph object, I was wondering if there is any mechanism to have some nodes of the graph "fixed" during the layout calculation (some proprety to add in the cell object, for example).
Also something like this can be fine, but no incoming and outgoing links should be retrieved by the getSubgraph API
var subGraph = graph.getSubgraph([A, B]);
joint.layout.DirectedGraph.layout(subGraph, layoutOpt);
Looking at the docs I was not able to identify this kind of feature.
If this feature is not supported, there is any other approach that I could use to achieve my goal? (Of course I can also layout the entire graph and apply my fixed chords when the operation ends, but I was looking for something better than this).
So I'm pretty sure that the auto layout functionality using Dagre is now only available in the Rappid version of joint js (i.e. paid for). What I do is use Dagre separately to perform the layout calculations and then iterate back through the elements and use the Dagre output to change their position manually. Not ideal, however it does allow you to do whatever you want in terms of only looking at a subset of nodes. Basic code below (graphObj is the jointjs graph object), you should be able to use this as a starting point if you wanted to work with a subset of elements and links:
var nodes = [];
var edges = [];
var elements = graphObj.getElements();
elements.forEach(function(element){
element.label = element.id;
element.width = element.attributes.size.width;
element.height = element.attributes.size.height;
});
var links = graphObj.getLinks();
links.forEach(function(link){
edges.push({source: link.getSourceElement(), target: link.getTargetElement()});
});
dagre.layout()
.nodeSep(150)
.edgeSep(100)
.rankSep(150)
.rankDir("LR")
.nodes(elements)
.edges(edges)
.run();
elements.forEach(function(element){
element.position(element.dagre.x, element.dagre.y);
element.attributes.prop.metadata.x = element.dagre.x;
element.attributes.prop.metadata.y = element.dagre.y;
});

Scaling Raster with Paper.js using Tween.js

I'm think I'm having a similar issue as this in that I can not work out (or know if it exists) whereby I can get access to the scaling applied to a given object (in my instance, a raster).
I need to know this so I can animate the scaling via Tween.js.
Anyone have any ideas or know if indeed it is possible to find out the current scaling applied to a raster (or any) object?
I thought it was an issue with Rasters so I tried tweening the scale property of a Path and then a Group and I couldn't get access to the values in order to animate it.
Because I am using Tween.js I can not simply use the object.scale(value) function.
UPDATE
I even tried applying an arbitrary (animated) number to the scale function and it failed to work... i.e.:
object.scale( 0 );
object.arbitraryNumber = 0;
createjs.Tween.get( object )
.to( { arbitraryNumber:1 } , 1000, createjs.Ease.getPowInOut(2) )
.addEventListener( "change", function( event ) {
event.target.target.scale( event.target.target.arbitraryNumber);
} );
Although this did not work, when the same approach was applied to the x position of the object, it animated fine.
Is there anything that needs to be flagged in order to update scaling of an object?
When calling Item.scale() method on each frame with values from 0 to 1, you are actually scaling down item exponentially because each call scales the item relatively to the previous value.
What you want to do is animate the Item.scaling property instead.
You also have to know that by default, PaperJS use global coordinates system and apply every transformations directly to points.
You can change this behavior by setting Item.applyMatrix property to false.
Doing this, scale change will affect item matrix instead of affecting points coordinates and you will be able to animate it as you expect.
Here is simple Sketch of a scale animation:
var circle = new Path.Circle(view.center, 50);
circle.fillColor = 'orange';
circle.applyMatrix = false;
function onFrame(event)
{
circle.scaling = Math.sin(1 + event.count * 0.05);
}
You should be able to transpose this example to your Tween.js context easily.

How can I stop elements overlapping using JavaScript and the Raphael JavaScript library

I’m generating multiple, random sized, circular elements using the Raphael JavaScript library but because it’s random a lot of the circular elements being generate overlap or cover each other. What I wanted to know, is there any way with JavaScript to tell if one element is in already in particular position so to avoid the overlapping? Essentially, I want to create random elements on a canvas, of a random size that don’t overlap or cover each other.
There's a couple of test files I created here to give you an idea of what I'm doing. The first one generates random objects and the second link sets them to a grid to stop the overlapping.
http://files.nicklowman.co.uk/movies/raphael_test_01/
http://files.nicklowman.co.uk/movies/raphael_test_03/
The easiest way is to create an object and give it a repulsive force that degrades towards zero at it's edge. As you drop these objects onto the canvas the objects will push away from each other until they reach a point of equilibrium.
Your examples aren't working for me, so I cannot visualize your exact scenario.
Before you "drop" an element on the canvas, you could query the positions of your other elements and do some calculations to check if the new element will overlap.
A very simple example of this concept using circle elements might look like this:
function overlap(circ1, circ2) {
var attrs = ["cx", "cy", "r"];
var c1 = circ1.attr(attrs);
var c2 = circ2.attr(attrs);
var dist = Math.sqrt(Math.pow(c1.cx - c2.cx ,2) + Math.pow(c1.cy - c2.cy, 2));
return (dist < (c1.r + c2.r));
}
var next_drop = paper.circle(x, y, r);
for (var i in circles) {
if (overlap(next_drop, circles[i])) {
// do something
}
}
Of course calculating just where you're going to place a circle after you've determined it overlaps with others is a little more complicated.

Categories

Resources