How to avoid network graph nodes overlapping? - vis.js

I was using Visjs and displaying rectanglar nodes with text. Some of the nodes can have a couple of lines of text so I added a heuristic algorithm to work out roughly where the line breaks should go to avoid very wide, single line chunks of text in very wide but very short nodes.
The trouble is, even with physics turned on, I still get overlapping nodes.
Is it possible to tell the layout engine that, under no circumstances (or physics models), should any two nodes overlap?

Well, check out the physics configuration example: as you can see, barnesHut solver has avoidOverlap property which prevents overlapping even when springConstant is equal to zero. Try this:
var options = {
"physics": {
"barnesHut": {
"springConstant": 0,
"avoidOverlap": 0.2
}
}
}
and tweak the constants to fit your needs (the example linked above is useful for that).
From its documentation:
Accepted range: [0 .. 1]. When larger than 0, the size of the node is taken into account. The distance will be calculated from the radius of the encompassing circle of the node for both the gravity model. Value 1 is maximum overlap avoidance.
To be noted, though: I've seen a recommendation to start with low values of avoidOverlap (like 0.1) to simplify the task for the solver. I can't recall where exactly I've read this.

I used levelSeparation to adjust the horizontal node distance, and nodeDistance to adjust the vertical node distance:
const options = {
layout: {
hierarchical: {
direction: 'LR',
sortMethod: 'directed',
levelSeparation: 300,
},
},
physics: {
hierarchicalRepulsion: {
nodeDistance: 140,
},
},
...
}

Related

Guaranteeing that node is above all other nodes in group

