Tell me more ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

I started implementing a graph drawing application in javascript and < canvas > element, and i just wanted to hear your thought on the progress so far. I'm very open to suggestions and i'm very interested to hear what you have to say.
So you can see the progress so far in the present code, if you have any questions about it feel free to ask.

var graph = {

init: function(edges) {
    graph.vertices = {};
    graph.edges = {};
    graph.canvas = document.getElementById('platno');
    graph.width = graph.canvas.width;
    graph.height = graph.canvas.height;
    graph.hookes_test = true;
    graph.ctx = graph.canvas.getContext('2d');

    document.addEventListener("mousedown", graph.klik, false);
    document.addEventListener("mouseup", graph.drop, false);
    document.addEventListener("dblclick", graph.dblclick, false);
    graph.addNodesFromEdgesList(edges);
    graph.addEdgesFromEdgesList(edges);
    graph.mapEdges(graph.oDuljina);
    setInterval(graph.draw, 1024 / 24);
},
addNodesFromEdgesList: function(EdgesList) {
    for (var r1 = 0; r1 < EdgesList.length; r1++) {
        for (var r2 = 0; r2 < EdgesList[r1].length - 1; r2++) {

            if ((typeof graph.vertices[EdgesList[r1][r2]]) === "undefined") {
                graph.addNode({
                    id: EdgesList[r1][r2],
                    x: Math.floor(graph.width / 2 + 100 * Math.cos(Math.PI * (EdgesList[r1][r2] * 2) / 11)),
                    y: Math.floor(graph.height / 2 + 100 * Math.sin(Math.PI * (EdgesList[r1][r2] * 2 / 11))),
                    size: 6,
                    ostalo: 100
                });
            }
        }
    }
},
addEdgesFromEdgesList: function(EdgesList) {
    for (var a = 0; a < EdgesList.length; a++) {
        graph.addEdge({
            from: EdgesList[a][0],
            to: EdgesList[a][1],
            id: a
        });
    }
},
node: function(node) {
    this.id = node.id;
    this.pos = new vektor(node.x, node.y);
    this.size = node.size;
    this._size = node.size;
    this.expanded = false;
},
addNode: function(node) {
    (typeof graph.vertices[node.id]) === "undefined" ? graph.vertices[node.id] = new graph.node(node) : console.log("Duplikat cvora! Id:" + node.id);
},
removeNode: function(id) {
    if (typeof graph.vertices[id] !== "undefined") {
        graph.removeEdgeByNodeId(id);
        if (id == graph.info_node.id) graph.info_node = false;
        delete graph.vertices[id];
    } else {
        console.log("Ne postoji node! Id:" + id);
    }
},
edge: function(edge) {
    this.id = edge.id;
    this.from = graph.vertices[edge.from];
    this.to = graph.vertices[edge.to];
},
addEdge: function(edge) {
    (typeof graph.edges[edge.id]) === "undefined" ? graph.edges[edge.id] = new graph.edge(edge) : console.log("Duplikat brida! Id:" + edge.id);
},
removeEdgeByEdgeId: function(id) {
    (typeof graph.edges[id]) !== "undefined" ? delete graph.edges[id] : console.log("Ne postoji brid! Id:" + id);
},
removeEdgeByNodeId: function(id) {
    if (typeof graph.vertices[id] !== "undefined") {
        for (var edge in graph.edges) {
            if (graph.edges.hasOwnProperty(edge) && (graph.edges[edge].from.id == id || graph.edges[edge].to.id == id)) {
                delete graph.edges[edge];
            }
        }
    } else {
        console.log("Ne postoji cvor! Id:" + id);
    }

},
clearGraph: function() {
    for (var id in graph.vertices) {
        if (graph.vertices.hasOwnProperty(id)) {
            graph.removeNode(id)
        }
    }
},
mapNodes: function(funkcija, obj) {
    var res = [],
        tmp, id;
    for (id in graph.vertices) {
        if (graph.vertices.hasOwnProperty(id)) {
            tmp = funkcija.apply(graph, [graph.vertices[id], obj || {}]);
            if (tmp) res.push(tmp);
        }
    }
    return res;
},
mapEdges: function(funkcija) {
    for (var id in graph.edges) {
        if (graph.edges.hasOwnProperty(id)) {
            funkcija.apply(graph, [graph.edges[id].from, graph.edges[id].to, graph.edges[id].id]);
        }
    }
},
vuci: function(e) {

    if (graph.drag) {
        graph.drag.pos.x = (e.pageX - graph.canvas.offsetLeft);
        graph.drag.pos.y = (e.pageY - graph.canvas.offsetTop);
        if (graph.drag.pos.x > graph.width - 6) graph.drag.pos.x = graph.width - 6;
        else if (graph.drag.pos.x < 6) graph.drag.pos.x = 6;
        else if (graph.drag.pos.y > graph.height - 6) graph.drag.pos.y = graph.height - 6;
        else if (graph.drag.pos.y < 6) graph.drag.pos.y = 6;
    }

},

klik: function(e) {

    graph.drag = graph.getNodeFromXY(e.pageX - graph.canvas.offsetLeft, e.pageY - graph.canvas.offsetTop)[0];
    document.addEventListener("mousemove", graph.vuci, false);
},
drop: function() {
    graph.drag = false;
    document.removeEventListener("mousemove", graph.vuci, false);
},
getNodeFromXY: function(_x, _y) {

    return graph.mapNodes(function(node, obj) {
        if ((obj.x > node.pos.x - node.size) && (obj.x < node.pos.x + node.size) && (obj.y > node.pos.y - node.size) && (obj.y < node.pos.y + node.size)) {
            return node;
        } else {
            return false
        };

    }, {
        x: _x,
        y: _y
    });

},
draw: function() {

    graph.ctx.clearRect(0, 0, graph.width, graph.height);
    background();
    graph.mapEdges(crtaj_v);
    graph.mapNodes(crtaj_n);
    graph.info();
    if (!graph.hookes_test) graph.mapEdges(graph.hookes);

    function background() {
        var grd = graph.ctx.createRadialGradient(graph.width / 2, graph.height / 2, 30, graph.width / 2, graph.height / 2, graph.height);
        grd.addColorStop(0, "#42586d");
        grd.addColorStop(0.5, "#36495a");
        grd.addColorStop(1, "#26323e");
        graph.ctx.fillStyle = grd;
        graph.ctx.fillRect(0, 0, graph.width, graph.height);

    }

    function crtaj_n(v) {
        graph.ctx.fillStyle = 'rgba(0,0,0,0.4)';
        graph.ctx.beginPath();
        graph.ctx.arc(v.pos.x, v.pos.y, v.size, 0, Math.PI * 2, true);
        graph.ctx.fill();
        graph.ctx.strokeStyle = '#818f9a'
        graph.ctx.arc(v.pos.x, v.pos.y, v.size, 0, Math.PI * 2, true);
        graph.ctx.stroke();
        return false;
    }

    function crtaj_v(v1, v2) {
        graph.ctx.beginPath();
        graph.ctx.strokeStyle = 'rga(129,143,154,0.1)';
        var duljina = [v1.pos.udaljenost(v2.pos) - v1.size, v1.pos.udaljenost(v2.pos) - v2.size];
        var kut = Math.atan2(v2.pos.y - v1.pos.y, v2.pos.x - v1.pos.x);
        graph.ctx.moveTo(v2.pos.x - (duljina[0] * Math.cos(kut)), v2.pos.y - (duljina[0] * Math.sin(kut)));
        graph.ctx.lineTo(v1.pos.x + (duljina[1] * Math.cos(kut)), v1.pos.y + (duljina[1] * Math.sin(kut)));
        graph.ctx.stroke();
    }

},

dblclick: function(e) {
    var dbl = graph.getNodeFromXY(e.pageX - platno.offsetLeft, e.pageY - platno.offsetTop)[0] || false;
    if (dbl.expanded) {
        dbl.size = dbl._size;
        dbl.expanded = false;
        graph.info_node = false;
    } else if (dbl) {
        graph.mapNodes(function(v1) {
            if (v1.expanded) {
                v1.size = v1._size;
                v1.expanded = false;
                graph.info_node = false;
            }
        })
        dbl.size = 30;
        dbl.expanded = true;
        graph.info_node = dbl;
    }
},
info: function() {
    if (graph.info_node) {
        graph.ctx.font = "10px Verdana";
        graph.ctx.textAlign = "center";
        graph.ctx.fillStyle = '#ffffff';
        graph.ctx.fillText("Node: " + graph.info_node.id, graph.info_node.pos.x, graph.info_node.pos.y + 3, 30);
    }

},
hookes: function(v1, v2, id) {
    var duljina = v1.pos.oduzmi(v2.pos),
        udaljenost = duljina.duljina() - (graph.edges[id].duljina),
        HL = 20 * (udaljenost / duljina.duljina()),
        kut = Math.atan2(v2.pos.y - v1.pos.y, v2.pos.x - v1.pos.x);
    (graph.drag && (graph.drag.id != v1.id)) || !graph.drag ? graph.zbrojiLokacija(v1, kut, HL) : false;
    (graph.drag && (graph.drag.id != v2.id)) || !graph.drag ? graph.oduzmiLokacija(v2, kut, HL) : false;

},
oDuljina: function(v1, v2, id) {
    graph.hookes_test = false;
    graph.edges[id].duljina = v1.pos.oduzmi(v2.pos).duljina();
},
zbrojiLokacija: function(v1, kut, HL) {
    var dis = new vektor(HL * Math.cos(kut), HL * Math.sin(kut))
    if (v1.pos.x + dis.x > graph.width - v1.size || v1.pos.x + dis.x < 0 + v1.size) {
        v1.pos.x += dis.x * (-1);
        v1.pos.y += dis.y;
    } else if (v1.pos.y + dis.y > graph.height - v1.size || v1.pos.y + dis.y < 0 + v1.size) {
        v1.pos.x += dis.x;
        v1.pos.y += dis.y * (-1);
    } else {
        v1.pos = v1.pos.zbroji(dis)
    }
},

oduzmiLokacija: function(v1, kut, HL) {
    var dis = new vektor(HL * Math.cos(kut), HL * Math.sin(kut))
    if (v1.pos.x + dis.x > graph.width - v1.size || v1.pos.x + dis.x < 0 + v1.size) {
        v1.pos.x -= dis.x * (-1);
        v1.pos.y -= dis.y;
    } else if (v1.pos.y + dis.y > graph.height - v1.size || v1.pos.y + dis.y < 0 + v1.size) {
        v1.pos.x -= dis.x;
        v1.pos.y -= dis.y * (-1);
    } else {
        v1.pos = v1.pos.oduzmi(dis)
    }
}

}

