VivaGraphJS
VivaGraphJS copied to clipboard
Bezier curve link webGL
I would like to have a visualization with bezier curves instead of line between the links. In the example "07 - Dual Show Links", there is an implementation of a curve between two nodes with svg render.
My graph can be up to 15k nodes, so I have to use webGL part of your code.
Have you an idea to have curve on webGL render ?
That would be really a nice add... vivagraph has very rudimentary webgl support, adding bezier curve is feasible but is not straightforward.
Thanks for the quick answer, I can give it a try. But can you tell me where I can access the vertices positions (x,y) coordinates ?
Sure, webgl graphics receives a link position when link is added, this happens in webglGraphics.js.
By default links in webgl are rendered by a program
: a combination of vertex and fragment shaders. In vivagraph program implements position() method, which accepts position for a link.
Example of how to create custom node program is available in webglCustomNode.html. Link program could be implemented in a similar way
Okay, so the method position() returns a table with the coordinates of each node ?
1st element is x of 1st node 2nd element is y of 1st node 3rd element is z of 1st node 4th element is x of 2nd node 5th element is y of 2nd node 6nd element is z of 2nd node 7nd element is x of 3rd node ...
I found an example of webGL with circles : on this site and try to use their function on your code. There is a function arc() to calculate the path of the curve approximation between 2 nodes:
function arc(cx, cy, r, a, b, n) {
var angle = a * Math.PI / 180;
var dA = (b * Math.PI / 180) / n;
var points = [];
for (var i = 0; i <= n; i++) {
var x = cx + r * Math.cos(angle + i * dA);
var y = cy + r * Math.sin(angle + i * dA);
points.push(x, y, 0);
}
return points;
}
I've made a fiddlejs to try a example with 1 node on the curve. I display in console the coordinates of nodes : there is a bug in my function because coordinates of start are not good. It should represent my link (even if position is false), but I do not see it. Maybe I do not use the render () function in the right way
The reason you're not seeing the lines is due to your points.push- the third argument is color, and you've specified 0. Swap that with positions[2] and I think you should get the color of the from position.
Having done that, though, all I'm seeing is a straight line, positioned incorrectly. Since I'm a complete amateur with glsl / webgl in general, I'm guessing it has to do with some sort of mismatch in array buffer size, perhaps?
Found the other problem, yet another silly mistake- instead of
gl.drawArrays(gl.LINES, 0, ap.length/3)
you should be doing
gl.drawArrays(gl.LINE_STRIP, 0, ap.length/3)
. That'll draw a line between each vertex, rather than drawing one line per vertex pairs. From there, it's just a matter of getting the maths right so that the curve starts and stops at the two points.
Thank you for your anwser @dzdrazil ! I have update my fiddle with your advice : this is an early response to my initial problem. I can draw a circle arc for the 1st link, but not for all... Have you any idea to draw all links ?
Here is my new fiddle update with Bezier quadratic curves and cubic curves with 2 control points (see on wikipedia). I wrote the function arc() called in render of Viva.Graph.View.webglLinkProgram :
/* function to draw links:
positions : position of all nodes in the graph
n : 1 = line / 2 = quadratic curve / 3 = cubic curve
m : number of point on the curve
*/
function arc(positions, n, m) {
for (var j = 0; j < positions.length; j++){
if (positions[j] != positions[j+3] || positions[j+1] != positions[j+4]){
points = [];
pointsMid = [];
var dx = positions[j] - positions[j+3],
dy = positions[j+1] - positions[j+4],
cx = (positions[j]+positions[j+3])/2,
cy = (positions[j+1]+positions[j+4])/2,
a = Math.atan2(dy, dx) * 180 / Math.PI,
r = Math.sqrt(dx * dx + dy * dy)/2,
angle = a * Math.PI / 180,
dA = Math.PI / n;
for (var i = 0; i <= n; i++) {
var x = cx + r * Math.cos(angle + i * dA);
var y = cy + r * Math.sin(angle + i * dA);
pointsMid.push(x, y,0.2);
}
if (n == 1){
points = pointsMid;
}else if (n == 2){
for (var k = 0; k <= m; k++){
t = k / m;
Ax = ( (1 - t) * pointsMid[0] ) + (t * pointsMid[3]);
Ay = ( (1 - t) * pointsMid[1] ) + (t * pointsMid[4]);
Bx = ( (1 - t) * pointsMid[3] ) + (t * pointsMid[6]);
By = ( (1 - t) * pointsMid[4] ) + (t * pointsMid[7]);
x = ( (1 - t) * Ax ) + (t * Bx);
y = ( (1 - t) * Ay ) + (t * By);
points.push(x, y,0.2);
}
} else if (n == 3){
for (var k = 0; k <= m; k++){
t = k / m;
Px0 = pointsMid[0];
Px1 = pointsMid[3];
Px2 = pointsMid[6];
Px3 = pointsMid[9];
Py0 = pointsMid[1];
Py1 = pointsMid[4];
Py2 = pointsMid[7];
Py3 = pointsMid[10];
x = Px0*(1-t)*(1-t)*(1-t) + 3*Px1*t*(1-t)*(1-t) + 3*Px2*t*t*(1-t) + Px3*t*t*t;
y = Py0*(1-t)*(1-t)*(1-t) + 3*Py1*t*(1-t)*(1-t) + 3*Py2*t*t*(1-t) + Py3*t*t*t;
points.push(x, y,0.2);
}
}
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(points), gl.DYNAMIC_DRAW);
if (sizeDirty) {
sizeDirty = false;
gl.uniformMatrix4fv(locations.transform, false, transform);
gl.uniform2f(locations.screenSize, width, height);
}
gl.vertexAttribPointer(locations.vertexPos, 2, gl.FLOAT, false, 3 * Float32Array.BYTES_PER_ELEMENT, 0);
gl.vertexAttribPointer(locations.color, 4, gl.UNSIGNED_BYTE, true, 3 * Float32Array.BYTES_PER_ELEMENT, 2 * 4);
// param : mode , 0, numItems
gl.drawArrays(gl.LINE_STRIP, 0, points.length/3)
}
j += 5;
}
}
In pointsMid array, I put 3 nodes :
- the starting node A
- the end node C
- the intermediate node B. B is a point lying on the intersection of a circle (having as center the middle of the segment AC and with radius AC/2) and a straight line (perpendicular to the segment AC through B).
In points array, I put m
nodes (m
defined in parameters) that are in the path of the curve.
Then I simply display these nodes.
Currently I have 3 problems:
- How do you manage the link color? When I push points, I use this
points.push(x, y,0.15);
. The third value is used to add colors, but I don’t really understand how it works, for instance 0.1 = cyan / blue = 0.2 / 0 and 0.5 hide the links, is there a way to have some RGB colors in there? - As curves do not exist webGL, I create
m
links (m
corresponding to the parameter value of the arc function. Regarding performance, the rendering becomes very slow withm
times fewer nodes on the screen... Do you have any idea on how to improve it? - Also, how can I add this feature to vivagraphJS properly and to have access to
n
parameter? (at the moment, I've put it in "Viva.Graph.View.webglLinkProgram" but I am not sure it is the correct location for this part of the code)
Can you do straight dynamic ribbons, like Almende does?