// // 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 canvasBackground; var mainNode; var balloonNode; 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 = "black" ctx.fillRect(0,0, canvas.width, canvas.height) ctx.imageSmoothingEnabled = true; var drawBackground = function(densityOfPoints, maxSize, minSize) { if (canvasBackground) { ctx.putImageData(canvasBackground, 0, 0); } else { minSize = minSize || 1; maxSize = maxSize || 3; densityOfPoints = densityOfPoints || 0.01; var numberOfPoints = densityOfPoints * canvas.width * canvas.height; for (var i = 0; i < numberOfPoints; i++) { var size = Math.random() * (maxSize - minSize) + minSize; var opacity = Math.random() * 0.2 + 0.4; ctx.fillStyle = 'rgba(220, 220, 255, ' + opacity + ')'; ctx.beginPath(); ctx.arc(Math.random() * canvas.width, Math.random() * canvas.height, size / 2, 0, 2 * Math.PI); ctx.fill(); } canvasBackground = ctx.getImageData(0, 0, canvas.width, canvas.height); } } drawBackground(0.02); // 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(); } var detectDestination = function(pt1, pt2, imageBox) { var destination = {}; var intersectingSegment = undefined; if (pt1.x != pt2.x) { var lineSlope = (pt1.y - pt2.y) / (pt1.x - pt2.x); var width = imageBox.width; var height = imageBox.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)) }; } return destination; } var setOpacity = function(color, opacity) { if (color.substring(0, 5) == 'rgba(') { color = color.replace(/,[^,]*\)$/, ',' + opacity + ')'); } return color; } var drawEdge = 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(255,255,255,.333)" ctx.lineWidth = edge.data.width || 3 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 = detectDestination(pt1, pt2, edge.target.data.imageObject); if (destination) { pt2 = destination; } } } if (mainNode && balloonNode) { if (edge.source.data.balloon || edge.target.data.balloon || (balloonNode != mainNode && particleSystem.getEdgesTo(balloonNode)[0].source == edge.target && edge.source == mainNode)) { ctx.strokeStyle = setOpacity(edge.target.data.color, 0.8); } } 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, color, strokeColor) { 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.save(); var color = color || 'black'; ctx.fillStyle = color; ctx.lineWidth = 5; ctx.beginPath(); ctx.arc(coords.x, coords.y, image.width / 2, 0, Math.PI * 2); ctx.clip(); ctx.drawImage(image, coords.x - image.width / 2, coords.y - image.height / 2, image.width, image.height); ctx.fill(); ctx.restore(); ctx.lineWidth = 3; ctx.strokeStyle = strokeColor; ctx.beginPath(); ctx.arc(coords.x, coords.y, image.width / 2, 0, Math.PI * 2); ctx.stroke(); }; var repositionBalloon = function(div, node) { var topOffset = node.data.p.y - node.data.height / 2; if (topOffset < 0) { topOffset = 0; } if (topOffset + div.outerHeight() > $(window).height()) { topOffset = $(window).height() - div.outerHeight(); } var leftMargin = node.data.width / 2 + 2; if (mainNode && node.data.p.x < mainNode.data.p.x) { leftMargin = -leftMargin - div.outerWidth(); } var leftOffset = node.data.p.x + leftMargin; if (leftOffset < 0) { leftOffset += div.outerWidth() + node.data.width + 4; } if (leftOffset + div.outerWidth() > $(window).width()) { leftOffset -= div.outerWidth() + node.data.width + 4; } div.css('top', topOffset); div.css('left', leftOffset); div.show(); } var currentZIndex = 0; var drawNode = function(node, pt){ // node: {mass:#, p:{x,y}, name:"", data:{}} // pt: {x:#, y:#} node position in screen coords node.data.p = pt; node.data.zindex = currentZIndex++; if (node.data.main) { mainNode = node; } if (node.data.image && node.data.width > 10) { var isHighlighted = function(node) { if (node.data.balloon) { return true; } if (balloonNode && mainNode) { if (node == mainNode) { return true; } var targets = particleSystem.getEdgesFrom(node); for (var t = 0; t < targets.length; t++) { if (targets[t].target == balloonNode) { return true; } } } } var strokeColor = isHighlighted(node) ? setOpacity(node.data.color, 0.8) : 'rgba(255, 255, 255, 0.7)'; if (node.data.imageObject) { node.data.imageObject.width = node.data.width; node.data.imageObject.height = node.data.height; drawImage(node.data.imageObject, pt, node.data.color, strokeColor); } else { var img = new Image(node.data.width, node.data.height); img.onload = function() { node.data.imageObject = img; drawImage(node.data.imageObject, pt, node.data.color, strokeColor); }; img.src = '_img/' + node.data.image; } } else { var color = node.data.color || 'black'; ctx.fillStyle = color; ctx.lineWidth = 5; ctx.strokeStyle = 'rgba(255,255,255,0.33)'; ctx.beginPath(); ctx.arc(pt.x, pt.y, node.data.width / 2, 0, Math.PI*2); ctx.stroke(); ctx.fill(); } if (node.data.balloon) { repositionBalloon(node.data.balloon, node); if (node.data.oldWidth) { node.data.width = node.data.oldWidth; } if (node.data.oldHeight) { node.data.height = node.data.oldHeight; } balloonNode = node; } }; particleSystem.eachEdge(drawEdge); particleSystem.eachNode(drawNode); if (balloonNode) { var selectedDimension = Math.max((balloonNode.data.image) ? 100 : 50, balloonNode.data.width); balloonNode.data.oldWidth = balloonNode.data.width; balloonNode.data.width = selectedDimension; balloonNode.data.oldHeight = balloonNode.data.height; balloonNode.data.height = selectedDimension; drawNode(balloonNode, balloonNode.data.p); } }, initMouseHandling:function(){ var balloon; // start listening $(canvas).click(function(e) { if (balloon) { balloon.data('node').data.balloon = undefined; balloon.remove(); balloon = undefined; balloonNode = undefined; } var pos = $(canvas).offset(); _mouseP = arbor.Point(e.pageX-pos.left, e.pageY-pos.top) var candidateNode = undefined; particleSystem.eachNode(function(node) { var distance = _mouseP.subtract(particleSystem.toScreen(node.p)); if (Math.sqrt(distance.x*distance.x + distance.y*distance.y) <= node.data.width / 2 + 3) { if (!candidateNode || candidateNode.data.zindex < node.data.zindex) { candidateNode = node; } } }); if (candidateNode) { balloon = $('