I am currently trying to make a group of arcs, with text above them. This seems to be working, but for only half the cases. The other half is ending up below the arc node and is invisible.
I have tried using node.tofront() , toback() etc but it is still not changing anything.
class pieslice extends Group{
pieslice(double centerx,double centery,double segpart,double totalseg){
Text value= new Text(String.format("%.2f",worth));
segment = totalseg;
Arc innerArc=new Arc();
Arc outerArc=new Arc();
outerArc.setType(ArcType.ROUND);
innerArc.setType(ArcType.ROUND);
value.setFill(Color.WHITE);
innerArc.setStrokeWidth(1);
innerArc.setRadiusX(150.0f);
innerArc.setRadiusY(150.0f);
outerArc.setRadiusX(innerArc.getRadiusX()+10);
outerArc.setRadiusY(innerArc.getRadiusY()+10);
outerArc.setFill(Color.WHITE);
innerArc.setStartAngle((360/segment)*segpart);
outerArc.setStartAngle((360/segment)*segpart);
innerArc.setCenterX(centerx);
innerArc.setCenterY(centery);
outerArc.setCenterX(centerx);
outerArc.setCenterX(centery);
innerArc.setLength(360/segment);
outerArc.setLength(360/segment);
innerArc.setStrokeWidth(1);
innerArc.setStroke(Color.BLACK);
innerArc.setFill(Color.color(Math.random(),Math.random(),Math.random()));
value.setX(150);
value.setFill(Color.BLACK);
value.getTransforms().add(new Rotate((360/segment)*segpart+((360/segment)/2),0,0));
System.out.println((360/segment)*segpart+((360/segment)/2));
this.getChildren().add(outerArc);
this.getChildren().get(0).setViewOrder(2);
this.getChildren().add(innerArc);
this.getChildren().add(value);}
I would expect that since I am adding the two arcs (Inner and outer for aesthetic effect only) and then the text, that the text would be rendered above the shapes, but that is not the case. Any ideas?

Given many rectangles, whats the best approach to group by line

TLDR: How to find boxes that are lined up horizontally
Given I have the data from an image like this:
We can visually see that we have two lines:
Tare: 11700 kg 10:40:58 am 16-May
Gross: 21300 kg 12:49:34 pm 9-Aug
The data I have for each blue box shown in the image is:
Top
Left
Width
Height
Coordinates for each corner of the box (X, Y)
My main thought is to start from the top of my "grid" and loop through each value of y, and then group boxes where they share the largest amount of matching "y" values, but it seems very over the top for something that seems simple.
Unsure really where to go from here
Example data set
I was able to get boxes lined up using this bit of code (in JavaScript), it essentially finds the first "most top left" box, and then finds any boxes that "intersect" with a line that starts from the middle of that first box
We don't care what order we get the boxes in, so as long as we start with the most left on any line we are golden.
function getMostTopLeftBox(boxes) {
const sorted = boxes.slice()
.sort(
(a, b) => {
if (a.Left === b.Left) {
return a.Top < b.Top ? -1 : 1;
}
return a.Left < b.Left ? -1 : 1;
}
);
return sorted[0];
}
function getAlignedBoxesFromSet(boxes) {
const mostTopLeftBox = getMostTopLeftBox(boxes);
const line = mostTopLeftBox.Top + (mostTopLeftBox.Height / 2);
return boxes
.filter(({ Top, Height }) => Top < line && (Top + Height) > line)
.sort(({ Left: a }, { Left: b }) => a < b ? -1 : 1)
}
function getAlignedBoxes(boxes) {
let remaining = boxes;
const lines = [];
const next = () => {
const line = getAlignedBoxesFromSet(remaining);
lines.push(line);
remaining = remaining.filter(box => line.indexOf(box) === -1);
if (!remaining.length) {
return;
}
return next();
};
next();
return lines;
}
The above code with the data set provided above gives us this result
However, it doesn't account for slight angles on the boxes, for example this image:
Another example of different boxes, with senstive information removed:
You can see from the above that the values below should be considered to be on the same line:
Product: [type]
Num Of [type]: 0
[value]: [value]
I may make a new question for this, but part of the answer to this is to figure out the actual curve of a line, and not just assume that the median angle of all lines is the actual "curve" of the line, so if I was to start with the most left box, then progress to the second box, now I have two distinct lines that I would want to find the smoothed curve for, which I would then use to find the next box, as I find each box I would want to adjust this curve to find the complete line, I will investigate this one further, if anyone has any hints, please do mention it.
I've managed to solve this, with a variant of the code posted in the question.
Here is a code sandbox of the solution, I will do a full write up of this, but here it is for now: https://codesandbox.io/s/102xnl7on3
Here is an example of grouped boxes based on angled lines calculated from the angle of all horizontal lines, if all the boxes were to be straight, then the result would be the lines being straight as well, so it should work in all scenarios.
Here is also an example where the lines are straight:
You can see the lines from the box before intersecting with the next box, it does this each time until it can find a complete line of boxes (till no more line up), this works out better than using an average angle from the entire data set.
I would like to be able to generate a mathematical curve for the already found boxes and apply that to find the next box, but for now, using the previous box as the anchor works pretty well.

Mathematically scale large elements lesser then small elements

10 elements with the class xxx have different widths and heights. Putting transform: scale(1.1) enlarges the big ones clearly but the small ones barely show difference. This is bad UX. The mathematical question is how to make the bigger elements scale less then the smaller ones:
width 10 should get scale 1.1
width 5 should get scale 1.2
How can i mathematically solve this?
The question lacks context and details, so it is hard to give a generally meaningful answer. However, the given examples indicate the following solution:
x_new = 1 + 1/x_old
Where x_old is the input value, i.e. 10 or 5.
Using logarithmic scaling instead of just 1/x_old might be another option, depending on the context.
To illustrate the scenarios i made these pens:
non logarithmic scale: http://codepen.io/anon/pen/bwRVpj
logarhitmic scale: http://codepen.io/anon/pen/VKWLJK
var inlineStyle = ''
var divs = document.getElementsByTagName('div')
var len = divs.length
while(len--) {
var elWidth = divs[len].offsetWidth
var scale = 1+9/elWidth
inlineStyle += `#${divs[len].id}:hover {
transform: scale(${scale})
}`
}
document.getElementById('lolStyle').innerHTML = inlineStyle

How to make a very large skybox? (babylon.js)

How can I make a very large skybox?
Example:
var skybox = BABYLON.Mesh.CreateBox("skyBox", 15000.0, this.scene);
The result is bad:
The first thing I suggest is to reduce the scale factor of your spaceship and planet models. It seems that having a SkyBox size larger than 10000 causes the ugly texture seams/tearing of the Skybox at particular camera angles and distances. So bring everything down in scale if possible to make more room inside the limits of the Skybox perimeter.
Next try this: set .infiniteDistance = true to keep the Skybox away from the camera, and also set .renderingGroupId = 0 on the Skybox. Lastly, set .renderingGroupId = 1 or more, on all the models and objects to help stop them from disappearing into thin air.
var skybox = BABYLON.MeshBuilder.CreateBox("skyBox", {size:10000.0},
this.scene);
skybox.infiniteDistance = true;
skybox.renderingGroupId = 0;
...and for models and sprite objects...
myModel.renderingGroupId = 1; /* greater than 0 */
These little tricks helped me to achieve a to-scale solar system simulation, but may not work in all cases.
Hello you need to increase camera.maxZ to a value larger than your skybox.

Selectively removing node labels in D3 force directed diagram

Overall context: I have a db of cross-references among pages in a wiki space, and want an incrementally-growing visualization of links.
I have working code that shows clusters of labels as you mouseover. But when you move away, rather than hiding all the labels, I want to keep certain key labels (e.g. the centers of clusters).
I forked an existing example and got it roughly working.
info is at http://webseitz.fluxent.com/wiki/WikiGraphBrowser
near the bottom of that or any other page in that space, in the block that starts with "BackLinks:", at the end you'll find "Click here for WikiGraphBrowser" which will launch a window with the interface
equivalent static subset example visible at http://www.wikigraph.net/static/d3/cgmartin/WikiGraphBrowser/:
code for that example is at https://github.com/BillSeitz/WikiGraphBrowser/blob/master/js/wiki_graph.js
Code that works at removing all labels:
i = j = 0;
if (!bo) { //bo=False - from mouseout
//labels.select('text.label').remove();
labels.filter(function(o) {
return !(o.name in clicked_names);
})
.text(function(o) { return ""; });
j++;
}
Code attempting to leave behind some labels, which does not work:
labels.forEach(function(o) {
if (!(d.name in clicked_names)) {
d.text.label.remove();
}
I know I'm just not grokking the d3 model at all....
thx
The problem comes down to your use of in to search for a name in an array. The Javascript in keyword searches object keys not object values. For an array, the keys are the index values. So testing (d.name in clicked_names) will always return false.
Try
i = j = 0;
if (!bo) { //bo=False - from mouseout
//labels.select('text.label').remove();
labels.filter(function(o) {
return (clicked_names.indexOf(o.name) < 0);
})
.text(function(o) { return ""; });
j++;
}
The array .indexOf(object) method returns -1 if none of the elements in the array are equal (by triple-equals standards) to the parameter. Alternatively, if you are trying to support IE8 (I'm assuming not, since you're using SVG), you could use a .some(function) test.
By the way, there's a difference between removing a label and just setting it's text content to the empty string. Which one to use will depend on whether you want to show the text again later. Either way, just be sure you don't end up with a proliferation of empty labels clogging up your browser.

Resources