diff --git a/app/assets/javascripts/src/Metamaps.JIT.js.erb b/app/assets/javascripts/src/Metamaps.JIT.js.erb index 744a78c3..4b72b714 100644 --- a/app/assets/javascripts/src/Metamaps.JIT.js.erb +++ b/app/assets/javascripts/src/Metamaps.JIT.js.erb @@ -1,1949 +1,1908 @@ Metamaps.JIT = { - events: { - topicDrag: 'Metamaps:JIT:events:topicDrag', - newTopic: 'Metamaps:JIT:events:newTopic', - deleteTopic: 'Metamaps:JIT:events:deleteTopic', - removeTopic: 'Metamaps:JIT:events:removeTopic', - newSynapse: 'Metamaps:JIT:events:newSynapse', - deleteSynapse: 'Metamaps:JIT:events:deleteSynapse', - removeSynapse: 'Metamaps:JIT:events:removeSynapse', - pan: 'Metamaps:JIT:events:pan', - zoom: 'Metamaps:JIT:events:zoom', - animationDone: 'Metamaps:JIT:events:animationDone', - }, - vizData: [], // contains the visualization-compatible graph - /** - * This method will bind the event handlers it is interested and initialize the class. - */ - init: function () { - var self = Metamaps.JIT; - - $(".zoomIn").click(self.zoomIn); - $(".zoomOut").click(self.zoomOut); - - var zoomExtents = function (event) { - self.zoomExtents(event, Metamaps.Visualize.mGraph.canvas); - }; - $(".zoomExtents").click(zoomExtents); - - $(".takeScreenshot").click(Metamaps.Map.exportImage); - - self.topicDescImage = new Image(); - self.topicDescImage.src = '<%= asset_path('topic_description_signifier.png') %>'; - - self.topicLinkImage = new Image(); - self.topicLinkImage.src = '<%= asset_path('topic_link_signifier.png') %>'; - }, - /** - * convert our topic JSON into something JIT can use - */ - convertModelsToJIT: function(topics, synapses) { - var jitReady = []; - - var synapsesToRemove = []; - var topic; - var mapping; - var node; - var nodes = {}; - var existingEdge; - var edge; - var edges = []; - - topics.each(function (t) { - node = t.createNode(); - nodes[node.id] = node; - }); - synapses.each(function (s) { - edge = s.createEdge(); - - if (topics.get(s.get('node1_id')) === undefined || topics.get(s.get('node2_id')) === undefined) { - // this means it's an invalid synapse - synapsesToRemove.push(s); - } - else if (nodes[edge.nodeFrom] && nodes[edge.nodeTo]) { - - existingEdge = _.findWhere(edges, { - nodeFrom: edge.nodeFrom, - nodeTo: edge.nodeTo - }) || - _.findWhere(edges, { - nodeFrom: edge.nodeTo, - nodeTo: edge.nodeFrom - }); - - if (existingEdge) { - // for when you're dealing with multiple relationships between the same two topics - if (Metamaps.Active.Map) { - mapping = s.getMapping(); - existingEdge.data['$mappingIDs'].push(mapping.id); - } - existingEdge.data['$synapseIDs'].push(s.id); - } else { - // for when you're dealing with a topic that has relationships to many different nodes - nodes[edge.nodeFrom].adjacencies.push(edge); - edges.push(edge); - } - } - }); - - _.each(nodes, function (node) { - jitReady.push(node); - }); - - return [jitReady, synapsesToRemove]; - }, - prepareVizData: function () { - var self = Metamaps.JIT; - var mapping; - - // reset/empty vizData - self.vizData = []; - Metamaps.Visualize.loadLater = false; - - var results = self.convertModelsToJIT(Metamaps.Topics, Metamaps.Synapses); - - self.vizData = results[0]; - - // clean up the synapses array in case of any faulty data - _.each(results[1], function (synapse) { - mapping = synapse.getMapping(); - Metamaps.Synapses.remove(synapse); - if (Metamaps.Mappings) Metamaps.Mappings.remove(mapping); - }); - - if (self.vizData.length == 0) { - Metamaps.Famous.viz.showInstructions(); - Metamaps.Visualize.loadLater = true; - } - else Metamaps.Famous.viz.hideInstructions(); - - Metamaps.Visualize.render(); - }, // prepareVizData - edgeRender: function (adj, canvas) { - //get nodes cartesian coordinates - var pos = adj.nodeFrom.pos.getc(true); - var posChild = adj.nodeTo.pos.getc(true); - - var synapse; - if(adj.getData("displayIndex")) { - synapse = adj.getData("synapses")[adj.getData("displayIndex")]; - if (!synapse) { - delete adj.data.$displayIndex; - synapse = adj.getData("synapses")[0]; - } - } - else { - synapse = adj.getData("synapses")[0]; - } - - if (!synapse) return; // this means there are no corresponding synapses for - // this edge, don't render it - - var directionCat = synapse.get("category"); - - //label placement on edges - if (canvas.denySelected) { - var color = Metamaps.Settings.colors.synapses.normal; - canvas.getCtx().fillStyle = canvas.getCtx().strokeStyle = color; - } - Metamaps.JIT.renderEdgeArrows($jit.Graph.Plot.edgeHelper, adj, synapse, canvas); - - //check for edge label in data - var desc = synapse.get("desc"); - - var showDesc = adj.getData("showDesc"); - - var drawSynapseCount = function (context, x, y, count) { - /* - circle size: 16x16px - positioning: overlay and center on top right corner of synapse label - 8px left and 8px down - color: #dab539 - border color: #424242 - border size: 1.5px - font: DIN medium - font-size: 14pt - font-color: #424242 - */ - context.beginPath(); - context.arc(x, y, 8, 0, 2 * Math.PI, false); - context.fillStyle = '#DAB539'; - context.strokeStyle = '#424242'; - context.lineWidth = 1.5; - context.closePath(); - context.fill(); - context.stroke(); - - // add the synapse count - context.fillStyle = '#424242'; - context.textAlign = 'center'; - context.font = '14px din-medium'; - - context.fillText(count, x, y + 5); - }; - - if (!canvas.denySelected && desc != "" && showDesc) { - // '&' to '&' - desc = Metamaps.Util.decodeEntities(desc); - - //now adjust the label placement - var ctx = canvas.getCtx(); - ctx.font = 'bold 14px arial'; - ctx.fillStyle = '#FFF'; - ctx.textBaseline = 'alphabetic'; - - var arrayOfLabelLines = Metamaps.Util.splitLine(desc, 30).split('\n'); - var index, lineWidths = []; - for (index = 0; index < arrayOfLabelLines.length; ++index) { - lineWidths.push(ctx.measureText(arrayOfLabelLines[index]).width) - } - var width = Math.max.apply(null, lineWidths) + 16; - var height = (16 * arrayOfLabelLines.length) + 8; - - var x = (pos.x + posChild.x - width) / 2; - var y = ((pos.y + posChild.y) / 2) - height / 2; - - var radius = 5; - - //render background - ctx.beginPath(); - ctx.moveTo(x + radius, y); - ctx.lineTo(x + width - radius, y); - ctx.quadraticCurveTo(x + width, y, x + width, y + radius); - ctx.lineTo(x + width, y + height - radius); - ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); - ctx.lineTo(x + radius, y + height); - ctx.quadraticCurveTo(x, y + height, x, y + height - radius); - ctx.lineTo(x, y + radius); - ctx.quadraticCurveTo(x, y, x + radius, y); - ctx.closePath(); - ctx.fill(); - - // get number of synapses - var synapseNum = adj.getData("synapses").length; - - //render text - ctx.fillStyle = '#424242'; - ctx.textAlign = 'center'; - for (index = 0; index < arrayOfLabelLines.length; ++index) { - ctx.fillText(arrayOfLabelLines[index], x + (width / 2), y + 18 + (16 * index)); - } - - if (synapseNum > 1) { - drawSynapseCount(ctx, x + width, y, synapseNum); - } - } - else if (!canvas.denySelected && showDesc) { - // get number of synapses - var synapseNum = adj.getData("synapses").length; - - if (synapseNum > 1) { - var ctx = canvas.getCtx(); - var x = (pos.x + posChild.x) / 2; - var y = (pos.y + posChild.y) / 2; - drawSynapseCount(ctx, x, y, synapseNum); - } - } - - }, // edgeRender - ForceDirected: { - animateSavedLayout: { - modes: ['linear'], - transition: $jit.Trans.Quad.easeInOut, - duration: 800, - onComplete: function () { - Metamaps.Visualize.mGraph.busy = false; - $(document).trigger(Metamaps.JIT.events.animationDone); - } - }, - animateFDLayout: { - modes: ['linear'], - transition: $jit.Trans.Elastic.easeOut, - duration: 800, - onComplete: function () { - Metamaps.Visualize.mGraph.busy = false; - } - }, - graphSettings: { - //id of the visualization container - injectInto: 'infovis', - //Enable zooming and panning - //by scrolling and DnD - Navigation: { - enable: true, - //Enable panning events only if we're dragging the empty - //canvas (and not a node). - panning: 'avoid nodes', - zooming: 28 //zoom speed. higher is more sensible - }, - //background: { - // type: 'Metamaps' - //}, - //NodeStyles: { - // enable: true, - // type: 'Native', - // stylesHover: { - // dim: 30 - // }, - // duration: 300 - //}, - // Change node and edge styles such as - // color and width. - // These properties are also set per node - // with dollar prefixed data-properties in the - // JSON structure. - Node: { - overridable: true, - color: '#2D6A5D', - type: 'customNode', - dim: 25 - }, - Edge: { - overridable: true, - color: Metamaps.Settings.colors.synapses.normal, - type: 'customEdge', - lineWidth: 2, - alpha: 1 - }, - //Native canvas text styling - Label: { - type: 'Native', //Native or HTML - size: 20, - family: 'arial', - textBaseline: 'alphabetic', - color: Metamaps.Settings.colors.labels.text - }, - //Add Tips - Tips: { - enable: false, - onShow: function (tip, node) {} - }, - // Add node events - Events: { - enable: true, - enableForEdges: true, - onMouseMove: function (node, eventInfo, e) { - Metamaps.JIT.onMouseMoveHandler(node, eventInfo, e); - //console.log('called mouse move handler'); - }, - //Update node positions when dragged - onDragMove: function (node, eventInfo, e) { - Metamaps.JIT.onDragMoveTopicHandler(node, eventInfo, e); - //console.log('called drag move handler'); - }, - onDragEnd: function (node, eventInfo, e) { - Metamaps.JIT.onDragEndTopicHandler(node, eventInfo, e, false); - //console.log('called drag end handler'); - }, - onDragCancel: function (node, eventInfo, e) { - Metamaps.JIT.onDragCancelHandler(node, eventInfo, e, false); - }, - //Implement the same handler for touchscreens - onTouchStart: function (node, eventInfo, e) { - //$jit.util.event.stop(e); //stop default touchmove event - //Metamaps.Visualize.mGraph.events.onMouseDown(e, null, eventInfo); - Metamaps.Visualize.mGraph.events.touched = true; - Metamaps.Touch.touchPos = eventInfo.getPos(); - var canvas = Metamaps.Visualize.mGraph.canvas, - ox = canvas.translateOffsetX; - oy = canvas.translateOffsetY, - sx = canvas.scaleOffsetX, - sy = canvas.scaleOffsetY; - Metamaps.Touch.touchPos.x *= sx; - Metamaps.Touch.touchPos.y *= sy; - Metamaps.Touch.touchPos.x += ox; - Metamaps.Touch.touchPos.y += oy; - - touchDragNode = node; - }, - //Implement the same handler for touchscreens - onTouchMove: function (node, eventInfo, e) { - if (Metamaps.Touch.touchDragNode) Metamaps.JIT.onDragMoveTopicHandler(Metamaps.Touch.touchDragNode, eventInfo, e); - else { - Metamaps.JIT.touchPanZoomHandler(eventInfo, e); - } - }, - //Implement the same handler for touchscreens - onTouchEnd: function (node, eventInfo, e) { - - }, - //Implement the same handler for touchscreens - onTouchCancel: function (node, eventInfo, e) { - - }, - //Add also a click handler to nodes - onClick: function (node, eventInfo, e) { - - // remove the rightclickmenu - $('.rightclickmenu').remove(); - - if (Metamaps.Mouse.boxStartCoordinates) { - if(e.ctrlKey){ - Metamaps.Visualize.mGraph.busy = false; - Metamaps.Mouse.boxEndCoordinates = eventInfo.getPos(); - - var bS = Metamaps.Mouse.boxStartCoordinates; - var bE = Metamaps.Mouse.boxEndCoordinates; - if (Math.abs(bS.x - bE.x) > 20 && Math.abs(bS.y - bE.y) > 20) { - Metamaps.JIT.zoomToBox(e); - return; - } - else { - Metamaps.Mouse.boxStartCoordinates = null; - Metamaps.Mouse.boxEndCoordinates = null; - } - //console.log('called zoom to box'); - } - - if (e.shiftKey) { - Metamaps.Visualize.mGraph.busy = false; - Metamaps.Mouse.boxEndCoordinates = eventInfo.getPos(); - Metamaps.JIT.selectWithBox(e); - //console.log('called select with box'); - return; - }; - } - - if (e.target.id != "infovis-canvas") return false; - - //clicking on a edge, node, or clicking on blank part of canvas? - if (node.nodeFrom) { - Metamaps.JIT.selectEdgeOnClickHandler(node, e); - //console.log('called selectEdgeOnClickHandler'); - } else if (node && !node.nodeFrom) { - Metamaps.JIT.selectNodeOnClickHandler(node, e); - //console.log('called selectNodeOnClickHandler'); - } else { - Metamaps.JIT.canvasClickHandler(eventInfo.getPos(), e); - //console.log('called canvasClickHandler'); - } //if - }, - //Add also a click handler to nodes - onRightClick: function (node, eventInfo, e) { - - // remove the rightclickmenu - $('.rightclickmenu').remove(); - - if (Metamaps.Mouse.boxStartCoordinates) { - Metamaps.Visualize.mGraph.busy = false; - Metamaps.Mouse.boxEndCoordinates = eventInfo.getPos(); - Metamaps.JIT.selectWithBox(e); - return; - } - - if (e.target.id != "infovis-canvas") return false; - - //clicking on a edge, node, or clicking on blank part of canvas? - if (node.nodeFrom) { - Metamaps.JIT.selectEdgeOnRightClickHandler(node, e); - } else if (node && !node.nodeFrom) { - Metamaps.JIT.selectNodeOnRightClickHandler(node, e); - } else { - //console.log('right clicked on open space'); - } - } - }, - //Number of iterations for the FD algorithm - iterations: 200, - //Edge length - levelDistance: 200, - }, - nodeSettings: { - 'customNode': { - 'render': function (node, canvas) { - var pos = node.pos.getc(true), - dim = node.getData('dim'), - topic = node.getData('topic'), - metacode = topic ? topic.getMetacode() : false, - ctx = canvas.getCtx(); - - // if the topic is selected draw a circle around it - if (!canvas.denySelected && node.selected) { - ctx.beginPath(); - ctx.arc(pos.x, pos.y, dim + 3, 0, 2 * Math.PI, false); - ctx.strokeStyle = Metamaps.Settings.colors.topics.selected; - ctx.lineWidth = 2; - ctx.stroke(); - } - - if (!metacode || - !metacode.get('image') || - !metacode.get('image').complete || - (typeof metacode.get('image').naturalWidth !== "undefined" && - metacode.get('image').naturalWidth === 0)) { - ctx.beginPath(); - ctx.arc(pos.x, pos.y, dim, 0, 2 * Math.PI, false); - ctx.fillStyle = '#B6B2FD'; - ctx.fill(); - } else { - ctx.drawImage(metacode.get('image'), pos.x - dim, pos.y - dim, dim * 2, dim * 2); - } - - // if the topic has a link, draw a small image to indicate that - var hasLink = topic && topic.get('link') !== "" && topic.get('link') !== null; - var linkImage = Metamaps.JIT.topicLinkImage; - var linkImageLoaded = linkImage.complete || - (typeof linkImage.naturalWidth !== "undefined" && - linkImage.naturalWidth !== 0) - if (hasLink && linkImageLoaded) { - ctx.drawImage(linkImage, pos.x - dim - 8, pos.y - dim - 8, 16, 16); - } - - // if the topic has a desc, draw a small image to indicate that - var hasDesc = topic && topic.get('desc') !== "" && topic.get('desc') !== null; - var descImage = Metamaps.JIT.topicDescImage; - var descImageLoaded = descImage.complete || - (typeof descImage.naturalWidth !== "undefined" && - descImage.naturalWidth !== 0) - if (hasDesc && descImageLoaded) { - ctx.drawImage(descImage, pos.x + dim - 8, pos.y - dim - 8, 16, 16); - } - }, - 'contains': function (node, pos) { - var npos = node.pos.getc(true), - dim = node.getData('dim'), - arrayOfLabelLines = Metamaps.Util.splitLine(node.name, 30).split('\n'), - ctx = Metamaps.Visualize.mGraph.canvas.getCtx(); - - var height = 25 * arrayOfLabelLines.length; - - var index, lineWidths = []; - for (index = 0; index < arrayOfLabelLines.length; ++index) { - lineWidths.push(ctx.measureText(arrayOfLabelLines[index]).width) - } - var width = Math.max.apply(null, lineWidths) + 8; - var labely = npos.y + node.getData("height") + 5 + height / 2; - - var overLabel = this.nodeHelper.rectangle.contains({ - x: npos.x, - y: labely - }, pos, width, height); - - return this.nodeHelper.circle.contains(npos, pos, dim) || overLabel; - } - } - }, - edgeSettings: { - 'customEdge': { - 'render': function (adj, canvas) { - Metamaps.JIT.edgeRender(adj, canvas) - }, - 'contains': function (adj, pos) { - var from = adj.nodeFrom.pos.getc(), - to = adj.nodeTo.pos.getc(); - - // this fixes an issue where when edges are perfectly horizontal or perfectly vertical - // it becomes incredibly difficult to hover over them - if (-1 < pos.x && pos.x < 1) pos.x = 0; - if (-1 < pos.y && pos.y < 1) pos.y = 0; - - return $jit.Graph.Plot.edgeHelper.line.contains(from, to, pos, adj.Edge.epsilon + 5); - } - } - } - }, // ForceDirected - ForceDirected3D: { - animate: { - modes: ['linear'], - transition: $jit.Trans.Elastic.easeOut, - duration: 2500, - onComplete: function () { - Metamaps.Visualize.mGraph.busy = false; - } - }, - graphSettings: { - //id of the visualization container - injectInto: 'infovis', - type: '3D', - Scene: { - Lighting: { - enable: false, - ambient: [0.5, 0.5, 0.5], - directional: { - direction: { - x: 1, - y: 0, - z: -1 - }, - color: [0.9, 0.9, 0.9] - } - } - }, - //Enable zooming and panning - //by scrolling and DnD - Navigation: { - enable: false, - //Enable panning events only if we're dragging the empty - //canvas (and not a node). - panning: 'avoid nodes', - zooming: 10 //zoom speed. higher is more sensible - }, - // Change node and edge styles such as - // color and width. - // These properties are also set per node - // with dollar prefixed data-properties in the - // JSON structure. - Node: { - overridable: true, - type: 'sphere', - dim: 15, - color: '#ffffff' - }, - Edge: { - overridable: false, - type: 'tube', - color: '#111', - lineWidth: 3 - }, - //Native canvas text styling - Label: { - type: 'HTML', //Native or HTML - size: 10, - style: 'bold' - }, - // Add node events - Events: { - enable: true, - type: 'Native', - i: 0, - onMouseMove: function (node, eventInfo, e) { - //if(this.i++ % 3) return; - var pos = eventInfo.getPos(); - Metamaps.Visualize.cameraPosition.x += (pos.x - Metamaps.Visualize.cameraPosition.x) * 0.5; - Metamaps.Visualize.cameraPosition.y += (-pos.y - Metamaps.Visualize.cameraPosition.y) * 0.5; - Metamaps.Visualize.mGraph.plot(); - }, - onMouseWheel: function (delta) { - Metamaps.Visualize.cameraPosition.z += -delta * 20; - Metamaps.Visualize.mGraph.plot(); - }, - onClick: function () {} - }, - //Number of iterations for the FD algorithm - iterations: 200, - //Edge length - levelDistance: 100 - }, - nodeSettings: { - - }, - edgeSettings: { - - } - }, // ForceDirected3D - RGraph: { - animate: { - modes: ['polar'], - duration: 800, - onComplete: function () { - Metamaps.Visualize.mGraph.busy = false; - } - }, - // this will just be used to patch the ForceDirected graphsettings with the few things which actually differ - background: { - //type: 'Metamaps', - levelDistance: 200, - numberOfCircles: 4, - CanvasStyles: { - strokeStyle: '#333', - lineWidth: 1.5 - } - }, - levelDistance: 200 - }, - onMouseEnter: function (edge) { - var filtered = edge.getData('alpha') === 0; - - // don't do anything if the edge is filtered - // or if the canvas is animating - if (filtered || Metamaps.Visualize.mGraph.busy) return; - - $('canvas').css('cursor', 'pointer'); - var edgeIsSelected = Metamaps.Selected.Edges.indexOf(edge); - //following if statement only executes if the edge being hovered over is not selected - if (edgeIsSelected == -1) { - edge.setData('showDesc', true, 'current'); - } - - edge.setDataset('end', { - lineWidth: 4 - }); - Metamaps.Visualize.mGraph.fx.animate({ - modes: ['edge-property:lineWidth'], - duration: 100 - }); - Metamaps.Visualize.mGraph.plot(); - }, // onMouseEnter - onMouseLeave: function (edge) { - if (edge.getData('alpha') === 0) return; // don't do anything if the edge is filtered - $('canvas').css('cursor', 'default'); - var edgeIsSelected = Metamaps.Selected.Edges.indexOf(edge); - //following if statement only executes if the edge being hovered over is not selected - if (edgeIsSelected == -1) { - edge.setData('showDesc', false, 'current'); - } - - edge.setDataset('end', { - lineWidth: 2 - }); - Metamaps.Visualize.mGraph.fx.animate({ - modes: ['edge-property:lineWidth'], - duration: 100 - }); - Metamaps.Visualize.mGraph.plot(); - }, // onMouseLeave - onMouseMoveHandler: function (node, eventInfo, e) { - - var self = Metamaps.JIT; - - if (Metamaps.Visualize.mGraph.busy) return; - - var node = eventInfo.getNode(); - var edge = eventInfo.getEdge(); - - //if we're on top of a node object, act like there aren't edges under it - if (node != false) { - if (Metamaps.Mouse.edgeHoveringOver) { - self.onMouseLeave(Metamaps.Mouse.edgeHoveringOver); - } - $('canvas').css('cursor', 'pointer'); - return; - } - - if (edge == false && Metamaps.Mouse.edgeHoveringOver != false) { - //mouse not on an edge, but we were on an edge previously - self.onMouseLeave(Metamaps.Mouse.edgeHoveringOver); - } else if (edge != false && Metamaps.Mouse.edgeHoveringOver == false) { - //mouse is on an edge, but there isn't a stored edge - self.onMouseEnter(edge); - } else if (edge != false && Metamaps.Mouse.edgeHoveringOver != edge) { - //mouse is on an edge, but a different edge is stored - self.onMouseLeave(Metamaps.Mouse.edgeHoveringOver) - self.onMouseEnter(edge); - } - - //could be false - Metamaps.Mouse.edgeHoveringOver = edge; - - if (!node && !edge) { - $('canvas').css('cursor', 'default'); - } - }, // onMouseMoveHandler - enterKeyHandler: function () { - var creatingMap = Metamaps.GlobalUI.lightbox; - if (creatingMap === "newmap" || creatingMap === "forkmap") { - Metamaps.GlobalUI.CreateMap.submit(); - } - // this is to submit new topic creation - else if (Metamaps.Create.newTopic.beingCreated) { - Metamaps.Topic.createTopicLocally(); - } - // to submit new synapse creation - else if (Metamaps.Create.newSynapse.beingCreated) { - Metamaps.Synapse.createSynapseLocally(); - } - }, //enterKeyHandler - escKeyHandler: function () { - Metamaps.Control.deselectAllEdges(); - Metamaps.Control.deselectAllNodes(); - }, //escKeyHandler - touchPanZoomHandler: function (eventInfo, e) { - if (e.touches.length == 1) { - var thispos = Metamaps.Touch.touchPos, - currentPos = eventInfo.getPos(), - canvas = Metamaps.Visualize.mGraph.canvas, - ox = canvas.translateOffsetX, - oy = canvas.translateOffsetY, - sx = canvas.scaleOffsetX, - sy = canvas.scaleOffsetY; - currentPos.x *= sx; - currentPos.y *= sy; - currentPos.x += ox; - currentPos.y += oy; - //var x = currentPos.x - thispos.x, - // y = currentPos.y - thispos.y; - var x = currentPos.x - thispos.x, - y = currentPos.y - thispos.y; - Metamaps.Touch.touchPos = currentPos; - Metamaps.Visualize.mGraph.canvas.translate(x * 1 / sx, y * 1 / sy); - } else if (e.touches.length == 2) { - var touch1 = e.touches[0]; - var touch2 = e.touches[1]; - - var dist = Metamaps.Util.getDistance({ - x: touch1.clientX, - y: touch1.clientY - }, { - x: touch2.clientX, - y: touch2.clientY - }); - - if (!lastDist) { - lastDist = dist; - } - - var scale = dist / lastDist; - - if (8 >= Metamaps.Visualize.mGraph.canvas.scaleOffsetX * scale && Metamaps.Visualize.mGraph.canvas.scaleOffsetX * scale >= 1) { - Metamaps.Visualize.mGraph.canvas.scale(scale, scale); - } - if (Metamaps.Visualize.mGraph.canvas.scaleOffsetX < 0.5) { - Metamaps.Visualize.mGraph.canvas.viz.labels.hideLabels(true); - } else if (Metamaps.Visualize.mGraph.canvas.scaleOffsetX > 0.5) { - Metamaps.Visualize.mGraph.canvas.viz.labels.hideLabels(false); - } - lastDist = dist; - } - - }, // touchPanZoomHandler - onDragMoveTopicHandler: function (node, eventInfo, e) { - - var self = Metamaps.JIT; - - // this is used to send nodes that are moving to - // other realtime collaborators on the same map - var positionsToSend = {}; - var topic; - - var authorized = Metamaps.Active.Map && Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); - - if (node && !node.nodeFrom) { - var pos = eventInfo.getPos(); - // if it's a left click, or a touch, move the node - if (e.touches || (e.button == 0 && !e.altKey && !e.ctrlKey && !e.shiftKey && (e.buttons == 0 || e.buttons == 1 || e.buttons == undefined))) { - //if the node dragged isn't already selected, select it - var whatToDo = self.handleSelectionBeforeDragging(node, e); - if (node.pos.rho || node.pos.rho === 0) { - // this means we're in topic view - var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y); - var theta = Math.atan2(pos.y, pos.x); - node.pos.setp(theta, rho); - } else if (whatToDo == 'only-drag-this-one') { - node.pos.setc(pos.x, pos.y); - - if (Metamaps.Active.Map) { - topic = node.getData('topic'); - // we use the topic ID not the node id - // because we can't depend on the node id - // to be the same as on other collaborators - // maps - positionsToSend[topic.id] = pos; - $(document).trigger(Metamaps.JIT.events.topicDrag, [positionsToSend]); - } - } else { - var len = Metamaps.Selected.Nodes.length; - - //first define offset for each node - var xOffset = new Array(); - var yOffset = new Array(); - for (var i = 0; i < len; i += 1) { - var n = Metamaps.Selected.Nodes[i]; - xOffset[i] = n.pos.x - node.pos.x; - yOffset[i] = n.pos.y - node.pos.y; - } //for - - for (var i = 0; i < len; i += 1) { - var n = Metamaps.Selected.Nodes[i]; - var x = pos.x + xOffset[i]; - var y = pos.y + yOffset[i]; - n.pos.setc(x, y); - - if (Metamaps.Active.Map) { - topic = n.getData('topic'); - // we use the topic ID not the node id - // because we can't depend on the node id - // to be the same as on other collaborators - // maps - positionsToSend[topic.id] = n.pos; - } - } //for - - if (Metamaps.Active.Map) { - $(document).trigger(Metamaps.JIT.events.topicDrag, [positionsToSend]); - } - } //if - - if (whatToDo == 'deselect') { - Metamaps.Control.deselectNode(node); - } - Metamaps.Visualize.mGraph.plot(); - } - // if it's a right click or holding down alt, start synapse creation ->third option is for firefox - else if ((e.button == 2 || (e.button == 0 && e.altKey) || e.buttons == 2) && authorized) { - if (Metamaps.tempInit == false) { - Metamaps.tempNode = node; - Metamaps.tempInit = true; - - Metamaps.Create.newTopic.hide(); - Metamaps.Create.newSynapse.hide(); - // set the draw synapse start positions - var l = Metamaps.Selected.Nodes.length; - if (l > 0) { - for (var i = l - 1; i >= 0; i -= 1) { - var n = Metamaps.Selected.Nodes[i]; - Metamaps.Mouse.synapseStartCoordinates.push({ - x: n.pos.getc().x, - y: n.pos.getc().y - }); - } - } else { - Metamaps.Mouse.synapseStartCoordinates = [{ - x: Metamaps.tempNode.pos.getc().x, - y: Metamaps.tempNode.pos.getc().y - }]; - } - Metamaps.Mouse.synapseEndCoordinates = { - x: pos.x, - y: pos.y - }; - } - // - temp = eventInfo.getNode(); - if (temp != false && temp.id != node.id && Metamaps.Selected.Nodes.indexOf(temp) == -1) { // this means a Node has been returned - Metamaps.tempNode2 = temp; - - Metamaps.Mouse.synapseEndCoordinates = { - x: Metamaps.tempNode2.pos.getc().x, - y: Metamaps.tempNode2.pos.getc().y - }; - - // before making the highlighted one bigger, make sure all the others are regular size - Metamaps.Visualize.mGraph.graph.eachNode(function (n) { - n.setData('dim', 25, 'current'); - }); - temp.setData('dim', 35, 'current'); - Metamaps.Visualize.mGraph.plot(); - } else if (!temp) { - Metamaps.tempNode2 = null; - Metamaps.Visualize.mGraph.graph.eachNode(function (n) { - n.setData('dim', 25, 'current'); - }); - //pop up node creation :) - var myX = e.clientX - 110; - var myY = e.clientY - 30; - $('#new_topic').css('left', myX + "px"); - $('#new_topic').css('top', myY + "px"); - Metamaps.Create.newTopic.x = eventInfo.getPos().x; - Metamaps.Create.newTopic.y = eventInfo.getPos().y; - Metamaps.Visualize.mGraph.plot(); - - Metamaps.Mouse.synapseEndCoordinates = { - x: pos.x, - y: pos.y - }; - } - } - else if ((e.button == 2 || (e.button == 0 && e.altKey) || e.buttons == 2) && Metamaps.Active.Topic) { - Metamaps.GlobalUI.notifyUser("Cannot create in Topic view."); - } - else if ((e.button == 2 || (e.button == 0 && e.altKey) || e.buttons == 2) && !authorized) { - Metamaps.GlobalUI.notifyUser("Cannot edit Public map."); - } - } - }, // onDragMoveTopicHandler - onDragCancelHandler: function (node, eventInfo, e) { - Metamaps.tempNode = null; - if (Metamaps.tempNode2) Metamaps.tempNode2.setData('dim', 25, 'current'); - Metamaps.tempNode2 = null; - Metamaps.tempInit = false; - // reset the draw synapse positions to false - Metamaps.Mouse.synapseStartCoordinates = []; - Metamaps.Mouse.synapseEndCoordinates = null; - Metamaps.Visualize.mGraph.plot(); - }, // onDragCancelHandler - onDragEndTopicHandler: function (node, eventInfo, e) { - var midpoint = {}, pixelPos, mapping; - - if (Metamaps.tempInit && Metamaps.tempNode2 == null) { - // this means you want to add a new topic, and then a synapse - Metamaps.Create.newTopic.addSynapse = true; - Metamaps.Create.newTopic.open(); - } else if (Metamaps.tempInit && Metamaps.tempNode2 != null) { - // this means you want to create a synapse between two existing topics - Metamaps.Create.newTopic.addSynapse = false; - Metamaps.Create.newSynapse.topic1id = Metamaps.tempNode.getData('topic').id; - Metamaps.Create.newSynapse.topic2id = Metamaps.tempNode2.getData('topic').id; - Metamaps.tempNode2.setData('dim', 25, 'current'); - Metamaps.Visualize.mGraph.plot(); - midpoint.x = Metamaps.tempNode.pos.getc().x + (Metamaps.tempNode2.pos.getc().x - Metamaps.tempNode.pos.getc().x) / 2; - midpoint.y = Metamaps.tempNode.pos.getc().y + (Metamaps.tempNode2.pos.getc().y - Metamaps.tempNode.pos.getc().y) / 2; - pixelPos = Metamaps.Util.coordsToPixels(midpoint); - $('#new_synapse').css('left', pixelPos.x + "px"); - $('#new_synapse').css('top', pixelPos.y + "px"); - Metamaps.Create.newSynapse.open(); - Metamaps.tempNode = null; - Metamaps.tempNode2 = null; - Metamaps.tempInit = false; - } else if (!Metamaps.tempInit && node && !node.nodeFrom) { - // this means you dragged an existing node, autosave that to the database - - // check whether to save mappings - var checkWhetherToSave = function() { - var map = Metamaps.Active.Map; - - if (!map) return false; - - var mapper = Metamaps.Active.Mapper; - // this case - // covers when it is a public map owned by you - // and also when it's a private map - var activeMappersMap = map.authorizePermissionChange(mapper); - var commonsMap = map.get('permission') === 'commons'; - var realtimeOn = Metamaps.Realtime.status; - - // don't save if commons map, and you have realtime off, - // even if you're map creator - return map && mapper && ((commonsMap && realtimeOn) || (activeMappersMap && !commonsMap)); - } - - if (checkWhetherToSave()) { - mapping = node.getData('mapping'); - mapping.save({ - xloc: node.getPos().x, - yloc: node.getPos().y - }); - // also save any other selected nodes that also got dragged along - var l = Metamaps.Selected.Nodes.length; - for (var i = l - 1; i >= 0; i -= 1) { - var n = Metamaps.Selected.Nodes[i]; - if (n !== node) { - mapping = n.getData('mapping'); - mapping.save({ - xloc: n.getPos().x, - yloc: n.getPos().y - }); - } - }; - } - } - }, //onDragEndTopicHandler - canvasClickHandler: function (canvasLoc, e) { - //grab the location and timestamp of the click - var storedTime = Metamaps.Mouse.lastCanvasClick; - var now = Date.now(); //not compatible with IE8 FYI - Metamaps.Mouse.lastCanvasClick = now; - - var authorized = Metamaps.Active.Map && Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); - - if (now - storedTime < Metamaps.Mouse.DOUBLE_CLICK_TOLERANCE && !Metamaps.Mouse.didPan) { - if (Metamaps.Active.Map && !authorized) { - Metamaps.GlobalUI.notifyUser("Cannot edit Public map."); - return; - } - else if (Metamaps.Active.Topic) { - Metamaps.GlobalUI.notifyUser("Cannot create in Topic view."); - return; - } - // DOUBLE CLICK - //pop up node creation :) - Metamaps.Create.newTopic.addSynapse = false; - Metamaps.Create.newTopic.x = canvasLoc.x; - Metamaps.Create.newTopic.y = canvasLoc.y; - $('#new_topic').css('left', e.clientX + "px"); - $('#new_topic').css('top', e.clientY + "px"); - Metamaps.Create.newTopic.open(); - } else if (!Metamaps.Mouse.didPan) { - // SINGLE CLICK, no pan - Metamaps.Filter.close(); - Metamaps.TopicCard.hideCard(); - Metamaps.SynapseCard.hideCard(); - Metamaps.Create.newTopic.hide(); - $('.rightclickmenu').remove(); - // reset the draw synapse positions to false - Metamaps.Mouse.synapseStartCoordinates = []; - Metamaps.Mouse.synapseEndCoordinates = null; - Metamaps.tempInit = false; - Metamaps.tempNode = null; - Metamaps.tempNode2 = null; - if (!e.ctrlKey && !e.shiftKey) { - Metamaps.Control.deselectAllEdges(); - Metamaps.Control.deselectAllNodes(); - } - } - }, //canvasClickHandler - nodeDoubleClickHandler: function (node, e) { - - Metamaps.TopicCard.showCard(node); - - }, // nodeDoubleClickHandler - edgeDoubleClickHandler: function (adj, e) { - - Metamaps.SynapseCard.showCard(adj, e); - - }, // nodeDoubleClickHandler - nodeWasDoubleClicked: function () { - //grab the timestamp of the click - var storedTime = Metamaps.Mouse.lastNodeClick; - var now = Date.now(); //not compatible with IE8 FYI - Metamaps.Mouse.lastNodeClick = now; - - if (now - storedTime < Metamaps.Mouse.DOUBLE_CLICK_TOLERANCE) { - return true; + events: { + topicDrag: 'Metamaps:JIT:events:topicDrag', + newTopic: 'Metamaps:JIT:events:newTopic', + deleteTopic: 'Metamaps:JIT:events:deleteTopic', + removeTopic: 'Metamaps:JIT:events:removeTopic', + newSynapse: 'Metamaps:JIT:events:newSynapse', + deleteSynapse: 'Metamaps:JIT:events:deleteSynapse', + removeSynapse: 'Metamaps:JIT:events:removeSynapse', + pan: 'Metamaps:JIT:events:pan', + zoom: 'Metamaps:JIT:events:zoom', + animationDone: 'Metamaps:JIT:events:animationDone', + }, + vizData: [], // contains the visualization-compatible graph + /** + * This method will bind the event handlers it is interested and initialize the class. + */ + init: function () { + var self = Metamaps.JIT + + $('.zoomIn').click(self.zoomIn) + $('.zoomOut').click(self.zoomOut) + + var zoomExtents = function (event) { + self.zoomExtents(event, Metamaps.Visualize.mGraph.canvas) + } + $('.zoomExtents').click(zoomExtents) + + $('.takeScreenshot').click(Metamaps.Map.exportImage) + + self.topicDescImage = new Image() + self.topicDescImage.src = '<%= asset_path('topic_description_signifier.png') %>' + + self.topicLinkImage = new Image() + self.topicLinkImage.src = '<%= asset_path('topic_link_signifier.png') %>' + }, + /** + * convert our topic JSON into something JIT can use + */ + convertModelsToJIT: function (topics, synapses) { + var jitReady = [] + + var synapsesToRemove = [] + var topic + var mapping + var node + var nodes = {} + var existingEdge + var edge + var edges = [] + + topics.each(function (t) { + node = t.createNode() + nodes[node.id] = node + }) + synapses.each(function (s) { + edge = s.createEdge() + + if (topics.get(s.get('node1_id')) === undefined || topics.get(s.get('node2_id')) === undefined) { + // this means it's an invalid synapse + synapsesToRemove.push(s) + } + else if (nodes[edge.nodeFrom] && nodes[edge.nodeTo]) { + existingEdge = _.findWhere(edges, { + nodeFrom: edge.nodeFrom, + nodeTo: edge.nodeTo + }) || + _.findWhere(edges, { + nodeFrom: edge.nodeTo, + nodeTo: edge.nodeFrom + }) + + if (existingEdge) { + // for when you're dealing with multiple relationships between the same two topics + if (Metamaps.Active.Map) { + mapping = s.getMapping() + existingEdge.data['$mappingIDs'].push(mapping.id) + } + existingEdge.data['$synapseIDs'].push(s.id) } else { - return false; + // for when you're dealing with a topic that has relationships to many different nodes + nodes[edge.nodeFrom].adjacencies.push(edge) + edges.push(edge) } - }, //nodeWasDoubleClicked; - handleSelectionBeforeDragging: function (node, e) { - // four cases: - // 1 nothing is selected, so pretend you aren't selecting - // 2 others are selected only and shift, so additionally select this one - // 3 others are selected only, no shift: drag only this one - // 4 this node and others were selected, so drag them (just return false) - //return value: deselect node again after? - if (Metamaps.Selected.Nodes.length == 0) { - return 'only-drag-this-one'; - } - if (Metamaps.Selected.Nodes.indexOf(node) == -1) { + } + }) + + _.each(nodes, function (node) { + jitReady.push(node) + }) + + return [jitReady, synapsesToRemove] + }, + prepareVizData: function () { + var self = Metamaps.JIT + var mapping + + // reset/empty vizData + self.vizData = [] + Metamaps.Visualize.loadLater = false + + var results = self.convertModelsToJIT(Metamaps.Topics, Metamaps.Synapses) + + self.vizData = results[0] + + // clean up the synapses array in case of any faulty data + _.each(results[1], function (synapse) { + mapping = synapse.getMapping() + Metamaps.Synapses.remove(synapse) + if (Metamaps.Mappings) Metamaps.Mappings.remove(mapping) + }) + + if (self.vizData.length == 0) { + Metamaps.Famous.viz.showInstructions() + Metamaps.Visualize.loadLater = true + } + else Metamaps.Famous.viz.hideInstructions() + + Metamaps.Visualize.render() + }, // prepareVizData + edgeRender: function (adj, canvas) { + // get nodes cartesian coordinates + var pos = adj.nodeFrom.pos.getc(true) + var posChild = adj.nodeTo.pos.getc(true) + + var synapse + if (adj.getData('displayIndex')) { + synapse = adj.getData('synapses')[adj.getData('displayIndex')] + if (!synapse) { + delete adj.data.$displayIndex + synapse = adj.getData('synapses')[0] + } + } else { + synapse = adj.getData('synapses')[0] + } + + if (!synapse) return // this means there are no corresponding synapses for + // this edge, don't render it + + var directionCat = synapse.get('category') + + // label placement on edges + if (canvas.denySelected) { + var color = Metamaps.Settings.colors.synapses.normal + canvas.getCtx().fillStyle = canvas.getCtx().strokeStyle = color + } + Metamaps.JIT.renderEdgeArrows($jit.Graph.Plot.edgeHelper, adj, synapse, canvas) + + // check for edge label in data + var desc = synapse.get('desc') + + var showDesc = adj.getData('showDesc') + + var drawSynapseCount = function (context, x, y, count) { + /* + circle size: 16x16px + positioning: overlay and center on top right corner of synapse label - 8px left and 8px down + color: #dab539 + border color: #424242 + border size: 1.5px + font: DIN medium + font-size: 14pt + font-color: #424242 + */ + context.beginPath() + context.arc(x, y, 8, 0, 2 * Math.PI, false) + context.fillStyle = '#DAB539' + context.strokeStyle = '#424242' + context.lineWidth = 1.5 + context.closePath() + context.fill() + context.stroke() + + // add the synapse count + context.fillStyle = '#424242' + context.textAlign = 'center' + context.font = '14px din-medium' + + context.fillText(count, x, y + 5) + } + + if (!canvas.denySelected && desc != '' && showDesc) { + // '&' to '&' + desc = Metamaps.Util.decodeEntities(desc) + + // now adjust the label placement + var ctx = canvas.getCtx() + ctx.font = 'bold 14px arial' + ctx.fillStyle = '#FFF' + ctx.textBaseline = 'alphabetic' + + var arrayOfLabelLines = Metamaps.Util.splitLine(desc, 30).split('\n') + var index, lineWidths = [] + for (index = 0; index < arrayOfLabelLines.length; ++index) { + lineWidths.push(ctx.measureText(arrayOfLabelLines[index]).width) + } + var width = Math.max.apply(null, lineWidths) + 16 + var height = (16 * arrayOfLabelLines.length) + 8 + + var x = (pos.x + posChild.x - width) / 2 + var y = ((pos.y + posChild.y) / 2) - height / 2 + + var radius = 5 + + // render background + ctx.beginPath() + ctx.moveTo(x + radius, y) + ctx.lineTo(x + width - radius, y) + ctx.quadraticCurveTo(x + width, y, x + width, y + radius) + ctx.lineTo(x + width, y + height - radius) + ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height) + ctx.lineTo(x + radius, y + height) + ctx.quadraticCurveTo(x, y + height, x, y + height - radius) + ctx.lineTo(x, y + radius) + ctx.quadraticCurveTo(x, y, x + radius, y) + ctx.closePath() + ctx.fill() + + // get number of synapses + var synapseNum = adj.getData('synapses').length + + // render text + ctx.fillStyle = '#424242' + ctx.textAlign = 'center' + for (index = 0; index < arrayOfLabelLines.length; ++index) { + ctx.fillText(arrayOfLabelLines[index], x + (width / 2), y + 18 + (16 * index)) + } + + if (synapseNum > 1) { + drawSynapseCount(ctx, x + width, y, synapseNum) + } + } + else if (!canvas.denySelected && showDesc) { + // get number of synapses + var synapseNum = adj.getData('synapses').length + + if (synapseNum > 1) { + var ctx = canvas.getCtx() + var x = (pos.x + posChild.x) / 2 + var y = (pos.y + posChild.y) / 2 + drawSynapseCount(ctx, x, y, synapseNum) + } + } + }, // edgeRender + ForceDirected: { + animateSavedLayout: { + modes: ['linear'], + transition: $jit.Trans.Quad.easeInOut, + duration: 800, + onComplete: function () { + Metamaps.Visualize.mGraph.busy = false + $(document).trigger(Metamaps.JIT.events.animationDone) + } + }, + animateFDLayout: { + modes: ['linear'], + transition: $jit.Trans.Elastic.easeOut, + duration: 800, + onComplete: function () { + Metamaps.Visualize.mGraph.busy = false + } + }, + graphSettings: { + // id of the visualization container + injectInto: 'infovis', + // Enable zooming and panning + // by scrolling and DnD + Navigation: { + enable: true, + // Enable panning events only if we're dragging the empty + // canvas (and not a node). + panning: 'avoid nodes', + zooming: 28 // zoom speed. higher is more sensible + }, + // background: { + // type: 'Metamaps' + // }, + // NodeStyles: { + // enable: true, + // type: 'Native', + // stylesHover: { + // dim: 30 + // }, + // duration: 300 + // }, + // Change node and edge styles such as + // color and width. + // These properties are also set per node + // with dollar prefixed data-properties in the + // JSON structure. + Node: { + overridable: true, + color: '#2D6A5D', + type: 'customNode', + dim: 25 + }, + Edge: { + overridable: true, + color: Metamaps.Settings.colors.synapses.normal, + type: 'customEdge', + lineWidth: 2, + alpha: 1 + }, + // Native canvas text styling + Label: { + type: 'Native', // Native or HTML + size: 20, + family: 'arial', + textBaseline: 'alphabetic', + color: Metamaps.Settings.colors.labels.text + }, + // Add Tips + Tips: { + enable: false, + onShow: function (tip, node) {} + }, + // Add node events + Events: { + enable: true, + enableForEdges: true, + onMouseMove: function (node, eventInfo, e) { + Metamaps.JIT.onMouseMoveHandler(node, eventInfo, e) + // console.log('called mouse move handler') + }, + // Update node positions when dragged + onDragMove: function (node, eventInfo, e) { + Metamaps.JIT.onDragMoveTopicHandler(node, eventInfo, e) + // console.log('called drag move handler') + }, + onDragEnd: function (node, eventInfo, e) { + Metamaps.JIT.onDragEndTopicHandler(node, eventInfo, e, false) + // console.log('called drag end handler') + }, + onDragCancel: function (node, eventInfo, e) { + Metamaps.JIT.onDragCancelHandler(node, eventInfo, e, false) + }, + // Implement the same handler for touchscreens + onTouchStart: function (node, eventInfo, e) { + Metamaps.Visualize.mGraph.events.touched = true + + var canvas = Metamaps.Visualize.mGraph.canvas + + Metamaps.Touch.touchPos = eventInfo.getPos() + Metamaps.Touch.touchPos.x *= canvas.scaleOffsetX + Metamaps.Touch.touchPos.y *= canvas.scaleOffsetY + Metamaps.Touch.touchPos.x += canvas.translateOffsetX + Metamaps.Touch.touchPos.y += canvas.translateOffsetY + + touchDragNode = node + }, + // Implement the same handler for touchscreens + onTouchMove: function (node, eventInfo, e) { + if (Metamaps.Touch.touchDragNode) { + Metamaps.JIT.onDragMoveTopicHandler(Metamaps.Touch.touchDragNode, eventInfo, e) + } else { + Metamaps.JIT.touchPanZoomHandler(eventInfo, e) + } + }, + // Implement the same handler for touchscreens + onTouchEnd: function (node, eventInfo, e) {}, + // Implement the same handler for touchscreens + onTouchCancel: function (node, eventInfo, e) {}, + // Add also a click handler to nodes + onClick: function (node, eventInfo, e) { + // remove the rightclickmenu + $('.rightclickmenu').remove() + + if (Metamaps.Mouse.boxStartCoordinates) { + if (e.ctrlKey) { + Metamaps.Visualize.mGraph.busy = false + Metamaps.Mouse.boxEndCoordinates = eventInfo.getPos() + + var bS = Metamaps.Mouse.boxStartCoordinates + var bE = Metamaps.Mouse.boxEndCoordinates + if (Math.abs(bS.x - bE.x) > 20 && Math.abs(bS.y - bE.y) > 20) { + Metamaps.JIT.zoomToBox(e) + return + } else { + Metamaps.Mouse.boxStartCoordinates = null + Metamaps.Mouse.boxEndCoordinates = null + } + // console.log('called zoom to box') + } + if (e.shiftKey) { - Metamaps.Control.selectNode(node,e); - return 'nothing'; - } else { - return 'only-drag-this-one'; + Metamaps.Visualize.mGraph.busy = false + Metamaps.Mouse.boxEndCoordinates = eventInfo.getPos() + Metamaps.JIT.selectWithBox(e) + // console.log('called select with box') + return } + } + + if (e.target.id != 'infovis-canvas') return false + + // clicking on a edge, node, or clicking on blank part of canvas? + if (node.nodeFrom) { + Metamaps.JIT.selectEdgeOnClickHandler(node, e) + // console.log('called selectEdgeOnClickHandler') + } else if (node && !node.nodeFrom) { + Metamaps.JIT.selectNodeOnClickHandler(node, e) + // console.log('called selectNodeOnClickHandler') + } else { + Metamaps.JIT.canvasClickHandler(eventInfo.getPos(), e) + // console.log('called canvasClickHandler') + } // if + }, + // Add also a click handler to nodes + onRightClick: function (node, eventInfo, e) { + // remove the rightclickmenu + $('.rightclickmenu').remove() + + if (Metamaps.Mouse.boxStartCoordinates) { + Metamaps.Visualize.mGraph.busy = false + Metamaps.Mouse.boxEndCoordinates = eventInfo.getPos() + Metamaps.JIT.selectWithBox(e) + return + } + + if (e.target.id != 'infovis-canvas') return false + + // clicking on a edge, node, or clicking on blank part of canvas? + if (node.nodeFrom) { + Metamaps.JIT.selectEdgeOnRightClickHandler(node, e) + } else if (node && !node.nodeFrom) { + Metamaps.JIT.selectNodeOnRightClickHandler(node, e) + } else { + // console.log('right clicked on open space') + } } - return 'nothing'; //case 4? - }, // handleSelectionBeforeDragging - selectWithBox: function (e) { + }, + // Number of iterations for the FD algorithm + iterations: 200, + // Edge length + levelDistance: 200, + }, + nodeSettings: { + 'customNode': { + 'render': function (node, canvas) { + var pos = node.pos.getc(true), + dim = node.getData('dim'), + topic = node.getData('topic'), + metacode = topic ? topic.getMetacode() : false, + ctx = canvas.getCtx() - var sX = Metamaps.Mouse.boxStartCoordinates.x, - sY = Metamaps.Mouse.boxStartCoordinates.y, - eX = Metamaps.Mouse.boxEndCoordinates.x, - eY = Metamaps.Mouse.boxEndCoordinates.y; - - if(!e.shiftKey){ - Metamaps.Control.deselectAllNodes(); - Metamaps.Control.deselectAllEdges(); - } + // if the topic is selected draw a circle around it + if (!canvas.denySelected && node.selected) { + ctx.beginPath() + ctx.arc(pos.x, pos.y, dim + 3, 0, 2 * Math.PI, false) + ctx.strokeStyle = Metamaps.Settings.colors.topics.selected + ctx.lineWidth = 2 + ctx.stroke() + } - //select all nodes that are within the box - Metamaps.Visualize.mGraph.graph.eachNode(function (n) { - var x = n.pos.x, - y = n.pos.y; + if (!metacode || + !metacode.get('image') || + !metacode.get('image').complete || + (typeof metacode.get('image').naturalWidth !== 'undefined' && + metacode.get('image').naturalWidth === 0)) { + ctx.beginPath() + ctx.arc(pos.x, pos.y, dim, 0, 2 * Math.PI, false) + ctx.fillStyle = '#B6B2FD' + ctx.fill() + } else { + ctx.drawImage(metacode.get('image'), pos.x - dim, pos.y - dim, dim * 2, dim * 2) + } - if ((sX < x && x < eX && sY < y && y < eY) || (sX > x && x > eX && sY > y && y > eY) || (sX > x && x > eX && sY < y && y < eY) || (sX < x && x < eX && sY > y && y > eY)) { - if(e.shiftKey){ - if(n.selected){ - Metamaps.Control.deselectNode(n); - } - else{ - Metamaps.Control.selectNode(n,e); - } - } - else{ - Metamaps.Control.selectNode(n,e); - } - } - }); + // if the topic has a link, draw a small image to indicate that + var hasLink = topic && topic.get('link') !== '' && topic.get('link') !== null + var linkImage = Metamaps.JIT.topicLinkImage + var linkImageLoaded = linkImage.complete || + (typeof linkImage.naturalWidth !== 'undefined' && + linkImage.naturalWidth !== 0) + if (hasLink && linkImageLoaded) { + ctx.drawImage(linkImage, pos.x - dim - 8, pos.y - dim - 8, 16, 16) + } - //Convert selection box coordinates to traditional coordinates (+,+) in upper right - sY = -1 * sY; - eY = -1 * eY + // if the topic has a desc, draw a small image to indicate that + var hasDesc = topic && topic.get('desc') !== '' && topic.get('desc') !== null + var descImage = Metamaps.JIT.topicDescImage + var descImageLoaded = descImage.complete || + (typeof descImage.naturalWidth !== 'undefined' && + descImage.naturalWidth !== 0) + if (hasDesc && descImageLoaded) { + ctx.drawImage(descImage, pos.x + dim - 8, pos.y - dim - 8, 16, 16) + } + }, + 'contains': function (node, pos) { + var npos = node.pos.getc(true), + dim = node.getData('dim'), + arrayOfLabelLines = Metamaps.Util.splitLine(node.name, 30).split('\n'), + ctx = Metamaps.Visualize.mGraph.canvas.getCtx() - var edgesToToggle = []; - Metamaps.Synapses.each(function(synapse) { - var e = synapse.get('edge'); - if (edgesToToggle.indexOf(e) === -1) { - edgesToToggle.push(e); - } - }); - edgesToToggle.forEach(function(edge) { - var fromNodeX = edge.nodeFrom.pos.x; - var fromNodeY = -1 * edge.nodeFrom.pos.y; - var toNodeX = edge.nodeTo.pos.x; - var toNodeY = -1 * edge.nodeTo.pos.y; + var height = 25 * arrayOfLabelLines.length - var maxX = fromNodeX; - var maxY = fromNodeY; - var minX = fromNodeX; - var minY = fromNodeY; - - //Correct maxX, MaxY values - (toNodeX > maxX) ? (maxX = toNodeX):(minX = toNodeX); - (toNodeY > maxY) ? (maxY = toNodeY):(minY = toNodeY); - - var maxBoxX = sX; - var maxBoxY = sY; - var minBoxX = sX; - var minBoxY = sY; - - //Correct maxBoxX, maxBoxY values - (eX > maxBoxX) ? (maxBoxX = eX):(minBoxX = eX); - (eY > maxBoxY) ? (maxBoxY = eY):(minBoxY = eY); - - //Find the slopes from the synapse fromNode to the 4 corners of the selection box - var slopes = []; - slopes.push( (sY - fromNodeY) / (sX - fromNodeX) ); - slopes.push( (sY - fromNodeY) / (eX - fromNodeX) ); - slopes.push( (eY - fromNodeY) / (eX - fromNodeX) ); - slopes.push( (eY - fromNodeY) / (sX - fromNodeX) ); - - var minSlope = slopes[0]; - var maxSlope = slopes[0]; - slopes.forEach(function(entry){ - if(entry > maxSlope) maxSlope = entry; - if(entry < minSlope) minSlope = entry; - }); - - //Find synapse-in-question's slope - var synSlope = (toNodeY - fromNodeY) / (toNodeX - fromNodeX); - var b = fromNodeY - synSlope * fromNodeX; + var index, lineWidths = [] + for (index = 0; index < arrayOfLabelLines.length; ++index) { + lineWidths.push(ctx.measureText(arrayOfLabelLines[index]).width) + } + var width = Math.max.apply(null, lineWidths) + 8 + var labely = npos.y + node.getData('height') + 5 + height / 2 - //Use the selection box edges as test cases for synapse intersection - var testX = sX; - var testY = synSlope * testX + b; + var overLabel = this.nodeHelper.rectangle.contains({ + x: npos.x, + y: labely + }, pos, width, height) - var selectTest; - - if(testX >= minX && testX <= maxX && testY >= minY && testY <= maxY && testY >= minBoxY && testY <= maxBoxY){ - selectTest = true; - } - - testX = eX; - testY = synSlope * testX + b; - - if(testX >= minX && testX <= maxX && testY >= minY && testY <= maxY && testY >= minBoxY && testY <= maxBoxY){ - selectTest = true; - } - - testY = sY; - testX = (testY - b)/synSlope; - - if(testX >= minX && testX <= maxX && testY >= minY && testY <= maxY && testX >= minBoxX && testX <= maxBoxX){ - selectTest = true; - } - - testY = eY; - testX = (testY - b)/synSlope; - - if(testX >= minX && testX <= maxX && testY >= minY && testY <= maxY && testX >= minBoxX && testX <= maxBoxX){ - selectTest = true; - } - - //Case where the synapse is wholly enclosed in the seldction box - if(fromNodeX >= minBoxX && fromNodeX <= maxBoxX && fromNodeY >= minBoxY && fromNodeY <= maxBoxY && toNodeX >= minBoxX && toNodeX <= maxBoxX && toNodeY >= minBoxY && toNodeY <= maxBoxY){ - selectTest = true; - } - - //The test synapse was selected! - - if(selectTest){ - // shiftKey = toggleSelect, otherwise - if(e.shiftKey){ - if(Metamaps.Selected.Edges.indexOf(edge) != -1 ){ - Metamaps.Control.deselectEdge(edge); - } - else{ - Metamaps.Control.selectEdge(edge); - } - } - else{ - Metamaps.Control.selectEdge(edge); - } - } - }); - Metamaps.Mouse.boxStartCoordinates = false; - Metamaps.Mouse.boxEndCoordinates = false; - Metamaps.Visualize.mGraph.plot(); - }, // selectWithBox - drawSelectBox: function (eventInfo, e) { - var ctx = Metamaps.Visualize.mGraph.canvas.getCtx(); - - var startX = Metamaps.Mouse.boxStartCoordinates.x, - startY = Metamaps.Mouse.boxStartCoordinates.y, - currX = eventInfo.getPos().x, - currY = eventInfo.getPos().y; - - Metamaps.Visualize.mGraph.canvas.clear(); - Metamaps.Visualize.mGraph.plot(); - - ctx.beginPath(); - ctx.moveTo(startX, startY); - ctx.lineTo(startX, currY); - ctx.lineTo(currX, currY); - ctx.lineTo(currX, startY); - ctx.lineTo(startX, startY); - ctx.strokeStyle = "black"; - ctx.stroke(); - }, // drawSelectBox - selectNodeOnClickHandler: function (node, e) { - if (Metamaps.Visualize.mGraph.busy) return; - - var self = Metamaps.JIT; - - // catch right click on mac, which is often like ctrl+click - if (navigator.platform.indexOf("Mac") != -1 && e.ctrlKey) { - self.selectNodeOnRightClickHandler(node, e) - return; + return this.nodeHelper.circle.contains(npos, pos, dim) || overLabel } + } + }, + edgeSettings: { + 'customEdge': { + 'render': function (adj, canvas) { + Metamaps.JIT.edgeRender(adj, canvas) + }, + 'contains': function (adj, pos) { + var from = adj.nodeFrom.pos.getc(), + to = adj.nodeTo.pos.getc() - // if on a topic page, let alt+click center you on a new topic - if (Metamaps.Active.Topic && e.altKey) { - Metamaps.RGraph.centerOn(node.id); - return; + // this fixes an issue where when edges are perfectly horizontal or perfectly vertical + // it becomes incredibly difficult to hover over them + if (-1 < pos.x && pos.x < 1) pos.x = 0 + if (-1 < pos.y && pos.y < 1) pos.y = 0 + + return $jit.Graph.Plot.edgeHelper.line.contains(from, to, pos, adj.Edge.epsilon + 5) } + } + } + }, // ForceDirected + ForceDirected3D: { + animate: { + modes: ['linear'], + transition: $jit.Trans.Elastic.easeOut, + duration: 2500, + onComplete: function () { + Metamaps.Visualize.mGraph.busy = false + } + }, + graphSettings: { + // id of the visualization container + injectInto: 'infovis', + type: '3D', + Scene: { + Lighting: { + enable: false, + ambient: [0.5, 0.5, 0.5], + directional: { + direction: { + x: 1, + y: 0, + z: -1 + }, + color: [0.9, 0.9, 0.9] + } + } + }, + // Enable zooming and panning + // by scrolling and DnD + Navigation: { + enable: false, + // Enable panning events only if we're dragging the empty + // canvas (and not a node). + panning: 'avoid nodes', + zooming: 10 // zoom speed. higher is more sensible + }, + // Change node and edge styles such as + // color and width. + // These properties are also set per node + // with dollar prefixed data-properties in the + // JSON structure. + Node: { + overridable: true, + type: 'sphere', + dim: 15, + color: '#ffffff' + }, + Edge: { + overridable: false, + type: 'tube', + color: '#111', + lineWidth: 3 + }, + // Native canvas text styling + Label: { + type: 'HTML', // Native or HTML + size: 10, + style: 'bold' + }, + // Add node events + Events: { + enable: true, + type: 'Native', + i: 0, + onMouseMove: function (node, eventInfo, e) { + // if(this.i++ % 3) return + var pos = eventInfo.getPos() + Metamaps.Visualize.cameraPosition.x += (pos.x - Metamaps.Visualize.cameraPosition.x) * 0.5 + Metamaps.Visualize.cameraPosition.y += (-pos.y - Metamaps.Visualize.cameraPosition.y) * 0.5 + Metamaps.Visualize.mGraph.plot() + }, + onMouseWheel: function (delta) { + Metamaps.Visualize.cameraPosition.z += -delta * 20 + Metamaps.Visualize.mGraph.plot() + }, + onClick: function () {} + }, + // Number of iterations for the FD algorithm + iterations: 200, + // Edge length + levelDistance: 100 + }, + nodeSettings: { - var check = self.nodeWasDoubleClicked(); - if (check) { - self.nodeDoubleClickHandler(node, e); - return; + }, + edgeSettings: { + + } + }, // ForceDirected3D + RGraph: { + animate: { + modes: ['polar'], + duration: 800, + onComplete: function () { + Metamaps.Visualize.mGraph.busy = false + } + }, + // this will just be used to patch the ForceDirected graphsettings with the few things which actually differ + background: { + // type: 'Metamaps', + levelDistance: 200, + numberOfCircles: 4, + CanvasStyles: { + strokeStyle: '#333', + lineWidth: 1.5 + } + }, + levelDistance: 200 + }, + onMouseEnter: function (edge) { + var filtered = edge.getData('alpha') === 0 + + // don't do anything if the edge is filtered + // or if the canvas is animating + if (filtered || Metamaps.Visualize.mGraph.busy) return + + $('canvas').css('cursor', 'pointer') + var edgeIsSelected = Metamaps.Selected.Edges.indexOf(edge) + // following if statement only executes if the edge being hovered over is not selected + if (edgeIsSelected == -1) { + edge.setData('showDesc', true, 'current') + } + + edge.setDataset('end', { + lineWidth: 4 + }) + Metamaps.Visualize.mGraph.fx.animate({ + modes: ['edge-property:lineWidth'], + duration: 100 + }) + Metamaps.Visualize.mGraph.plot() + }, // onMouseEnter + onMouseLeave: function (edge) { + if (edge.getData('alpha') === 0) return; // don't do anything if the edge is filtered + $('canvas').css('cursor', 'default') + var edgeIsSelected = Metamaps.Selected.Edges.indexOf(edge) + // following if statement only executes if the edge being hovered over is not selected + if (edgeIsSelected == -1) { + edge.setData('showDesc', false, 'current') + } + + edge.setDataset('end', { + lineWidth: 2 + }) + Metamaps.Visualize.mGraph.fx.animate({ + modes: ['edge-property:lineWidth'], + duration: 100 + }) + Metamaps.Visualize.mGraph.plot() + }, // onMouseLeave + onMouseMoveHandler: function (node, eventInfo, e) { + var self = Metamaps.JIT + + if (Metamaps.Visualize.mGraph.busy) return + + var node = eventInfo.getNode() + var edge = eventInfo.getEdge() + + // if we're on top of a node object, act like there aren't edges under it + if (node != false) { + if (Metamaps.Mouse.edgeHoveringOver) { + self.onMouseLeave(Metamaps.Mouse.edgeHoveringOver) + } + $('canvas').css('cursor', 'pointer') + return + } + + if (edge == false && Metamaps.Mouse.edgeHoveringOver != false) { + // mouse not on an edge, but we were on an edge previously + self.onMouseLeave(Metamaps.Mouse.edgeHoveringOver) + } else if (edge != false && Metamaps.Mouse.edgeHoveringOver == false) { + // mouse is on an edge, but there isn't a stored edge + self.onMouseEnter(edge) + } else if (edge != false && Metamaps.Mouse.edgeHoveringOver != edge) { + // mouse is on an edge, but a different edge is stored + self.onMouseLeave(Metamaps.Mouse.edgeHoveringOver) + self.onMouseEnter(edge) + } + + // could be false + Metamaps.Mouse.edgeHoveringOver = edge + + if (!node && !edge) { + $('canvas').css('cursor', 'default') + } + }, // onMouseMoveHandler + enterKeyHandler: function () { + var creatingMap = Metamaps.GlobalUI.lightbox + if (creatingMap === 'newmap' || creatingMap === 'forkmap') { + Metamaps.GlobalUI.CreateMap.submit() + } + // this is to submit new topic creation + else if (Metamaps.Create.newTopic.beingCreated) { + Metamaps.Topic.createTopicLocally() + } + // to submit new synapse creation + else if (Metamaps.Create.newSynapse.beingCreated) { + Metamaps.Synapse.createSynapseLocally() + } + }, // enterKeyHandler + escKeyHandler: function () { + Metamaps.Control.deselectAllEdges() + Metamaps.Control.deselectAllNodes() + }, // escKeyHandler + touchPanZoomHandler: function (eventInfo, e) { + if (e.touches.length == 1) { + var thispos = Metamaps.Touch.touchPos, + currentPos = eventInfo.getPos(), + canvas = Metamaps.Visualize.mGraph.canvas, + ox = canvas.translateOffsetX, + oy = canvas.translateOffsetY, + sx = canvas.scaleOffsetX, + sy = canvas.scaleOffsetY + currentPos.x *= sx + currentPos.y *= sy + currentPos.x += ox + currentPos.y += oy + // var x = currentPos.x - thispos.x, + // y = currentPos.y - thispos.y + var x = currentPos.x - thispos.x, + y = currentPos.y - thispos.y + Metamaps.Touch.touchPos = currentPos + Metamaps.Visualize.mGraph.canvas.translate(x * 1 / sx, y * 1 / sy) + } else if (e.touches.length == 2) { + var touch1 = e.touches[0] + var touch2 = e.touches[1] + + var dist = Metamaps.Util.getDistance({ + x: touch1.clientX, + y: touch1.clientY + }, { + x: touch2.clientX, + y: touch2.clientY + }) + + if (!lastDist) { + lastDist = dist + } + + var scale = dist / lastDist + + if (8 >= Metamaps.Visualize.mGraph.canvas.scaleOffsetX * scale && Metamaps.Visualize.mGraph.canvas.scaleOffsetX * scale >= 1) { + Metamaps.Visualize.mGraph.canvas.scale(scale, scale) + } + if (Metamaps.Visualize.mGraph.canvas.scaleOffsetX < 0.5) { + Metamaps.Visualize.mGraph.canvas.viz.labels.hideLabels(true) + } else if (Metamaps.Visualize.mGraph.canvas.scaleOffsetX > 0.5) { + Metamaps.Visualize.mGraph.canvas.viz.labels.hideLabels(false) + } + lastDist = dist + } + }, // touchPanZoomHandler + onDragMoveTopicHandler: function (node, eventInfo, e) { + var self = Metamaps.JIT + + // this is used to send nodes that are moving to + // other realtime collaborators on the same map + var positionsToSend = {} + var topic + + var authorized = Metamaps.Active.Map && Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + + if (node && !node.nodeFrom) { + var pos = eventInfo.getPos() + // if it's a left click, or a touch, move the node + if (e.touches || (e.button == 0 && !e.altKey && !e.ctrlKey && !e.shiftKey && (e.buttons == 0 || e.buttons == 1 || e.buttons == undefined))) { + // if the node dragged isn't already selected, select it + var whatToDo = self.handleSelectionBeforeDragging(node, e) + if (node.pos.rho || node.pos.rho === 0) { + // this means we're in topic view + var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y) + var theta = Math.atan2(pos.y, pos.x) + node.pos.setp(theta, rho) + } else if (whatToDo == 'only-drag-this-one') { + node.pos.setc(pos.x, pos.y) + + if (Metamaps.Active.Map) { + topic = node.getData('topic') + // we use the topic ID not the node id + // because we can't depend on the node id + // to be the same as on other collaborators + // maps + positionsToSend[topic.id] = pos + $(document).trigger(Metamaps.JIT.events.topicDrag, [positionsToSend]) + } } else { - // wait a certain length of time, then check again, then run this code - setTimeout(function () { - if (!Metamaps.JIT.nodeWasDoubleClicked()) { + var len = Metamaps.Selected.Nodes.length - var nodeAlreadySelected = node.selected; + // first define offset for each node + var xOffset = [] + var yOffset = [] + for (var i = 0; i < len; i += 1) { + var n = Metamaps.Selected.Nodes[i] + xOffset[i] = n.pos.x - node.pos.x + yOffset[i] = n.pos.y - node.pos.y + } // for - if (!e.shiftKey) { - Metamaps.Control.deselectAllNodes(); - Metamaps.Control.deselectAllEdges(); - } - - if (nodeAlreadySelected) { - Metamaps.Control.deselectNode(node); - } else { - Metamaps.Control.selectNode(node,e); - } - - //trigger animation to final styles - Metamaps.Visualize.mGraph.fx.animate({ - modes: ['edge-property:lineWidth:color:alpha'], - duration: 500 - }); - Metamaps.Visualize.mGraph.plot(); - } - }, Metamaps.Mouse.DOUBLE_CLICK_TOLERANCE); + for (var i = 0; i < len; i += 1) { + var n = Metamaps.Selected.Nodes[i] + var x = pos.x + xOffset[i] + var y = pos.y + yOffset[i] + n.pos.setc(x, y) + + if (Metamaps.Active.Map) { + topic = n.getData('topic') + // we use the topic ID not the node id + // because we can't depend on the node id + // to be the same as on other collaborators + // maps + positionsToSend[topic.id] = n.pos + } + } // for + + if (Metamaps.Active.Map) { + $(document).trigger(Metamaps.JIT.events.topicDrag, [positionsToSend]) + } + } // if + + if (whatToDo == 'deselect') { + Metamaps.Control.deselectNode(node) } - }, //selectNodeOnClickHandler - selectNodeOnRightClickHandler: function (node, e) { - // the 'node' variable is a JIT node, the one that was clicked on - // the 'e' variable is the click event + Metamaps.Visualize.mGraph.plot() + } + // if it's a right click or holding down alt, start synapse creation ->third option is for firefox + else if ((e.button == 2 || (e.button == 0 && e.altKey) || e.buttons == 2) && authorized) { + if (Metamaps.tempInit == false) { + Metamaps.tempNode = node + Metamaps.tempInit = true - e.preventDefault(); - e.stopPropagation(); - - if (Metamaps.Visualize.mGraph.busy) return; - - // select the node - Metamaps.Control.selectNode(node, e); - - // delete old right click menu - $('.rightclickmenu').remove(); - // create new menu for clicked on node - var rightclickmenu = document.createElement("div"); - rightclickmenu.className = "rightclickmenu"; - // add the proper options to the menu - var menustring = ''; - rightclickmenu.innerHTML = menustring; + if (adj.getData('alpha') === 0) return; // don't do anything if the edge is filtered - // position the menu where the click happened - var position = {}; - var RIGHTCLICK_WIDTH = 300; - var RIGHTCLICK_HEIGHT = 144; // this does vary somewhat, but we can use static - var SUBMENUS_WIDTH = 256; - var MAX_SUBMENU_HEIGHT = 270; - var windowWidth = $(window).width(); - var windowHeight = $(window).height(); + var authorized - if (windowWidth - e.clientX < SUBMENUS_WIDTH) { - position.right = windowWidth - e.clientX; - $(rightclickmenu).addClass('moveMenusToLeft'); - } - else if (windowWidth - e.clientX < RIGHTCLICK_WIDTH) { - position.right = windowWidth - e.clientX; - } - else if (windowWidth - e.clientX < RIGHTCLICK_WIDTH + SUBMENUS_WIDTH) { - position.left = e.clientX; - $(rightclickmenu).addClass('moveMenusToLeft'); - } - else position.left = e.clientX; + e.preventDefault() + e.stopPropagation() - if (windowHeight - e.clientY < MAX_SUBMENU_HEIGHT) { - position.bottom = windowHeight - e.clientY; - $(rightclickmenu).addClass('moveMenusUp'); - } - else if (windowHeight - e.clientY < RIGHTCLICK_HEIGHT + MAX_SUBMENU_HEIGHT) { - position.top = e.clientY; - $(rightclickmenu).addClass('moveMenusUp'); - } - else position.top = e.clientY; + if (Metamaps.Visualize.mGraph.busy) return - $(rightclickmenu).css(position); - //add the menu to the page - $('#wrapper').append(rightclickmenu); + Metamaps.Control.selectEdge(adj) - // attach events to clicks on the list items + // delete old right click menu + $('.rightclickmenu').remove() + // create new menu for clicked on node + var rightclickmenu = document.createElement('div') + rightclickmenu.className = 'rightclickmenu' - // delete the selected things from the database - if (authorized) { - $('.rc-delete').click(function () { - $('.rightclickmenu').remove(); - Metamaps.Control.deleteSelected(); - }); - } + // add the proper options to the menu + var menustring = '' + rightclickmenu.innerHTML = menustring + + // position the menu where the click happened + var position = {} + var RIGHTCLICK_WIDTH = 300 + var RIGHTCLICK_HEIGHT = 144; // this does vary somewhat, but we can use static + var SUBMENUS_WIDTH = 256 + var MAX_SUBMENU_HEIGHT = 270 + var windowWidth = $(window).width() + var windowHeight = $(window).height() + + if (windowWidth - e.clientX < SUBMENUS_WIDTH) { + position.right = windowWidth - e.clientX + $(rightclickmenu).addClass('moveMenusToLeft') + } + else if (windowWidth - e.clientX < RIGHTCLICK_WIDTH) { + position.right = windowWidth - e.clientX + } + else position.left = e.clientX + + if (windowHeight - e.clientY < MAX_SUBMENU_HEIGHT) { + position.bottom = windowHeight - e.clientY + $(rightclickmenu).addClass('moveMenusUp') + } + else if (windowHeight - e.clientY < RIGHTCLICK_HEIGHT + MAX_SUBMENU_HEIGHT) { + position.top = e.clientY + $(rightclickmenu).addClass('moveMenusUp') + } + else position.top = e.clientY + + $(rightclickmenu).css(position) + + // add the menu to the page + $('#wrapper').append(rightclickmenu) + + // attach events to clicks on the list items + + // delete the selected things from the database + if (authorized) { + $('.rc-delete').click(function () { + $('.rightclickmenu').remove() + Metamaps.Control.deleteSelected() + }) + } + + // remove the selected things from the map + if (authorized) { + $('.rc-remove').click(function () { + $('.rightclickmenu').remove() + Metamaps.Control.removeSelectedEdges() + Metamaps.Control.removeSelectedNodes() + }) + } + + // hide selected nodes and synapses until refresh + $('.rc-hide').click(function () { + $('.rightclickmenu').remove() + Metamaps.Control.hideSelectedEdges() + Metamaps.Control.hideSelectedNodes() + }) + + // change the permission of all the selected nodes and synapses that you were the originator of + $('.rc-permission li').click(function () { + $('.rightclickmenu').remove() + // $(this).text() will be 'commons' 'public' or 'private' + Metamaps.Control.updateSelectedPermissions($(this).text()) + }) + }, // selectEdgeOnRightClickHandler + SmoothPanning: function () { + var sx = Metamaps.Visualize.mGraph.canvas.scaleOffsetX, + sy = Metamaps.Visualize.mGraph.canvas.scaleOffsetY, + y_velocity = Metamaps.Mouse.changeInY, // initial y velocity + x_velocity = Metamaps.Mouse.changeInX, // initial x velocity + easing = 1 // frictional value + + easing = 1 + window.clearInterval(Metamaps.panningInt) + Metamaps.panningInt = setInterval(function () { + myTimer() + }, 1) + + function myTimer () { + Metamaps.Visualize.mGraph.canvas.translate(x_velocity * easing * 1 / sx, y_velocity * easing * 1 / sy) + $(document).trigger(Metamaps.JIT.events.pan) + easing = easing * 0.75 + + if (easing < 0.1) window.clearInterval(Metamaps.panningInt) + } + }, // SmoothPanning + renderMidArrow: function (from, to, dim, swap, canvas, placement, newSynapse) { + var ctx = canvas.getCtx() + // invert edge direction + if (swap) { + var tmp = from + from = to + to = tmp + } + // vect represents a line from tip to tail of the arrow + var vect = new $jit.Complex(to.x - from.x, to.y - from.y) + // scale it + vect.$scale(dim / vect.norm()) + // compute the midpoint of the edge line + var newX = (to.x - from.x) * placement + from.x + var newY = (to.y - from.y) * placement + from.y + var midPoint = new $jit.Complex(newX, newY) + + // move midpoint by half the "length" of the arrow so the arrow is centered on the midpoint + var arrowPoint = new $jit.Complex((vect.x / 0.7) + midPoint.x, (vect.y / 0.7) + midPoint.y) + // compute the tail intersection point with the edge line + var intermediatePoint = new $jit.Complex(arrowPoint.x - vect.x, arrowPoint.y - vect.y) + // vector perpendicular to vect + var normal = new $jit.Complex(-vect.y / 2, vect.x / 2) + var v1 = intermediatePoint.add(normal) + var v2 = intermediatePoint.$add(normal.$scale(-1)) + + if (newSynapse) { + ctx.strokeStyle = '#4fc059' + ctx.lineWidth = 2 + ctx.globalAlpha = 1 + } + ctx.beginPath() + ctx.moveTo(from.x, from.y) + ctx.lineTo(to.x, to.y) + ctx.stroke() + ctx.beginPath() + ctx.moveTo(v1.x, v1.y) + ctx.lineTo(arrowPoint.x, arrowPoint.y) + ctx.lineTo(v2.x, v2.y) + ctx.stroke() + }, // renderMidArrow + renderEdgeArrows: function (edgeHelper, adj, synapse, canvas) { + var self = Metamaps.JIT + + var directionCat = synapse.get('category') + var direction = synapse.getDirection() + + var pos = adj.nodeFrom.pos.getc(true) + var posChild = adj.nodeTo.pos.getc(true) + + // plot arrow edge + if (!direction) { + // render nothing for this arrow if the direction couldn't be retrieved + } else if (directionCat == 'none') { + edgeHelper.line.render({ + x: pos.x, + y: pos.y + }, { + x: posChild.x, + y: posChild.y + }, canvas) + } else if (directionCat == 'both') { + self.renderMidArrow({ + x: pos.x, + y: pos.y + }, { + x: posChild.x, + y: posChild.y + }, 13, true, canvas, 0.7) + self.renderMidArrow({ + x: pos.x, + y: pos.y + }, { + x: posChild.x, + y: posChild.y + }, 13, false, canvas, 0.7) + } else if (directionCat == 'from-to') { + var inv = (direction[0] != adj.nodeFrom.id) + self.renderMidArrow({ + x: pos.x, + y: pos.y + }, { + x: posChild.x, + y: posChild.y + }, 13, inv, canvas, 0.7) + self.renderMidArrow({ + x: pos.x, + y: pos.y + }, { + x: posChild.x, + y: posChild.y + }, 13, inv, canvas, 0.3) + } + }, // renderEdgeArrows + zoomIn: function (event) { + Metamaps.Visualize.mGraph.canvas.scale(1.25, 1.25) + $(document).trigger(Metamaps.JIT.events.zoom, [event]) + }, + zoomOut: function (event) { + Metamaps.Visualize.mGraph.canvas.scale(0.8, 0.8) + $(document).trigger(Metamaps.JIT.events.zoom, [event]) + }, + centerMap: function (canvas) { + var offsetScale = canvas.scaleOffsetX + + canvas.scale(1 / offsetScale, 1 / offsetScale) + + var offsetX = canvas.translateOffsetX + var offsetY = canvas.translateOffsetY + + canvas.translate(-1 * offsetX, -1 * offsetY) + }, + zoomToBox: function (event) { + var sX = Metamaps.Mouse.boxStartCoordinates.x, + sY = Metamaps.Mouse.boxStartCoordinates.y, + eX = Metamaps.Mouse.boxEndCoordinates.x, + eY = Metamaps.Mouse.boxEndCoordinates.y + + var canvas = Metamaps.Visualize.mGraph.canvas + Metamaps.JIT.centerMap(canvas) + + var height = $(document).height(), + width = $(document).width() + + var spanX = Math.abs(sX - eX) + var spanY = Math.abs(sY - eY) + var ratioX = width / spanX + var ratioY = height / spanY + + var newRatio = Math.min(ratioX, ratioY) + + if (canvas.scaleOffsetX * newRatio <= 5 && canvas.scaleOffsetX * newRatio >= 0.2) { + canvas.scale(newRatio, newRatio) + } + else if (canvas.scaleOffsetX * newRatio > 5) { + newRatio = 5 / canvas.scaleOffsetX + canvas.scale(newRatio, newRatio) + } else { + newRatio = 0.2 / canvas.scaleOffsetX + canvas.scale(newRatio, newRatio) + } + + var cogX = (sX + eX) / 2 + var cogY = (sY + eY) / 2 + + canvas.translate(-1 * cogX, -1 * cogY) + $(document).trigger(Metamaps.JIT.events.zoom, [event]) + + Metamaps.Mouse.boxStartCoordinates = false + Metamaps.Mouse.boxEndCoordinates = false + Metamaps.Visualize.mGraph.plot() + }, + zoomExtents: function (event, canvas, denySelected) { + Metamaps.JIT.centerMap(canvas) + var height = canvas.getSize().height, + width = canvas.getSize().width, + maxX, minX, maxY, minY, counter = 0 + + if (!denySelected && Metamaps.Selected.Nodes.length > 0) { + var nodes = Metamaps.Selected.Nodes + } else { + var nodes = _.values(Metamaps.Visualize.mGraph.graph.nodes) + } + + if (nodes.length > 1) { + nodes.forEach(function (n) { + var x = n.pos.x, + y = n.pos.y + + if (counter == 0 && n.getData('alpha') == 1) { + maxX = x + minX = x + maxY = y + minY = y + } + + var arrayOfLabelLines = Metamaps.Util.splitLine(n.name, 30).split('\n'), + dim = n.getData('dim'), + ctx = canvas.getCtx() + + var height = 25 * arrayOfLabelLines.length + + var index, lineWidths = [] + for (index = 0; index < arrayOfLabelLines.length; ++index) { + lineWidths.push(ctx.measureText(arrayOfLabelLines[index]).width) + } + var width = Math.max.apply(null, lineWidths) + 8 + + // only adjust these values if the node is not filtered + if (n.getData('alpha') == 1) { + maxX = Math.max(x + width / 2, maxX) + maxY = Math.max(y + n.getData('height') + 5 + height, maxY) + minX = Math.min(x - width / 2, minX) + minY = Math.min(y - dim, minY) + + counter++ + } + }) + + var spanX = maxX - minX + var spanY = maxY - minY + var ratioX = spanX / width + var ratioY = spanY / height + + var cogX = (maxX + minX) / 2 + var cogY = (maxY + minY) / 2 + + canvas.translate(-1 * cogX, -1 * cogY) + + var newRatio = Math.max(ratioX, ratioY) + var scaleMultiplier = 1 / newRatio * 0.9 + + if (canvas.scaleOffsetX * scaleMultiplier <= 3 && canvas.scaleOffsetX * scaleMultiplier >= 0.2) { + canvas.scale(scaleMultiplier, scaleMultiplier) + } + else if (canvas.scaleOffsetX * scaleMultiplier > 3) { + scaleMultiplier = 3 / canvas.scaleOffsetX + canvas.scale(scaleMultiplier, scaleMultiplier) + } else { + scaleMultiplier = 0.2 / canvas.scaleOffsetX + canvas.scale(scaleMultiplier, scaleMultiplier) + } + + $(document).trigger(Metamaps.JIT.events.zoom, [event]) + } + else if (nodes.length == 1) { + nodes.forEach(function (n) { + var x = n.pos.x, + y = n.pos.y + + canvas.translate(-1 * x, -1 * y) + $(document).trigger(Metamaps.JIT.events.zoom, [event]) + }) + } + } +}