function vektor(x, y) {
    this.x = x;
    this.y = y;
}
vektor.prototype.zbroji = function(v1) {
    return new vektor(this.x + v1.x, this.y + v1.y);
}
vektor.prototype.oduzmi = function(v1) {
    return new vektor(this.x - v1.x, this.y - v1.y);
}

vektor.prototype.division = function(x) {
    return new vektor(this.x / x, this.y / x);
}

vektor.prototype.multiply = function(x) {
    return new vektor(this.x * x, this.y * x);
}

vektor.prototype.udaljenost = function(v1) {
    return Math.sqrt(Math.pow(v1.x - this.x, 2) + Math.pow(v1.y - this.y, 2));
}

vektor.prototype.duljina = function() {
    return Math.max(20, Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2)));
}
//////////////////////////////////////
//  Test data                       //
/////////////////////////////////////
var edges = [
    [1, 2, 1],
    [1, 3, 1],
    [2, 3, 1],
    [3, 4, 1],
    [3, 5, 1],
    [3, 6, 1],
    [4, 1, 1],
    [4, 2, 1],
    [5, 6, 1]
    ];
graph.init(edges);

UPDATE: added new code, this is just a preview i didn't have time to optimize the code, and also some of function names and varibles are written in my native language.

Also I've added a jsfiddle link so you can see the work so far in action. http://jsfiddle.net/nNcHJ/1/

share|improve this question
Maybe you can provide an example of using it? – Joseph Weissman Sep 4 '11 at 18:33
@Muha An example would be good. I would also start from the API, how would you expect the user to enter the values of a graph. Say you were to define a Traveling Salesman problem? See github.com/ajaxorg/apf/blob/master/CODING_STANDARDS for a good style guide – yannis Sep 4 '11 at 18:53
@Muha Thanks, you will get a better review, if you translate some of the code to English, klik and vektor are fine but what is zbrojiLokacija? (See en.wikipedia.org/wiki/Hungarian_notation). In general though it is shaping up and looks interesting. Also you need to change the canvas size in the jsFiddle in order to see it say 300px x 300px. – yannis Sep 4 '11 at 20:42
thank you very much for your feedback, i'm planning to convert the names but i didn't have plenty of time today. but i will get to it soon. – Muha Sep 4 '11 at 21:37
I would consider using a library to help abstract out some of the Canvas stuff you are doing. I haven't done anything on Canvas yet, but when I used to do stuff like this I would use raphaeljs.com. (Generates SVG). – Travis Sep 5 '11 at 13:45
show 3 more comments

Know someone who can answer? Share a link to this question via email, Google+, Twitter, or Facebook.

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Browse other questions tagged or ask your own question.