Create reversed mxHierarchical layout in mxgraph - javascript

Greetings to the community! I am currently creating a custom mxgraph editor and I am converting the graphs using different layouts. I would like to convert my graph layout into an mxHierarchicalLayout but reversed (instead of top-to-down I would like it to be down-to-top). Example of what I do and what I would like to do.
My graph
My graph converted with mxHierarchicalLayout
Code snippet:
let layout = new mxHierarchicalLayout(graph);
layout.execute(graph.getDefaultParent());
How I want to convert the graph
I found in the mxHierarchicalLayout that there is an orientation member variable which is by default 'NORTH'. However when I tried to do the following
let layout = new mxHierarchicalLayout(graph);
layout.orientation=mxConstants.DIRECTION_SOUTH;
layout.execute(graph.getDefaultParent());
The graph went out of my "canvas" and cannot be seen to check if this is the responsible parameter to "reverse" the tree. Could anyone help ? Thanks in advance.
PS
When I use layout.orientation = mxConstants.DIRECTION_SOUTH;
it seems that the layout is converted as I want to but cannot be seen because the coordinates of the svg elements are of type x=-10, y=-5 (negatives), any workaround for this?

SOLVED
I do not know why mxgraph has that buggy behaviour with orientation = south in mxHierarchicalLayout but I managed to create a workaround solution for my problem since I realized that the newly generated layout put my mxCell children objects of the graph to the north ( negative y coordinate). So what I did was after layout.execute(graph.getDefaultParent()); I get each and every child of the graph and retrieve the most negative y coordinate and then move all the cells of the graph from their new coordinates to a new incremented by the absolute value of the most negative y-coordinated element.
Code
function convertGraphToInverseHorizontalTree(){
let layout = new mxHierarchicalLayout(graph);
layout.orientation = mxConstants.DIRECTION_SOUTH;
layout.execute(graph.getDefaultParent());
graph.model.beginUpdate();
//get the most negative y
let mostNegativeY = getMostNegativeCoordinateY(graph);
let children = graph.getChildCells();
graph.moveCells(children, undefined , Math.abs(mostNegativeY));
graph.model.endUpdate();
}
function getMostNegativeCoordinateY(graph){
let children = graph.getChildCells();
let mostNegative = 10000;
for(let i = 0; i < children.length; i++){
if(children[i].geometry != undefined){
if(children[i].geometry.y < mostNegative){
mostNegative = children[i].geometry.y;
}
}
}
return mostNegative;
}
Now the graph is like this

Related

MxGraph JavaScript: How to calculate edge length?

I am looking for a way to calculate the length of the edges in one graph.
As an example this image:
The connecting edge contains three parts, (a,b and c)
I have no idea how to retrieve this information that I can sum up the distances of a + b + c.
On a more complexe graph I want to calculate the length for each edge. There I would loop through all models of the graph, check with .isEdge if it is an edge and then calculate the length of each edge.
let cells = graph.getModel().cells;
for(let key in cells){
let mxCell = cells[key];
if(mxCell.isEdge())
{
calcLength(mxCell)
} }
calcLength() is the function I need. This one should return the length of the edge.
I used the helloPorts example from jGraph.
Thanks in advance!!
Back again,
together with a friend we found the solution. The information is stored under the graphView in the state as the length.
graphView.getState(mxCell).length
This is part of the mxCell object. If you are looking for the Euclidian length of an edge, this is stored under terminalDistance.
graphView.getState(edge).terminalDistance
The code would look like this to access it:
let cells = graph.getModel().cells;
let graphView = graph.getView();
// The getModel needs to be triggered before the loop otherwise the the mxCell state is undefined
graph.getModel().endUpdate();
for(let key in cells){
let mxCell = cells[key];
if(mxCell.isEdge())
{
let state = graphView.getState(mxCell).length;
}
}

Programatically create skeleton in Three.js

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 :)

Cytoscape.js zigzag like edges

I am trying to create a zigzag like edges in cytoscape.js. I have found zigzag edges on github but I guess it's not yet implemented or it's abandoned, so I am trying to calculate the points but I am having some trouble.
My graph looks like this:
I assume that there are 3 cases here. One when the source is on the left of the target, other when the source and target have same x value and the third case when the source is on the right of the target.
The second case is basic and I have calculated it (just a straight line from source to the target) but I am having trouble calculating the other two cases.
I am trying to make the edges look like this:
I am using the following function to try to calculate them:
cy.elements('edge').css({'segment-distances':
function(ele){
var target = ele.target();
var source = ele.source();
var source_id = source.data().id;
var target_id = target.data().id;
var sou_pos_x = (cy.$('#' + source_id).position('x'));
var tar_pos_x = (cy.$('#' + target_id).position('x'));
var sou_pos_y = (cy.$('#' + source_id).position('y'));
var tar_pos_y = (cy.$('#' + target_id).position('y'));
var sou_tar_distance_x = (sou_pos_x - tar_pos_x);
var sou_tar_distance_y = (sou_pos_y - tar_pos_y);
// source on the left of the target
if (sou_pos_x < tar_pos_x){
return 0;
}
// source on same position as target
else if (sou_pos_x == tar_pos_x){
return 0;
}
// source on the right of the target
else{
return 0;
}
}
});
From what I have read from the documentation on segments-edges, I should have 2 points.
The calculation would have been a lot easier if you could specify coordinates for each segment point. With the way that is right now it's hard to calculate the values perpendicular to the line formed from the source to the target because those values might vary depending how far apart is the source from the target node
Can anyone help me with this issue or at least give me a guidance on how to achieve what I am looking for? I have been stuck with this problem for quite some time.
EDIT
I have added:
'source-endpoint': '180deg',
'target-endpoint': '0deg'
so now the all the edges start and end from same position, which would simplify the calculation.
EDIT 2:
This has been implemented by taxi edges like this

