var Metamaps = {}; // this variable declaration defines a Javascript object that will contain all the variables and functions used by us, broken down into 'sub-modules' that look something like this var labelType, useGradients, nativeTextSupport, animate; (function () { var ua = navigator.userAgent, iStuff = ua.match(/iPhone/i) || ua.match(/iPad/i), typeOfCanvas = typeof HTMLCanvasElement, nativeCanvasSupport = (typeOfCanvas == 'object' || typeOfCanvas == 'function'), textSupport = nativeCanvasSupport && (typeof document.createElement('canvas').getContext('2d').fillText == 'function'); //I'm setting this based on the fact that ExCanvas provides text support for IE //and that as of today iPhone/iPad current text support is lame labelType = (!nativeCanvasSupport || (textSupport && !iStuff)) ? 'Native' : 'HTML'; nativeTextSupport = labelType == 'Native'; useGradients = nativeCanvasSupport; animate = !(iStuff || !nativeCanvasSupport); })(); var panningInt; // this variable is used to store a 'setInterval' for the Metamaps.JIT.SmoothPanning() function, so that it can be cleared with window.clearInterval var tempNode = null, tempInit = false, tempNode2 = null; $(document).ready(function () { _.templateSettings = { interpolate: /{{(.+?)}}/g }; for (var prop in Metamaps) { if (Metamaps.hasOwnProperty(prop) && Metamaps[prop].hasOwnProperty('init') && typeof (Metamaps[prop].init) == 'function' ) { Metamaps[prop].init(); } } Metamaps.JIT.prepareVizData(); }); Metamaps.Settings = { realtime: false, // indicates whether the user wants to be playing in the app with realtime updates to their topics embed: false, // indicates that the app is on a page that is optimized for embedding in iFrames on other web pages sandbox: false, // puts the app into a mode (when true) where it only creates data locally, and isn't writing it to the database selectedMetacodeSet: null, // will get initialized selectedMetacodeSetIndex: null, // will get initialized colors: { background: '#344A58', synapses: { normal: '#222222', hover: '#222222', selected: '#FFFFFF' }, topics: { selected: '#FFFFFF' }, labels: { background: '#18202E', text: '#DDD' } } }; // only one of these will be active at a time // Map if you're on a map page // Topic if you're on a topic page Metamaps.Active = { Map: null, Topic: null }; Metamaps.Backbone = { init: function () { var self = Metamaps.Backbone; self.Metacode = Backbone.Model.extend({}); self.MetacodeCollection = Backbone.Collection.extend({ model: this.Metacode, url: '/metacodes' }); self.Topic = Backbone.Model.extend({ urlRoot: '/topics', blacklist: ['mappings'], toJSON: function (options) { return _.omit(this.attributes, this.blacklist); }, initialize: function () { if (this.isNew()) { this.set({ "user_id": Metamaps.Active.Mapper.id, "desc": '', "link": '', "permission": Metamaps.Active.Map ? Metamaps.Active.Map.get('permission') : 'commons', "mappings": [] }); } }, authorizeToEdit: function (mapper) { if (mapper && (this.get('permission') === "commons" || this.get('user_id') === mapper.get('id'))) return true; else return false; }, authorizePermissionChange: function (mapper) { if (mapper && this.get('user_id') === mapper.get('id')) return true; else return false; }, updateMappings: function () { var mappings = this.get('mappings'), l = mappings.length, i; for (i = 0; i < l; i++) { mappings[i].set('topic_id', this.id); if (!mappings[i].isNew()) mappings[i].save(); } }, getDate: function () { }, getUser: function () { return Metamaps.Mapper.get(this.get('user_id')); }, getMetacode: function () { return Metamaps.Metacodes.get(this.get('metacode_id')); } }); self.TopicCollection = Backbone.Collection.extend({ model: self.Topic, url: '/topics', comparator: function (a, b) { a = a.get('name').toLowerCase(); b = b.get('name').toLowerCase(); return a > b ? 1 : a < b ? -1 : 0; } }); self.Synapse = Backbone.Model.extend({ urlRoot: '/synapses', initialize: function () { if (this.isNew()) { this.set({ "user_id": Metamaps.Active.Mapper.id, "permission": Metamaps.Active.Map.get('permission'), "category": "from-to" }); } }, authorizeToEdit: function (mapper) { if (mapper && (this.get('permission') === "commons" || this.get('user_id') === mapper.get('id'))) return true; else return false; }, authorizePermissionChange: function (mapper) { if (mapper && this.get('user_id') === mapper.get('id')) return true; else return false; }, updateMappings: function () { var mappings = this.get('mappings'), l = mappings.length, i; for (i = 0; i < l; i++) { mappings[i].set('synapse_id', this.id); if (!mappings[i].isNew()) mappings[i].save(); } }, getUser: function () { return Metamaps.Mapper.get(this.get('user_id')); }, getTopic1: function () { return Metamaps.Topic.get(this.get('node1_id')); }, getTopic2: function () { return Metamaps.Topic.get(this.get('node2_id')); }, getDirection: function () { var mapping1 = this.getTopic1().get('mappings')[0], mapping2 = this.getTopic2().get('mappings')[0]; return [ mapping1.isNew() ? mapping1.cid : mapping1.id, mapping2.isNew() ? mapping2.cid : mapping2.id ]; } }); self.SynapseCollection = Backbone.Collection.extend({ model: self.Synapse, url: '/synapses' }); self.Mapping = Backbone.Model.extend({ urlRoot: '/mappings', blacklist: ['node', 'edge'], toJSON: function (options) { return _.omit(this.attributes, this.blacklist); }, initialize: function () { var topic = this.getTopic(), synapse = this.getSynapse(); if (this.isNew()) { this.set({ "user_id": Metamaps.Active.Mapper.id, "map_id": Metamaps.Active.Map ? Metamaps.Active.Map.id : null }); } if (topic) topic.set('mappings', [this]); else if (synapse) synapse.set('mappings', [this]); }, getUser: function () { return Metamaps.Mapper.get(this.get('user_id')); }, getMap: function () { return Metamaps.Map.get(this.get('map_id')); }, getTopic: function () { if (this.get('category') === 'Topic') return Metamaps.Topic.get(this.get('topic_id')); else return false; }, getSynapse: function () { if (this.get('category') === 'Synapse') return Metamaps.Synapse.get(this.get('synapse_id')); else return false; }, createNode: function () { var topic = this.getTopic(); if (topic) { var node = { id: this.isNew() ? this.cid : this.id, name: topic.get('name') }; return node; } else return false; }, updateNode: function () { var topic = this.getTopic(); if (topic) { var node = this.get('node'); node.setData('topic', topic); node.setData('mapping', this); node.id = this.isNew() ? this.cid : this.id; return node; } else return false; }, createEdge: function () { var synapse = this.getSynapse(); if (synapse) { var edge = { nodeFrom: synapse.getTopic1().get('mappings')[0].id, nodeTo: synapse.getTopic2().get('mappings')[0].id, data: { $synapses: [], $mappings: [] } }; return edge; } else return false; }, updateEdge: function () { var synapse = this.getSynapse(); if (synapse) { var edge = this.get('edge'); edge.getData('synapses').push(synapse); edge.getData('mappings').push(this); return edge; } else return false; }, }); self.MappingCollection = Backbone.Collection.extend({ model: self.Mapping, url: '/mappings' }); self.Map = Backbone.Model.extend({ urlRoot: '/maps', authorizeToEdit: function (mapper) { if (mapper && (this.get('permission') === "commons" || this.get('user_id') === mapper.get('id'))) return true; else return false; } }); self.MapsCollection = Backbone.Collection.extend({ model: self.Map, url: '/maps' }); self.Mapper = Backbone.Model.extend({ urlRoot: '/users' }); self.MapperCollection = Backbone.Collection.extend({ model: self.Mapper, url: '/users' }); Metamaps.Metacodes = new self.MetacodeCollection(Metamaps.Metacodes); Metamaps.Active.Mapper = new self.Mapper({ id: userid, name: username }); Metamaps.Mappers = new self.MapperCollection([Metamaps.Active.Mapper]); Metamaps.Topics = new self.TopicCollection(Metamaps.Topics); Metamaps.Synapses = new self.SynapseCollection(Metamaps.Synapses); Metamaps.Mappings = new self.MappingCollection(Metamaps.Mappings); Metamaps.Active.Map = new self.Map(Metamaps.Active.Map); Metamaps.Maps = new self.MapsCollection([Metamaps.Active.Map]); } }; Metamaps.Control = { init: function () { }, selectNode: function (node) { if (Metamaps.Selected.Topics.indexOf(node) != -1) return; node.selected = true; node.setData('dim', 30, 'current'); node.eachAdjacency(function (adj) { Metamaps.Control.selectEdge(adj); }); Metamaps.Selected.Topics.push(node); }, deselectAllNodes: function () { var l = Metamaps.Selected.Topics.length; for (var i = l - 1; i >= 0; i -= 1) { var node = Metamaps.Selected.Topics[i]; Metamaps.Control.deselectNode(node); } Metamaps.Visualize.mGraph.plot(); }, deselectNode: function (node) { delete node.selected; node.eachAdjacency(function (adj) { Metamaps.Control.deselectEdge(adj); }); node.setData('dim', 25, 'current'); //remove the node Metamaps.Selected.Topics.splice( Metamaps.Selected.Topics.indexOf(node), 1); }, deleteSelectedNodes: function () { // refers to deleting topics permanently var l = Metamaps.Selected.Topics.length; for (var i = l - 1; i >= 0; i -= 1) { var node = Metamaps.Selected.Topics[i]; Metamaps.Control.deleteNode(node.id); } }, deleteNode: function (nodeid) { // refers to deleting topics permanently var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid); var id = node.getData('id'); Metamaps.Control.deselectNode(node); Metamaps.Topics.get(id).destroy(); Metamaps.Control.hideNode(nodeid); }, removeSelectedNodes: function () { // refers to removing topics permanently from a map var l = Metamaps.Selected.Topics.length, i, node, mapperm = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); if (mapperm) { for (i = l - 1; i >= 0; i -= 1) { node = Metamaps.Selected.Topics[i]; Metamaps.Control.removeNode(node.id); } } }, removeNode: function (nodeid) { // refers to removing topics permanently from a map var mapperm = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid); var mappingid = node.getData("mappingid"); if (mapperm) { Metamaps.Control.deselectNode(node); Metamaps.Mappings.get(mappingid).destroy(); Metamaps.Control.hideNode(nodeid); } }, hideSelectedNodes: function () { var l = Metamaps.Selected.Topics.length, i, node; for (i = l - 1; i >= 0; i -= 1) { node = Metamaps.Selected.Topics[i]; Metamaps.Control.hideNode(node.id); } }, hideNode: function (nodeid) { var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid); if (nodeid == Metamaps.Visualize.mGraph.root) { // && Metamaps.Visualize.type === "RGraph" alert("You can't hide this topic, it is the root of your graph."); return; } Metamaps.Control.deselectNode(node); node.setData('alpha', 0, 'end'); node.eachAdjacency(function (adj) { adj.setData('alpha', 0, 'end'); }); Metamaps.Visualize.mGraph.fx.animate({ modes: ['node-property:alpha', 'edge-property:alpha' ], duration: 500 }); setTimeout(function () { Metamaps.Visualize.mGraph.graph.removeNode(nodeid); }, 500); }, selectEdge: function (edge) { if (Metamaps.Selected.Synapses.indexOf(edge) != -1) return; edge.setData('showDesc', true, 'current'); if (!Metamaps.Settings.embed) { edge.setDataset('end', { lineWidth: 4, color: Metamaps.Settings.colors.synapses.selected, alpha: 1 }); } else if (Metamaps.Settings.embed) { edge.setDataset('end', { lineWidth: 4, color: Metamaps.Settings.colors.synapses.selected, alpha: 1 }); } Metamaps.Visualize.mGraph.fx.animate({ modes: ['edge-property:lineWidth:color:alpha'], duration: 100 }); Metamaps.Selected.Synapses.push(edge); }, deselectAllEdges: function () { var l = Metamaps.Selected.Synapses.length; for (var i = l - 1; i >= 0; i -= 1) { var edge = Metamaps.Selected.Synapses[i]; Metamaps.Control.deselectEdge(edge); } Metamaps.Visualize.mGraph.plot(); }, deselectEdge: function (edge) { edge.setData('showDesc', false, 'current'); edge.setDataset('end', { lineWidth: 2, color: Metamaps.Settings.colors.synapses.normal, alpha: 0.4 }); if (Metamaps.Mouse.edgeHoveringOver == edge) { edge.setData('showDesc', true, 'current'); edge.setDataset('end', { lineWidth: 4, color: Metamaps.Settings.colors.synapses.hover, alpha: 1 }); } Metamaps.Visualize.mGraph.fx.animate({ modes: ['edge-property:lineWidth:color:alpha'], duration: 100 }); //remove the edge Metamaps.Selected.Synapses.splice( Metamaps.Selected.Synapses.indexOf(edge), 1); }, deleteSelectedEdges: function () { // refers to deleting topics permanently var edge, l = Metamaps.Selected.Synapses.length; for (var i = l - 1; i >= 0; i -= 1) { edge = Metamaps.Selected.Synapses[i]; Metamaps.Control.deleteEdge(edge); } }, deleteEdge: function (edge) { var id = edge.getData("id"); Metamaps.Synapses.get(id).destroy(); Metamaps.Control.hideEdge(edge); }, removeSelectedEdges: function () { var l = Metamaps.Selected.Synapses.length, i, edge; if (Metamaps.Active.Map) { for (i = l - 1; i >= 0; i -= 1) { edge = Metamaps.Selected.Synapses[i]; Metamaps.Control.removeEdge(edge); } Metamaps.Selected.Synapses = new Array(); } }, removeEdge: function (edge) { var mappingid = edge.getData("mappingid"); Metamaps.Mappings.get(mappingid).destroy(); Metamaps.Control.hideEdge(edge); }, hideSelectedEdges: function () { var edge, l = Metamaps.Selected.Synapses.length, i; for (i = l - 1; i >= 0; i -= 1) { edge = Metamaps.Selected.Synapses[i]; Metamaps.Control.hideEdge(edge); } Metamaps.Selected.Synapses = new Array(); }, hideEdge: function (edge) { var from = edge.nodeFrom.id; var to = edge.nodeTo.id; edge.setData('alpha', 0, 'end'); Metamaps.Visualize.mGraph.fx.animate({ modes: ['edge-property:alpha'], duration: 500 }); setTimeout(function () { Metamaps.Visualize.mGraph.graph.removeAdjacence(from, to); }, 500); }, updateSelectedPermissions: function (permission) { // TODO if ($('.notice.metamaps').length == 0) { $('body').prepend('
'); } $('.notice.metamaps').hide().html('Working...').fadeIn('fast'); // variables to keep track of how many nodes and synapses you had the ability to change the permission of var nCount = 0, sCount = 0; // change the permission of the selected synapses, if logged in user is the original creator var l = Metamaps.Selected.Synapses.length; for (var i = l - 1; i >= 0; i -= 1) { var edge = Metamaps.Selected.Synapses[i]; if (edge.getData('userid') == userid) { Metamaps.Control.updateSynapsePermission(edge, permission); sCount++; } } // change the permission of the selected topics, if logged in user is the original creator var l = Metamaps.Selected.Topics.length; for (var i = l - 1; i >= 0; i -= 1) { var node = Metamaps.Selected.Topics[i]; if (node.getData('userid') == userid) { Metamaps.Control.updateTopicPermission(node, permission); nCount++; } } var nString = nCount == 1 ? (nCount.toString() + ' topic and ') : (nCount.toString() + ' topics and '); var sString = sCount == 1 ? (sCount.toString() + ' synapse') : (sCount.toString() + ' synapses'); $('.notice.metamaps').html(nString + sString + ' you created updated to ' + permission) setTimeout(function () { // TODO make it so that this can be cancelled by another message which is trying to be shown $('.notice.metamaps').fadeOut('fast'); }, 8000); }, updateTopicPermission: function (node, permission) { var mdata = { "topic": { "permission": permission } }; $.ajax({ type: "PUT", dataType: 'json', url: "/topics/" + node.id, data: mdata, success: function (data) { $('.showcard .mapPerm').removeClass('co pu pr minimize').addClass(permission.substring(0, 2)); $('.permissionSelect').remove(); node.setData("permission", permission); }, error: function () { console.log('failed to update permission'); } }); }, updateSynapsePermission: function (edge, permission) { var mdata = { "synapse": { "permission": permission } }; $.ajax({ type: "PUT", dataType: 'json', url: "/synapses/" + edge.data.$id, data: mdata, success: function (data) { $('#edit_synapse .mapPerm').removeClass('co pu pr minimize').addClass(permission.substring(0, 2)); $('#edit_synapse .permissionSelect').remove(); edge.setData("permission", permission); }, error: function () { console.log('failed to update permission'); } }); }, updateMapPermission: function (permission) { var mdata = { "map": { "permission": permission } }; $.ajax({ type: "PUT", dataType: 'json', url: "/maps/" + Metamaps.Active.Map.id, data: mdata, success: function (data) { $('.mapPermission').removeClass('commons public private minimize').addClass(permission); $('.mapPermission .permissionSelect').remove(); }, error: function () { console.log('failed to update permission'); } }); } }; Metamaps.Create = { init: function () { Metamaps.Create.newTopic.init(); Metamaps.Create.newSynapse.init(); }, newTopic: { init: function () { $('#new_topic').bind('contextmenu', function (e) { return false; }); $('#topic_name').keyup(function () { Metamaps.Create.newTopic.name = $(this).val(); }); // initialize the autocomplete results for the metacode spinner $('#topic_name').typeahead([ { name: 'topic_autocomplete', limit: 8, template: $('#topicAutocompleteTemplate').html(), remote: { url: '/topics/autocomplete_topic?term=%QUERY' }, engine: Hogan } ]); // tell the autocomplete to submit the form with the topic you clicked on if you pick from the autocomplete $('#topic_name').bind('typeahead:selected', function (event, datum, dataset) { Metamaps.Topic.getTopicFromAutocomplete(datum.id); }); // initialize metacode spinner and then hide it $("#metacodeImg").CloudCarousel({ titleBox: $('#metacodeImgTitle'), yRadius: 40, xPos: 150, yPos: 40, speed: 0.3, mouseWheel: true, bringToFront: true }); $('.new_topic').hide(); }, name: null, newId: 1, beingCreated: false, metacode: null, x: null, y: null, addSynapse: false, open: function () { $('#new_topic').fadeIn('fast', function () { $('#topic_name').focus(); }); Metamaps.Create.newTopic.beingCreated = true; }, hide: function () { $('#new_topic').fadeOut('fast'); $("#topic_name").typeahead('setQuery', ''); Metamaps.Create.newTopic.beingCreated = false; } }, newSynapse: { init: function () { // keep the right click menu from opening $('#new_synapse').bind('contextmenu', function (e) { return false; }); $('#synapse_desc').keyup(function () { Metamaps.Create.newSynapse.description = $(this).val(); }); // initialize the autocomplete results for synapse creation $('#synapse_desc').typeahead([ { name: 'synapse_autocomplete', template: "
{{label}}
", remote: { url: '/search/synapses?term=%QUERY' }, engine: Hogan }, { name: 'existing_synapses', limit: 50, template: $('#synapseAutocompleteTemplate').html(), remote: { url: '/search/synapses', replace: function () { var q = '/search/synapses?topic1id=' + Metamaps.Create.newSynapse.topic1id + '&topic2id=' + Metamaps.Create.newSynapse.topic2id; return q; } }, engine: Hogan, header: "

