// // main.js // // A project template for using arbor.js // (function($){ var Renderer = function(canvas){ var canvas = $(canvas).get(0) var ctx = canvas.getContext("2d"); var particleSystem var that = { init:function(system){ // // the particle system will call the init function once, right before the // first frame is to be drawn. it's a good place to set up the canvas and // to pass the canvas size to the particle system // // save a reference to the particle system for use in the .redraw() loop particleSystem = system // inform the system of the screen dimensions so it can map coords for us. // if the canvas is ever resized, screenSize should be called again with // the new dimensions particleSystem.screenSize(canvas.width, canvas.height) particleSystem.screenPadding(50) // leave an extra 80px of whitespace per side // set up some event handlers to allow for node-dragging that.initMouseHandling() }, redraw:function(){ // // redraw will be called repeatedly during the run whenever the node positions // change. the new positions for the nodes can be accessed by looking at the // .p attribute of a given node. however the p.x & p.y values are in the coordinates // of the particle system rather than the screen. you can either map them to // the screen yourself, or use the convenience iterators .eachNode (and .eachEdge) // which allow you to step through the actual node objects but also pass an // x,y point in the screen's coordinate system // ctx.fillStyle = "white" ctx.fillRect(0,0, canvas.width, canvas.height) // JESTEM BOGIEM PRZEKSZTAŁCEŃ 2D :O var drawArrow = function(pt1, pt2, length, style) { var angle = Math.atan2(pt2.y - pt1.y, pt2.x - pt1.x); ctx.fillStyle = style; ctx.setLineDash([]); ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(pt2.x, pt2.y); ctx.lineTo(pt2.x - length * Math.cos(angle - Math.PI/6), pt2.y - length * Math.sin(angle - Math.PI/6)); ctx.lineTo(pt2.x - length * (Math.cos(angle + Math.PI/6) + Math.cos(angle - Math.PI/6))/3, pt2.y - length * (Math.sin(angle - Math.PI/6) + Math.sin(angle + Math.PI/6))/3); ctx.lineTo(pt2.x - length * Math.cos(angle + Math.PI/6), pt2.y - length * Math.sin(angle + Math.PI/6)); ctx.lineTo(pt2.x, pt2.y); ctx.stroke(); ctx.fill(); } particleSystem.eachEdge(function(edge, pt1, pt2){ // edge: {source:Node, target:Node, length:#, data:{}} // pt1: {x:#, y:#} source position in screen coords // pt2: {x:#, y:#} target position in screen coords ctx.strokeStyle = edge.data.style || "rgba(0,0,0, .333)" ctx.lineWidth = edge.data.width || 1 ctx.setLineDash(edge.data.dash || []) // draw a line from pt1 to pt2 ctx.beginPath() ctx.moveTo(pt1.x, pt1.y) if (edge.data.arrow) { if (edge.target.data.imageObject) { var destination = {}; var intersectingSegment = undefined; if (pt1.x != pt2.x) { var lineSlope = (pt1.y - pt2.y) / (pt1.x - pt2.x); var width = edge.target.data.imageObject.width; var height = edge.target.data.imageObject.height; if ((-height / 2 <= lineSlope * width / 2) && (lineSlope * width / 2 <= height / 2)) { if (pt1.x > pt2.x) { intersectingSegment = [ { x: pt2.x + width / 2, y: pt2.y - height / 2 }, { x: pt2.x + width / 2, y: pt2.y + height / 2 } ]; } else { intersectingSegment = [ { x: pt2.x - width / 2, y: pt2.y - height / 2 }, { x: pt2.x - width / 2, y: pt2.y + height / 2 } ]; } } if ((-width / 2 <= (height / 2) / lineSlope) && ((height / 2) / lineSlope <= width / 2)) { if (pt1.y > pt2.y) { intersectingSegment = [ { x: pt2.x - width / 2, y: pt2.y + height / 2 }, { x: pt2.x + width / 2, y: pt2.y + height / 2 } ]; } else { intersectingSegment = [ { x: pt2.x - width / 2, y: pt2.y - height / 2 }, { x: pt2.x + width / 2, y: pt2.y - height / 2 } ]; } } } else { intersectingSegment = (pt1.y > pt2.y) ? [ { x: pt2.x - width / 2, y: pt2.y - height / 2 }, { x: pt2.x + width / 2, y: pt2.y - height / 2 } ] : [ { x: pt2.x - width / 2, y: pt2.y + height / 2 }, { x: pt2.x + width / 2, y: pt2.y + height / 2 } ]; } if (intersectingSegment) { var intersectAngle = ((intersectingSegment[1].y - intersectingSegment[0].y) * (pt2.x - pt1.x)) - ((intersectingSegment[1].x - intersectingSegment[0].x) * (pt2.y - pt1.y)); var aSlope = pt1.y - intersectingSegment[0].y; var bSlope = pt1.x - intersectingSegment[0].x; var numerator1 = ((intersectingSegment[1].x - intersectingSegment[0].x) * aSlope) - ((intersectingSegment[1].y - intersectingSegment[0].y) * bSlope); var intersectSlope = numerator1 / intersectAngle; destination = { x: pt1.x + (intersectSlope * (pt2.x - pt1.x)), y: pt1.y + (intersectSlope * (pt2.y - pt1.y)) }; } } } if (destination) { pt2 = destination; } ctx.lineTo(pt2.x, pt2.y); ctx.stroke(); if (edge.data.arrow) { drawArrow(pt1, pt2, edge.data.arrow, ctx.strokeStyle); } }) var drawImage = function(image, coords) { if (!image.height) { image.height = image.width ? image.width * image.naturalHeight / image.naturalWidth : image.naturalHeight; } if (!image.width) { image.width = image.height ? image.height * image.naturalWidth / image.naturalHeight : image.naturalWidth; } ctx.drawImage(image, coords.x - image.width / 2, coords.y - image.height / 2, image.width, image.height); }; particleSystem.eachNode(function(node, pt){ // node: {mass:#, p:{x,y}, name:"", data:{}} // pt: {x:#, y:#} node position in screen coords if (node.data.imageObject) { drawImage(node.data.imageObject, pt); } else { var img = new Image(node.data.width, node.data.height); img.onload = function() { node.data.imageObject = img; drawImage(node.data.imageObject, pt); }; img.src = '_img/' + node.data.image; } }) }, initMouseHandling:function(){ // no-nonsense drag and drop (thanks springy.js) var dragged = null; // set up a handler object that will initially listen for mousedowns then // for moves and mouseups while dragging var handler = { clicked:function(e){ var pos = $(canvas).offset(); _mouseP = arbor.Point(e.pageX-pos.left, e.pageY-pos.top) dragged = particleSystem.nearest(_mouseP); if (dragged && dragged.node !== null){ // while we're dragging, don't let physics move the node dragged.node.fixed = true } $(canvas).bind('mousemove', handler.dragged) $(window).bind('mouseup', handler.dropped) return false }, dragged:function(e){ var pos = $(canvas).offset(); var s = arbor.Point(e.pageX-pos.left, e.pageY-pos.top) if (dragged && dragged.node !== null){ var p = particleSystem.fromScreen(s) dragged.node.p = p } return false }, dropped:function(e){ if (dragged===null || dragged.node===undefined) return if (dragged.node !== null) dragged.node.fixed = false dragged.node.tempMass = 1000 dragged = null $(canvas).unbind('mousemove', handler.dragged) $(window).unbind('mouseup', handler.dropped) _mouseP = null return false } } // start listening $(canvas).mousedown(handler.clicked); }, } return that } $(document).ready(function() { $.getJSON('graph.json', function(graph) { graph.repulsion = graph.repulsion || 1000; graph.stiffness = graph.stiffness || 600; graph.friction = graph.friction || 0.5; var sys = arbor.ParticleSystem(graph.repulsion, graph.stiffness, graph.friction); // create the system with sensible repulsion/stiffness/friction sys.parameters({gravity:true}); // use center-gravity to make the graph settle nicely (ymmv) sys.renderer = Renderer("#viewport"); // our newly created renderer will have its .init() method called shortly by sys... sys.graft(graph); }); }) })(this.jQuery)