Chart.js: Get point index from chart.getPointsAtEvent(e)

I am looking to make my chart (made with Chart.js) a little more interactive and I would like to get a point's index (of its dataset) with the following code:
canvas.onclick = function(e) {
const points = chart.getPointsAtEvent(e);
// something like `point.getIndex()` would be great so that I know where this point is in the original dataset
};
Anybody have a good solution for this?
You should be able to do an indexOf (instead of a label lookup) because it refers to the exact same object in the points collection
canvas.onclick = function (evt) {
var points = chart.getPointsAtEvent(evt);
alert(chart.datasets[0].points.indexOf(points[0]));
};
Also, for a multi series line chart getPointsAtEvent(evt) returns points from all datasets. So the same code would work irrespective of how many datasets you have or which of the datasets you click on.
Fiddle - http://jsfiddle.net/yxz2sjam/

Generic: Naming structure and drawing KineticJS

This one takes a bit more to describe, sorry for the shorter title.
I currently do not have my code in front of me but might update this with the full details of how it works and where the problem is located.
Basically I notice that without doing a more or less global name (window.whatever) for the shapes/groups it will never draw them. I have done logs of the objects to see if they are proper and have seen nothing wrong with them.
The full scope is a layer first, that is passed to a function that i then create shapes and groups in a logical way, without passing the groups back and instead sending the layer as a parameter to add it from within the function. When I do it this way it seems that I can never get it to draw to the container (stage).
To add to it, I am looping to create the shapes, as I am making them variable and more percentage based then exact pixels, so I am holding the objects in an array that is generated through the loop.
I have done this flat first and it worked, however after moving it to the function it stopped working, is there any reason for this I may be missing?
Another note if it is relevant, I am using Adobe AIR.
If anyone has any ideas let me know, I will post the actual code when I can (few hours from this posting).
UPDATE:
Basically the issue I am having is that when i separate the shapes/groups into their own function it does not want to draw them at all. I have tried to call the draw function inline and also just add them to the stage later, both to my understanding will trigger the draw.
here is the code
var forms = {
selector:function(params,bind){
//globals
var parent,obj,lastWidth=0,items=[];
//setup defaults
var params = params || {};
params.x = params.x || 0;
params.y = params.y || 0;
params.active = params.active || 0;
params.height = params.height || 200;
params.children = params.children || [{
fill:'yellow'
}];
params.width = params.width || bind.getWidth();
params.margins = params.margins || 5;
//container for selector
parent = new Kinetic.Group({
x:params.x,
y:params.y,
height:params.height,
width:params.width
});
air.Introspector.Console.log(params);
var totalMargins = params.margins*(params.children.length+1),
activeWidth = (params.width-totalMargins)/2,
subItems = params.children.length-1,
subWidth = activeWidth/subItems,
itemHeight = (params.height-params.margins)/2;
//loop all children
for(var i=0;i<params.children.length;i++){
//use an array to store objects
items[i]={};
//create default object for rectangle
obj = {};
obj.y = params.margins;
obj.fill = params.children[i].fill;
if(params.active==i){
obj.width = activeWidth;
}else{
obj.width = subWidth;
}
obj.x = params.margins+lastWidth;
obj.height = itemHeight;
lastWidth = lastWidth+(params.margins+obj.width);
//create group for text
items[i].text = new Kinetic.Group({
x:0,
y:itemHeight+params.margins
});
//create box for text
items[i].box = new Kinetic.Rect({
x: params.margins,
y: params.margins,
width:params.width-(params.margins*2),
height:(params.height-itemHeight)-(params.margins*2),
fill:'yellow'
});
air.Introspector.Console.log(items[i].box);
//add box to text groups
items[i].text.add(items[i].box);
//create item
items[i].item = new Kinetic.Rect(obj);
//add groups to parent
parent
.add(items[i].item)
.add(items[i].text);
}
//add parent to the bound container
bind.add(parent);
}
}
From your question, I'm assuming bind is your Kinetic.Layer.
Make sure bind has been added to the stage: stage.add(bind);
After adding your groups/rects to bind, also do bind.draw();
I have since figured it out.
The issue is that the layer must be added to the stage before anything else is added it it. It was not in my code because I did not see it as an issue as it worked elsewhere.
Basically if you add anything to a layer, then add the layer to the stage it will fail. it must go like this:
CREATE STAGE
CREATE LAYER
ADD LAYER TO STAGE
ADD GROUPS TO LAYER
DRAW IF NEEDED
The way it was done originally is like so:
CREATE STAGE
CREATE LAYER
ADD GROUPS TO LAYER
ADD LAYER TO STAGE
DRAW IF NEEDED
The question will be updated to make this apparent, this is something that is not in the documentation as a problem (as far as I have seen) and I can see it as confusing some.

Categories

Resources