I'm using dagre-d3 to display directed graphs in a Javascript application, and its default node shape is a rectangle. I'd prefer ellipses, but it's not at all obvious to me if there's a programmatic way of changing the default (I'd rather not hack up NODE_DEFAULT_ATTRS in render.js, for example). Any suggestions? Thanks for your attention.
I did a little experimenting, and came up with an approach that doesn't seem too clumsy. Basically, I walked the graph's nodes and set their shapes before handing the graph off to dagreD3.render.
var g = graphlibDot.read(treeData);
g.nodes().forEach(function(v) {
var node = g.node(v);
node.shape = "ellipse";
});
var render = new dagreD3.render();
var svg = document.querySelector('#graphContainer');
render(d3.select("svg g"), g);
You can set the shape when you create the node:
g.setNode(id, { shape: 'ellipse' });
Related
I am loading a model of a mechanism (e.g. a robot arm) in Three.js. Sadly the models I am using don't have a skeleton, but I have the locations, axes and so on of the joints. In order to use e.g. inverse kinematic solvers like Three-IK, I want to create a skeleton from these parameters. Since I want to use many different models I would prefer to not create the skeletons by hand but in code.
I have been trying for over a week now to create a valid bone structure from these values that reflects the model, but nothing succeeded. For example, if I create a chain of bones using the positions of the joints I get a very long skeleton which in no way matches the positions I used.
let boneParent;
let bonepos = [];
let bones = [];
model.traverse(child => {
switch(child.type) {
case "joint":
let p = new Vector3();
child.getWorldPosition(p);
bonepos.push(p);
let bone = new Bone();
boneParent && boneParent.add(p);
bone.worldToLocal(p.clone());
bone.position.copy(p);
bone.rotation.copy(child.rotation);
bone.scale.copy(child.scale);
boneParent = bone;
bones.push(bone);
break;
}
});
showPoints(scene, bonepos, 0xff0000);
const skeletonHelper = new SkeletonHelper(bones[0]);
skeletonHelper.visible = true;
scene.add(skeletonHelper);
The code above results in the screenshot below. The red markers are the positions I get from the robot joints, the line snaking into the distance is the skeleton as visualized by the SkeletonHelper.
So my question is this: it seems like I don't understand well enough how bones are handled in Three.js. How can I create a skeleton that reflects my existing model from its joint locations and orientations?
Thanks in advance!
child.getWorldPosition(p);
I'm afraid it's incorrect to apply the position in world space to Bone.position which represents the position in local space.
boneParent = bone;
This line looks problematic, too. A bone can have multiple child elements. It seems to me that this use case is not considered of your code.
After some fiddling around I found a solution:
let root = new Bone();
let parent = root;
let pos = new Vector3();
for (let joint of robot.arm.movable) {
let link = robot.getLinkForJoint(joint);
link.getWorldPosition(pos);
let bone = new Bone();
parent.add(bone);
parent.lookAt(pos);
parent.updateMatrixWorld(); // crucial for worldToLocal!
bone.position.copy(bone.worldToLocal(pos));
parent = bone;
}
The important part is to call updateMatrixWOrld() after lookAt() so that bone.worldToLocal() works correctly. Also lookAt() saves a lot of matrix hassles :)
Ive been trying to make persistent tooltip by drawing a layer over the graph and track the tooltip based on mouse position. Unfortunately I kept getting an error on line xScaleFunc.invert(d3.mouse(this)[0]). Then i came across this custom invert code for scaleband scales. From here : https://bl.ocks.org/shimizu/808e0f5cadb6a63f28bb00082dc8fe3f .
The question is i'm not understanding whats going on here.I'm kinda new to D3 , any help would be much appreciated.
xScaleFunc.invert = (function(){
var domain = xScaleFunc.domain()
var range = xScaleFunc.range()
var scale = d3.scaleQuantize().domain(range).range(domain)
return function(x){
return scale(x)
}
})()
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());
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
As I'm a beginner, here's another question about D3.js.
I have constructed a force directed graph, just as a lot did.
All the nodes are stored in one array and all links in another.
Now I want the central node to be fixed in the middle of the svg and the others lingering around it.
Is there any possibility to cut only one node out of the force layout without it falling out of my net? Has anybody already made it?
You can set the fixed property of a node to true, then place it where you like - see this related question.
// explicity set node position
var fixedNode = graph.nodes[0];
fixedNode.fixed = true;
fixedNode.x = width/2;
fixedNode.y = height/2;
Working fiddle: http://jsfiddle.net/nrabinowitz/z2cye/