Existing Synapses

" }, ]); $('#synapse_desc').bind('typeahead:selected', function (event, datum, dataset) { if (datum.id) { // if they clicked on an existing synapse get it Metamaps.Synapse.getSynapseFromAutocomplete(datum.id); } }); }, beingCreated: false, description: null, topic1id: null, topic2id: null, newSynapseId: null, open: function () { $('#new_synapse').fadeIn('fast', function () { $('#synapse_desc').focus(); }); Metamaps.Create.newSynapse.beingCreated = true; }, hide: function () { $('#new_synapse').fadeOut('fast'); Metamaps.Create.newSynapse.beingCreated = false; Metamaps.Create.newTopic.addSynapse = false; Metamaps.Create.newSynapse.topic1id = 0; Metamaps.Create.newSynapse.topic2id = 0; } } }; Metamaps.Topic = { // this function is to retrieve a topic JSON object from the database // @param id = the id of the topic to retrieve get: function (id, callback) { // if the desired topic is not yet in the local topic repository, fetch it if (Metamaps.Topics.get(id) == undefined) { //console.log("Ajax call!"); if (!callback) { var e = $.ajax({ url: "/topics/" + id + ".json", async: false }); Metamaps.Topics.add($.parseJSON(e.responseText)); return Metamaps.Topics.get(id); } else { return $.ajax({ url: "/topics/" + id + ".json", success: function (data) { Metamaps.Topics.add(data); callback(Metamaps.Topics.get(id)); } }); } } else { if (!callback) { return Metamaps.Topics.get(id); } else { return callback(Metamaps.Topics.get(id)); } } }, /* * * */ renderTopic: function (mapping, topic, createNewInDB) { var self = Metamaps.Topic; var nodeOnViz, tempPos; var newnode = mapping.createNode(); if (!$.isEmptyObject(Metamaps.Visualize.mGraph.graph.nodes)) { Metamaps.Visualize.mGraph.graph.addNode(newnode); Metamaps.Visualize.mGraph.graph.eachNode(function (n) { n.setData("dim", 25, "start"); n.setData("dim", 25, "end"); }); nodeOnViz = Metamaps.Visualize.mGraph.graph.getNode(newnode.id); mapping.set('node', nodeOnViz); mapping.updateNode(); // links the topic and the mapping to the node nodeOnViz.setData("dim", 1, "start"); nodeOnViz.setData("dim", 40, "end"); if (Metamaps.Visualize.type === "RGraph") { tempPos = new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')); tempPos = tempPos.toPolar(); nodeOnViz.setPos(tempPos, "current"); nodeOnViz.setPos(tempPos, "start"); nodeOnViz.setPos(tempPos, "end"); } else if (Metamaps.Visualize.type === "ForceDirected") { nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), "current"); nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), "start"); nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), "end"); } if (Metamaps.Create.newTopic.addSynapse) { Metamaps.Create.newSynapse.topic1id = tempNode.id; Metamaps.Create.newSynapse.topic2id = nodeOnViz.id; Metamaps.Create.newSynapse.open(); Metamaps.Create.newSynapse.beingCreated = true; Metamaps.Visualize.mGraph.fx.animate({ modes: ["node-property:dim"], duration: 500, onComplete: function () { tempNode = null; tempNode2 = null; tempInit = false; } }); } else { Metamaps.Visualize.mGraph.fx.plotNode(nodeOnViz, Metamaps.Visualize.mGraph.canvas); Metamaps.Visualize.mGraph.fx.animate({ modes: ["node-property:dim"], duration: 500, onComplete: function () { } }); } } else { Metamaps.Visualize.mGraph.loadJSON(newnode); nodeOnViz = Metamaps.Visualize.mGraph.graph.getNode(newnode.id); mapping.set('node', nodeOnViz); mapping.updateNode(); // links the topic and the mapping to the node nodeOnViz.setData("dim", 1, "start"); nodeOnViz.setData("dim", 25, "end"); nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), "current"); nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), "start"); nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), "end"); Metamaps.Visualize.mGraph.fx.plotNode(nodeOnViz, Metamaps.Visualize.mGraph.canvas); Metamaps.Visualize.mGraph.fx.animate({ modes: ["node-property:dim"], duration: 500, onComplete: function () { } }); } if (!Metamaps.Settings.sandbox && createNewInDB) { if (topic.isNew()) { topic.save(null, { success: function (topicModel, response) { topicModel.updateMappings(); if (Metamaps.Active.Map) { mapping.save(); } }, error: function (model, response) { console.log('error saving topic to database'); } }); } else if (!topic.isNew() && Metamaps.Active.Map) { mapping.save(); } } }, createTopicLocally: function () { var self = Metamaps.Topic; var metacode = Metamaps.Metacodes.findWhere({ name: Metamaps.Create.newTopic.metacode }); var topic = new Metamaps.Backbone.Topic({ name: Metamaps.Create.newTopic.name, metacode_id: metacode.id }); Metamaps.Topics.add(topic); var mapping = new Metamaps.Backbone.Mapping({ category: "Topic", xloc: Metamaps.Create.newTopic.x, yloc: Metamaps.Create.newTopic.y, topic_id: topic.cid }); Metamaps.Mappings.add(mapping); //these can't happen until the value is retrieved, which happens in the line above Metamaps.Create.newTopic.hide(); Metamaps.Create.newTopic.addSynapse = false; self.renderTopic(mapping, topic, true); // this function also includes the creation of the topic in the database }, getTopicFromAutocomplete: function (id) { var self = Metamaps.Topic; Metamaps.Create.newTopic.hide(); var topic = self.get(id); var mapping = new Metamaps.Backbone.Mapping({ category: "Topic", xloc: Metamaps.Create.newTopic.x, yloc: Metamaps.Create.newTopic.y, topic_id: topic.id }); Metamaps.Mappings.add(mapping); self.renderTopic(mapping, topic, false); } }; Metamaps.TopicCard = { openTopicCard: null, //stores the JIT local ID of the topic with the topic card open init: function () { //$('.best_in_place').best_in_place(); Metamaps.TopicCard.generateShowcardHTML = Hogan.compile($('#topicCardTemplate').html()); }, fadeInShowCard: function (topic) { $('.showcard').fadeIn('fast'); Metamaps.TopicCard.openTopicCard = topic.isNew() ? topic.cid : topic.id; }, /** * Will open the Topic Card for the node that it's passed * @param {$jit.Graph.Node} node */ showCard: function (node) { var topic = node.getData('topic'); //populate the card that's about to show with the right topics data Metamaps.TopicCard.populateShowCard(topic); Metamaps.TopicCard.fadeInShowCard(topic); }, hideCard: function () { $('.showcard').fadeOut('fast'); Metamaps.TopicCard.openTopicCard = null; }, bindShowCardListeners: function (topic) { var self = Metamaps.TopicCard; var showCard = document.getElementById('showcard'); var selectingMetacode = false; // attach the listener that shows the metacode title when you hover over the image $('.showcard .metacodeImage').mouseenter(function () { $('.showcard .icon').css('z-index', '4'); $('.showcard .metacodeTitle').show(); }); $('.showcard .linkItem.icon').mouseleave(function () { if (!selectingMetacode) { $('.showcard .metacodeTitle').hide(); $('.showcard .icon').css('z-index', '1'); } }); $('.showcard .metacodeTitle').click(function () { if (!selectingMetacode) { selectingMetacode = true; $(this).addClass('minimize'); // this line flips the drop down arrow to a pull up arrow $('.metacodeSelect').show(); // add the scroll bar to the list of metacode select options if it isn't already there if (!$('.metacodeSelect ul').hasClass('mCustomScrollbar')) { $('.metacodeSelect ul').mCustomScrollbar({ mouseWheelPixels: 200, advanced: { updateOnContentResize: true } }); $('.metacodeSelect li').click(function () { selectingMetacode = false; var metacodeName = $(this).find('.mSelectName').text(); //updateMetacode(node, metacodeName); //TODO }); } } else { selectingMetacode = false; $(this).removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow $('.metacodeSelect').hide(); } }); // ability to change permission var selectingPermission = false; if (topic.authorizePermissionChange(Metamaps.Active.Mapper)) { $('.showcard .yourTopic .mapPerm').click(function () { if (!selectingPermission) { selectingPermission = true; $(this).addClass('minimize'); // this line flips the drop down arrow to a pull up arrow if ($(this).hasClass('co')) { $(this).append(''); } else if ($(this).hasClass('pu')) { $(this).append(''); } else if ($(this).hasClass('pr')) { $(this).append(''); } $('.permissionSelect li').click(function (event) { selectingPermission = false; var permission = $(this).attr('class'); // Metamaps.Control.updateTopicPermission(node, permission); // TODO event.stopPropagation(); }); } else { selectingPermission = false; $(this).removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow $('.permissionSelect').remove(); } }); } // when you're typing a description, resize the scroll box to have space $('.best_in_place_desc textarea').bind('keyup', function () { var s = $('.showcard').find('.scroll'); s.height(s.height()).mCustomScrollbar('update'); }); //bind best_in_place ajax callbacks $(showCard).find('.best_in_place_name').bind("ajax:success", function () { var s = $('.showcard').find('.scroll'); s.height(s.height()).mCustomScrollbar('update'); var name = $(this).html(); topic.set("name", Metamaps.Util.decodeEntities(name)); Metamaps.Visualize.mGraph.plot(); }); $(showCard).find('.best_in_place_desc').bind("ajax:success", function () { this.innerHTML = this.innerHTML.replace(/\r/g, '') var s = $('.showcard').find('.scroll'); s.height(s.height()).mCustomScrollbar('update'); var desc = $(this).html(); topic.set("desc", desc); }); $(showCard).find('.best_in_place_link').bind("ajax:success", function () { var link = $(this).html(); $(showCard).find('.go-link').attr('href', link); topic.set("link", link); }); }, populateShowCard: function (topic) { var self = Metamaps.TopicCard; var showCard = document.getElementById('showcard'); $(showCard).find('.permission').remove(); var html = self.generateShowcardHTML.render(self.buildObject(topic)); if (topic.authorizeToEdit(Metamaps.Active.Mapper)) { var perm = document.createElement('div'); var string = 'permission canEdit'; if (topic.authorizePermissionChange(Metamaps.Active.Mapper)) string += ' yourTopic'; perm.className = string; perm.innerHTML = html; showCard.appendChild(perm); } else { var perm = document.createElement('div'); perm.className = 'permission cannotEdit'; perm.innerHTML = html; showCard.appendChild(perm); } Metamaps.TopicCard.bindShowCardListeners(topic); }, generateShowcardHTML: null, // will be initialized into a Hogan template within init function //generateShowcardHTML buildObject: function (topic) { var nodeValues = {}; var authorized = topic.authorizeToEdit(Metamaps.Active.Mapper); //link is rendered differently if user is logged out or in var go_link, a_tag, close_a_tag; if (!authorized) { go_link = ''; if (topic.get("link") != "") { a_tag = ''; close_a_tag = ''; } else { a_tag = ''; close_a_tag = ''; } } else { go_link = ''; a_tag = ''; close_a_tag = ''; } var desc_nil = "Click to add description..."; var link_nil = "Click to add link..."; nodeValues.permission = topic.get("permission"); nodeValues.mk_permission = topic.get("permission").substring(0, 2); //nodeValues.map_count = topic.get("inmaps").length; //nodeValues.synapse_count = topic.get("synapseCount"); nodeValues.id = topic.isNew() ? topic.cid : topic.id; nodeValues.metacode = topic.getMetacode().get("name"); nodeValues.metacode_class = 'mbg' + topic.getMetacode().get("name").replace(/\s/g, ''); nodeValues.imgsrc = topic.getMetacode().get("icon"); nodeValues.name = topic.get("name"); nodeValues.userid = topic.get("user_id"); nodeValues.username = topic.getUser().get("name"); nodeValues.date = topic.getDate(); // the code for this is stored in /views/main/_metacodeOptions.html.erb nodeValues.metacode_select = $('#metacodeOptions').html(); nodeValues.go_link = go_link; nodeValues.a_tag = a_tag; nodeValues.close_a_tag = close_a_tag; nodeValues.link_nil = link_nil; nodeValues.link = (topic.get("link") == "" && authorized) ? link_nil : topic.get("link"); nodeValues.desc_nil = desc_nil; nodeValues.desc = (topic.get("desc") == "" && authorized) ? desc_nil : topic.get("desc"); return nodeValues; } }; Metamaps.Find = { filters: { name: "", type: [] }, init: function () { $('#filterText').on('input', function () { Metamaps.Find.filterText(); }); }, filterText: function () { var duration = 500; query = $('#filterText').val(); dim = $('#dim').is(":checked"); if (dim) { alpha = 0.2; } else { alpha = 0.0; } Metamaps.Visualize.mGraph.graph.eachNode(function (n) { //if node name doesnt matche query if (query != "") { if (n.name.toLowerCase().indexOf(query.toLowerCase()) === -1) { n.setData('alpha', alpha, 'end'); n.eachAdjacency(function (adj) { adj.setData('alpha', alpha, 'end'); }); } } else { n.setData('alpha', 1.0, 'end'); n.eachAdjacency(function (adj) { adj.setData('alpha', 1.0, 'end'); }); } }); //Metamaps.Visualize.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout); Metamaps.Visualize.mGraph.animate({ modes: ['node-property:alpha', 'edge-property:alpha'], duration: duration }); }, passFilters: function (topic) { var self = Metamaps.Find; var filters = self.filters; var passesName = filters.name == "" ? true : false, passesType = filters.type == [] ? true : false; //filter by name if (topic.get('1')[1][0].toLowerCase().indexOf(filters.name) !== -1) { passesName = true; } // filter by type if (!filters.type == []) { // get the array of types that your topic 'is' var metacodes = topic.get('2') ? topic.get('2')[1] : []; if (_.intersection(filters.type, metacodes).length == 0) passesType = true; } if (passesName && passesType) { return true; } else { return false; } } }; Metamaps.JIT = { vizData: [], // contains the visualization-compatible graph graphRendered: false, // flag indicates if we have rendered the data so we don't bother doing it again wastefully metacodeIMGinit: false, /** * This method will bind the event handlers it is interested and initialize the class. */ init: function () { var self = Metamaps.JIT; }, /** * convert our topic JSON into something JIT can use */ prepareVizData: function () { var self = Metamaps.JIT; var topic; Metamaps.Mappings.each(function (m) { if (m.get("category") === "Topic") { self.vizData.push(m.createNode()); } }); if (self.vizData.length == 0) { Metamaps.Visualize.loadLater = true; } Metamaps.Visualize.render("infovis", self.vizData); }, // 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 = adj.getData("synapses")[0]; // for now, just grab the first synapse var directionCat = synapse.get("category"); //label placement on edges Metamaps.JIT.renderEdgeArrows(this.edgeHelper, adj, synapse); //check for edge label in data var desc = synapse.get("desc"); var showDesc = adj.getData("showDesc"); if (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 = 'hanging'; 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) + 8; 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(); //render text ctx.fillStyle = '#222222'; ctx.textAlign = 'center'; for (index = 0; index < arrayOfLabelLines.length; ++index) { ctx.fillText(arrayOfLabelLines[index], x + (width / 2), y + 5 + (16 * index)); } } }, // edgeRender edgeRenderEmbed: function (adj, canvas) { //get nodes cartesian coordinates var pos = adj.nodeFrom.pos.getc(true); var posChild = adj.nodeTo.pos.getc(true); var directionCat = adj.getData("category"); //label placement on edges Metamaps.JIT.renderEdgeArrows(this.edgeHelper, adj); //check for edge label in data var desc = adj.getData("desc"); var showDesc = adj.getData("showDesc"); if (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 = 'hanging'; 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) + 8; 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(); //render text ctx.fillStyle = '#222222'; ctx.textAlign = 'center'; for (index = 0; index < arrayOfLabelLines.length; ++index) { ctx.fillText(arrayOfLabelLines[index], x + (width / 2), y + 5 + (16 * index)); } } }, // edgeRenderEmbed ForceDirected: { animateSavedLayout: { modes: ['linear'], transition: $jit.Trans.Quad.easeInOut, duration: 800, onComplete: function () { Metamaps.Visualize.mGraph.busy = false; } }, animateFDLayout: { modes: ['linear'], transition: $jit.Trans.Elastic.easeOut, duration: 2500, 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: 0.4 }, //Native canvas text styling Label: { type: 'Native', //Native or HTML size: 20, family: 'arial', textBaseline: 'hanging', 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); }, //Update node positions when dragged onDragMove: function (node, eventInfo, e) { Metamaps.JIT.onDragMoveTopicHandler(node, eventInfo, e); }, onDragEnd: function (node, eventInfo, e) { Metamaps.JIT.onDragEndTopicHandler(node, eventInfo, e, false); }, 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) { Metamaps.Visualize.mGraph.busy = false; Metamaps.Mouse.boxEndCoordinates = eventInfo.getPos(); Metamaps.JIT.selectNodesWithBox(); 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); } else if (node && !node.nodeFrom) { Metamaps.JIT.selectNodeOnClickHandler(node, e); } else { Metamaps.JIT.canvasClickHandler(eventInfo.getPos(), e); } //if }, //Add also a click handler to nodes onRightClick: function (node, eventInfo, e) { node = eventInfo.getNode(); //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 { } //if } }, //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'), cat = topic ? topic.getMetacode().get('name') : false, ctx = canvas.getCtx(); // if the topic is selected draw a circle around it if (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 (!cat || !imgArray[cat].complete || (typeof imgArray[cat].naturalWidth !== "undefined" && imgArray[cat].naturalWidth === 0)) { ctx.beginPath(); ctx.arc(pos.x, pos.y, dim, 0, 2 * Math.PI, false); ctx.fillStyle = '#B6B2FD'; ctx.fill(); } else { ctx.drawImage(imgArray[cat], pos.x - dim, pos.y - dim, dim * 2, dim * 2); } }, '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(true), to = adj.nodeTo.pos.getc(true); return this.edgeHelper.line.contains(from, to, pos, adj.Edge.epsilon); } } }, embed: { graphSettings: { }, nodeSettings: { }, edgeSettings: { 'customEdge': { 'render': function (adj, canvas) { Metamaps.JIT.edgeRenderEmbed(adj, canvas) }, 'contains': function (adj, pos) { var from = adj.nodeFrom.pos.getc(true), to = adj.nodeTo.pos.getc(true); return this.edgeHelper.line.contains(from, to, pos, adj.Edge.epsilon); } } } } }, // 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: { }, embed: { graphSettings: { }, nodeSettings: { }, edgeSettings: { } } }, // ForceDirected3D RGraph: { animate: { modes: ['polar'], duration: 2000, 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, type: 'HTML', //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', CanvasStyles: { strokeStyle: '#333', lineWidth: 1.5 } }, //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: '#222222', type: 'customEdge', lineWidth: 2, alpha: 0.4 }, //Native canvas text styling Label: { type: 'HTML', //Native or HTML size: 20, //style: 'bold' }, //Add Tips Tips: { enable: false, onShow: function (tip, node) {} }, // Add node events Events: { enable: true, enableForEdges: true, type: 'HTML', onMouseMove: function (node, eventInfo, e) { Metamaps.JIT.onMouseMoveHandler(node, eventInfo, e); }, //Update node positions when dragged onDragMove: function (node, eventInfo, e) { Metamaps.JIT.onDragMoveTopicHandler(node, eventInfo, e); }, onDragEnd: function (node, eventInfo, e) { Metamaps.JIT.onDragEndTopicHandler(node, eventInfo, e, false); }, 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); Metamaps.Visualize.mGraph.labels.hideLabel(Metamaps.Visualize.mGraph.graph.getNode(Metamaps.TopicCard.openTopicCard)); } }, //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) { if (Metamaps.Mouse.boxStartCoordinates) { Metamaps.Visualize.mGraph.busy = false; Metamaps.Mouse.boxEndCoordinates = eventInfo.getPos(); Metamaps.JIT.selectNodesWithBox(); 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); } else if (node && !node.nodeFrom) { Metamaps.JIT.selectNodeOnClickHandler(node, e); } else { Metamaps.JIT.canvasClickHandler(eventInfo.getPos(), e); } //if } }, //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'), cat = node.getData('metacode'), ctx = canvas.getCtx(); // if the topic is on the Canvas draw a white circle around it if (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(); } try { ctx.drawImage(imgArray[cat], pos.x - dim, pos.y - dim, dim * 2, dim * 2); } catch (e) { alert("You've got an topic causing an issue! It's ->this-> one: " + cat); } }, 'contains': function (node, pos) { var npos = node.pos.getc(true), dim = node.getData('dim'); return this.nodeHelper.circle.contains(npos, pos, dim); } } }, edgeSettings: { 'customEdge': { 'render': function (adj, canvas) { Metamaps.JIT.edgeRender(adj, canvas) }, 'contains': function (adj, pos) { var from = adj.nodeFrom.pos.getc(true), to = adj.nodeTo.pos.getc(true); return this.edgeHelper.line.contains(from, to, pos, adj.Edge.epsilon); } } }, embed: { graphSettings: { }, nodeSettings: { }, edgeSettings: { 'customEdge': { 'render': function (adj, canvas) { Metamaps.JIT.edgeRenderEmbed(adj, canvas) }, 'contains': function (adj, pos) { var from = adj.nodeFrom.pos.getc(true), to = adj.nodeTo.pos.getc(true); return this.edgeHelper.line.contains(from, to, pos, adj.Edge.epsilon); } } } } }, // RGraph addMetacode: function () { var self = Metamaps.JIT; // code from http://www.professorcloud.com/mainsite/carousel-integration.htm //mouseWheel:true, if (!self.metacodeIMGinit) { $("#metacodeImg").CloudCarousel({ titleBox: $('#metacodeImgTitle'), yRadius: 40, xPos: 150, yPos: 40, speed: 0.15, mouseWheel: true, bringToFront: true }); self.metacodeIMGinit = true; $('#topic_inlanguage').typeahead([ { name: 'topics', template: '

