anvaka / VivaGraphJS

Graph drawing library for JavaScript
Other
3.73k stars 423 forks source link

Bezier curve link webGL #78

Open Matthi0uw opened 10 years ago

Matthi0uw commented 10 years ago

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 ?

anvaka commented 10 years ago

That would be really a nice add... vivagraph has very rudimentary webgl support, adding bezier curve is feasible but is not straightforward.

Matthi0uw commented 10 years ago

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 ?

anvaka commented 10 years ago

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

Matthi0uw commented 10 years ago

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

dzdrazil commented 10 years ago

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?

dzdrazil commented 10 years ago

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.

Matthi0uw commented 10 years ago

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 ?

Matthi0uw commented 10 years ago

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 :

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:

valtih1978 commented 9 years ago

Can you do straight dynamic ribbons, like Almende does?