{{value}}

{{type}}
{{type}}', remote: { url: '/topics/autocomplete_topic_inlanguage?term=%QUERY' }, engine: Hogan } ]); $('#topic_inlanguage').bind('typeahead:selected', function (event, datum, dataset) { Metamaps.Topic.getTopicFromAutocomplete(datum.id); }); } }, // addMetacode editMetacodeSet: function (id) { $('#metacodeImg, #metacodeImgTitle').empty(); $('#metacodeImg').append('809'); $('#metacodeImg').removeData('cloudcarousel'); var newMetacodes = ""; /* _.each(topics, function (topic) { newMetacodes += '' + topic.id + ''; }); $('#metacodeImg').empty().append(newMetacodes).CloudCarousel({ titleBox: $('#metacodeImgTitle'), yRadius: 40, xPos: 150, yPos: 40, speed: 0.15, mouseWheel: true, bringToFront: true }); */ }, // editMetacode onMouseEnter: function (edge) { $('canvas').css('cursor', 'pointer'); var edgeIsSelected = Metamaps.Selected.Synapses.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, alpha: 1 }); Metamaps.Visualize.mGraph.fx.animate({ modes: ['edge-property:lineWidth:color:alpha'], duration: 100 }); Metamaps.Visualize.mGraph.plot(); } }, // onMouseEnter onMouseLeave: function (edge) { $('canvas').css('cursor', 'default'); var edgeIsSelected = Metamaps.Selected.Synapses.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, alpha: 0.4 }); Metamaps.Visualize.mGraph.fx.animate({ modes: ['edge-property:lineWidth:color:alpha'], 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 () { // this is to submit new topic creation if (Metamaps.Create.newTopic.beingCreated) { Metamaps.Topic.createTopicLocally(); } 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; console.log(scale); 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; if (node && !node.nodeFrom) { Metamaps.Create.newTopic.hide(); Metamaps.Create.newSynapse.hide(); 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.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) { 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); node.setData('xloc', pos.x); node.setData('yloc', pos.y); } else { var len = Metamaps.Selected.Topics.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.Topics[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.Topics[i]; var x = pos.x + xOffset[i]; var y = pos.y + yOffset[i]; n.pos.setc(x, y); n.setData('xloc', x); n.setData('yloc', y); } //for } //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) && userid != null) { if (tempInit == false) { tempNode = node; tempInit = true; // set the draw synapse start positions var l = Metamaps.Selected.Topics.length; if (l > 0) { for (var i = l - 1; i >= 0; i -= 1) { var n = Metamaps.Selected.Topics[i]; Metamaps.Mouse.synapseStartCoordinates.push({ x: n.pos.getc().x, y: n.pos.getc().y }); } } else { Metamaps.Mouse.synapseStartCoordinates = [{ x: tempNode.pos.getc().x, y: 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.Topics.indexOf(temp) == -1) { // this means a Node has been returned tempNode2 = temp; Metamaps.Visualize.mGraph.plot(); Metamaps.Mouse.synapseEndCoordinates = { x: tempNode2.pos.getc().x, y: 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.fx.plotNode(tempNode, Metamaps.Visualize.mGraph.canvas); Metamaps.Visualize.mGraph.fx.plotNode(temp, Metamaps.Visualize.mGraph.canvas); } else if (!temp) { 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"); $('#new_synapse').css('left', myX + "px"); $('#new_synapse').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 }; } } } }, // onDragMoveTopicHandler onDragCancelHandler: function (node, eventInfo, e, centred) { tempNode = null; tempNode2 = null; 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, allowRealtime) { if (tempInit && tempNode2 == null) { Metamaps.Create.newTopic.addSynapse = true; $('#new_topic').fadeIn('fast'); Metamaps.Create.newTopic.beingCreated = true; Metamaps.JIT.addMetacode(); $('#topic_name').focus(); } else if (tempInit && tempNode2 != null) { Metamaps.Create.newTopic.addSynapse = false; Metamaps.Create.newSynapse.topic1id = tempNode.id; Metamaps.Create.newSynapse.topic2id = tempNode2.id; Metamaps.Create.newSynapse.open(); Metamaps.Create.newSynapse.beingCreated = true tempNode = null; tempNode2 = null; tempInit = false; } }, //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; if (now - storedTime < Metamaps.Mouse.DOUBLE_CLICK_TOLERANCE) { // 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"); $('#new_topic').fadeIn('fast'); Metamaps.Create.newTopic.beingCreated = true; Metamaps.JIT.addMetacode(); $('#topic_name').focus(); } else if (!Metamaps.Mouse.didPan) { // SINGLE CLICK Metamaps.TopicCard.hideCard(); $('#new_topic').fadeOut('fast'); $("#topic_name").typeahead('setQuery', ''); Metamaps.Create.newTopic.beingCreated = false; $('#new_synapse').fadeOut('fast'); Metamaps.Create.newSynapse.beingCreated = false; $('.rightclickmenu').remove(); // reset the draw synapse positions to false Metamaps.Mouse.synapseStartCoordinates = []; Metamaps.Mouse.synapseEndCoordinates = null; tempInit = false; tempNode = null; tempNode2 = null; Metamaps.Control.deselectAllEdges(); Metamaps.Control.deselectAllNodes(); } }, //canvasClickHandler nodeDoubleClickHandler: function (node, e) { Metamaps.TopicCard.showCard(node); }, // 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; } else { return false; } }, //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.Topics.length == 0) { Metamaps.Control.selectNode(node); return 'deselect'; } if (Metamaps.Selected.Topics.indexOf(node) == -1) { if (e.shiftKey) { Metamaps.Control.selectNode(node); return 'nothing'; } else { return 'only-drag-this-one'; } } return 'nothing'; //case 4? }, // handleSelectionBeforeDragging selectNodesWithBox: function () { var sX = Metamaps.Mouse.boxStartCoordinates.x, sY = Metamaps.Mouse.boxStartCoordinates.y, eX = Metamaps.Mouse.boxEndCoordinates.x, eY = Metamaps.Mouse.boxEndCoordinates.y; Metamaps.Visualize.mGraph.graph.eachNode(function (n) { var x = n.pos.x, y = n.pos.y; 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)) { var nodeIsSelected = Metamaps.Selected.Topics.indexOf(n); if (nodeIsSelected == -1) Metamaps.Control.selectNode(n); // the node is not selected, so select it else if (nodeIsSelected != -1) Metamaps.Control.deselectNode(n); // the node is selected, so deselect it } }); Metamaps.Mouse.boxStartCoordinates = false; Metamaps.Mouse.boxEndCoordinates = false; Metamaps.Visualize.mGraph.plot(); }, // selectNodesWithBox 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; } // 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; } var check = self.nodeWasDoubleClicked(); if (check) { self.nodeDoubleClickHandler(node, e); return; } else { // wait a certain length of time, then check again, then run this code setTimeout(function () { if (!Metamaps.JIT.nodeWasDoubleClicked()) { if (!e.shiftKey) { Metamaps.Visualize.mGraph.graph.eachNode(function (n) { if (n.id != node.id) { Metamaps.Control.deselectNode(n); } }); } if (node.selected) { Metamaps.Control.deselectNode(node); } else { Metamaps.Control.selectNode(node); } //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); } }, //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 e.preventDefault(); e.stopPropagation(); if (Metamaps.Visualize.mGraph.busy) return; // 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; // position the menu where the click happened $(rightclickmenu).css({ left: e.clientX, top: e.clientY }); //add the menu to the page $('#center-container').append(rightclickmenu); // attach events to clicks on the list items // delete the selected things from the database $('.rc-delete').click(function () { $('.rightclickmenu').remove(); var n = Metamaps.Selected.Topics.length; var e = Metamaps.Selected.Synapses.length; var ntext = n == 1 ? "1 topic" : n + " topics"; var etext = e == 1 ? "1 synapse" : e + " synapses"; var text = "You have " + ntext + " and " + etext + " selected. "; var r = confirm(text + "Are you sure you want to permanently delete them all? This will remove them from all maps they appear on."); if (r == true) { Metamaps.Control.deleteSelectedEdges(); Metamaps.Control.deleteSelectedNodes(); } }); // remove the selected things from the map $('.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(); }); // when in radial, center on the topic you picked $('.rc-center').click(function () { $('.rightclickmenu').remove(); centerOn(node.id); }); // open the entity in a new tab $('.rc-popout').click(function () { $('.rightclickmenu').remove(); var win = window.open('/topics/' + node.id, '_blank'); win.focus(); }); // 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()); }); }, //selectNodeOnRightClickHandler selectEdgeOnClickHandler: function (adj, e) { if (Metamaps.Visualize.mGraph.busy) return; //editing overrides everything else if (e.altKey) { //in select-edit-delete-nodes-and-edges.js // editEdge(adj, e); TODO need to find and reimplement this function return; } var edgeIsSelected = Metamaps.Selected.Synapses.indexOf(adj); if (edgeIsSelected == -1) edgeIsSelected = false; else if (edgeIsSelected != -1) edgeIsSelected = true; if (edgeIsSelected && e.shiftKey) { //deselecting an edge with shift Metamaps.Control.deselectEdge(adj); } else if (!edgeIsSelected && e.shiftKey) { //selecting an edge with shift Metamaps.Control.selectEdge(adj); } else if (edgeIsSelected && !e.shiftKey) { //deselecting an edge without shift - unselect all Metamaps.Control.deselectAllEdges(); } else if (!edgeIsSelected && !e.shiftKey) { //selecting an edge without shift - unselect all but new one Metamaps.Control.deselectAllEdges(); Metamaps.Control.selectEdge(adj); } Metamaps.Visualize.mGraph.plot(); }, //selectEdgeOnClickHandler selectEdgeOnRightClickHandler: function (adj, e) { if (Metamaps.Visualize.mGraph.busy) return; console.log('edge right click handler working'); }, //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(panningInt) panningInt = setInterval(function () { myTimer() }, 1); function myTimer() { Metamaps.Visualize.mGraph.canvas.translate(x_velocity * easing * 1 / sx, y_velocity * easing * 1 / sy); easing = easing * 0.75; if (easing < 0.1) window.clearInterval(panningInt); } }, // SmoothPanning generateLittleHTML: function (node) { var mapperm = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); var littleHTML = '
{{ name }}
'; if (userid == null || !Metamaps.Active.Map || !mapperm) { //unauthenticated, not on a map: can remove from canvas littleHTML += ''; } else if (mapperm) { //permission to remove nodes from the map littleHTML += ''; } if (userid == node.getData('userid')) { //logged in, and owner of the topic, thus permission to delete littleHTML += ''; } littleHTML += '
'; var render = _.template(littleHTML); var map; Metamaps.Active.Map ? map = Metamaps.Active.Topic.id : map = null; return render({ id: node.id, mapid: map, name: node.name }); }, // generateLittleHTML 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 = "#222222"; ctx.lineWidth = 2; ctx.globalAlpha = 0.4; } 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) { var self = Metamaps.JIT; var canvas = Metamaps.Visualize.mGraph.canvas; 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 (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 direction = adj.data.$direction; var inv = (direction && direction.length > 1 && 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 }; Metamaps.Listeners = { init: function () { $(document).on('keydown', function (e) { switch (e.which) { case 13: Metamaps.JIT.enterKeyHandler(); e.preventDefault(); break; case 27: Metamaps.JIT.escKeyHandler(); break; default: break; //alert(e.which); } }); //$(window).resize(function () { // Metamaps.Visualize.mGraph.canvas.resize($(window).width(), $(window).height()); //}); } }; Metamaps.Map = { // this function is to retrieve a map JSON object from the database // @param id = the id of the map to retrieve get: function (id, callback) { // if the desired topic is not yet in the local topic repository, fetch it if (Metamaps.Maps.get(id) == undefined) { if (!callback) { var e = $.ajax({ url: "/maps/" + id + ".json", async: false }); Metamaps.Maps.add($.parseJSON(e.responseText)); return Metamaps.Maps.get(id); } else { return $.ajax({ url: "/users/" + id + ".json", success: function (data) { Metamaps.Maps.add(data); callback(Metamaps.Maps.get(id)); } }); } } else { if (!callback) { return Metamaps.Maps.get(id); } else { return callback(Metamaps.Maps.get(id)); } } }, }; Metamaps.Maps = null; // will be initialized in Metamaps.Backbone.init as a MapCollection Metamaps.Mapper = { // this function is to retrieve a mapper JSON object from the database // @param id = the id of the mapper to retrieve get: function (id, callback) { // if the desired topic is not yet in the local topic repository, fetch it if (Metamaps.Mappers.get(id) == undefined) { if (!callback) { var e = $.ajax({ url: "/users/" + id + ".json", async: false }); Metamaps.Mappers.add($.parseJSON(e.responseText)); return Metamaps.Mappers.get(id); } else { return $.ajax({ url: "/users/" + id + ".json", success: function (data) { Metamaps.Mappers.add(data); callback(Metamaps.Mappers.get(id)); } }); } } else { if (!callback) { return Metamaps.Mappers.get(id); } else { return callback(Metamaps.Mappers.get(id)); } } }, }; Metamaps.Mappers = null; // will be initialized in Metamaps.Backbone.init as a MapperCollection Metamaps.Mouse = { didPan: false, changeInX: 0, changeInY: 0, edgeHoveringOver: false, boxStartCoordinates: false, boxEndCoordinates: false, synapseStartCoordinates: [], synapseEndCoordinates: null, lastNodeClick: 0, lastCanvasClick: 0, DOUBLE_CLICK_TOLERANCE: 300 }; Metamaps.Organize = { init: function () { }, arrange: function (layout, centerNode) { console.log(centerNode); // first option for layout to implement is 'grid', will do an evenly spaced grid with its center at the 0,0 origin if (layout == 'grid') { var numNodes = _.size(Metamaps.Visualize.mGraph.graph.nodes); // this will always be an integer, the # of nodes on your graph visualization var numColumns = Math.floor(Math.sqrt(numNodes)); // the number of columns to make an even grid var GRIDSPACE = 400; var row = 0; var column = 0; Metamaps.Visualize.mGraph.graph.eachNode(function (n) { if (column == numColumns) { column = 0; row += 1; } var newPos = new $jit.Complex(); newPos.x = column * GRIDSPACE; newPos.y = row * GRIDSPACE; n.setPos(newPos, 'end'); column += 1; }); Metamaps.Visualize.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout); } else if (layout == 'grid_full') { var numNodes = _.size(Metamaps.Visualize.mGraph.graph.nodes); // this will always be an integer, the # of nodes on your graph visualization //var numColumns = Math.floor(Math.sqrt(numNodes)); // the number of columns to make an even grid //var GRIDSPACE = 400; var height = Metamaps.Visualize.mGraph.canvas.getSize(0).height; var width = Metamaps.Visualize.mGraph.canvas.getSize(0).width; var totalArea = height * width; var cellArea = totalArea / numNodes; var ratio = height / width; var cellWidth = sqrt(cellArea / ratio); var cellHeight = cellArea / cellWidth; var row = floor(height / cellHeight); var column = floor(width / cellWidth); var totalCells = row * column; if (totalCells) Metamaps.Visualize.mGraph.graph.eachNode(function (n) { if (column == numColumns) { column = 0; row += 1; } var newPos = new $jit.Complex(); newPos.x = column * GRIDSPACE; newPos.y = row * GRIDSPACE; n.setPos(newPos, 'end'); column += 1; }); Metamaps.Visualize.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout); } else if (layout == 'radial') { var centerX = centerNode.getPos().x; var centerY = centerNode.getPos().y; centerNode.setPos(centerNode.getPos(), 'end'); console.log(centerNode.adjacencies); var lineLength = 200; /* centerNode.eachAdjacency(function(n) { var newPos = new $jit.Complex(); newPos.x = lineLength*Math.sin(degree) +centerX; newPos.y = lineLength*Math.cos(degree) +centerY; n.nodeTo.setPos(newPos, 'end'); degree += angle; }); Metamaps.Visualize.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout); */ var usedNodes = {}; usedNodes[centerNode.id] = centerNode; var radial = function (node, level, degree) { if (level == 1) { var numLinksTemp = _.size(node.adjacencies); var angleTemp = 2 * Math.PI / numLinksTemp; } else { angleTemp = 2 * Math.PI / 20 }; node.eachAdjacency(function (a) { var isSecondLevelNode = (centerNode.adjacencies[a.nodeTo.id] != undefined && level > 1); if (usedNodes[a.nodeTo.id] == undefined && !isSecondLevelNode) { var newPos = new $jit.Complex(); newPos.x = level * lineLength * Math.sin(degree) + centerX; newPos.y = level * lineLength * Math.cos(degree) + centerY; a.nodeTo.setPos(newPos, 'end'); usedNodes[a.nodeTo.id] = a.nodeTo; radial(a.nodeTo, level + 1, degree); degree += angleTemp; }; }); }; radial(centerNode, 1, 0); Metamaps.Visualize.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout); /* var radial = function (input) { if (column == numColumns) { column = 0; row += 1; } var newPos = new $jit.Complex(); newPos.x = column*GRIDSPACE; newPos.y = row*GRIDSPACE; n.setPos(newPos, 'end'); }); Metamaps.Visualize.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout);*/ } else if (layout == 'center_viewport') { var lowX = 0, lowY = 0, highX = 0, highY = 0; var oldOriginX = Metamaps.Visualize.mGraph.canvas.translateOffsetX; var oldOriginY = Metamaps.Visualize.mGraph.canvas.translateOffsetY; Metamaps.Visualize.mGraph.graph.eachNode(function (n) { if (n.id === 1) { lowX = n.getPos().x; lowY = n.getPos().y; highX = n.getPos().x; highY = n.getPos().y; }; if (n.getPos().x < lowX) lowX = n.getPos().x; if (n.getPos().y < lowY) lowY = n.getPos().y; if (n.getPos().x > highX) highX = n.getPos().x; if (n.getPos().y > highY) highY = n.getPos().y; }); console.log(lowX, lowY, highX, highY); var newOriginX = (lowX + highX) / 2; var newOriginY = (lowY + highY) / 2; console.log("new offset: (before translate)", Metamaps.Visualize.mGraph.canvas.translateOffsetX, Metamaps.Visualize.mGraph.canvas.translateOffsetY); console.log("new offset: (before translate)", Metamaps.Visualize.mGraph.canvas.translateOffsetX, Metamaps.Visualize.mGraph.canvas.translateOffsetY); Metamaps.Visualize.mGraph.canvas.translate(newOriginX - oldOriginX, newOriginY - oldOriginY); console.log("ideal offset: ", newOriginX, newOriginY); console.log("new offset: ", Metamaps.Visualize.mGraph.canvas.translateOffsetX, Metamaps.Visualize.mGraph.canvas.translateOffsetY); console.log("new offset: ", Metamaps.Visualize.mGraph.canvas.translateOffsetX, Metamaps.Visualize.mGraph.canvas.translateOffsetY); } else alert('please call function with a valid layout dammit!'); }, loadSavedLayout: function (id) { Metamaps.Visualize.computePositions(); Metamaps.Visualize.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout); }, saveNewLayout: function () { var layoutName = prompt("Enter a name for your layout..."); if (layoutName === null) return; var layoutObject = {}; Metamaps.Visualize.mGraph.graph.eachNode(function (n) { layoutObject[n.data.$id] = { x: n.getPos().x, y: n.getPos().y }; }); function addMetadata(e) { // add the 'expressed In English' field Metamaps.Field.createFieldInDatabase("topic", e.id, 1, layoutName); // add the 'is a' synapses Metamaps.Synapse.createSynapseInDatabase(e.id, 2, 1087, false); // add the 'saved coordinates' field Metamaps.Field.createFieldInDatabase("topic", e.id, 1088, JSON.stringify(layoutObject)); // add the 'is layout for' synapses Metamaps.Synapse.createSynapseInDatabase(e.id, 1089, Metamaps.Active.Topic.id, false); $('#saveLayout').text('Saved!'); setTimeout(function () { $('#saveLayout').text('Save New Layout') }, 1500); Metamaps.Topic.get(e.id, function (ee) { // add the new layout to the list $('#selectSavedLayout').append(Metamaps.Util.generateOptionsList([ee])); // switch to the new layout $('#selectSavedLayout').val(ee.id); }); } // call create topic which will run 'addMetadata' when it works Metamaps.Topic.createTopicInDatabase(addMetadata, []); }, saveCurrentLayout: function () { var id = $('#selectSavedLayout option:selected').val(); if (id != "") { Metamaps.Topic.get(id, function (e) { // this is where the actual saved coordinates are var fieldID = e[1088][2][0]; var layoutObject = {}; Metamaps.Visualize.mGraph.graph.eachNode(function (n) { layoutObject[n.data.$id] = { x: n.getPos().x, y: n.getPos().y }; }); // update the 'saved coordinates' field Metamaps.Field.updateFieldInDatabase(fieldID, JSON.stringify(layoutObject)); Metamaps.Topic.refresh(e.id); $('#updateLayout').text('Updated!'); setTimeout(function () { $('#updateLayout').text('Overwrite Layout') }, 1500); }); } } }; Metamaps.Selected = { Topics: [], Synapses: [] }; Metamaps.Synapse = { // this function is to retrieve a synapse JSON object from the database // @param id = the id of the synapse to retrieve get: function (id, callback) { // if the desired topic is not yet in the local topic repository, fetch it if (Metamaps.Synapses.get(id) == undefined) { if (!callback) { var e = $.ajax({ url: "/synapses/" + id + ".json", async: false }); Metamaps.Synapses.add($.parseJSON(e.responseText)); return Metamaps.Synapses.get(id); } else { return $.ajax({ url: "/synapses/" + id + ".json", success: function (data) { Metamaps.Synapses.add(data); callback(Metamaps.Synapses.get(id)); } }); } } else { if (!callback) { return Metamaps.Synapses.get(id); } else { return callback(Metamaps.Synapses.get(id)); } } }, /* * * */ renderSynapse: function (mapping, synapse, node1, node2, createNewInDB) { var self = Metamaps.Synapse; var edgeOnViz; var newedge = mapping.createEdge(); Metamaps.Visualize.mGraph.graph.addAdjacence(node1, node2, newedge.data); edgeOnViz = Metamaps.Visualize.mGraph.graph.getAdjacence(node1.id, node2.id); mapping.set('edge', edgeOnViz); mapping.updateEdge(); // links the topic and the mapping to the node Metamaps.Visualize.mGraph.fx.plotLine(edgeOnViz, Metamaps.Visualize.mGraph.canvas); Metamaps.Control.selectEdge(edgeOnViz); if (!Metamaps.Settings.sandbox && createNewInDB) { if (synapse.isNew()) { synapse.save(null, { success: function (synapseModel, response) { synapseModel.updateMappings(); if (Metamaps.Active.Map) { mapping.save(); } }, error: function (model, response) { console.log('error saving synapse to database'); } }); } else if (!synapse.isNew() && Metamaps.Active.Map) { mapping.save(); } } }, createSynapseLocally: function () { var self = Metamaps.Synapse, topic1, topic2, node1, node2, synapse, mapping; //for each node in this array we will create a synapse going to the position2 node. var synapsesToCreate = []; node2 = Metamaps.Visualize.mGraph.graph.getNode(Metamaps.Create.newSynapse.topic2id); topic2 = node2.getData('topic'); var len = Metamaps.Selected.Topics.length; if (len == 0) { synapsesToCreate[0] = Metamaps.Visualize.mGraph.graph.getNode(Metamaps.Create.newSynapse.topic1id); } else if (len > 0) { synapsesToCreate = Metamaps.Selected.Topics; } for (var i = 0; i < synapsesToCreate.length; i++) { node1 = synapsesToCreate[i]; topic1 = node1.getData('topic'); synapse = new Metamaps.Backbone.Synapse({ desc: Metamaps.Create.newSynapse.description, node1_id: topic1.isNew() ? topic1.cid : topic1.id, node2_id: topic2.isNew() ? topic2.cid : topic2.id, }); Metamaps.Synapses.add(synapse); mapping = new Metamaps.Backbone.Mapping({ category: "Synapse", synapse_id: synapse.cid }); Metamaps.Mappings.add(mapping); // this function also includes the creation of the synapse in the database self.renderSynapse(mapping, synapse, node1, node2, true); } // for each in synapsesToCreate Metamaps.Create.newSynapse.hide(); }, getSynapseFromAutocomplete: function (id) { var self = Metamaps.Synapse; Metamaps.Create.newSynapse.hide(); var synapse = self.get(id); var mapping = new Metamaps.Backbone.Mapping({ category: "Synapse", synapse_id: synapse.id }); Metamaps.Mappings.add(mapping); self.renderSynapse(mapping, synapse, false); } }; Metamaps.Synapses = {}; Metamaps.SynapseCard = { openSynapseCard: null }; Metamaps.Touch = { touchPos: null, // this stores the x and y values of a current touch event touchDragNode: null // this stores a reference to a JIT node that is being dragged }; Metamaps.Util = { // helper function to determine how many lines are needed // Line Splitter Function // copyright Stephen Chapman, 19th April 2006 // you may copy this code but please keep the copyright notice as well splitLine: function (st, n) { var b = ''; var s = st; while (s.length > n) { var c = s.substring(0, n); var d = c.lastIndexOf(' '); var e = c.lastIndexOf('\n'); if (e != -1) d = e; if (d == -1) d = n; b += c.substring(0, d) + '\n'; s = s.substring(d + 1); } return b + s; }, decodeEntities: function (desc) { var str, temp = document.createElement('p'); temp.innerHTML = desc; //browser handles the topics str = temp.textContent || temp.innerText; temp = null; //delete the element; return str; }, //decodeEntities getDistance: function (p1, p2) { return Math.sqrt(Math.pow((p2.x - p1.x), 2) + Math.pow((p2.y - p1.y), 2)); }, generateOptionsList: function (data) { var newlist = ""; for (var i = 0; i < data.length; i++) { newlist = newlist + ''; } return newlist; }, checkURLisImage: function (url) { // when the page reloads the following regular expression will be screwed up // please replace it with this one before you save: /*backslashhere*.(jpeg|jpg|gif|png)$/ return (url.match(/\.(jpeg|jpg|gif|png)$/) != null); }, checkURLisYoutubeVideo: function (url) { return (url.match(/^http:\/\/(?:www\.)?youtube.com\/watch\?(?=[^?]*v=\w+)(?:[^\s?]+)?$/) != null); } }; Metamaps.Visualize = { mGraph: {}, // a reference to the graph object. cameraPosition: null, // stores the camera position when using a 3D visualization type: "ForceDirected", // the type of graph we're building, could be "RGraph", "ForceDirected", or "ForceDirected3D" savedLayout: true, // indicates whether the map has a saved layout or not loadLater: false, // indicates whether there is JSON that should be loaded right in the offset, or whether to wait till the first topic is created target: null, // the selector representing the location to render the graph init: function () { var self = Metamaps.Visualize; // disable awkward dragging of the canvas element that would sometimes happen $('#infovis-canvas').on('dragstart', function (event) { event.preventDefault(); }); // prevent touch events on the canvas from default behaviour $("#infovis-canvas").bind('touchstart', function (event) { event.preventDefault(); self.mGraph.events.touched = true; }); // prevent touch events on the canvas from default behaviour $("#infovis-canvas").bind('touchmove', function (event) { //Metamaps.JIT.touchPanZoomHandler(event); }); // prevent touch events on the canvas from default behaviour $("#infovis-canvas").bind('touchend touchcancel', function (event) { lastDist = 0; if (!self.mGraph.events.touchMoved && !Metamaps.Touch.touchDragNode) Metamaps.TopicCard.hideCurrentCard(); self.mGraph.events.touched = self.mGraph.events.touchMoved = false; Metamaps.Touch.touchDragNode = false; }); }, render: function (targetID, vizData) { var self = Metamaps.Visualize; self.mGraph = {}; self.target = targetID; self.__buildGraph(vizData); }, computePositions: function () { var self = Metamaps.Visualize; if (self.type == "RGraph") { self.mGraph.graph.eachNode(function (n) { var pos = n.getPos(); pos.setc(-200, -200); }); self.mGraph.compute('end'); } else if (self.type == "ForceDirected" && self.savedLayout) { var startPos, endPos, mapping; self.mGraph.graph.eachNode(function (n) { mapping = Metamaps.Mappings.get(n.id); mapping.set('node', n); mapping.updateNode(); startPos = new $jit.Complex(0, 0); endPos = new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')); n.setPos(startPos, 'start'); n.setPos(endPos, 'end'); }); } else if (self.type == "ForceDirected3D" || !self.savedLayout) { self.mGraph.compute(); } }, /** * __buildGraph does the heavy lifting of creating the engine that renders the graph with the properties we desire * * @param vizData a json structure containing the data to be rendered. */ __buildGraph: function (vizData) { var self = Metamaps.Visualize; // normally this will be true, and will enter into this first scenario if (!Metamaps.Settings.embed) { if (self.type == "RGraph") { $jit.RGraph.Plot.NodeTypes.implement(Metamaps.JIT.RGraph.nodeSettings); $jit.RGraph.Plot.EdgeTypes.implement(Metamaps.JIT.RGraph.edgeSettings); self.mGraph = new $jit.RGraph(Metamaps.JIT.RGraph.graphSettings); } else if (self.type == "ForceDirected") { $jit.ForceDirected.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings); $jit.ForceDirected.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings); self.mGraph = new $jit.ForceDirected(Metamaps.JIT.ForceDirected.graphSettings); } else if (self.type == "ForceDirected3D") { // init ForceDirected3D self.mGraph = new $jit.ForceDirected3D(Metamaps.JIT.ForceDirected3D.graphSettings); self.cameraPosition = self.mGraph.canvas.canvases[0].camera.position; } } else { // in the case where these visualizations are to be embedded in other sites TODO if (self.type == "RGraph") { $jit.RGraph.Plot.NodeTypes.implement(Metamaps.JIT.RGraph.embed.nodeSettings); $jit.RGraph.Plot.EdgeTypes.implement(Metamaps.JIT.RGraph.embed.edgeSettings); self.mGraph = new $jit.RGraph(Metamaps.JIT.RGraph.embed.graphSettings); } else if (self.type == "ForceDirected") { $jit.ForceDirected.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.embed.nodeSettings); $jit.ForceDirected.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.embed.edgeSettings); self.mGraph = new $jit.ForceDirected(Metamaps.JIT.ForceDirected.embed.graphSettings); } else if (self.type == "ForceDirected3D") { // init ForceDirected3D self.mGraph = new $jit.ForceDirected3D(Metamaps.JIT.ForceDirected3D.embed.graphSettings); self.cameraPosition = self.mGraph.canvas.canvases[0].camera.position; } } // load JSON data, if it's not empty if (!self.loadLater) { //load JSON data. self.mGraph.loadJSON(vizData); //compute positions and plot. self.computePositions(); if (self.type == "RGraph") { self.mGraph.fx.animate(Metamaps.JIT.RGraph.animate); } else if (self.type == "ForceDirected" && self.savedLayout) { return Metamaps.Organize.loadSavedLayout(); //self.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout); } else if (self.type == "ForceDirected3D" || !self.savedLayout) { self.mGraph.animate(Metamaps.JIT.ForceDirected.animateFDLayout); } } } }; Metamaps.Realtime = { // this is for the heroku staging environment //Metamaps.Realtime.socket = io.connect('http://gentle-savannah-1303.herokuapp.com'); // this is for metamaps.cc //Metamaps.Realtime.socket = io.connect('http://metamaps.cc:5001'); // this is for localhost development //Metamaps.Realtime.socket = io.connect('http://localhost:5001'); socket: null, notifyTimeOut: null, init: function () { var mapperm = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); if (mapperm) { Metamaps.Realtime.socket = io.connect('http://localhost:5001'); Metamaps.Realtime.socket.on('connect', function () { console.log('socket connected'); Metamaps.Realtime.setupSocket(); }); } }, notifyUser: function (message) { if ($('.notice.metamaps').length == 0) { $('body').prepend('
'); } $('.notice.metamaps').hide().html(message).fadeIn('fast'); clearTimeout(Metamaps.Realtime.notifyTimeOut); Metamaps.Realtime.notifyTimeOut = setTimeout(function () { $('.notice.metamaps').fadeOut('fast'); }, 8000); }, setupSocket: function () { var socket = Metamaps.Realtime.socket; socket.emit('newMapperNotify', { userid: userid, username: username, mapid: Metamaps.Active.Map.id }); // if you're the 'new guy' update your list with who's already online socket.on(userid + '-' + Metamaps.Active.Map.id + '-UpdateMapperList', function (data) { // data.userid // data.username // data.userrealtime MetamapsModel.mappersOnMap[data.userid] = { name: data.username, realtime: data.userrealtime }; var onOff = data.userrealtime ? "On" : "Off"; var mapperListItem = '
  • ' + data.username + '
  • '; $('#mapper' + data.userid).remove(); $('.realtimeMapperList ul').append(mapperListItem); }); // receive word that there's a new mapper on the map socket.on('maps-' + Metamaps.Active.Map.id + '-newmapper', function (data) { // data.userid // data.username MetamapsModel.mappersOnMap[data.userid] = { name: data.username, realtime: false }; var mapperListItem = '
  • ' + data.username + '
  • '; $('#mapper' + data.userid).remove(); $('.realtimeMapperList ul').append(mapperListItem); Metamaps.Realtime.notifyUser(data.username + ' just joined the map'); // send this new mapper back your details, and the awareness that you've loaded the map var update = { userToNotify: data.userid, username: username, userid: userid, userrealtime: goRealtime, mapid: Metamaps.Active.Map.id }; socket.emit('updateNewMapperList', update); }); // receive word that a mapper left the map socket.on('maps-' + Metamaps.Active.Map.id + '-lostmapper', function (data) { // data.userid // data.username delete MetamapsModel.mappersOnMap[data.userid]; $('#mapper' + data.userid).remove(); Metamaps.Realtime.notifyUser(data.username + ' just left the map'); }); // receive word that there's a mapper turned on realtime socket.on('maps-' + Metamaps.Active.Map.id + '-newrealtime', function (data) { // data.userid // data.username MetamapsModel.mappersOnMap[data.userid].realtime = true; $('#mapper' + data.userid).removeClass('littleRtOff').addClass('littleRtOn'); Metamaps.Realtime.notifyUser(data.username + ' just turned on realtime'); }); // receive word that there's a mapper turned on realtime socket.on('maps-' + Metamaps.Active.Map.id + '-lostrealtime', function (data) { // data.userid // data.username MetamapsModel.mappersOnMap[data.userid].realtime = false; $('#mapper' + data.userid).removeClass('littleRtOn').addClass('littleRtOff'); Metamaps.Realtime.notifyUser(data.username + ' just turned off realtime'); }); socket.on('maps-' + Metamaps.Active.Map.id, function (data) { //as long as you weren't the origin of the changes, update your map if (data.origin != userid && goRealtime) { if (data.resource == 'Topic') { topic = $.parseJSON(data.obj); if (data.action == 'create') { Metamaps.Realtime.addTopicToMap(topic); } else if (data.action == 'update' && Metamaps.Visualize.mGraph.graph.getNode(topic.id) != 'undefined') { Metamaps.Realtime.updateTopicOnMap(topic); } else if (data.action == 'destroy' && Metamaps.Visualize.mGraph.graph.getNode(topic.id) != 'undefined') { hideNode(topic.id) } return; } else if (data.resource == 'Synapse') { synapse = $.parseJSON(data.obj); if (data.action == 'create') { Metamaps.Realtime.addSynapseToMap(synapse); } else if (data.action == 'update' && Metamaps.Visualize.mGraph.graph.getAdjacence(synapse.data.$direction['0'], synapse.data.$direction['1']) != 'undefined') { Metamaps.Realtime.updateSynapseOnMap(synapse); } else if (data.action == 'destroy' && Metamaps.Visualize.mGraph.graph.getAdjacence(synapse.data.$direction['0'], synapse.data.$direction['1']) != 'undefined') { var edge = Metamaps.Visualize.mGraph.graph.getAdjacence(synapse.data.$direction['0'], synapse.data.$direction['1']); hideEdge(edge); } return; } } }); }, sendRealtimeOn: function () { // send this new mapper back your details, and the awareness that you're online var update = { username: username, userid: userid, mapid: Metamaps.Active.Map.id }; Metamaps.Realtime.socket.emit('notifyStartRealtime', update); }, sendRealtimeOff: function () { // send this new mapper back your details, and the awareness that you're online var update = { username: username, userid: userid, mapid: Metamaps.Active.Map.id }; Metamaps.Realtime.socket.emit('notifyStopRealtime', update); }, addTopicToMap: function (topic) { var newPos, tempForT; Metamaps.Visualize.mGraph.graph.addNode(topic); tempForT = Metamaps.Visualize.mGraph.graph.getNode(topic.id); tempForT.setData('dim', 1, 'start'); tempForT.setData('dim', 25, 'end'); newPos = new $jit.Complex(); newPos.x = tempForT.data.$xloc; newPos.y = tempForT.data.$yloc; tempForT.setPos(newPos, 'start'); tempForT.setPos(newPos, 'current'); tempForT.setPos(newPos, 'end'); Metamaps.Visualize.mGraph.fx.plotNode(tempForT, Metamaps.Visualize.mGraph.canvas); return Metamaps.Visualize.mGraph.labels.plotLabel(Metamaps.Visualize.mGraph.canvas, tempForT, Metamaps.Visualize.mGraph.config); }, updateTopicOnMap: function (topic) { var newPos, tempForT; tempForT = Metamaps.Visualize.mGraph.graph.getNode(topic.id); tempForT.data = topic.data; tempForT.name = topic.name; if (MetamapsModel.showcardInUse === topic.id) { populateShowCard(tempForT); } newPos = new $jit.Complex(); newPos.x = tempForT.data.$xloc; newPos.y = tempForT.data.$yloc; tempForT.setPos(newPos, 'start'); tempForT.setPos(newPos, 'current'); tempForT.setPos(newPos, 'end'); return Metamaps.Visualize.mGraph.fx.animate({ modes: ['linear', 'node-property:dim', 'edge-property:lineWidth'], transition: $jit.Trans.Quad.easeInOut, duration: 500 }); }, addSynapseToMap: function (synapse) { var Node1, Node2, tempForS; Node1 = Metamaps.Visualize.mGraph.graph.getNode(synapse.data.$direction[0]); Node2 = Metamaps.Visualize.mGraph.graph.getNode(synapse.data.$direction[1]); Metamaps.Visualize.mGraph.graph.addAdjacence(Node1, Node2, {}); tempForS = Metamaps.Visualize.mGraph.graph.getAdjacence(Node1.id, Node2.id); tempForS.setDataset('start', { lineWidth: 0.4 }); tempForS.setDataset('end', { lineWidth: 2 }); tempForS.data = synapse.data; Metamaps.Visualize.mGraph.fx.plotLine(tempForS, Metamaps.Visualize.mGraph.canvas); return Metamaps.Visualize.mGraph.fx.animate({ modes: ['linear', 'node-property:dim', 'edge-property:lineWidth'], transition: $jit.Trans.Quad.easeInOut, duration: 500 }); }, updateSynapseOnMap: function (synapse) { var k, tempForS, v, wasShowDesc, _ref; tempForS = Metamaps.Visualize.mGraph.graph.getAdjacence(synapse.data.$direction[0], synapse.data.$direction[1]); wasShowDesc = tempForS.data.$showDesc; _ref = synapse.data; for (k in _ref) { v = _ref[k]; tempForS.data[k] = v; } tempForS.data.$showDesc = wasShowDesc; if (MetamapsModel.edgecardInUse === synapse.data.$id) { editEdge(tempForS, false); } return Metamaps.Visualize.mGraph.plot(); } };