diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 3556d617..3e19bbc2 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -25,6 +25,15 @@ //= require ./src/views/room //= require ./src/JIT //= require ./src/Metamaps +//= require ./src/Metamaps.Control +//= require ./src/Metamaps.Filter +//= require ./src/Metamaps.Listeners +//= require ./src/Metamaps.Organize +//= require ./src/Metamaps.Topic +//= require ./src/Metamaps.Synapse +//= require ./src/Metamaps.Map +//= require ./src/Metamaps.Account +//= require ./src/Metamaps.Mapper //= require ./src/Metamaps.Admin //= require ./src/Metamaps.Import //= require ./src/Metamaps.JIT diff --git a/app/assets/javascripts/src/Metamaps.Account.js.erb b/app/assets/javascripts/src/Metamaps.Account.js.erb new file mode 100644 index 00000000..89f8cfca --- /dev/null +++ b/app/assets/javascripts/src/Metamaps.Account.js.erb @@ -0,0 +1,121 @@ +/* global Metamaps, $ */ + +/* + * Metamaps.Account.js.erb + * + * Dependencies: none! + */ + +Metamaps.Account = { + listenersInitialized: false, + init: function () { + var self = Metamaps.Account + }, + initListeners: function () { + var self = Metamaps.Account + + $('#user_image').change(self.showImagePreview) + self.listenersInitialized = true + }, + toggleChangePicture: function () { + var self = Metamaps.Account + + $('.userImageMenu').toggle() + if (!self.listenersInitialized) self.initListeners() + }, + openChangePicture: function () { + var self = Metamaps.Account + + $('.userImageMenu').show() + if (!self.listenersInitialized) self.initListeners() + }, + closeChangePicture: function () { + var self = Metamaps.Account + + $('.userImageMenu').hide() + }, + showLoading: function () { + var self = Metamaps.Account + + var loader = new CanvasLoader('accountPageLoading') + loader.setColor('#4FC059'); // default is '#000000' + loader.setDiameter(28) // default is 40 + loader.setDensity(41) // default is 40 + loader.setRange(0.9); // default is 1.3 + loader.show() // Hidden by default + $('#accountPageLoading').show() + }, + showImagePreview: function () { + var self = Metamaps.Account + + var file = $('#user_image')[0].files[0] + + var reader = new FileReader() + + reader.onload = function (e) { + var $canvas = $('').attr({ + width: 84, + height: 84 + }) + var context = $canvas[0].getContext('2d') + var imageObj = new Image() + + imageObj.onload = function () { + $('.userImageDiv canvas').remove() + $('.userImageDiv img').hide() + + var imgWidth = imageObj.width + var imgHeight = imageObj.height + + var dimensionToMatch = imgWidth > imgHeight ? imgHeight : imgWidth + // draw cropped image + var nonZero = Math.abs(imgHeight - imgWidth) / 2 + var sourceX = dimensionToMatch === imgWidth ? 0 : nonZero + var sourceY = dimensionToMatch === imgHeight ? 0 : nonZero + var sourceWidth = dimensionToMatch + var sourceHeight = dimensionToMatch + var destX = 0 + var destY = 0 + var destWidth = 84 + var destHeight = 84 + + context.drawImage(imageObj, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight) + $('.userImageDiv').prepend($canvas) + } + imageObj.src = reader.result + } + + if (file) { + reader.readAsDataURL(file) + $('.userImageMenu').hide() + $('#remove_image').val('0') + } + }, + removePicture: function () { + var self = Metamaps.Account + + $('.userImageDiv canvas').remove() + $('.userImageDiv img').attr('src', '<%= asset_path('user.png') %>').show() + $('.userImageMenu').hide() + + var input = $('#user_image') + input.replaceWith(input.val('').clone(true)) + $('#remove_image').val('1') + }, + changeName: function () { + $('.accountName').hide() + $('.changeName').show() + }, + showPass: function () { + $('.toHide').show() + $('.changePass').hide() + }, + hidePass: function () { + $('.toHide').hide() + $('.changePass').show() + + $('#current_password').val('') + $('#user_password').val('') + $('#user_password_confirmation').val('') + } +} diff --git a/app/assets/javascripts/src/Metamaps.Admin.js.erb b/app/assets/javascripts/src/Metamaps.Admin.js similarity index 100% rename from app/assets/javascripts/src/Metamaps.Admin.js.erb rename to app/assets/javascripts/src/Metamaps.Admin.js diff --git a/app/assets/javascripts/src/Metamaps.Backbone.js.erb b/app/assets/javascripts/src/Metamaps.Backbone.js similarity index 100% rename from app/assets/javascripts/src/Metamaps.Backbone.js.erb rename to app/assets/javascripts/src/Metamaps.Backbone.js diff --git a/app/assets/javascripts/src/Metamaps.Control.js b/app/assets/javascripts/src/Metamaps.Control.js new file mode 100644 index 00000000..4db7f82b --- /dev/null +++ b/app/assets/javascripts/src/Metamaps.Control.js @@ -0,0 +1,437 @@ +/* global Metamaps, $ */ + +/* + * Metamaps.Control.js.erb + * + * Dependencies: + * - Metamaps.Active + * - Metamaps.Control + * - Metamaps.Filter + * - Metamaps.GlobalUI + * - Metamaps.JIT + * - Metamaps.Mappings + * - Metamaps.Metacodes + * - Metamaps.Mouse + * - Metamaps.Selected + * - Metamaps.Settings + * - Metamaps.Synapses + * - Metamaps.Topics + * - Metamaps.Visualize + */ + +Metamaps.Control = { + init: function () {}, + selectNode: function (node, e) { + var filtered = node.getData('alpha') === 0 + + if (filtered || Metamaps.Selected.Nodes.indexOf(node) != -1) return + node.selected = true + node.setData('dim', 30, 'current') + Metamaps.Selected.Nodes.push(node) + }, + deselectAllNodes: function () { + var l = Metamaps.Selected.Nodes.length + for (var i = l - 1; i >= 0; i -= 1) { + var node = Metamaps.Selected.Nodes[i] + Metamaps.Control.deselectNode(node) + } + Metamaps.Visualize.mGraph.plot() + }, + deselectNode: function (node) { + delete node.selected + node.setData('dim', 25, 'current') + + // remove the node + Metamaps.Selected.Nodes.splice( + Metamaps.Selected.Nodes.indexOf(node), 1) + }, + deleteSelected: function () { + if (!Metamaps.Active.Map) return + + var n = Metamaps.Selected.Nodes.length + var e = Metamaps.Selected.Edges.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 authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + + if (!authorized) { + Metamaps.GlobalUI.notifyUser('Cannot edit Public map.') + return + } + + 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() + } + }, + deleteSelectedNodes: function () { // refers to deleting topics permanently + if (!Metamaps.Active.Map) return + + var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + + if (!authorized) { + Metamaps.GlobalUI.notifyUser('Cannot edit Public map.') + return + } + + var l = Metamaps.Selected.Nodes.length + for (var i = l - 1; i >= 0; i -= 1) { + var node = Metamaps.Selected.Nodes[i] + Metamaps.Control.deleteNode(node.id) + } + }, + deleteNode: function (nodeid) { // refers to deleting topics permanently + if (!Metamaps.Active.Map) return + + var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + + if (!authorized) { + Metamaps.GlobalUI.notifyUser('Cannot edit Public map.') + return + } + + var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid) + var topic = node.getData('topic') + + var permToDelete = Metamaps.Active.Mapper.id === topic.get('user_id') || Metamaps.Active.Mapper.get('admin') + if (permToDelete) { + var mappableid = topic.id + var mapping = node.getData('mapping') + topic.destroy() + Metamaps.Mappings.remove(mapping) + $(document).trigger(Metamaps.JIT.events.deleteTopic, [{ + mappableid: mappableid + }]) + Metamaps.Control.hideNode(nodeid) + } else { + Metamaps.GlobalUI.notifyUser('Only topics you created can be deleted') + } + }, + removeSelectedNodes: function () { // refers to removing topics permanently from a map + if (!Metamaps.Active.Map) return + + var l = Metamaps.Selected.Nodes.length, + i, + node, + authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + + if (!authorized) { + Metamaps.GlobalUI.notifyUser('Cannot edit Public map.') + return + } + + for (i = l - 1; i >= 0; i -= 1) { + node = Metamaps.Selected.Nodes[i] + Metamaps.Control.removeNode(node.id) + } + }, + removeNode: function (nodeid) { // refers to removing topics permanently from a map + if (!Metamaps.Active.Map) return + + var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid) + + if (!authorized) { + Metamaps.GlobalUI.notifyUser('Cannot edit Public map.') + return + } + + var topic = node.getData('topic') + var mappableid = topic.id + var mapping = node.getData('mapping') + mapping.destroy() + Metamaps.Topics.remove(topic) + $(document).trigger(Metamaps.JIT.events.removeTopic, [{ + mappableid: mappableid + }]) + Metamaps.Control.hideNode(nodeid) + }, + hideSelectedNodes: function () { + var l = Metamaps.Selected.Nodes.length, + i, + node + + for (i = l - 1; i >= 0; i -= 1) { + node = Metamaps.Selected.Nodes[i] + Metamaps.Control.hideNode(node.id) + } + }, + hideNode: function (nodeid) { + var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid) + var graph = Metamaps.Visualize.mGraph + + 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 () { + if (nodeid == Metamaps.Visualize.mGraph.root) { // && Metamaps.Visualize.type === "RGraph" + var newroot = _.find(graph.graph.nodes, function (n) { return n.id !== nodeid; }) + graph.root = newroot ? newroot.id : null + } + Metamaps.Visualize.mGraph.graph.removeNode(nodeid) + }, 500) + Metamaps.Filter.checkMetacodes() + Metamaps.Filter.checkMappers() + }, + selectEdge: function (edge) { + var filtered = edge.getData('alpha') === 0; // don't select if the edge is filtered + + if (filtered || Metamaps.Selected.Edges.indexOf(edge) != -1) return + + var width = Metamaps.Mouse.edgeHoveringOver === edge ? 4 : 2 + edge.setDataset('current', { + showDesc: true, + lineWidth: width, + color: Metamaps.Settings.colors.synapses.selected + }) + Metamaps.Visualize.mGraph.plot() + + Metamaps.Selected.Edges.push(edge) + }, + deselectAllEdges: function () { + var l = Metamaps.Selected.Edges.length + for (var i = l - 1; i >= 0; i -= 1) { + var edge = Metamaps.Selected.Edges[i] + Metamaps.Control.deselectEdge(edge) + } + Metamaps.Visualize.mGraph.plot() + }, + deselectEdge: function (edge) { + edge.setData('showDesc', false, 'current') + + edge.setDataset('current', { + lineWidth: 2, + color: Metamaps.Settings.colors.synapses.normal + }) + + if (Metamaps.Mouse.edgeHoveringOver == edge) { + edge.setDataset('current', { + showDesc: true, + lineWidth: 4 + }) + } + + Metamaps.Visualize.mGraph.plot() + + // remove the edge + Metamaps.Selected.Edges.splice( + Metamaps.Selected.Edges.indexOf(edge), 1) + }, + deleteSelectedEdges: function () { // refers to deleting topics permanently + var edge, + l = Metamaps.Selected.Edges.length + + if (!Metamaps.Active.Map) return + + var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + + if (!authorized) { + Metamaps.GlobalUI.notifyUser('Cannot edit Public map.') + return + } + + for (var i = l - 1; i >= 0; i -= 1) { + edge = Metamaps.Selected.Edges[i] + Metamaps.Control.deleteEdge(edge) + } + }, + deleteEdge: function (edge) { + if (!Metamaps.Active.Map) return + + var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + + if (!authorized) { + Metamaps.GlobalUI.notifyUser('Cannot edit Public map.') + return + } + + var index = edge.getData('displayIndex') ? edge.getData('displayIndex') : 0 + + var synapse = edge.getData('synapses')[index] + var mapping = edge.getData('mappings')[index] + + var permToDelete = Metamaps.Active.Mapper.id === synapse.get('user_id') || Metamaps.Active.Mapper.get('admin') + if (permToDelete) { + if (edge.getData('synapses').length - 1 === 0) { + Metamaps.Control.hideEdge(edge) + } + var mappableid = synapse.id + synapse.destroy() + + // the server will destroy the mapping, we just need to remove it here + Metamaps.Mappings.remove(mapping) + edge.getData('mappings').splice(index, 1) + edge.getData('synapses').splice(index, 1) + if (edge.getData('displayIndex')) { + delete edge.data.$displayIndex + } + $(document).trigger(Metamaps.JIT.events.deleteSynapse, [{ + mappableid: mappableid + }]) + } else { + Metamaps.GlobalUI.notifyUser('Only synapses you created can be deleted') + } + }, + removeSelectedEdges: function () { + var l = Metamaps.Selected.Edges.length, + i, + edge + + if (!Metamaps.Active.Map) return + + var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + + if (!authorized) { + Metamaps.GlobalUI.notifyUser('Cannot edit Public map.') + return + } + + for (i = l - 1; i >= 0; i -= 1) { + edge = Metamaps.Selected.Edges[i] + Metamaps.Control.removeEdge(edge) + } + Metamaps.Selected.Edges = [ ] + }, + removeEdge: function (edge) { + if (!Metamaps.Active.Map) return + + var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + + if (!authorized) { + Metamaps.GlobalUI.notifyUser('Cannot edit Public map.') + return + } + + if (edge.getData('mappings').length - 1 === 0) { + Metamaps.Control.hideEdge(edge) + } + + var index = edge.getData('displayIndex') ? edge.getData('displayIndex') : 0 + + var synapse = edge.getData('synapses')[index] + var mapping = edge.getData('mappings')[index] + var mappableid = synapse.id + mapping.destroy() + + Metamaps.Synapses.remove(synapse) + + edge.getData('mappings').splice(index, 1) + edge.getData('synapses').splice(index, 1) + if (edge.getData('displayIndex')) { + delete edge.data.$displayIndex + } + $(document).trigger(Metamaps.JIT.events.removeSynapse, [{ + mappableid: mappableid + }]) + }, + hideSelectedEdges: function () { + var edge, + l = Metamaps.Selected.Edges.length, + i + for (i = l - 1; i >= 0; i -= 1) { + edge = Metamaps.Selected.Edges[i] + Metamaps.Control.hideEdge(edge) + } + Metamaps.Selected.Edges = [ ] + }, + hideEdge: function (edge) { + var from = edge.nodeFrom.id + var to = edge.nodeTo.id + edge.setData('alpha', 0, 'end') + Metamaps.Control.deselectEdge(edge) + Metamaps.Visualize.mGraph.fx.animate({ + modes: ['edge-property:alpha'], + duration: 500 + }) + setTimeout(function () { + Metamaps.Visualize.mGraph.graph.removeAdjacence(from, to) + }, 500) + Metamaps.Filter.checkSynapses() + Metamaps.Filter.checkMappers() + }, + updateSelectedPermissions: function (permission) { + var edge, synapse, node, topic + + Metamaps.GlobalUI.notifyUser('Working...') + + // 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.Edges.length + for (var i = l - 1; i >= 0; i -= 1) { + edge = Metamaps.Selected.Edges[i] + synapse = edge.getData('synapses')[0] + + if (synapse.authorizePermissionChange(Metamaps.Active.Mapper)) { + synapse.save({ + permission: permission + }) + sCount++ + } + } + + // change the permission of the selected topics, if logged in user is the original creator + var l = Metamaps.Selected.Nodes.length + for (var i = l - 1; i >= 0; i -= 1) { + node = Metamaps.Selected.Nodes[i] + topic = node.getData('topic') + + if (topic.authorizePermissionChange(Metamaps.Active.Mapper)) { + topic.save({ + permission: permission + }) + nCount++ + } + } + + var nString = nCount == 1 ? (nCount.toString() + ' topic and ') : (nCount.toString() + ' topics and ') + var sString = sCount == 1 ? (sCount.toString() + ' synapse') : (sCount.toString() + ' synapses') + + var message = nString + sString + ' you created updated to ' + permission + Metamaps.GlobalUI.notifyUser(message) + }, + updateSelectedMetacodes: function (metacode_id) { + var node, topic + + Metamaps.GlobalUI.notifyUser('Working...') + + var metacode = Metamaps.Metacodes.get(metacode_id) + + // variables to keep track of how many nodes and synapses you had the ability to change the permission of + var nCount = 0 + + // change the permission of the selected topics, if logged in user is the original creator + var l = Metamaps.Selected.Nodes.length + for (var i = l - 1; i >= 0; i -= 1) { + node = Metamaps.Selected.Nodes[i] + topic = node.getData('topic') + + if (topic.authorizeToEdit(Metamaps.Active.Mapper)) { + topic.save({ + 'metacode_id': metacode_id + }) + nCount++ + } + } + + var nString = nCount == 1 ? (nCount.toString() + ' topic') : (nCount.toString() + ' topics') + + var message = nString + ' you can edit updated to ' + metacode.get('name') + Metamaps.GlobalUI.notifyUser(message) + Metamaps.Visualize.mGraph.plot() + }, +}; // end Metamaps.Control diff --git a/app/assets/javascripts/src/Metamaps.Debug.js.erb b/app/assets/javascripts/src/Metamaps.Debug.js similarity index 72% rename from app/assets/javascripts/src/Metamaps.Debug.js.erb rename to app/assets/javascripts/src/Metamaps.Debug.js index 3989b7fb..7dc61246 100644 --- a/app/assets/javascripts/src/Metamaps.Debug.js.erb +++ b/app/assets/javascripts/src/Metamaps.Debug.js @@ -4,10 +4,10 @@ * Dependencies: none! */ -Metamaps.Debug = function() { +Metamaps.Debug = function () { console.debug(Metamaps) console.debug(`Metamaps Version: ${Metamaps.VERSION}`) } -Metamaps.debug = function() { +Metamaps.debug = function () { Metamaps.Debug() } diff --git a/app/assets/javascripts/src/Metamaps.Filter.js b/app/assets/javascripts/src/Metamaps.Filter.js new file mode 100644 index 00000000..1dba099c --- /dev/null +++ b/app/assets/javascripts/src/Metamaps.Filter.js @@ -0,0 +1,466 @@ +/* global Metamaps, $ */ + +/* + * Metamaps.Filter.js.erb + * + * Dependencies: + * - Metamaps.Active + * - Metamaps.Control + * - Metamaps.Creators + * - Metamaps.GlobalUI + * - Metamaps.Mappers + * - Metamaps.Metacodes + * - Metamaps.Settings + * - Metamaps.Synapses + * - Metamaps.Topics + * - Metamaps.Visualize + */ +Metamaps.Filter = { + filters: { + name: '', + metacodes: [], + mappers: [], + synapses: [] + }, + visible: { + metacodes: [], + mappers: [], + synapses: [] + }, + isOpen: false, + changing: false, + init: function () { + var self = Metamaps.Filter + + $('.sidebarFilterIcon').click(self.toggleBox) + + $('.sidebarFilterBox .showAllMetacodes').click(self.filterNoMetacodes) + $('.sidebarFilterBox .showAllSynapses').click(self.filterNoSynapses) + $('.sidebarFilterBox .showAllMappers').click(self.filterNoMappers) + $('.sidebarFilterBox .hideAllMetacodes').click(self.filterAllMetacodes) + $('.sidebarFilterBox .hideAllSynapses').click(self.filterAllSynapses) + $('.sidebarFilterBox .hideAllMappers').click(self.filterAllMappers) + + self.bindLiClicks() + self.getFilterData() + }, + toggleBox: function (event) { + var self = Metamaps.Filter + + if (self.isOpen) self.close() + else self.open() + + event.stopPropagation() + }, + open: function () { + var self = Metamaps.Filter + + Metamaps.GlobalUI.Account.close() + $('.sidebarFilterIcon div').addClass('hide') + + if (!self.isOpen && !self.changing) { + self.changing = true + + var height = $(document).height() - 108 + $('.sidebarFilterBox').css('max-height', height + 'px').fadeIn(200, function () { + self.changing = false + self.isOpen = true + }) + } + }, + close: function () { + var self = Metamaps.Filter + $('.sidebarFilterIcon div').removeClass('hide') + + if (!self.changing) { + self.changing = true + + $('.sidebarFilterBox').fadeOut(200, function () { + self.changing = false + self.isOpen = false + }) + } + }, + reset: function () { + var self = Metamaps.Filter + + self.filters.metacodes = [] + self.filters.mappers = [] + self.filters.synapses = [] + self.visible.metacodes = [] + self.visible.mappers = [] + self.visible.synapses = [] + + $('#filter_by_metacode ul').empty() + $('#filter_by_mapper ul').empty() + $('#filter_by_synapse ul').empty() + + $('.filterBox .showAll').addClass('active') + }, + /* + Most of this data essentially depends on the ruby function which are happening for filter inside view filterBox + But what these function do is load this data into three accessible array within java : metacodes, mappers and synapses + */ + getFilterData: function () { + var self = Metamaps.Filter + + var metacode, mapper, synapse + + $('#filter_by_metacode li').each(function () { + metacode = $(this).attr('data-id') + self.filters.metacodes.push(metacode) + self.visible.metacodes.push(metacode) + }) + + $('#filter_by_mapper li').each(function () { + mapper = ($(this).attr('data-id')) + self.filters.mappers.push(mapper) + self.visible.mappers.push(mapper) + }) + + $('#filter_by_synapse li').each(function () { + synapse = ($(this).attr('data-id')) + self.filters.synapses.push(synapse) + self.visible.synapses.push(synapse) + }) + }, + bindLiClicks: function () { + var self = Metamaps.Filter + $('#filter_by_metacode ul li').unbind().click(self.toggleMetacode) + $('#filter_by_mapper ul li').unbind().click(self.toggleMapper) + $('#filter_by_synapse ul li').unbind().click(self.toggleSynapse) + }, + // an abstraction function for checkMetacodes, checkMappers, checkSynapses to reduce + // code redundancy + /* + @param + */ + updateFilters: function (collection, propertyToCheck, correlatedModel, filtersToUse, listToModify) { + var self = Metamaps.Filter + + var newList = [] + var removed = [] + var added = [] + + // the first option enables us to accept + // ['Topics', 'Synapses'] as 'collection' + if (typeof collection === 'object') { + Metamaps[collection[0]].each(function (model) { + var prop = model.get(propertyToCheck) + if (prop !== null) { + prop = prop.toString() + if (newList.indexOf(prop) === -1) { + newList.push(prop) + } + } + }) + Metamaps[collection[1]].each(function (model) { + var prop = model.get(propertyToCheck) + if (prop !== null) { + prop = prop.toString() + if (newList.indexOf(prop) === -1) { + newList.push(prop) + } + } + }) + } else if (typeof collection === 'string') { + Metamaps[collection].each(function (model) { + var prop = model.get(propertyToCheck) + if (prop !== null) { + prop = prop.toString() + if (newList.indexOf(prop) === -1) { + newList.push(prop) + } + } + }) + } + + removed = _.difference(self.filters[filtersToUse], newList) + added = _.difference(newList, self.filters[filtersToUse]) + + // remove the list items for things no longer present on the map + _.each(removed, function (identifier) { + $('#filter_by_' + listToModify + ' li[data-id="' + identifier + '"]').fadeOut('fast', function () { + $(this).remove() + }) + index = self.visible[filtersToUse].indexOf(identifier) + self.visible[filtersToUse].splice(index, 1) + }) + + var model, li, jQueryLi + function sortAlpha (a, b) { + return a.childNodes[1].innerHTML.toLowerCase() > b.childNodes[1].innerHTML.toLowerCase() ? 1 : -1 + } + // for each new filter to be added, create a list item for it and fade it in + _.each(added, function (identifier) { + model = Metamaps[correlatedModel].get(identifier) || + Metamaps[correlatedModel].find(function (model) { + return model.get(propertyToCheck) === identifier + }) + li = model.prepareLiForFilter() + jQueryLi = $(li).hide() + $('li', '#filter_by_' + listToModify + ' ul').add(jQueryLi.fadeIn('fast')) + .sort(sortAlpha).appendTo('#filter_by_' + listToModify + ' ul') + self.visible[filtersToUse].push(identifier) + }) + + // update the list of filters with the new list we just generated + self.filters[filtersToUse] = newList + + // make sure clicks on list items still trigger the right events + self.bindLiClicks() + }, + checkMetacodes: function () { + var self = Metamaps.Filter + self.updateFilters('Topics', 'metacode_id', 'Metacodes', 'metacodes', 'metacode') + }, + checkMappers: function () { + var self = Metamaps.Filter + var onMap = Metamaps.Active.Map ? true : false + if (onMap) { + self.updateFilters('Mappings', 'user_id', 'Mappers', 'mappers', 'mapper') + } else { + // on topic view + self.updateFilters(['Topics', 'Synapses'], 'user_id', 'Creators', 'mappers', 'mapper') + } + }, + checkSynapses: function () { + var self = Metamaps.Filter + self.updateFilters('Synapses', 'desc', 'Synapses', 'synapses', 'synapse') + }, + filterAllMetacodes: function (e) { + var self = Metamaps.Filter + $('#filter_by_metacode ul li').addClass('toggledOff') + $('.showAllMetacodes').removeClass('active') + $('.hideAllMetacodes').addClass('active') + self.visible.metacodes = [] + self.passFilters() + }, + filterNoMetacodes: function (e) { + var self = Metamaps.Filter + $('#filter_by_metacode ul li').removeClass('toggledOff') + $('.showAllMetacodes').addClass('active') + $('.hideAllMetacodes').removeClass('active') + self.visible.metacodes = self.filters.metacodes.slice() + self.passFilters() + }, + filterAllMappers: function (e) { + var self = Metamaps.Filter + $('#filter_by_mapper ul li').addClass('toggledOff') + $('.showAllMappers').removeClass('active') + $('.hideAllMappers').addClass('active') + self.visible.mappers = [] + self.passFilters() + }, + filterNoMappers: function (e) { + var self = Metamaps.Filter + $('#filter_by_mapper ul li').removeClass('toggledOff') + $('.showAllMappers').addClass('active') + $('.hideAllMappers').removeClass('active') + self.visible.mappers = self.filters.mappers.slice() + self.passFilters() + }, + filterAllSynapses: function (e) { + var self = Metamaps.Filter + $('#filter_by_synapse ul li').addClass('toggledOff') + $('.showAllSynapses').removeClass('active') + $('.hideAllSynapses').addClass('active') + self.visible.synapses = [] + self.passFilters() + }, + filterNoSynapses: function (e) { + var self = Metamaps.Filter + $('#filter_by_synapse ul li').removeClass('toggledOff') + $('.showAllSynapses').addClass('active') + $('.hideAllSynapses').removeClass('active') + self.visible.synapses = self.filters.synapses.slice() + self.passFilters() + }, + // an abstraction function for toggleMetacode, toggleMapper, toggleSynapse + // to reduce code redundancy + // gets called in the context of a list item in a filter box + toggleLi: function (whichToFilter) { + var self = Metamaps.Filter, index + var id = $(this).attr('data-id') + if (self.visible[whichToFilter].indexOf(id) == -1) { + self.visible[whichToFilter].push(id) + $(this).removeClass('toggledOff') + } else { + index = self.visible[whichToFilter].indexOf(id) + self.visible[whichToFilter].splice(index, 1) + $(this).addClass('toggledOff') + } + self.passFilters() + }, + toggleMetacode: function () { + var self = Metamaps.Filter + self.toggleLi.call(this, 'metacodes') + + if (self.visible.metacodes.length === self.filters.metacodes.length) { + $('.showAllMetacodes').addClass('active') + $('.hideAllMetacodes').removeClass('active') + } + else if (self.visible.metacodes.length === 0) { + $('.showAllMetacodes').removeClass('active') + $('.hideAllMetacodes').addClass('active') + } else { + $('.showAllMetacodes').removeClass('active') + $('.hideAllMetacodes').removeClass('active') + } + }, + toggleMapper: function () { + var self = Metamaps.Filter + self.toggleLi.call(this, 'mappers') + + if (self.visible.mappers.length === self.filters.mappers.length) { + $('.showAllMappers').addClass('active') + $('.hideAllMappers').removeClass('active') + } + else if (self.visible.mappers.length === 0) { + $('.showAllMappers').removeClass('active') + $('.hideAllMappers').addClass('active') + } else { + $('.showAllMappers').removeClass('active') + $('.hideAllMappers').removeClass('active') + } + }, + toggleSynapse: function () { + var self = Metamaps.Filter + self.toggleLi.call(this, 'synapses') + + if (self.visible.synapses.length === self.filters.synapses.length) { + $('.showAllSynapses').addClass('active') + $('.hideAllSynapses').removeClass('active') + } + else if (self.visible.synapses.length === 0) { + $('.showAllSynapses').removeClass('active') + $('.hideAllSynapses').addClass('active') + } else { + $('.showAllSynapses').removeClass('active') + $('.hideAllSynapses').removeClass('active') + } + }, + passFilters: function () { + var self = Metamaps.Filter + var visible = self.visible + + var passesMetacode, passesMapper, passesSynapse + var onMap + + if (Metamaps.Active.Map) { + onMap = true + } + else if (Metamaps.Active.Topic) { + onMap = false + } + + var opacityForFilter = onMap ? 0 : 0.4 + + Metamaps.Topics.each(function (topic) { + var n = topic.get('node') + var metacode_id = topic.get('metacode_id').toString() + + if (visible.metacodes.indexOf(metacode_id) == -1) passesMetacode = false + else passesMetacode = true + + if (onMap) { + // when on a map, + // we filter by mapper according to the person who added the + // topic or synapse to the map + var user_id = topic.getMapping().get('user_id').toString() + if (visible.mappers.indexOf(user_id) == -1) passesMapper = false + else passesMapper = true + } else { + // when on a topic view, + // we filter by mapper according to the person who created the + // topic or synapse + var user_id = topic.get('user_id').toString() + if (visible.mappers.indexOf(user_id) == -1) passesMapper = false + else passesMapper = true + } + + if (passesMetacode && passesMapper) { + if (n) { + n.setData('alpha', 1, 'end') + } + else console.log(topic) + } else { + if (n) { + Metamaps.Control.deselectNode(n, true) + n.setData('alpha', opacityForFilter, 'end') + n.eachAdjacency(function (e) { + Metamaps.Control.deselectEdge(e, true) + }) + } + else console.log(topic) + } + }) + + // flag all the edges back to 'untouched' + Metamaps.Synapses.each(function (synapse) { + var e = synapse.get('edge') + e.setData('touched', false) + }) + Metamaps.Synapses.each(function (synapse) { + var e = synapse.get('edge') + var desc + var user_id = synapse.get('user_id').toString() + + if (e && !e.getData('touched')) { + var synapses = e.getData('synapses') + + // if any of the synapses represent by the edge are still unfiltered + // leave the edge visible + passesSynapse = false + for (var i = 0; i < synapses.length; i++) { + desc = synapses[i].get('desc') + if (visible.synapses.indexOf(desc) > -1) passesSynapse = true + } + + // if the synapse description being displayed is now being + // filtered, set the displayIndex to the first unfiltered synapse if there is one + var displayIndex = e.getData('displayIndex') ? e.getData('displayIndex') : 0 + var displayedSynapse = synapses[displayIndex] + desc = displayedSynapse.get('desc') + if (passesSynapse && visible.synapses.indexOf(desc) == -1) { + // iterate and find an unfiltered one + for (var i = 0; i < synapses.length; i++) { + desc = synapses[i].get('desc') + if (visible.synapses.indexOf(desc) > -1) { + e.setData('displayIndex', i) + break + } + } + } + + if (onMap) { + // when on a map, + // we filter by mapper according to the person who added the + // topic or synapse to the map + user_id = synapse.getMapping().get('user_id').toString() + } + if (visible.mappers.indexOf(user_id) == -1) passesMapper = false + else passesMapper = true + + var color = Metamaps.Settings.colors.synapses.normal + if (passesSynapse && passesMapper) { + e.setData('alpha', 1, 'end') + e.setData('color', color, 'end') + } else { + Metamaps.Control.deselectEdge(e, true) + e.setData('alpha', opacityForFilter, 'end') + } + + e.setData('touched', true) + } + else if (!e) console.log(synapse) + }) + + // run the animation + Metamaps.Visualize.mGraph.fx.animate({ + modes: ['node-property:alpha', + 'edge-property:alpha'], + duration: 200 + }) + } +}; // end Metamaps.Filter diff --git a/app/assets/javascripts/src/Metamaps.Import.js.erb b/app/assets/javascripts/src/Metamaps.Import.js similarity index 99% rename from app/assets/javascripts/src/Metamaps.Import.js.erb rename to app/assets/javascripts/src/Metamaps.Import.js index 6578afc7..e82a05c5 100644 --- a/app/assets/javascripts/src/Metamaps.Import.js.erb +++ b/app/assets/javascripts/src/Metamaps.Import.js @@ -229,7 +229,7 @@ Metamaps.Import = { importSynapses: function (parsedSynapses) { var self = Metamaps.Import - parsedSynapses.forEach(function(synapse) { + parsedSynapses.forEach(function (synapse) { // only createSynapseWithParameters once both topics are persisted var topic1 = Metamaps.Topics.get(self.cidMappings[synapse.topic1]) var topic2 = Metamaps.Topics.get(self.cidMappings[synapse.topic2]) diff --git a/app/assets/javascripts/src/Metamaps.JIT.js.erb b/app/assets/javascripts/src/Metamaps.JIT.js.erb index 744a78c3..4b72b714 100644 --- a/app/assets/javascripts/src/Metamaps.JIT.js.erb +++ b/app/assets/javascripts/src/Metamaps.JIT.js.erb @@ -1,1949 +1,1908 @@ Metamaps.JIT = { - events: { - topicDrag: 'Metamaps:JIT:events:topicDrag', - newTopic: 'Metamaps:JIT:events:newTopic', - deleteTopic: 'Metamaps:JIT:events:deleteTopic', - removeTopic: 'Metamaps:JIT:events:removeTopic', - newSynapse: 'Metamaps:JIT:events:newSynapse', - deleteSynapse: 'Metamaps:JIT:events:deleteSynapse', - removeSynapse: 'Metamaps:JIT:events:removeSynapse', - pan: 'Metamaps:JIT:events:pan', - zoom: 'Metamaps:JIT:events:zoom', - animationDone: 'Metamaps:JIT:events:animationDone', - }, - vizData: [], // contains the visualization-compatible graph - /** - * This method will bind the event handlers it is interested and initialize the class. - */ - init: function () { - var self = Metamaps.JIT; - - $(".zoomIn").click(self.zoomIn); - $(".zoomOut").click(self.zoomOut); - - var zoomExtents = function (event) { - self.zoomExtents(event, Metamaps.Visualize.mGraph.canvas); - }; - $(".zoomExtents").click(zoomExtents); - - $(".takeScreenshot").click(Metamaps.Map.exportImage); - - self.topicDescImage = new Image(); - self.topicDescImage.src = '<%= asset_path('topic_description_signifier.png') %>'; - - self.topicLinkImage = new Image(); - self.topicLinkImage.src = '<%= asset_path('topic_link_signifier.png') %>'; - }, - /** - * convert our topic JSON into something JIT can use - */ - convertModelsToJIT: function(topics, synapses) { - var jitReady = []; - - var synapsesToRemove = []; - var topic; - var mapping; - var node; - var nodes = {}; - var existingEdge; - var edge; - var edges = []; - - topics.each(function (t) { - node = t.createNode(); - nodes[node.id] = node; - }); - synapses.each(function (s) { - edge = s.createEdge(); - - if (topics.get(s.get('node1_id')) === undefined || topics.get(s.get('node2_id')) === undefined) { - // this means it's an invalid synapse - synapsesToRemove.push(s); - } - else if (nodes[edge.nodeFrom] && nodes[edge.nodeTo]) { - - existingEdge = _.findWhere(edges, { - nodeFrom: edge.nodeFrom, - nodeTo: edge.nodeTo - }) || - _.findWhere(edges, { - nodeFrom: edge.nodeTo, - nodeTo: edge.nodeFrom - }); - - if (existingEdge) { - // for when you're dealing with multiple relationships between the same two topics - if (Metamaps.Active.Map) { - mapping = s.getMapping(); - existingEdge.data['$mappingIDs'].push(mapping.id); - } - existingEdge.data['$synapseIDs'].push(s.id); - } else { - // for when you're dealing with a topic that has relationships to many different nodes - nodes[edge.nodeFrom].adjacencies.push(edge); - edges.push(edge); - } - } - }); - - _.each(nodes, function (node) { - jitReady.push(node); - }); - - return [jitReady, synapsesToRemove]; - }, - prepareVizData: function () { - var self = Metamaps.JIT; - var mapping; - - // reset/empty vizData - self.vizData = []; - Metamaps.Visualize.loadLater = false; - - var results = self.convertModelsToJIT(Metamaps.Topics, Metamaps.Synapses); - - self.vizData = results[0]; - - // clean up the synapses array in case of any faulty data - _.each(results[1], function (synapse) { - mapping = synapse.getMapping(); - Metamaps.Synapses.remove(synapse); - if (Metamaps.Mappings) Metamaps.Mappings.remove(mapping); - }); - - if (self.vizData.length == 0) { - Metamaps.Famous.viz.showInstructions(); - Metamaps.Visualize.loadLater = true; - } - else Metamaps.Famous.viz.hideInstructions(); - - Metamaps.Visualize.render(); - }, // prepareVizData - edgeRender: function (adj, canvas) { - //get nodes cartesian coordinates - var pos = adj.nodeFrom.pos.getc(true); - var posChild = adj.nodeTo.pos.getc(true); - - var synapse; - if(adj.getData("displayIndex")) { - synapse = adj.getData("synapses")[adj.getData("displayIndex")]; - if (!synapse) { - delete adj.data.$displayIndex; - synapse = adj.getData("synapses")[0]; - } - } - else { - synapse = adj.getData("synapses")[0]; - } - - if (!synapse) return; // this means there are no corresponding synapses for - // this edge, don't render it - - var directionCat = synapse.get("category"); - - //label placement on edges - if (canvas.denySelected) { - var color = Metamaps.Settings.colors.synapses.normal; - canvas.getCtx().fillStyle = canvas.getCtx().strokeStyle = color; - } - Metamaps.JIT.renderEdgeArrows($jit.Graph.Plot.edgeHelper, adj, synapse, canvas); - - //check for edge label in data - var desc = synapse.get("desc"); - - var showDesc = adj.getData("showDesc"); - - var drawSynapseCount = function (context, x, y, count) { - /* - circle size: 16x16px - positioning: overlay and center on top right corner of synapse label - 8px left and 8px down - color: #dab539 - border color: #424242 - border size: 1.5px - font: DIN medium - font-size: 14pt - font-color: #424242 - */ - context.beginPath(); - context.arc(x, y, 8, 0, 2 * Math.PI, false); - context.fillStyle = '#DAB539'; - context.strokeStyle = '#424242'; - context.lineWidth = 1.5; - context.closePath(); - context.fill(); - context.stroke(); - - // add the synapse count - context.fillStyle = '#424242'; - context.textAlign = 'center'; - context.font = '14px din-medium'; - - context.fillText(count, x, y + 5); - }; - - if (!canvas.denySelected && desc != "" && showDesc) { - // '&' to '&' - desc = Metamaps.Util.decodeEntities(desc); - - //now adjust the label placement - var ctx = canvas.getCtx(); - ctx.font = 'bold 14px arial'; - ctx.fillStyle = '#FFF'; - ctx.textBaseline = 'alphabetic'; - - var arrayOfLabelLines = Metamaps.Util.splitLine(desc, 30).split('\n'); - var index, lineWidths = []; - for (index = 0; index < arrayOfLabelLines.length; ++index) { - lineWidths.push(ctx.measureText(arrayOfLabelLines[index]).width) - } - var width = Math.max.apply(null, lineWidths) + 16; - var height = (16 * arrayOfLabelLines.length) + 8; - - var x = (pos.x + posChild.x - width) / 2; - var y = ((pos.y + posChild.y) / 2) - height / 2; - - var radius = 5; - - //render background - ctx.beginPath(); - ctx.moveTo(x + radius, y); - ctx.lineTo(x + width - radius, y); - ctx.quadraticCurveTo(x + width, y, x + width, y + radius); - ctx.lineTo(x + width, y + height - radius); - ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); - ctx.lineTo(x + radius, y + height); - ctx.quadraticCurveTo(x, y + height, x, y + height - radius); - ctx.lineTo(x, y + radius); - ctx.quadraticCurveTo(x, y, x + radius, y); - ctx.closePath(); - ctx.fill(); - - // get number of synapses - var synapseNum = adj.getData("synapses").length; - - //render text - ctx.fillStyle = '#424242'; - ctx.textAlign = 'center'; - for (index = 0; index < arrayOfLabelLines.length; ++index) { - ctx.fillText(arrayOfLabelLines[index], x + (width / 2), y + 18 + (16 * index)); - } - - if (synapseNum > 1) { - drawSynapseCount(ctx, x + width, y, synapseNum); - } - } - else if (!canvas.denySelected && showDesc) { - // get number of synapses - var synapseNum = adj.getData("synapses").length; - - if (synapseNum > 1) { - var ctx = canvas.getCtx(); - var x = (pos.x + posChild.x) / 2; - var y = (pos.y + posChild.y) / 2; - drawSynapseCount(ctx, x, y, synapseNum); - } - } - - }, // edgeRender - ForceDirected: { - animateSavedLayout: { - modes: ['linear'], - transition: $jit.Trans.Quad.easeInOut, - duration: 800, - onComplete: function () { - Metamaps.Visualize.mGraph.busy = false; - $(document).trigger(Metamaps.JIT.events.animationDone); - } - }, - animateFDLayout: { - modes: ['linear'], - transition: $jit.Trans.Elastic.easeOut, - duration: 800, - onComplete: function () { - Metamaps.Visualize.mGraph.busy = false; - } - }, - graphSettings: { - //id of the visualization container - injectInto: 'infovis', - //Enable zooming and panning - //by scrolling and DnD - Navigation: { - enable: true, - //Enable panning events only if we're dragging the empty - //canvas (and not a node). - panning: 'avoid nodes', - zooming: 28 //zoom speed. higher is more sensible - }, - //background: { - // type: 'Metamaps' - //}, - //NodeStyles: { - // enable: true, - // type: 'Native', - // stylesHover: { - // dim: 30 - // }, - // duration: 300 - //}, - // Change node and edge styles such as - // color and width. - // These properties are also set per node - // with dollar prefixed data-properties in the - // JSON structure. - Node: { - overridable: true, - color: '#2D6A5D', - type: 'customNode', - dim: 25 - }, - Edge: { - overridable: true, - color: Metamaps.Settings.colors.synapses.normal, - type: 'customEdge', - lineWidth: 2, - alpha: 1 - }, - //Native canvas text styling - Label: { - type: 'Native', //Native or HTML - size: 20, - family: 'arial', - textBaseline: 'alphabetic', - color: Metamaps.Settings.colors.labels.text - }, - //Add Tips - Tips: { - enable: false, - onShow: function (tip, node) {} - }, - // Add node events - Events: { - enable: true, - enableForEdges: true, - onMouseMove: function (node, eventInfo, e) { - Metamaps.JIT.onMouseMoveHandler(node, eventInfo, e); - //console.log('called mouse move handler'); - }, - //Update node positions when dragged - onDragMove: function (node, eventInfo, e) { - Metamaps.JIT.onDragMoveTopicHandler(node, eventInfo, e); - //console.log('called drag move handler'); - }, - onDragEnd: function (node, eventInfo, e) { - Metamaps.JIT.onDragEndTopicHandler(node, eventInfo, e, false); - //console.log('called drag end handler'); - }, - onDragCancel: function (node, eventInfo, e) { - Metamaps.JIT.onDragCancelHandler(node, eventInfo, e, false); - }, - //Implement the same handler for touchscreens - onTouchStart: function (node, eventInfo, e) { - //$jit.util.event.stop(e); //stop default touchmove event - //Metamaps.Visualize.mGraph.events.onMouseDown(e, null, eventInfo); - Metamaps.Visualize.mGraph.events.touched = true; - Metamaps.Touch.touchPos = eventInfo.getPos(); - var canvas = Metamaps.Visualize.mGraph.canvas, - ox = canvas.translateOffsetX; - oy = canvas.translateOffsetY, - sx = canvas.scaleOffsetX, - sy = canvas.scaleOffsetY; - Metamaps.Touch.touchPos.x *= sx; - Metamaps.Touch.touchPos.y *= sy; - Metamaps.Touch.touchPos.x += ox; - Metamaps.Touch.touchPos.y += oy; - - touchDragNode = node; - }, - //Implement the same handler for touchscreens - onTouchMove: function (node, eventInfo, e) { - if (Metamaps.Touch.touchDragNode) Metamaps.JIT.onDragMoveTopicHandler(Metamaps.Touch.touchDragNode, eventInfo, e); - else { - Metamaps.JIT.touchPanZoomHandler(eventInfo, e); - } - }, - //Implement the same handler for touchscreens - onTouchEnd: function (node, eventInfo, e) { - - }, - //Implement the same handler for touchscreens - onTouchCancel: function (node, eventInfo, e) { - - }, - //Add also a click handler to nodes - onClick: function (node, eventInfo, e) { - - // remove the rightclickmenu - $('.rightclickmenu').remove(); - - if (Metamaps.Mouse.boxStartCoordinates) { - if(e.ctrlKey){ - Metamaps.Visualize.mGraph.busy = false; - Metamaps.Mouse.boxEndCoordinates = eventInfo.getPos(); - - var bS = Metamaps.Mouse.boxStartCoordinates; - var bE = Metamaps.Mouse.boxEndCoordinates; - if (Math.abs(bS.x - bE.x) > 20 && Math.abs(bS.y - bE.y) > 20) { - Metamaps.JIT.zoomToBox(e); - return; - } - else { - Metamaps.Mouse.boxStartCoordinates = null; - Metamaps.Mouse.boxEndCoordinates = null; - } - //console.log('called zoom to box'); - } - - if (e.shiftKey) { - Metamaps.Visualize.mGraph.busy = false; - Metamaps.Mouse.boxEndCoordinates = eventInfo.getPos(); - Metamaps.JIT.selectWithBox(e); - //console.log('called select with box'); - return; - }; - } - - if (e.target.id != "infovis-canvas") return false; - - //clicking on a edge, node, or clicking on blank part of canvas? - if (node.nodeFrom) { - Metamaps.JIT.selectEdgeOnClickHandler(node, e); - //console.log('called selectEdgeOnClickHandler'); - } else if (node && !node.nodeFrom) { - Metamaps.JIT.selectNodeOnClickHandler(node, e); - //console.log('called selectNodeOnClickHandler'); - } else { - Metamaps.JIT.canvasClickHandler(eventInfo.getPos(), e); - //console.log('called canvasClickHandler'); - } //if - }, - //Add also a click handler to nodes - onRightClick: function (node, eventInfo, e) { - - // remove the rightclickmenu - $('.rightclickmenu').remove(); - - if (Metamaps.Mouse.boxStartCoordinates) { - Metamaps.Visualize.mGraph.busy = false; - Metamaps.Mouse.boxEndCoordinates = eventInfo.getPos(); - Metamaps.JIT.selectWithBox(e); - return; - } - - if (e.target.id != "infovis-canvas") return false; - - //clicking on a edge, node, or clicking on blank part of canvas? - if (node.nodeFrom) { - Metamaps.JIT.selectEdgeOnRightClickHandler(node, e); - } else if (node && !node.nodeFrom) { - Metamaps.JIT.selectNodeOnRightClickHandler(node, e); - } else { - //console.log('right clicked on open space'); - } - } - }, - //Number of iterations for the FD algorithm - iterations: 200, - //Edge length - levelDistance: 200, - }, - nodeSettings: { - 'customNode': { - 'render': function (node, canvas) { - var pos = node.pos.getc(true), - dim = node.getData('dim'), - topic = node.getData('topic'), - metacode = topic ? topic.getMetacode() : false, - ctx = canvas.getCtx(); - - // if the topic is selected draw a circle around it - if (!canvas.denySelected && node.selected) { - ctx.beginPath(); - ctx.arc(pos.x, pos.y, dim + 3, 0, 2 * Math.PI, false); - ctx.strokeStyle = Metamaps.Settings.colors.topics.selected; - ctx.lineWidth = 2; - ctx.stroke(); - } - - if (!metacode || - !metacode.get('image') || - !metacode.get('image').complete || - (typeof metacode.get('image').naturalWidth !== "undefined" && - metacode.get('image').naturalWidth === 0)) { - ctx.beginPath(); - ctx.arc(pos.x, pos.y, dim, 0, 2 * Math.PI, false); - ctx.fillStyle = '#B6B2FD'; - ctx.fill(); - } else { - ctx.drawImage(metacode.get('image'), pos.x - dim, pos.y - dim, dim * 2, dim * 2); - } - - // if the topic has a link, draw a small image to indicate that - var hasLink = topic && topic.get('link') !== "" && topic.get('link') !== null; - var linkImage = Metamaps.JIT.topicLinkImage; - var linkImageLoaded = linkImage.complete || - (typeof linkImage.naturalWidth !== "undefined" && - linkImage.naturalWidth !== 0) - if (hasLink && linkImageLoaded) { - ctx.drawImage(linkImage, pos.x - dim - 8, pos.y - dim - 8, 16, 16); - } - - // if the topic has a desc, draw a small image to indicate that - var hasDesc = topic && topic.get('desc') !== "" && topic.get('desc') !== null; - var descImage = Metamaps.JIT.topicDescImage; - var descImageLoaded = descImage.complete || - (typeof descImage.naturalWidth !== "undefined" && - descImage.naturalWidth !== 0) - if (hasDesc && descImageLoaded) { - ctx.drawImage(descImage, pos.x + dim - 8, pos.y - dim - 8, 16, 16); - } - }, - 'contains': function (node, pos) { - var npos = node.pos.getc(true), - dim = node.getData('dim'), - arrayOfLabelLines = Metamaps.Util.splitLine(node.name, 30).split('\n'), - ctx = Metamaps.Visualize.mGraph.canvas.getCtx(); - - var height = 25 * arrayOfLabelLines.length; - - var index, lineWidths = []; - for (index = 0; index < arrayOfLabelLines.length; ++index) { - lineWidths.push(ctx.measureText(arrayOfLabelLines[index]).width) - } - var width = Math.max.apply(null, lineWidths) + 8; - var labely = npos.y + node.getData("height") + 5 + height / 2; - - var overLabel = this.nodeHelper.rectangle.contains({ - x: npos.x, - y: labely - }, pos, width, height); - - return this.nodeHelper.circle.contains(npos, pos, dim) || overLabel; - } - } - }, - edgeSettings: { - 'customEdge': { - 'render': function (adj, canvas) { - Metamaps.JIT.edgeRender(adj, canvas) - }, - 'contains': function (adj, pos) { - var from = adj.nodeFrom.pos.getc(), - to = adj.nodeTo.pos.getc(); - - // this fixes an issue where when edges are perfectly horizontal or perfectly vertical - // it becomes incredibly difficult to hover over them - if (-1 < pos.x && pos.x < 1) pos.x = 0; - if (-1 < pos.y && pos.y < 1) pos.y = 0; - - return $jit.Graph.Plot.edgeHelper.line.contains(from, to, pos, adj.Edge.epsilon + 5); - } - } - } - }, // ForceDirected - ForceDirected3D: { - animate: { - modes: ['linear'], - transition: $jit.Trans.Elastic.easeOut, - duration: 2500, - onComplete: function () { - Metamaps.Visualize.mGraph.busy = false; - } - }, - graphSettings: { - //id of the visualization container - injectInto: 'infovis', - type: '3D', - Scene: { - Lighting: { - enable: false, - ambient: [0.5, 0.5, 0.5], - directional: { - direction: { - x: 1, - y: 0, - z: -1 - }, - color: [0.9, 0.9, 0.9] - } - } - }, - //Enable zooming and panning - //by scrolling and DnD - Navigation: { - enable: false, - //Enable panning events only if we're dragging the empty - //canvas (and not a node). - panning: 'avoid nodes', - zooming: 10 //zoom speed. higher is more sensible - }, - // Change node and edge styles such as - // color and width. - // These properties are also set per node - // with dollar prefixed data-properties in the - // JSON structure. - Node: { - overridable: true, - type: 'sphere', - dim: 15, - color: '#ffffff' - }, - Edge: { - overridable: false, - type: 'tube', - color: '#111', - lineWidth: 3 - }, - //Native canvas text styling - Label: { - type: 'HTML', //Native or HTML - size: 10, - style: 'bold' - }, - // Add node events - Events: { - enable: true, - type: 'Native', - i: 0, - onMouseMove: function (node, eventInfo, e) { - //if(this.i++ % 3) return; - var pos = eventInfo.getPos(); - Metamaps.Visualize.cameraPosition.x += (pos.x - Metamaps.Visualize.cameraPosition.x) * 0.5; - Metamaps.Visualize.cameraPosition.y += (-pos.y - Metamaps.Visualize.cameraPosition.y) * 0.5; - Metamaps.Visualize.mGraph.plot(); - }, - onMouseWheel: function (delta) { - Metamaps.Visualize.cameraPosition.z += -delta * 20; - Metamaps.Visualize.mGraph.plot(); - }, - onClick: function () {} - }, - //Number of iterations for the FD algorithm - iterations: 200, - //Edge length - levelDistance: 100 - }, - nodeSettings: { - - }, - edgeSettings: { - - } - }, // ForceDirected3D - RGraph: { - animate: { - modes: ['polar'], - duration: 800, - onComplete: function () { - Metamaps.Visualize.mGraph.busy = false; - } - }, - // this will just be used to patch the ForceDirected graphsettings with the few things which actually differ - background: { - //type: 'Metamaps', - levelDistance: 200, - numberOfCircles: 4, - CanvasStyles: { - strokeStyle: '#333', - lineWidth: 1.5 - } - }, - levelDistance: 200 - }, - onMouseEnter: function (edge) { - var filtered = edge.getData('alpha') === 0; - - // don't do anything if the edge is filtered - // or if the canvas is animating - if (filtered || Metamaps.Visualize.mGraph.busy) return; - - $('canvas').css('cursor', 'pointer'); - var edgeIsSelected = Metamaps.Selected.Edges.indexOf(edge); - //following if statement only executes if the edge being hovered over is not selected - if (edgeIsSelected == -1) { - edge.setData('showDesc', true, 'current'); - } - - edge.setDataset('end', { - lineWidth: 4 - }); - Metamaps.Visualize.mGraph.fx.animate({ - modes: ['edge-property:lineWidth'], - duration: 100 - }); - Metamaps.Visualize.mGraph.plot(); - }, // onMouseEnter - onMouseLeave: function (edge) { - if (edge.getData('alpha') === 0) return; // don't do anything if the edge is filtered - $('canvas').css('cursor', 'default'); - var edgeIsSelected = Metamaps.Selected.Edges.indexOf(edge); - //following if statement only executes if the edge being hovered over is not selected - if (edgeIsSelected == -1) { - edge.setData('showDesc', false, 'current'); - } - - edge.setDataset('end', { - lineWidth: 2 - }); - Metamaps.Visualize.mGraph.fx.animate({ - modes: ['edge-property:lineWidth'], - duration: 100 - }); - Metamaps.Visualize.mGraph.plot(); - }, // onMouseLeave - onMouseMoveHandler: function (node, eventInfo, e) { - - var self = Metamaps.JIT; - - if (Metamaps.Visualize.mGraph.busy) return; - - var node = eventInfo.getNode(); - var edge = eventInfo.getEdge(); - - //if we're on top of a node object, act like there aren't edges under it - if (node != false) { - if (Metamaps.Mouse.edgeHoveringOver) { - self.onMouseLeave(Metamaps.Mouse.edgeHoveringOver); - } - $('canvas').css('cursor', 'pointer'); - return; - } - - if (edge == false && Metamaps.Mouse.edgeHoveringOver != false) { - //mouse not on an edge, but we were on an edge previously - self.onMouseLeave(Metamaps.Mouse.edgeHoveringOver); - } else if (edge != false && Metamaps.Mouse.edgeHoveringOver == false) { - //mouse is on an edge, but there isn't a stored edge - self.onMouseEnter(edge); - } else if (edge != false && Metamaps.Mouse.edgeHoveringOver != edge) { - //mouse is on an edge, but a different edge is stored - self.onMouseLeave(Metamaps.Mouse.edgeHoveringOver) - self.onMouseEnter(edge); - } - - //could be false - Metamaps.Mouse.edgeHoveringOver = edge; - - if (!node && !edge) { - $('canvas').css('cursor', 'default'); - } - }, // onMouseMoveHandler - enterKeyHandler: function () { - var creatingMap = Metamaps.GlobalUI.lightbox; - if (creatingMap === "newmap" || creatingMap === "forkmap") { - Metamaps.GlobalUI.CreateMap.submit(); - } - // this is to submit new topic creation - else if (Metamaps.Create.newTopic.beingCreated) { - Metamaps.Topic.createTopicLocally(); - } - // to submit new synapse creation - else if (Metamaps.Create.newSynapse.beingCreated) { - Metamaps.Synapse.createSynapseLocally(); - } - }, //enterKeyHandler - escKeyHandler: function () { - Metamaps.Control.deselectAllEdges(); - Metamaps.Control.deselectAllNodes(); - }, //escKeyHandler - touchPanZoomHandler: function (eventInfo, e) { - if (e.touches.length == 1) { - var thispos = Metamaps.Touch.touchPos, - currentPos = eventInfo.getPos(), - canvas = Metamaps.Visualize.mGraph.canvas, - ox = canvas.translateOffsetX, - oy = canvas.translateOffsetY, - sx = canvas.scaleOffsetX, - sy = canvas.scaleOffsetY; - currentPos.x *= sx; - currentPos.y *= sy; - currentPos.x += ox; - currentPos.y += oy; - //var x = currentPos.x - thispos.x, - // y = currentPos.y - thispos.y; - var x = currentPos.x - thispos.x, - y = currentPos.y - thispos.y; - Metamaps.Touch.touchPos = currentPos; - Metamaps.Visualize.mGraph.canvas.translate(x * 1 / sx, y * 1 / sy); - } else if (e.touches.length == 2) { - var touch1 = e.touches[0]; - var touch2 = e.touches[1]; - - var dist = Metamaps.Util.getDistance({ - x: touch1.clientX, - y: touch1.clientY - }, { - x: touch2.clientX, - y: touch2.clientY - }); - - if (!lastDist) { - lastDist = dist; - } - - var scale = dist / lastDist; - - if (8 >= Metamaps.Visualize.mGraph.canvas.scaleOffsetX * scale && Metamaps.Visualize.mGraph.canvas.scaleOffsetX * scale >= 1) { - Metamaps.Visualize.mGraph.canvas.scale(scale, scale); - } - if (Metamaps.Visualize.mGraph.canvas.scaleOffsetX < 0.5) { - Metamaps.Visualize.mGraph.canvas.viz.labels.hideLabels(true); - } else if (Metamaps.Visualize.mGraph.canvas.scaleOffsetX > 0.5) { - Metamaps.Visualize.mGraph.canvas.viz.labels.hideLabels(false); - } - lastDist = dist; - } - - }, // touchPanZoomHandler - onDragMoveTopicHandler: function (node, eventInfo, e) { - - var self = Metamaps.JIT; - - // this is used to send nodes that are moving to - // other realtime collaborators on the same map - var positionsToSend = {}; - var topic; - - var authorized = Metamaps.Active.Map && Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); - - if (node && !node.nodeFrom) { - var pos = eventInfo.getPos(); - // if it's a left click, or a touch, move the node - if (e.touches || (e.button == 0 && !e.altKey && !e.ctrlKey && !e.shiftKey && (e.buttons == 0 || e.buttons == 1 || e.buttons == undefined))) { - //if the node dragged isn't already selected, select it - var whatToDo = self.handleSelectionBeforeDragging(node, e); - if (node.pos.rho || node.pos.rho === 0) { - // this means we're in topic view - var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y); - var theta = Math.atan2(pos.y, pos.x); - node.pos.setp(theta, rho); - } else if (whatToDo == 'only-drag-this-one') { - node.pos.setc(pos.x, pos.y); - - if (Metamaps.Active.Map) { - topic = node.getData('topic'); - // we use the topic ID not the node id - // because we can't depend on the node id - // to be the same as on other collaborators - // maps - positionsToSend[topic.id] = pos; - $(document).trigger(Metamaps.JIT.events.topicDrag, [positionsToSend]); - } - } else { - var len = Metamaps.Selected.Nodes.length; - - //first define offset for each node - var xOffset = new Array(); - var yOffset = new Array(); - for (var i = 0; i < len; i += 1) { - var n = Metamaps.Selected.Nodes[i]; - xOffset[i] = n.pos.x - node.pos.x; - yOffset[i] = n.pos.y - node.pos.y; - } //for - - for (var i = 0; i < len; i += 1) { - var n = Metamaps.Selected.Nodes[i]; - var x = pos.x + xOffset[i]; - var y = pos.y + yOffset[i]; - n.pos.setc(x, y); - - if (Metamaps.Active.Map) { - topic = n.getData('topic'); - // we use the topic ID not the node id - // because we can't depend on the node id - // to be the same as on other collaborators - // maps - positionsToSend[topic.id] = n.pos; - } - } //for - - if (Metamaps.Active.Map) { - $(document).trigger(Metamaps.JIT.events.topicDrag, [positionsToSend]); - } - } //if - - if (whatToDo == 'deselect') { - Metamaps.Control.deselectNode(node); - } - Metamaps.Visualize.mGraph.plot(); - } - // if it's a right click or holding down alt, start synapse creation ->third option is for firefox - else if ((e.button == 2 || (e.button == 0 && e.altKey) || e.buttons == 2) && authorized) { - if (Metamaps.tempInit == false) { - Metamaps.tempNode = node; - Metamaps.tempInit = true; - - Metamaps.Create.newTopic.hide(); - Metamaps.Create.newSynapse.hide(); - // set the draw synapse start positions - var l = Metamaps.Selected.Nodes.length; - if (l > 0) { - for (var i = l - 1; i >= 0; i -= 1) { - var n = Metamaps.Selected.Nodes[i]; - Metamaps.Mouse.synapseStartCoordinates.push({ - x: n.pos.getc().x, - y: n.pos.getc().y - }); - } - } else { - Metamaps.Mouse.synapseStartCoordinates = [{ - x: Metamaps.tempNode.pos.getc().x, - y: Metamaps.tempNode.pos.getc().y - }]; - } - Metamaps.Mouse.synapseEndCoordinates = { - x: pos.x, - y: pos.y - }; - } - // - temp = eventInfo.getNode(); - if (temp != false && temp.id != node.id && Metamaps.Selected.Nodes.indexOf(temp) == -1) { // this means a Node has been returned - Metamaps.tempNode2 = temp; - - Metamaps.Mouse.synapseEndCoordinates = { - x: Metamaps.tempNode2.pos.getc().x, - y: Metamaps.tempNode2.pos.getc().y - }; - - // before making the highlighted one bigger, make sure all the others are regular size - Metamaps.Visualize.mGraph.graph.eachNode(function (n) { - n.setData('dim', 25, 'current'); - }); - temp.setData('dim', 35, 'current'); - Metamaps.Visualize.mGraph.plot(); - } else if (!temp) { - Metamaps.tempNode2 = null; - Metamaps.Visualize.mGraph.graph.eachNode(function (n) { - n.setData('dim', 25, 'current'); - }); - //pop up node creation :) - var myX = e.clientX - 110; - var myY = e.clientY - 30; - $('#new_topic').css('left', myX + "px"); - $('#new_topic').css('top', myY + "px"); - Metamaps.Create.newTopic.x = eventInfo.getPos().x; - Metamaps.Create.newTopic.y = eventInfo.getPos().y; - Metamaps.Visualize.mGraph.plot(); - - Metamaps.Mouse.synapseEndCoordinates = { - x: pos.x, - y: pos.y - }; - } - } - else if ((e.button == 2 || (e.button == 0 && e.altKey) || e.buttons == 2) && Metamaps.Active.Topic) { - Metamaps.GlobalUI.notifyUser("Cannot create in Topic view."); - } - else if ((e.button == 2 || (e.button == 0 && e.altKey) || e.buttons == 2) && !authorized) { - Metamaps.GlobalUI.notifyUser("Cannot edit Public map."); - } - } - }, // onDragMoveTopicHandler - onDragCancelHandler: function (node, eventInfo, e) { - Metamaps.tempNode = null; - if (Metamaps.tempNode2) Metamaps.tempNode2.setData('dim', 25, 'current'); - Metamaps.tempNode2 = null; - Metamaps.tempInit = false; - // reset the draw synapse positions to false - Metamaps.Mouse.synapseStartCoordinates = []; - Metamaps.Mouse.synapseEndCoordinates = null; - Metamaps.Visualize.mGraph.plot(); - }, // onDragCancelHandler - onDragEndTopicHandler: function (node, eventInfo, e) { - var midpoint = {}, pixelPos, mapping; - - if (Metamaps.tempInit && Metamaps.tempNode2 == null) { - // this means you want to add a new topic, and then a synapse - Metamaps.Create.newTopic.addSynapse = true; - Metamaps.Create.newTopic.open(); - } else if (Metamaps.tempInit && Metamaps.tempNode2 != null) { - // this means you want to create a synapse between two existing topics - Metamaps.Create.newTopic.addSynapse = false; - Metamaps.Create.newSynapse.topic1id = Metamaps.tempNode.getData('topic').id; - Metamaps.Create.newSynapse.topic2id = Metamaps.tempNode2.getData('topic').id; - Metamaps.tempNode2.setData('dim', 25, 'current'); - Metamaps.Visualize.mGraph.plot(); - midpoint.x = Metamaps.tempNode.pos.getc().x + (Metamaps.tempNode2.pos.getc().x - Metamaps.tempNode.pos.getc().x) / 2; - midpoint.y = Metamaps.tempNode.pos.getc().y + (Metamaps.tempNode2.pos.getc().y - Metamaps.tempNode.pos.getc().y) / 2; - pixelPos = Metamaps.Util.coordsToPixels(midpoint); - $('#new_synapse').css('left', pixelPos.x + "px"); - $('#new_synapse').css('top', pixelPos.y + "px"); - Metamaps.Create.newSynapse.open(); - Metamaps.tempNode = null; - Metamaps.tempNode2 = null; - Metamaps.tempInit = false; - } else if (!Metamaps.tempInit && node && !node.nodeFrom) { - // this means you dragged an existing node, autosave that to the database - - // check whether to save mappings - var checkWhetherToSave = function() { - var map = Metamaps.Active.Map; - - if (!map) return false; - - var mapper = Metamaps.Active.Mapper; - // this case - // covers when it is a public map owned by you - // and also when it's a private map - var activeMappersMap = map.authorizePermissionChange(mapper); - var commonsMap = map.get('permission') === 'commons'; - var realtimeOn = Metamaps.Realtime.status; - - // don't save if commons map, and you have realtime off, - // even if you're map creator - return map && mapper && ((commonsMap && realtimeOn) || (activeMappersMap && !commonsMap)); - } - - if (checkWhetherToSave()) { - mapping = node.getData('mapping'); - mapping.save({ - xloc: node.getPos().x, - yloc: node.getPos().y - }); - // also save any other selected nodes that also got dragged along - var l = Metamaps.Selected.Nodes.length; - for (var i = l - 1; i >= 0; i -= 1) { - var n = Metamaps.Selected.Nodes[i]; - if (n !== node) { - mapping = n.getData('mapping'); - mapping.save({ - xloc: n.getPos().x, - yloc: n.getPos().y - }); - } - }; - } - } - }, //onDragEndTopicHandler - canvasClickHandler: function (canvasLoc, e) { - //grab the location and timestamp of the click - var storedTime = Metamaps.Mouse.lastCanvasClick; - var now = Date.now(); //not compatible with IE8 FYI - Metamaps.Mouse.lastCanvasClick = now; - - var authorized = Metamaps.Active.Map && Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); - - if (now - storedTime < Metamaps.Mouse.DOUBLE_CLICK_TOLERANCE && !Metamaps.Mouse.didPan) { - if (Metamaps.Active.Map && !authorized) { - Metamaps.GlobalUI.notifyUser("Cannot edit Public map."); - return; - } - else if (Metamaps.Active.Topic) { - Metamaps.GlobalUI.notifyUser("Cannot create in Topic view."); - return; - } - // DOUBLE CLICK - //pop up node creation :) - Metamaps.Create.newTopic.addSynapse = false; - Metamaps.Create.newTopic.x = canvasLoc.x; - Metamaps.Create.newTopic.y = canvasLoc.y; - $('#new_topic').css('left', e.clientX + "px"); - $('#new_topic').css('top', e.clientY + "px"); - Metamaps.Create.newTopic.open(); - } else if (!Metamaps.Mouse.didPan) { - // SINGLE CLICK, no pan - Metamaps.Filter.close(); - Metamaps.TopicCard.hideCard(); - Metamaps.SynapseCard.hideCard(); - Metamaps.Create.newTopic.hide(); - $('.rightclickmenu').remove(); - // reset the draw synapse positions to false - Metamaps.Mouse.synapseStartCoordinates = []; - Metamaps.Mouse.synapseEndCoordinates = null; - Metamaps.tempInit = false; - Metamaps.tempNode = null; - Metamaps.tempNode2 = null; - if (!e.ctrlKey && !e.shiftKey) { - Metamaps.Control.deselectAllEdges(); - Metamaps.Control.deselectAllNodes(); - } - } - }, //canvasClickHandler - nodeDoubleClickHandler: function (node, e) { - - Metamaps.TopicCard.showCard(node); - - }, // nodeDoubleClickHandler - edgeDoubleClickHandler: function (adj, e) { - - Metamaps.SynapseCard.showCard(adj, e); - - }, // nodeDoubleClickHandler - nodeWasDoubleClicked: function () { - //grab the timestamp of the click - var storedTime = Metamaps.Mouse.lastNodeClick; - var now = Date.now(); //not compatible with IE8 FYI - Metamaps.Mouse.lastNodeClick = now; - - if (now - storedTime < Metamaps.Mouse.DOUBLE_CLICK_TOLERANCE) { - return true; + events: { + topicDrag: 'Metamaps:JIT:events:topicDrag', + newTopic: 'Metamaps:JIT:events:newTopic', + deleteTopic: 'Metamaps:JIT:events:deleteTopic', + removeTopic: 'Metamaps:JIT:events:removeTopic', + newSynapse: 'Metamaps:JIT:events:newSynapse', + deleteSynapse: 'Metamaps:JIT:events:deleteSynapse', + removeSynapse: 'Metamaps:JIT:events:removeSynapse', + pan: 'Metamaps:JIT:events:pan', + zoom: 'Metamaps:JIT:events:zoom', + animationDone: 'Metamaps:JIT:events:animationDone', + }, + vizData: [], // contains the visualization-compatible graph + /** + * This method will bind the event handlers it is interested and initialize the class. + */ + init: function () { + var self = Metamaps.JIT + + $('.zoomIn').click(self.zoomIn) + $('.zoomOut').click(self.zoomOut) + + var zoomExtents = function (event) { + self.zoomExtents(event, Metamaps.Visualize.mGraph.canvas) + } + $('.zoomExtents').click(zoomExtents) + + $('.takeScreenshot').click(Metamaps.Map.exportImage) + + self.topicDescImage = new Image() + self.topicDescImage.src = '<%= asset_path('topic_description_signifier.png') %>' + + self.topicLinkImage = new Image() + self.topicLinkImage.src = '<%= asset_path('topic_link_signifier.png') %>' + }, + /** + * convert our topic JSON into something JIT can use + */ + convertModelsToJIT: function (topics, synapses) { + var jitReady = [] + + var synapsesToRemove = [] + var topic + var mapping + var node + var nodes = {} + var existingEdge + var edge + var edges = [] + + topics.each(function (t) { + node = t.createNode() + nodes[node.id] = node + }) + synapses.each(function (s) { + edge = s.createEdge() + + if (topics.get(s.get('node1_id')) === undefined || topics.get(s.get('node2_id')) === undefined) { + // this means it's an invalid synapse + synapsesToRemove.push(s) + } + else if (nodes[edge.nodeFrom] && nodes[edge.nodeTo]) { + existingEdge = _.findWhere(edges, { + nodeFrom: edge.nodeFrom, + nodeTo: edge.nodeTo + }) || + _.findWhere(edges, { + nodeFrom: edge.nodeTo, + nodeTo: edge.nodeFrom + }) + + if (existingEdge) { + // for when you're dealing with multiple relationships between the same two topics + if (Metamaps.Active.Map) { + mapping = s.getMapping() + existingEdge.data['$mappingIDs'].push(mapping.id) + } + existingEdge.data['$synapseIDs'].push(s.id) } else { - return false; + // for when you're dealing with a topic that has relationships to many different nodes + nodes[edge.nodeFrom].adjacencies.push(edge) + edges.push(edge) } - }, //nodeWasDoubleClicked; - handleSelectionBeforeDragging: function (node, e) { - // four cases: - // 1 nothing is selected, so pretend you aren't selecting - // 2 others are selected only and shift, so additionally select this one - // 3 others are selected only, no shift: drag only this one - // 4 this node and others were selected, so drag them (just return false) - //return value: deselect node again after? - if (Metamaps.Selected.Nodes.length == 0) { - return 'only-drag-this-one'; - } - if (Metamaps.Selected.Nodes.indexOf(node) == -1) { + } + }) + + _.each(nodes, function (node) { + jitReady.push(node) + }) + + return [jitReady, synapsesToRemove] + }, + prepareVizData: function () { + var self = Metamaps.JIT + var mapping + + // reset/empty vizData + self.vizData = [] + Metamaps.Visualize.loadLater = false + + var results = self.convertModelsToJIT(Metamaps.Topics, Metamaps.Synapses) + + self.vizData = results[0] + + // clean up the synapses array in case of any faulty data + _.each(results[1], function (synapse) { + mapping = synapse.getMapping() + Metamaps.Synapses.remove(synapse) + if (Metamaps.Mappings) Metamaps.Mappings.remove(mapping) + }) + + if (self.vizData.length == 0) { + Metamaps.Famous.viz.showInstructions() + Metamaps.Visualize.loadLater = true + } + else Metamaps.Famous.viz.hideInstructions() + + Metamaps.Visualize.render() + }, // prepareVizData + edgeRender: function (adj, canvas) { + // get nodes cartesian coordinates + var pos = adj.nodeFrom.pos.getc(true) + var posChild = adj.nodeTo.pos.getc(true) + + var synapse + if (adj.getData('displayIndex')) { + synapse = adj.getData('synapses')[adj.getData('displayIndex')] + if (!synapse) { + delete adj.data.$displayIndex + synapse = adj.getData('synapses')[0] + } + } else { + synapse = adj.getData('synapses')[0] + } + + if (!synapse) return // this means there are no corresponding synapses for + // this edge, don't render it + + var directionCat = synapse.get('category') + + // label placement on edges + if (canvas.denySelected) { + var color = Metamaps.Settings.colors.synapses.normal + canvas.getCtx().fillStyle = canvas.getCtx().strokeStyle = color + } + Metamaps.JIT.renderEdgeArrows($jit.Graph.Plot.edgeHelper, adj, synapse, canvas) + + // check for edge label in data + var desc = synapse.get('desc') + + var showDesc = adj.getData('showDesc') + + var drawSynapseCount = function (context, x, y, count) { + /* + circle size: 16x16px + positioning: overlay and center on top right corner of synapse label - 8px left and 8px down + color: #dab539 + border color: #424242 + border size: 1.5px + font: DIN medium + font-size: 14pt + font-color: #424242 + */ + context.beginPath() + context.arc(x, y, 8, 0, 2 * Math.PI, false) + context.fillStyle = '#DAB539' + context.strokeStyle = '#424242' + context.lineWidth = 1.5 + context.closePath() + context.fill() + context.stroke() + + // add the synapse count + context.fillStyle = '#424242' + context.textAlign = 'center' + context.font = '14px din-medium' + + context.fillText(count, x, y + 5) + } + + if (!canvas.denySelected && desc != '' && showDesc) { + // '&' to '&' + desc = Metamaps.Util.decodeEntities(desc) + + // now adjust the label placement + var ctx = canvas.getCtx() + ctx.font = 'bold 14px arial' + ctx.fillStyle = '#FFF' + ctx.textBaseline = 'alphabetic' + + var arrayOfLabelLines = Metamaps.Util.splitLine(desc, 30).split('\n') + var index, lineWidths = [] + for (index = 0; index < arrayOfLabelLines.length; ++index) { + lineWidths.push(ctx.measureText(arrayOfLabelLines[index]).width) + } + var width = Math.max.apply(null, lineWidths) + 16 + var height = (16 * arrayOfLabelLines.length) + 8 + + var x = (pos.x + posChild.x - width) / 2 + var y = ((pos.y + posChild.y) / 2) - height / 2 + + var radius = 5 + + // render background + ctx.beginPath() + ctx.moveTo(x + radius, y) + ctx.lineTo(x + width - radius, y) + ctx.quadraticCurveTo(x + width, y, x + width, y + radius) + ctx.lineTo(x + width, y + height - radius) + ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height) + ctx.lineTo(x + radius, y + height) + ctx.quadraticCurveTo(x, y + height, x, y + height - radius) + ctx.lineTo(x, y + radius) + ctx.quadraticCurveTo(x, y, x + radius, y) + ctx.closePath() + ctx.fill() + + // get number of synapses + var synapseNum = adj.getData('synapses').length + + // render text + ctx.fillStyle = '#424242' + ctx.textAlign = 'center' + for (index = 0; index < arrayOfLabelLines.length; ++index) { + ctx.fillText(arrayOfLabelLines[index], x + (width / 2), y + 18 + (16 * index)) + } + + if (synapseNum > 1) { + drawSynapseCount(ctx, x + width, y, synapseNum) + } + } + else if (!canvas.denySelected && showDesc) { + // get number of synapses + var synapseNum = adj.getData('synapses').length + + if (synapseNum > 1) { + var ctx = canvas.getCtx() + var x = (pos.x + posChild.x) / 2 + var y = (pos.y + posChild.y) / 2 + drawSynapseCount(ctx, x, y, synapseNum) + } + } + }, // edgeRender + ForceDirected: { + animateSavedLayout: { + modes: ['linear'], + transition: $jit.Trans.Quad.easeInOut, + duration: 800, + onComplete: function () { + Metamaps.Visualize.mGraph.busy = false + $(document).trigger(Metamaps.JIT.events.animationDone) + } + }, + animateFDLayout: { + modes: ['linear'], + transition: $jit.Trans.Elastic.easeOut, + duration: 800, + onComplete: function () { + Metamaps.Visualize.mGraph.busy = false + } + }, + graphSettings: { + // id of the visualization container + injectInto: 'infovis', + // Enable zooming and panning + // by scrolling and DnD + Navigation: { + enable: true, + // Enable panning events only if we're dragging the empty + // canvas (and not a node). + panning: 'avoid nodes', + zooming: 28 // zoom speed. higher is more sensible + }, + // background: { + // type: 'Metamaps' + // }, + // NodeStyles: { + // enable: true, + // type: 'Native', + // stylesHover: { + // dim: 30 + // }, + // duration: 300 + // }, + // Change node and edge styles such as + // color and width. + // These properties are also set per node + // with dollar prefixed data-properties in the + // JSON structure. + Node: { + overridable: true, + color: '#2D6A5D', + type: 'customNode', + dim: 25 + }, + Edge: { + overridable: true, + color: Metamaps.Settings.colors.synapses.normal, + type: 'customEdge', + lineWidth: 2, + alpha: 1 + }, + // Native canvas text styling + Label: { + type: 'Native', // Native or HTML + size: 20, + family: 'arial', + textBaseline: 'alphabetic', + color: Metamaps.Settings.colors.labels.text + }, + // Add Tips + Tips: { + enable: false, + onShow: function (tip, node) {} + }, + // Add node events + Events: { + enable: true, + enableForEdges: true, + onMouseMove: function (node, eventInfo, e) { + Metamaps.JIT.onMouseMoveHandler(node, eventInfo, e) + // console.log('called mouse move handler') + }, + // Update node positions when dragged + onDragMove: function (node, eventInfo, e) { + Metamaps.JIT.onDragMoveTopicHandler(node, eventInfo, e) + // console.log('called drag move handler') + }, + onDragEnd: function (node, eventInfo, e) { + Metamaps.JIT.onDragEndTopicHandler(node, eventInfo, e, false) + // console.log('called drag end handler') + }, + onDragCancel: function (node, eventInfo, e) { + Metamaps.JIT.onDragCancelHandler(node, eventInfo, e, false) + }, + // Implement the same handler for touchscreens + onTouchStart: function (node, eventInfo, e) { + Metamaps.Visualize.mGraph.events.touched = true + + var canvas = Metamaps.Visualize.mGraph.canvas + + Metamaps.Touch.touchPos = eventInfo.getPos() + Metamaps.Touch.touchPos.x *= canvas.scaleOffsetX + Metamaps.Touch.touchPos.y *= canvas.scaleOffsetY + Metamaps.Touch.touchPos.x += canvas.translateOffsetX + Metamaps.Touch.touchPos.y += canvas.translateOffsetY + + touchDragNode = node + }, + // Implement the same handler for touchscreens + onTouchMove: function (node, eventInfo, e) { + if (Metamaps.Touch.touchDragNode) { + Metamaps.JIT.onDragMoveTopicHandler(Metamaps.Touch.touchDragNode, eventInfo, e) + } else { + Metamaps.JIT.touchPanZoomHandler(eventInfo, e) + } + }, + // Implement the same handler for touchscreens + onTouchEnd: function (node, eventInfo, e) {}, + // Implement the same handler for touchscreens + onTouchCancel: function (node, eventInfo, e) {}, + // Add also a click handler to nodes + onClick: function (node, eventInfo, e) { + // remove the rightclickmenu + $('.rightclickmenu').remove() + + if (Metamaps.Mouse.boxStartCoordinates) { + if (e.ctrlKey) { + Metamaps.Visualize.mGraph.busy = false + Metamaps.Mouse.boxEndCoordinates = eventInfo.getPos() + + var bS = Metamaps.Mouse.boxStartCoordinates + var bE = Metamaps.Mouse.boxEndCoordinates + if (Math.abs(bS.x - bE.x) > 20 && Math.abs(bS.y - bE.y) > 20) { + Metamaps.JIT.zoomToBox(e) + return + } else { + Metamaps.Mouse.boxStartCoordinates = null + Metamaps.Mouse.boxEndCoordinates = null + } + // console.log('called zoom to box') + } + if (e.shiftKey) { - Metamaps.Control.selectNode(node,e); - return 'nothing'; - } else { - return 'only-drag-this-one'; + Metamaps.Visualize.mGraph.busy = false + Metamaps.Mouse.boxEndCoordinates = eventInfo.getPos() + Metamaps.JIT.selectWithBox(e) + // console.log('called select with box') + return } + } + + if (e.target.id != 'infovis-canvas') return false + + // clicking on a edge, node, or clicking on blank part of canvas? + if (node.nodeFrom) { + Metamaps.JIT.selectEdgeOnClickHandler(node, e) + // console.log('called selectEdgeOnClickHandler') + } else if (node && !node.nodeFrom) { + Metamaps.JIT.selectNodeOnClickHandler(node, e) + // console.log('called selectNodeOnClickHandler') + } else { + Metamaps.JIT.canvasClickHandler(eventInfo.getPos(), e) + // console.log('called canvasClickHandler') + } // if + }, + // Add also a click handler to nodes + onRightClick: function (node, eventInfo, e) { + // remove the rightclickmenu + $('.rightclickmenu').remove() + + if (Metamaps.Mouse.boxStartCoordinates) { + Metamaps.Visualize.mGraph.busy = false + Metamaps.Mouse.boxEndCoordinates = eventInfo.getPos() + Metamaps.JIT.selectWithBox(e) + return + } + + if (e.target.id != 'infovis-canvas') return false + + // clicking on a edge, node, or clicking on blank part of canvas? + if (node.nodeFrom) { + Metamaps.JIT.selectEdgeOnRightClickHandler(node, e) + } else if (node && !node.nodeFrom) { + Metamaps.JIT.selectNodeOnRightClickHandler(node, e) + } else { + // console.log('right clicked on open space') + } } - return 'nothing'; //case 4? - }, // handleSelectionBeforeDragging - selectWithBox: function (e) { + }, + // Number of iterations for the FD algorithm + iterations: 200, + // Edge length + levelDistance: 200, + }, + nodeSettings: { + 'customNode': { + 'render': function (node, canvas) { + var pos = node.pos.getc(true), + dim = node.getData('dim'), + topic = node.getData('topic'), + metacode = topic ? topic.getMetacode() : false, + ctx = canvas.getCtx() - var sX = Metamaps.Mouse.boxStartCoordinates.x, - sY = Metamaps.Mouse.boxStartCoordinates.y, - eX = Metamaps.Mouse.boxEndCoordinates.x, - eY = Metamaps.Mouse.boxEndCoordinates.y; - - if(!e.shiftKey){ - Metamaps.Control.deselectAllNodes(); - Metamaps.Control.deselectAllEdges(); - } + // if the topic is selected draw a circle around it + if (!canvas.denySelected && node.selected) { + ctx.beginPath() + ctx.arc(pos.x, pos.y, dim + 3, 0, 2 * Math.PI, false) + ctx.strokeStyle = Metamaps.Settings.colors.topics.selected + ctx.lineWidth = 2 + ctx.stroke() + } - //select all nodes that are within the box - Metamaps.Visualize.mGraph.graph.eachNode(function (n) { - var x = n.pos.x, - y = n.pos.y; + if (!metacode || + !metacode.get('image') || + !metacode.get('image').complete || + (typeof metacode.get('image').naturalWidth !== 'undefined' && + metacode.get('image').naturalWidth === 0)) { + ctx.beginPath() + ctx.arc(pos.x, pos.y, dim, 0, 2 * Math.PI, false) + ctx.fillStyle = '#B6B2FD' + ctx.fill() + } else { + ctx.drawImage(metacode.get('image'), pos.x - dim, pos.y - dim, dim * 2, dim * 2) + } - if ((sX < x && x < eX && sY < y && y < eY) || (sX > x && x > eX && sY > y && y > eY) || (sX > x && x > eX && sY < y && y < eY) || (sX < x && x < eX && sY > y && y > eY)) { - if(e.shiftKey){ - if(n.selected){ - Metamaps.Control.deselectNode(n); - } - else{ - Metamaps.Control.selectNode(n,e); - } - } - else{ - Metamaps.Control.selectNode(n,e); - } - } - }); + // if the topic has a link, draw a small image to indicate that + var hasLink = topic && topic.get('link') !== '' && topic.get('link') !== null + var linkImage = Metamaps.JIT.topicLinkImage + var linkImageLoaded = linkImage.complete || + (typeof linkImage.naturalWidth !== 'undefined' && + linkImage.naturalWidth !== 0) + if (hasLink && linkImageLoaded) { + ctx.drawImage(linkImage, pos.x - dim - 8, pos.y - dim - 8, 16, 16) + } - //Convert selection box coordinates to traditional coordinates (+,+) in upper right - sY = -1 * sY; - eY = -1 * eY + // if the topic has a desc, draw a small image to indicate that + var hasDesc = topic && topic.get('desc') !== '' && topic.get('desc') !== null + var descImage = Metamaps.JIT.topicDescImage + var descImageLoaded = descImage.complete || + (typeof descImage.naturalWidth !== 'undefined' && + descImage.naturalWidth !== 0) + if (hasDesc && descImageLoaded) { + ctx.drawImage(descImage, pos.x + dim - 8, pos.y - dim - 8, 16, 16) + } + }, + 'contains': function (node, pos) { + var npos = node.pos.getc(true), + dim = node.getData('dim'), + arrayOfLabelLines = Metamaps.Util.splitLine(node.name, 30).split('\n'), + ctx = Metamaps.Visualize.mGraph.canvas.getCtx() - var edgesToToggle = []; - Metamaps.Synapses.each(function(synapse) { - var e = synapse.get('edge'); - if (edgesToToggle.indexOf(e) === -1) { - edgesToToggle.push(e); - } - }); - edgesToToggle.forEach(function(edge) { - var fromNodeX = edge.nodeFrom.pos.x; - var fromNodeY = -1 * edge.nodeFrom.pos.y; - var toNodeX = edge.nodeTo.pos.x; - var toNodeY = -1 * edge.nodeTo.pos.y; + var height = 25 * arrayOfLabelLines.length - var maxX = fromNodeX; - var maxY = fromNodeY; - var minX = fromNodeX; - var minY = fromNodeY; - - //Correct maxX, MaxY values - (toNodeX > maxX) ? (maxX = toNodeX):(minX = toNodeX); - (toNodeY > maxY) ? (maxY = toNodeY):(minY = toNodeY); - - var maxBoxX = sX; - var maxBoxY = sY; - var minBoxX = sX; - var minBoxY = sY; - - //Correct maxBoxX, maxBoxY values - (eX > maxBoxX) ? (maxBoxX = eX):(minBoxX = eX); - (eY > maxBoxY) ? (maxBoxY = eY):(minBoxY = eY); - - //Find the slopes from the synapse fromNode to the 4 corners of the selection box - var slopes = []; - slopes.push( (sY - fromNodeY) / (sX - fromNodeX) ); - slopes.push( (sY - fromNodeY) / (eX - fromNodeX) ); - slopes.push( (eY - fromNodeY) / (eX - fromNodeX) ); - slopes.push( (eY - fromNodeY) / (sX - fromNodeX) ); - - var minSlope = slopes[0]; - var maxSlope = slopes[0]; - slopes.forEach(function(entry){ - if(entry > maxSlope) maxSlope = entry; - if(entry < minSlope) minSlope = entry; - }); - - //Find synapse-in-question's slope - var synSlope = (toNodeY - fromNodeY) / (toNodeX - fromNodeX); - var b = fromNodeY - synSlope * fromNodeX; + var index, lineWidths = [] + for (index = 0; index < arrayOfLabelLines.length; ++index) { + lineWidths.push(ctx.measureText(arrayOfLabelLines[index]).width) + } + var width = Math.max.apply(null, lineWidths) + 8 + var labely = npos.y + node.getData('height') + 5 + height / 2 - //Use the selection box edges as test cases for synapse intersection - var testX = sX; - var testY = synSlope * testX + b; + var overLabel = this.nodeHelper.rectangle.contains({ + x: npos.x, + y: labely + }, pos, width, height) - var selectTest; - - if(testX >= minX && testX <= maxX && testY >= minY && testY <= maxY && testY >= minBoxY && testY <= maxBoxY){ - selectTest = true; - } - - testX = eX; - testY = synSlope * testX + b; - - if(testX >= minX && testX <= maxX && testY >= minY && testY <= maxY && testY >= minBoxY && testY <= maxBoxY){ - selectTest = true; - } - - testY = sY; - testX = (testY - b)/synSlope; - - if(testX >= minX && testX <= maxX && testY >= minY && testY <= maxY && testX >= minBoxX && testX <= maxBoxX){ - selectTest = true; - } - - testY = eY; - testX = (testY - b)/synSlope; - - if(testX >= minX && testX <= maxX && testY >= minY && testY <= maxY && testX >= minBoxX && testX <= maxBoxX){ - selectTest = true; - } - - //Case where the synapse is wholly enclosed in the seldction box - if(fromNodeX >= minBoxX && fromNodeX <= maxBoxX && fromNodeY >= minBoxY && fromNodeY <= maxBoxY && toNodeX >= minBoxX && toNodeX <= maxBoxX && toNodeY >= minBoxY && toNodeY <= maxBoxY){ - selectTest = true; - } - - //The test synapse was selected! - - if(selectTest){ - // shiftKey = toggleSelect, otherwise - if(e.shiftKey){ - if(Metamaps.Selected.Edges.indexOf(edge) != -1 ){ - Metamaps.Control.deselectEdge(edge); - } - else{ - Metamaps.Control.selectEdge(edge); - } - } - else{ - Metamaps.Control.selectEdge(edge); - } - } - }); - Metamaps.Mouse.boxStartCoordinates = false; - Metamaps.Mouse.boxEndCoordinates = false; - Metamaps.Visualize.mGraph.plot(); - }, // selectWithBox - drawSelectBox: function (eventInfo, e) { - var ctx = Metamaps.Visualize.mGraph.canvas.getCtx(); - - var startX = Metamaps.Mouse.boxStartCoordinates.x, - startY = Metamaps.Mouse.boxStartCoordinates.y, - currX = eventInfo.getPos().x, - currY = eventInfo.getPos().y; - - Metamaps.Visualize.mGraph.canvas.clear(); - Metamaps.Visualize.mGraph.plot(); - - ctx.beginPath(); - ctx.moveTo(startX, startY); - ctx.lineTo(startX, currY); - ctx.lineTo(currX, currY); - ctx.lineTo(currX, startY); - ctx.lineTo(startX, startY); - ctx.strokeStyle = "black"; - ctx.stroke(); - }, // drawSelectBox - selectNodeOnClickHandler: function (node, e) { - if (Metamaps.Visualize.mGraph.busy) return; - - var self = Metamaps.JIT; - - // catch right click on mac, which is often like ctrl+click - if (navigator.platform.indexOf("Mac") != -1 && e.ctrlKey) { - self.selectNodeOnRightClickHandler(node, e) - return; + return this.nodeHelper.circle.contains(npos, pos, dim) || overLabel } + } + }, + edgeSettings: { + 'customEdge': { + 'render': function (adj, canvas) { + Metamaps.JIT.edgeRender(adj, canvas) + }, + 'contains': function (adj, pos) { + var from = adj.nodeFrom.pos.getc(), + to = adj.nodeTo.pos.getc() - // if on a topic page, let alt+click center you on a new topic - if (Metamaps.Active.Topic && e.altKey) { - Metamaps.RGraph.centerOn(node.id); - return; + // this fixes an issue where when edges are perfectly horizontal or perfectly vertical + // it becomes incredibly difficult to hover over them + if (-1 < pos.x && pos.x < 1) pos.x = 0 + if (-1 < pos.y && pos.y < 1) pos.y = 0 + + return $jit.Graph.Plot.edgeHelper.line.contains(from, to, pos, adj.Edge.epsilon + 5) } + } + } + }, // ForceDirected + ForceDirected3D: { + animate: { + modes: ['linear'], + transition: $jit.Trans.Elastic.easeOut, + duration: 2500, + onComplete: function () { + Metamaps.Visualize.mGraph.busy = false + } + }, + graphSettings: { + // id of the visualization container + injectInto: 'infovis', + type: '3D', + Scene: { + Lighting: { + enable: false, + ambient: [0.5, 0.5, 0.5], + directional: { + direction: { + x: 1, + y: 0, + z: -1 + }, + color: [0.9, 0.9, 0.9] + } + } + }, + // Enable zooming and panning + // by scrolling and DnD + Navigation: { + enable: false, + // Enable panning events only if we're dragging the empty + // canvas (and not a node). + panning: 'avoid nodes', + zooming: 10 // zoom speed. higher is more sensible + }, + // Change node and edge styles such as + // color and width. + // These properties are also set per node + // with dollar prefixed data-properties in the + // JSON structure. + Node: { + overridable: true, + type: 'sphere', + dim: 15, + color: '#ffffff' + }, + Edge: { + overridable: false, + type: 'tube', + color: '#111', + lineWidth: 3 + }, + // Native canvas text styling + Label: { + type: 'HTML', // Native or HTML + size: 10, + style: 'bold' + }, + // Add node events + Events: { + enable: true, + type: 'Native', + i: 0, + onMouseMove: function (node, eventInfo, e) { + // if(this.i++ % 3) return + var pos = eventInfo.getPos() + Metamaps.Visualize.cameraPosition.x += (pos.x - Metamaps.Visualize.cameraPosition.x) * 0.5 + Metamaps.Visualize.cameraPosition.y += (-pos.y - Metamaps.Visualize.cameraPosition.y) * 0.5 + Metamaps.Visualize.mGraph.plot() + }, + onMouseWheel: function (delta) { + Metamaps.Visualize.cameraPosition.z += -delta * 20 + Metamaps.Visualize.mGraph.plot() + }, + onClick: function () {} + }, + // Number of iterations for the FD algorithm + iterations: 200, + // Edge length + levelDistance: 100 + }, + nodeSettings: { - var check = self.nodeWasDoubleClicked(); - if (check) { - self.nodeDoubleClickHandler(node, e); - return; + }, + edgeSettings: { + + } + }, // ForceDirected3D + RGraph: { + animate: { + modes: ['polar'], + duration: 800, + onComplete: function () { + Metamaps.Visualize.mGraph.busy = false + } + }, + // this will just be used to patch the ForceDirected graphsettings with the few things which actually differ + background: { + // type: 'Metamaps', + levelDistance: 200, + numberOfCircles: 4, + CanvasStyles: { + strokeStyle: '#333', + lineWidth: 1.5 + } + }, + levelDistance: 200 + }, + onMouseEnter: function (edge) { + var filtered = edge.getData('alpha') === 0 + + // don't do anything if the edge is filtered + // or if the canvas is animating + if (filtered || Metamaps.Visualize.mGraph.busy) return + + $('canvas').css('cursor', 'pointer') + var edgeIsSelected = Metamaps.Selected.Edges.indexOf(edge) + // following if statement only executes if the edge being hovered over is not selected + if (edgeIsSelected == -1) { + edge.setData('showDesc', true, 'current') + } + + edge.setDataset('end', { + lineWidth: 4 + }) + Metamaps.Visualize.mGraph.fx.animate({ + modes: ['edge-property:lineWidth'], + duration: 100 + }) + Metamaps.Visualize.mGraph.plot() + }, // onMouseEnter + onMouseLeave: function (edge) { + if (edge.getData('alpha') === 0) return; // don't do anything if the edge is filtered + $('canvas').css('cursor', 'default') + var edgeIsSelected = Metamaps.Selected.Edges.indexOf(edge) + // following if statement only executes if the edge being hovered over is not selected + if (edgeIsSelected == -1) { + edge.setData('showDesc', false, 'current') + } + + edge.setDataset('end', { + lineWidth: 2 + }) + Metamaps.Visualize.mGraph.fx.animate({ + modes: ['edge-property:lineWidth'], + duration: 100 + }) + Metamaps.Visualize.mGraph.plot() + }, // onMouseLeave + onMouseMoveHandler: function (node, eventInfo, e) { + var self = Metamaps.JIT + + if (Metamaps.Visualize.mGraph.busy) return + + var node = eventInfo.getNode() + var edge = eventInfo.getEdge() + + // if we're on top of a node object, act like there aren't edges under it + if (node != false) { + if (Metamaps.Mouse.edgeHoveringOver) { + self.onMouseLeave(Metamaps.Mouse.edgeHoveringOver) + } + $('canvas').css('cursor', 'pointer') + return + } + + if (edge == false && Metamaps.Mouse.edgeHoveringOver != false) { + // mouse not on an edge, but we were on an edge previously + self.onMouseLeave(Metamaps.Mouse.edgeHoveringOver) + } else if (edge != false && Metamaps.Mouse.edgeHoveringOver == false) { + // mouse is on an edge, but there isn't a stored edge + self.onMouseEnter(edge) + } else if (edge != false && Metamaps.Mouse.edgeHoveringOver != edge) { + // mouse is on an edge, but a different edge is stored + self.onMouseLeave(Metamaps.Mouse.edgeHoveringOver) + self.onMouseEnter(edge) + } + + // could be false + Metamaps.Mouse.edgeHoveringOver = edge + + if (!node && !edge) { + $('canvas').css('cursor', 'default') + } + }, // onMouseMoveHandler + enterKeyHandler: function () { + var creatingMap = Metamaps.GlobalUI.lightbox + if (creatingMap === 'newmap' || creatingMap === 'forkmap') { + Metamaps.GlobalUI.CreateMap.submit() + } + // this is to submit new topic creation + else if (Metamaps.Create.newTopic.beingCreated) { + Metamaps.Topic.createTopicLocally() + } + // to submit new synapse creation + else if (Metamaps.Create.newSynapse.beingCreated) { + Metamaps.Synapse.createSynapseLocally() + } + }, // enterKeyHandler + escKeyHandler: function () { + Metamaps.Control.deselectAllEdges() + Metamaps.Control.deselectAllNodes() + }, // escKeyHandler + touchPanZoomHandler: function (eventInfo, e) { + if (e.touches.length == 1) { + var thispos = Metamaps.Touch.touchPos, + currentPos = eventInfo.getPos(), + canvas = Metamaps.Visualize.mGraph.canvas, + ox = canvas.translateOffsetX, + oy = canvas.translateOffsetY, + sx = canvas.scaleOffsetX, + sy = canvas.scaleOffsetY + currentPos.x *= sx + currentPos.y *= sy + currentPos.x += ox + currentPos.y += oy + // var x = currentPos.x - thispos.x, + // y = currentPos.y - thispos.y + var x = currentPos.x - thispos.x, + y = currentPos.y - thispos.y + Metamaps.Touch.touchPos = currentPos + Metamaps.Visualize.mGraph.canvas.translate(x * 1 / sx, y * 1 / sy) + } else if (e.touches.length == 2) { + var touch1 = e.touches[0] + var touch2 = e.touches[1] + + var dist = Metamaps.Util.getDistance({ + x: touch1.clientX, + y: touch1.clientY + }, { + x: touch2.clientX, + y: touch2.clientY + }) + + if (!lastDist) { + lastDist = dist + } + + var scale = dist / lastDist + + if (8 >= Metamaps.Visualize.mGraph.canvas.scaleOffsetX * scale && Metamaps.Visualize.mGraph.canvas.scaleOffsetX * scale >= 1) { + Metamaps.Visualize.mGraph.canvas.scale(scale, scale) + } + if (Metamaps.Visualize.mGraph.canvas.scaleOffsetX < 0.5) { + Metamaps.Visualize.mGraph.canvas.viz.labels.hideLabels(true) + } else if (Metamaps.Visualize.mGraph.canvas.scaleOffsetX > 0.5) { + Metamaps.Visualize.mGraph.canvas.viz.labels.hideLabels(false) + } + lastDist = dist + } + }, // touchPanZoomHandler + onDragMoveTopicHandler: function (node, eventInfo, e) { + var self = Metamaps.JIT + + // this is used to send nodes that are moving to + // other realtime collaborators on the same map + var positionsToSend = {} + var topic + + var authorized = Metamaps.Active.Map && Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + + if (node && !node.nodeFrom) { + var pos = eventInfo.getPos() + // if it's a left click, or a touch, move the node + if (e.touches || (e.button == 0 && !e.altKey && !e.ctrlKey && !e.shiftKey && (e.buttons == 0 || e.buttons == 1 || e.buttons == undefined))) { + // if the node dragged isn't already selected, select it + var whatToDo = self.handleSelectionBeforeDragging(node, e) + if (node.pos.rho || node.pos.rho === 0) { + // this means we're in topic view + var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y) + var theta = Math.atan2(pos.y, pos.x) + node.pos.setp(theta, rho) + } else if (whatToDo == 'only-drag-this-one') { + node.pos.setc(pos.x, pos.y) + + if (Metamaps.Active.Map) { + topic = node.getData('topic') + // we use the topic ID not the node id + // because we can't depend on the node id + // to be the same as on other collaborators + // maps + positionsToSend[topic.id] = pos + $(document).trigger(Metamaps.JIT.events.topicDrag, [positionsToSend]) + } } else { - // wait a certain length of time, then check again, then run this code - setTimeout(function () { - if (!Metamaps.JIT.nodeWasDoubleClicked()) { + var len = Metamaps.Selected.Nodes.length - var nodeAlreadySelected = node.selected; + // first define offset for each node + var xOffset = [] + var yOffset = [] + for (var i = 0; i < len; i += 1) { + var n = Metamaps.Selected.Nodes[i] + xOffset[i] = n.pos.x - node.pos.x + yOffset[i] = n.pos.y - node.pos.y + } // for - if (!e.shiftKey) { - Metamaps.Control.deselectAllNodes(); - Metamaps.Control.deselectAllEdges(); - } - - if (nodeAlreadySelected) { - Metamaps.Control.deselectNode(node); - } else { - Metamaps.Control.selectNode(node,e); - } - - //trigger animation to final styles - Metamaps.Visualize.mGraph.fx.animate({ - modes: ['edge-property:lineWidth:color:alpha'], - duration: 500 - }); - Metamaps.Visualize.mGraph.plot(); - } - }, Metamaps.Mouse.DOUBLE_CLICK_TOLERANCE); + for (var i = 0; i < len; i += 1) { + var n = Metamaps.Selected.Nodes[i] + var x = pos.x + xOffset[i] + var y = pos.y + yOffset[i] + n.pos.setc(x, y) + + if (Metamaps.Active.Map) { + topic = n.getData('topic') + // we use the topic ID not the node id + // because we can't depend on the node id + // to be the same as on other collaborators + // maps + positionsToSend[topic.id] = n.pos + } + } // for + + if (Metamaps.Active.Map) { + $(document).trigger(Metamaps.JIT.events.topicDrag, [positionsToSend]) + } + } // if + + if (whatToDo == 'deselect') { + Metamaps.Control.deselectNode(node) } - }, //selectNodeOnClickHandler - selectNodeOnRightClickHandler: function (node, e) { - // the 'node' variable is a JIT node, the one that was clicked on - // the 'e' variable is the click event + Metamaps.Visualize.mGraph.plot() + } + // if it's a right click or holding down alt, start synapse creation ->third option is for firefox + else if ((e.button == 2 || (e.button == 0 && e.altKey) || e.buttons == 2) && authorized) { + if (Metamaps.tempInit == false) { + Metamaps.tempNode = node + Metamaps.tempInit = true - e.preventDefault(); - e.stopPropagation(); - - if (Metamaps.Visualize.mGraph.busy) return; - - // select the node - Metamaps.Control.selectNode(node, e); - - // delete old right click menu - $('.rightclickmenu').remove(); - // create new menu for clicked on node - var rightclickmenu = document.createElement("div"); - rightclickmenu.className = "rightclickmenu"; - // add the proper options to the menu - var menustring = ''; - rightclickmenu.innerHTML = menustring; + if (adj.getData('alpha') === 0) return; // don't do anything if the edge is filtered - // position the menu where the click happened - var position = {}; - var RIGHTCLICK_WIDTH = 300; - var RIGHTCLICK_HEIGHT = 144; // this does vary somewhat, but we can use static - var SUBMENUS_WIDTH = 256; - var MAX_SUBMENU_HEIGHT = 270; - var windowWidth = $(window).width(); - var windowHeight = $(window).height(); + var authorized - if (windowWidth - e.clientX < SUBMENUS_WIDTH) { - position.right = windowWidth - e.clientX; - $(rightclickmenu).addClass('moveMenusToLeft'); - } - else if (windowWidth - e.clientX < RIGHTCLICK_WIDTH) { - position.right = windowWidth - e.clientX; - } - else if (windowWidth - e.clientX < RIGHTCLICK_WIDTH + SUBMENUS_WIDTH) { - position.left = e.clientX; - $(rightclickmenu).addClass('moveMenusToLeft'); - } - else position.left = e.clientX; + e.preventDefault() + e.stopPropagation() - if (windowHeight - e.clientY < MAX_SUBMENU_HEIGHT) { - position.bottom = windowHeight - e.clientY; - $(rightclickmenu).addClass('moveMenusUp'); - } - else if (windowHeight - e.clientY < RIGHTCLICK_HEIGHT + MAX_SUBMENU_HEIGHT) { - position.top = e.clientY; - $(rightclickmenu).addClass('moveMenusUp'); - } - else position.top = e.clientY; + if (Metamaps.Visualize.mGraph.busy) return - $(rightclickmenu).css(position); - //add the menu to the page - $('#wrapper').append(rightclickmenu); + Metamaps.Control.selectEdge(adj) - // attach events to clicks on the list items + // delete old right click menu + $('.rightclickmenu').remove() + // create new menu for clicked on node + var rightclickmenu = document.createElement('div') + rightclickmenu.className = 'rightclickmenu' - // delete the selected things from the database - if (authorized) { - $('.rc-delete').click(function () { - $('.rightclickmenu').remove(); - Metamaps.Control.deleteSelected(); - }); - } + // add the proper options to the menu + var menustring = '' + rightclickmenu.innerHTML = menustring + + // position the menu where the click happened + var position = {} + var RIGHTCLICK_WIDTH = 300 + var RIGHTCLICK_HEIGHT = 144; // this does vary somewhat, but we can use static + var SUBMENUS_WIDTH = 256 + var MAX_SUBMENU_HEIGHT = 270 + var windowWidth = $(window).width() + var windowHeight = $(window).height() + + if (windowWidth - e.clientX < SUBMENUS_WIDTH) { + position.right = windowWidth - e.clientX + $(rightclickmenu).addClass('moveMenusToLeft') + } + else if (windowWidth - e.clientX < RIGHTCLICK_WIDTH) { + position.right = windowWidth - e.clientX + } + else position.left = e.clientX + + if (windowHeight - e.clientY < MAX_SUBMENU_HEIGHT) { + position.bottom = windowHeight - e.clientY + $(rightclickmenu).addClass('moveMenusUp') + } + else if (windowHeight - e.clientY < RIGHTCLICK_HEIGHT + MAX_SUBMENU_HEIGHT) { + position.top = e.clientY + $(rightclickmenu).addClass('moveMenusUp') + } + else position.top = e.clientY + + $(rightclickmenu).css(position) + + // add the menu to the page + $('#wrapper').append(rightclickmenu) + + // attach events to clicks on the list items + + // delete the selected things from the database + if (authorized) { + $('.rc-delete').click(function () { + $('.rightclickmenu').remove() + Metamaps.Control.deleteSelected() + }) + } + + // remove the selected things from the map + if (authorized) { + $('.rc-remove').click(function () { + $('.rightclickmenu').remove() + Metamaps.Control.removeSelectedEdges() + Metamaps.Control.removeSelectedNodes() + }) + } + + // hide selected nodes and synapses until refresh + $('.rc-hide').click(function () { + $('.rightclickmenu').remove() + Metamaps.Control.hideSelectedEdges() + Metamaps.Control.hideSelectedNodes() + }) + + // change the permission of all the selected nodes and synapses that you were the originator of + $('.rc-permission li').click(function () { + $('.rightclickmenu').remove() + // $(this).text() will be 'commons' 'public' or 'private' + Metamaps.Control.updateSelectedPermissions($(this).text()) + }) + }, // selectEdgeOnRightClickHandler + SmoothPanning: function () { + var sx = Metamaps.Visualize.mGraph.canvas.scaleOffsetX, + sy = Metamaps.Visualize.mGraph.canvas.scaleOffsetY, + y_velocity = Metamaps.Mouse.changeInY, // initial y velocity + x_velocity = Metamaps.Mouse.changeInX, // initial x velocity + easing = 1 // frictional value + + easing = 1 + window.clearInterval(Metamaps.panningInt) + Metamaps.panningInt = setInterval(function () { + myTimer() + }, 1) + + function myTimer () { + Metamaps.Visualize.mGraph.canvas.translate(x_velocity * easing * 1 / sx, y_velocity * easing * 1 / sy) + $(document).trigger(Metamaps.JIT.events.pan) + easing = easing * 0.75 + + if (easing < 0.1) window.clearInterval(Metamaps.panningInt) + } + }, // SmoothPanning + renderMidArrow: function (from, to, dim, swap, canvas, placement, newSynapse) { + var ctx = canvas.getCtx() + // invert edge direction + if (swap) { + var tmp = from + from = to + to = tmp + } + // vect represents a line from tip to tail of the arrow + var vect = new $jit.Complex(to.x - from.x, to.y - from.y) + // scale it + vect.$scale(dim / vect.norm()) + // compute the midpoint of the edge line + var newX = (to.x - from.x) * placement + from.x + var newY = (to.y - from.y) * placement + from.y + var midPoint = new $jit.Complex(newX, newY) + + // move midpoint by half the "length" of the arrow so the arrow is centered on the midpoint + var arrowPoint = new $jit.Complex((vect.x / 0.7) + midPoint.x, (vect.y / 0.7) + midPoint.y) + // compute the tail intersection point with the edge line + var intermediatePoint = new $jit.Complex(arrowPoint.x - vect.x, arrowPoint.y - vect.y) + // vector perpendicular to vect + var normal = new $jit.Complex(-vect.y / 2, vect.x / 2) + var v1 = intermediatePoint.add(normal) + var v2 = intermediatePoint.$add(normal.$scale(-1)) + + if (newSynapse) { + ctx.strokeStyle = '#4fc059' + ctx.lineWidth = 2 + ctx.globalAlpha = 1 + } + ctx.beginPath() + ctx.moveTo(from.x, from.y) + ctx.lineTo(to.x, to.y) + ctx.stroke() + ctx.beginPath() + ctx.moveTo(v1.x, v1.y) + ctx.lineTo(arrowPoint.x, arrowPoint.y) + ctx.lineTo(v2.x, v2.y) + ctx.stroke() + }, // renderMidArrow + renderEdgeArrows: function (edgeHelper, adj, synapse, canvas) { + var self = Metamaps.JIT + + var directionCat = synapse.get('category') + var direction = synapse.getDirection() + + var pos = adj.nodeFrom.pos.getc(true) + var posChild = adj.nodeTo.pos.getc(true) + + // plot arrow edge + if (!direction) { + // render nothing for this arrow if the direction couldn't be retrieved + } else if (directionCat == 'none') { + edgeHelper.line.render({ + x: pos.x, + y: pos.y + }, { + x: posChild.x, + y: posChild.y + }, canvas) + } else if (directionCat == 'both') { + self.renderMidArrow({ + x: pos.x, + y: pos.y + }, { + x: posChild.x, + y: posChild.y + }, 13, true, canvas, 0.7) + self.renderMidArrow({ + x: pos.x, + y: pos.y + }, { + x: posChild.x, + y: posChild.y + }, 13, false, canvas, 0.7) + } else if (directionCat == 'from-to') { + var inv = (direction[0] != adj.nodeFrom.id) + self.renderMidArrow({ + x: pos.x, + y: pos.y + }, { + x: posChild.x, + y: posChild.y + }, 13, inv, canvas, 0.7) + self.renderMidArrow({ + x: pos.x, + y: pos.y + }, { + x: posChild.x, + y: posChild.y + }, 13, inv, canvas, 0.3) + } + }, // renderEdgeArrows + zoomIn: function (event) { + Metamaps.Visualize.mGraph.canvas.scale(1.25, 1.25) + $(document).trigger(Metamaps.JIT.events.zoom, [event]) + }, + zoomOut: function (event) { + Metamaps.Visualize.mGraph.canvas.scale(0.8, 0.8) + $(document).trigger(Metamaps.JIT.events.zoom, [event]) + }, + centerMap: function (canvas) { + var offsetScale = canvas.scaleOffsetX + + canvas.scale(1 / offsetScale, 1 / offsetScale) + + var offsetX = canvas.translateOffsetX + var offsetY = canvas.translateOffsetY + + canvas.translate(-1 * offsetX, -1 * offsetY) + }, + zoomToBox: function (event) { + var sX = Metamaps.Mouse.boxStartCoordinates.x, + sY = Metamaps.Mouse.boxStartCoordinates.y, + eX = Metamaps.Mouse.boxEndCoordinates.x, + eY = Metamaps.Mouse.boxEndCoordinates.y + + var canvas = Metamaps.Visualize.mGraph.canvas + Metamaps.JIT.centerMap(canvas) + + var height = $(document).height(), + width = $(document).width() + + var spanX = Math.abs(sX - eX) + var spanY = Math.abs(sY - eY) + var ratioX = width / spanX + var ratioY = height / spanY + + var newRatio = Math.min(ratioX, ratioY) + + if (canvas.scaleOffsetX * newRatio <= 5 && canvas.scaleOffsetX * newRatio >= 0.2) { + canvas.scale(newRatio, newRatio) + } + else if (canvas.scaleOffsetX * newRatio > 5) { + newRatio = 5 / canvas.scaleOffsetX + canvas.scale(newRatio, newRatio) + } else { + newRatio = 0.2 / canvas.scaleOffsetX + canvas.scale(newRatio, newRatio) + } + + var cogX = (sX + eX) / 2 + var cogY = (sY + eY) / 2 + + canvas.translate(-1 * cogX, -1 * cogY) + $(document).trigger(Metamaps.JIT.events.zoom, [event]) + + Metamaps.Mouse.boxStartCoordinates = false + Metamaps.Mouse.boxEndCoordinates = false + Metamaps.Visualize.mGraph.plot() + }, + zoomExtents: function (event, canvas, denySelected) { + Metamaps.JIT.centerMap(canvas) + var height = canvas.getSize().height, + width = canvas.getSize().width, + maxX, minX, maxY, minY, counter = 0 + + if (!denySelected && Metamaps.Selected.Nodes.length > 0) { + var nodes = Metamaps.Selected.Nodes + } else { + var nodes = _.values(Metamaps.Visualize.mGraph.graph.nodes) + } + + if (nodes.length > 1) { + nodes.forEach(function (n) { + var x = n.pos.x, + y = n.pos.y + + if (counter == 0 && n.getData('alpha') == 1) { + maxX = x + minX = x + maxY = y + minY = y + } + + var arrayOfLabelLines = Metamaps.Util.splitLine(n.name, 30).split('\n'), + dim = n.getData('dim'), + ctx = canvas.getCtx() + + var height = 25 * arrayOfLabelLines.length + + var index, lineWidths = [] + for (index = 0; index < arrayOfLabelLines.length; ++index) { + lineWidths.push(ctx.measureText(arrayOfLabelLines[index]).width) + } + var width = Math.max.apply(null, lineWidths) + 8 + + // only adjust these values if the node is not filtered + if (n.getData('alpha') == 1) { + maxX = Math.max(x + width / 2, maxX) + maxY = Math.max(y + n.getData('height') + 5 + height, maxY) + minX = Math.min(x - width / 2, minX) + minY = Math.min(y - dim, minY) + + counter++ + } + }) + + var spanX = maxX - minX + var spanY = maxY - minY + var ratioX = spanX / width + var ratioY = spanY / height + + var cogX = (maxX + minX) / 2 + var cogY = (maxY + minY) / 2 + + canvas.translate(-1 * cogX, -1 * cogY) + + var newRatio = Math.max(ratioX, ratioY) + var scaleMultiplier = 1 / newRatio * 0.9 + + if (canvas.scaleOffsetX * scaleMultiplier <= 3 && canvas.scaleOffsetX * scaleMultiplier >= 0.2) { + canvas.scale(scaleMultiplier, scaleMultiplier) + } + else if (canvas.scaleOffsetX * scaleMultiplier > 3) { + scaleMultiplier = 3 / canvas.scaleOffsetX + canvas.scale(scaleMultiplier, scaleMultiplier) + } else { + scaleMultiplier = 0.2 / canvas.scaleOffsetX + canvas.scale(scaleMultiplier, scaleMultiplier) + } + + $(document).trigger(Metamaps.JIT.events.zoom, [event]) + } + else if (nodes.length == 1) { + nodes.forEach(function (n) { + var x = n.pos.x, + y = n.pos.y + + canvas.translate(-1 * x, -1 * y) + $(document).trigger(Metamaps.JIT.events.zoom, [event]) + }) + } + } +} diff --git a/app/assets/javascripts/src/Metamaps.Listeners.js b/app/assets/javascripts/src/Metamaps.Listeners.js new file mode 100644 index 00000000..83204eee --- /dev/null +++ b/app/assets/javascripts/src/Metamaps.Listeners.js @@ -0,0 +1,78 @@ +/* global Metamaps, $ */ + +/* + * Metamaps.Listeners.js.erb + * + * Dependencies: + * - Metamaps.Active + * - Metamaps.Control + * - Metamaps.JIT + * - Metamaps.Visualize + */ +Metamaps.Listeners = { + init: function () { + $(document).on('keydown', function (e) { + if (!(Metamaps.Active.Map || Metamaps.Active.Topic)) return + + switch (e.which) { + case 13: // if enter key is pressed + Metamaps.JIT.enterKeyHandler() + e.preventDefault() + break + case 27: // if esc key is pressed + Metamaps.JIT.escKeyHandler() + break + case 65: // if a or A is pressed + if (e.ctrlKey) { + Metamaps.Control.deselectAllNodes() + Metamaps.Control.deselectAllEdges() + + e.preventDefault() + Metamaps.Visualize.mGraph.graph.eachNode(function (n) { + Metamaps.Control.selectNode(n, e) + }) + + Metamaps.Visualize.mGraph.plot() + } + + break + case 69: // if e or E is pressed + if (e.ctrlKey) { + e.preventDefault() + if (Metamaps.Active.Map) { + Metamaps.JIT.zoomExtents(null, Metamaps.Visualize.mGraph.canvas) + } + } + break + case 77: // if m or M is pressed + if (e.ctrlKey) { + e.preventDefault() + Metamaps.Control.removeSelectedNodes() + Metamaps.Control.removeSelectedEdges() + } + break + case 68: // if d or D is pressed + if (e.ctrlKey) { + e.preventDefault() + Metamaps.Control.deleteSelected() + } + break + case 72: // if h or H is pressed + if (e.ctrlKey) { + e.preventDefault() + Metamaps.Control.hideSelectedNodes() + Metamaps.Control.hideSelectedEdges() + } + break + default: + break; // alert(e.which) + } + }) + + $(window).resize(function () { + if (Metamaps.Visualize && Metamaps.Visualize.mGraph) Metamaps.Visualize.mGraph.canvas.resize($(window).width(), $(window).height()) + if ((Metamaps.Active.Map || Metamaps.Active.Topic) && Metamaps.Famous && Metamaps.Famous.maps.surf) Metamaps.Famous.maps.reposition() + if (Metamaps.Active.Map && Metamaps.Realtime.inConversation) Metamaps.Realtime.positionVideos() + }) + } +}; // end Metamaps.Listeners diff --git a/app/assets/javascripts/src/Metamaps.Map.js.erb b/app/assets/javascripts/src/Metamaps.Map.js.erb new file mode 100644 index 00000000..0207d081 --- /dev/null +++ b/app/assets/javascripts/src/Metamaps.Map.js.erb @@ -0,0 +1,667 @@ +/* global Metamaps, $ */ + +/* + * Metamaps.Map.js.erb + * + * Dependencies: + * Metamaps.Create + * Metamaps.Filter + * Metamaps.JIT + * Metamaps.Loading + * Metamaps.Maps + * Metamaps.Realtime + * Metamaps.Router + * Metamaps.Selected + * Metamaps.SynapseCard + * Metamaps.TopicCard + * Metamaps.Visualize + * - Metamaps.Active + * - Metamaps.Backbone + * - Metamaps.GlobalUI + * - Metamaps.Mappers + * - Metamaps.Mappings + * - Metamaps.Messages + * - Metamaps.Synapses + * - Metamaps.Topics + * + * Major sub-modules: + * - Metamaps.Map.CheatSheet + * - Metamaps.Map.InfoBox + */ + +Metamaps.Map = { + events: { + editedByActiveMapper: 'Metamaps:Map:events:editedByActiveMapper' + }, + nextX: 0, + nextY: 0, + sideLength: 1, + turnCount: 0, + nextXshift: 1, + nextYshift: 0, + timeToTurn: 0, + init: function () { + var self = Metamaps.Map + + // prevent right clicks on the main canvas, so as to not get in the way of our right clicks + $('#center-container').bind('contextmenu', function (e) { + return false + }) + + $('.sidebarFork').click(function () { + self.fork() + }) + + Metamaps.GlobalUI.CreateMap.emptyForkMapForm = $('#fork_map').html() + + self.InfoBox.init() + self.CheatSheet.init() + + $(document).on(Metamaps.Map.events.editedByActiveMapper, self.editedByActiveMapper) + }, + launch: function (id) { + var bb = Metamaps.Backbone + var start = function (data) { + Metamaps.Active.Map = new bb.Map(data.map) + Metamaps.Mappers = new bb.MapperCollection(data.mappers) + Metamaps.Topics = new bb.TopicCollection(data.topics) + Metamaps.Synapses = new bb.SynapseCollection(data.synapses) + Metamaps.Mappings = new bb.MappingCollection(data.mappings) + Metamaps.Messages = data.messages + Metamaps.Backbone.attachCollectionEvents() + + var map = Metamaps.Active.Map + var mapper = Metamaps.Active.Mapper + + // add class to .wrapper for specifying whether you can edit the map + if (map.authorizeToEdit(mapper)) { + $('.wrapper').addClass('canEditMap') + } + + // add class to .wrapper for specifying if the map can + // be collaborated on + if (map.get('permission') === 'commons') { + $('.wrapper').addClass('commonsMap') + } + + // set filter mapper H3 text + $('#filter_by_mapper h3').html('MAPPERS') + + // build and render the visualization + Metamaps.Visualize.type = 'ForceDirected' + Metamaps.JIT.prepareVizData() + + // update filters + Metamaps.Filter.reset() + + // reset selected arrays + Metamaps.Selected.reset() + + // set the proper mapinfobox content + Metamaps.Map.InfoBox.load() + + // these three update the actual filter box with the right list items + Metamaps.Filter.checkMetacodes() + Metamaps.Filter.checkSynapses() + Metamaps.Filter.checkMappers() + + Metamaps.Realtime.startActiveMap() + Metamaps.Loading.hide() + } + + $.ajax({ + url: '/maps/' + id + '/contains.json', + success: start + }) + }, + end: function () { + if (Metamaps.Active.Map) { + $('.wrapper').removeClass('canEditMap commonsMap') + Metamaps.Map.resetSpiral() + + $('.rightclickmenu').remove() + Metamaps.TopicCard.hideCard() + Metamaps.SynapseCard.hideCard() + Metamaps.Create.newTopic.hide() + Metamaps.Create.newSynapse.hide() + Metamaps.Filter.close() + Metamaps.Map.InfoBox.close() + Metamaps.Realtime.endActiveMap() + } + }, + fork: function () { + Metamaps.GlobalUI.openLightbox('forkmap') + + var nodes_data = '', + synapses_data = '' + var nodes_array = [] + var synapses_array = [] + // collect the unfiltered topics + Metamaps.Visualize.mGraph.graph.eachNode(function (n) { + // if the opacity is less than 1 then it's filtered + if (n.getData('alpha') === 1) { + var id = n.getData('topic').id + nodes_array.push(id) + var x, y + if (n.pos.x && n.pos.y) { + x = n.pos.x + y = n.pos.y + } else { + var x = Math.cos(n.pos.theta) * n.pos.rho + var y = Math.sin(n.pos.theta) * n.pos.rho + } + nodes_data += id + '/' + x + '/' + y + ',' + } + }) + // collect the unfiltered synapses + Metamaps.Synapses.each(function (synapse) { + var desc = synapse.get('desc') + + var descNotFiltered = Metamaps.Filter.visible.synapses.indexOf(desc) > -1 + // make sure that both topics are being added, otherwise, it + // doesn't make sense to add the synapse + var topicsNotFiltered = nodes_array.indexOf(synapse.get('node1_id')) > -1 + topicsNotFiltered = topicsNotFiltered && nodes_array.indexOf(synapse.get('node2_id')) > -1 + if (descNotFiltered && topicsNotFiltered) { + synapses_array.push(synapse.id) + } + }) + + synapses_data = synapses_array.join() + nodes_data = nodes_data.slice(0, -1) + + Metamaps.GlobalUI.CreateMap.topicsToMap = nodes_data + Metamaps.GlobalUI.CreateMap.synapsesToMap = synapses_data + }, + leavePrivateMap: function () { + var map = Metamaps.Active.Map + Metamaps.Maps.Active.remove(map) + Metamaps.Maps.Featured.remove(map) + Metamaps.Router.home() + Metamaps.GlobalUI.notifyUser('Sorry! That map has been changed to Private.') + }, + commonsToPublic: function () { + Metamaps.Realtime.turnOff(true); // true is for 'silence' + Metamaps.GlobalUI.notifyUser('Map was changed to Public. Editing is disabled.') + Metamaps.Active.Map.trigger('changeByOther') + }, + publicToCommons: function () { + var confirmString = 'This map permission has been changed to Commons! ' + confirmString += 'Do you want to reload and enable realtime collaboration?' + var c = confirm(confirmString) + if (c) { + Metamaps.Router.maps(Metamaps.Active.Map.id) + } + }, + editedByActiveMapper: function () { + if (Metamaps.Active.Mapper) { + Metamaps.Mappers.add(Metamaps.Active.Mapper) + } + }, + getNextCoord: function () { + var self = Metamaps.Map + var nextX = self.nextX + var nextY = self.nextY + + var DISTANCE_BETWEEN = 120 + + self.nextX = self.nextX + DISTANCE_BETWEEN * self.nextXshift + self.nextY = self.nextY + DISTANCE_BETWEEN * self.nextYshift + + self.timeToTurn += 1 + // if true, it's time to turn + if (self.timeToTurn === self.sideLength) { + self.turnCount += 1 + // if true, it's time to increase side length + if (self.turnCount % 2 === 0) { + self.sideLength += 1 + } + self.timeToTurn = 0 + + // going right? turn down + if (self.nextXshift == 1 && self.nextYshift == 0) { + self.nextXshift = 0 + self.nextYshift = 1 + } + // going down? turn left + else if (self.nextXshift == 0 && self.nextYshift == 1) { + self.nextXshift = -1 + self.nextYshift = 0 + } + // going left? turn up + else if (self.nextXshift == -1 && self.nextYshift == 0) { + self.nextXshift = 0 + self.nextYshift = -1 + } + // going up? turn right + else if (self.nextXshift == 0 && self.nextYshift == -1) { + self.nextXshift = 1 + self.nextYshift = 0 + } + } + + return { + x: nextX, + y: nextY + } + }, + resetSpiral: function () { + Metamaps.Map.nextX = 0 + Metamaps.Map.nextY = 0 + Metamaps.Map.nextXshift = 1 + Metamaps.Map.nextYshift = 0 + Metamaps.Map.sideLength = 1 + Metamaps.Map.timeToTurn = 0 + Metamaps.Map.turnCount = 0 + }, + exportImage: function () { + var canvas = {} + + canvas.canvas = document.createElement('canvas') + canvas.canvas.width = 1880 // 960 + canvas.canvas.height = 1260 // 630 + + canvas.scaleOffsetX = 1 + canvas.scaleOffsetY = 1 + canvas.translateOffsetY = 0 + canvas.translateOffsetX = 0 + canvas.denySelected = true + + canvas.getSize = function () { + if (this.size) return this.size + var canvas = this.canvas + return this.size = { + width: canvas.width, + height: canvas.height + } + } + canvas.scale = function (x, y) { + var px = this.scaleOffsetX * x, + py = this.scaleOffsetY * y + var dx = this.translateOffsetX * (x - 1) / px, + dy = this.translateOffsetY * (y - 1) / py + this.scaleOffsetX = px + this.scaleOffsetY = py + this.getCtx().scale(x, y) + this.translate(dx, dy) + } + canvas.translate = function (x, y) { + var sx = this.scaleOffsetX, + sy = this.scaleOffsetY + this.translateOffsetX += x * sx + this.translateOffsetY += y * sy + this.getCtx().translate(x, y) + } + canvas.getCtx = function () { + return this.canvas.getContext('2d') + } + // center it + canvas.getCtx().translate(1880 / 2, 1260 / 2) + + var mGraph = Metamaps.Visualize.mGraph + + var id = mGraph.root + var root = mGraph.graph.getNode(id) + var T = !!root.visited + + // pass true to avoid basing it on a selection + Metamaps.JIT.zoomExtents(null, canvas, true) + + var c = canvas.canvas, + ctx = canvas.getCtx(), + scale = canvas.scaleOffsetX + + // draw a grey background + ctx.fillStyle = '#d8d9da' + var xPoint = (-(c.width / scale) / 2) - (canvas.translateOffsetX / scale), + yPoint = (-(c.height / scale) / 2) - (canvas.translateOffsetY / scale) + ctx.fillRect(xPoint, yPoint, c.width / scale, c.height / scale) + + // draw the graph + mGraph.graph.eachNode(function (node) { + var nodeAlpha = node.getData('alpha') + node.eachAdjacency(function (adj) { + var nodeTo = adj.nodeTo + if (!!nodeTo.visited === T && node.drawn && nodeTo.drawn) { + mGraph.fx.plotLine(adj, canvas) + } + }) + if (node.drawn) { + mGraph.fx.plotNode(node, canvas) + } + if (!mGraph.labelsHidden) { + if (node.drawn && nodeAlpha >= 0.95) { + mGraph.labels.plotLabel(canvas, node) + } else { + mGraph.labels.hideLabel(node, false) + } + } + node.visited = !T + }) + + var imageData = { + encoded_image: canvas.canvas.toDataURL() + } + + var map = Metamaps.Active.Map + + var today = new Date() + var dd = today.getDate() + var mm = today.getMonth() + 1; // January is 0! + var yyyy = today.getFullYear() + if (dd < 10) { + dd = '0' + dd + } + if (mm < 10) { + mm = '0' + mm + } + today = mm + '/' + dd + '/' + yyyy + + var mapName = map.get('name').split(' ').join([separator = '-']) + var downloadMessage = '' + downloadMessage += 'Captured map screenshot! ' + downloadMessage += "DOWNLOAD" + Metamaps.GlobalUI.notifyUser(downloadMessage) + + $.ajax({ + type: 'POST', + dataType: 'json', + url: '/maps/' + Metamaps.Active.Map.id + '/upload_screenshot', + data: imageData, + success: function (data) { + console.log('successfully uploaded map screenshot') + }, + error: function () { + console.log('failed to save map screenshot') + } + }) + } +} + +/* + * + * CHEATSHEET + * + */ +Metamaps.Map.CheatSheet = { + init: function () { + // tab the cheatsheet + $('#cheatSheet').tabs() + $('#quickReference').tabs().addClass('ui-tabs-vertical ui-helper-clearfix') + $('#quickReference .ui-tabs-nav li').removeClass('ui-corner-top').addClass('ui-corner-left') + + // id = the id of a vimeo video + var switchVideo = function (element, id) { + $('.tutorialItem').removeClass('active') + $(element).addClass('active') + $('#tutorialVideo').attr('src', '//player.vimeo.com/video/' + id) + } + + $('#gettingStarted').click(function () { + // switchVideo(this,'88334167') + }) + $('#upYourSkillz').click(function () { + // switchVideo(this,'100118167') + }) + $('#advancedMapping').click(function () { + // switchVideo(this,'88334167') + }) + } +}; // end Metamaps.Map.CheatSheet + +/* + * + * INFOBOX + * + */ +Metamaps.Map.InfoBox = { + isOpen: false, + changing: false, + selectingPermission: false, + changePermissionText: "
As the creator, you can change the permission of this map, but the permissions of the topics and synapses on it must be changed independently.
", + nameHTML: '{{name}}', + descHTML: '{{desc}}', + init: function () { + var self = Metamaps.Map.InfoBox + + $('.mapInfoIcon').click(self.toggleBox) + $('.mapInfoBox').click(function (event) { + event.stopPropagation() + }) + $('body').click(self.close) + + self.attachEventListeners() + + self.generateBoxHTML = Hogan.compile($('#mapInfoBoxTemplate').html()) + }, + toggleBox: function (event) { + var self = Metamaps.Map.InfoBox + + if (self.isOpen) self.close() + else self.open() + + event.stopPropagation() + }, + open: function () { + var self = Metamaps.Map.InfoBox + $('.mapInfoIcon div').addClass('hide') + if (!self.isOpen && !self.changing) { + self.changing = true + $('.mapInfoBox').fadeIn(200, function () { + self.changing = false + self.isOpen = true + }) + } + }, + close: function () { + var self = Metamaps.Map.InfoBox + + $('.mapInfoIcon div').removeClass('hide') + if (!self.changing) { + self.changing = true + $('.mapInfoBox').fadeOut(200, function () { + self.changing = false + self.isOpen = false + self.hidePermissionSelect() + $('.mapContributors .tip').hide() + }) + } + }, + load: function () { + var self = Metamaps.Map.InfoBox + + var map = Metamaps.Active.Map + + var obj = map.pick('permission', 'contributor_count', 'topic_count', 'synapse_count') + + var isCreator = map.authorizePermissionChange(Metamaps.Active.Mapper) + var canEdit = map.authorizeToEdit(Metamaps.Active.Mapper) + var shareable = map.get('permission') !== 'private' + + obj['name'] = canEdit ? Hogan.compile(self.nameHTML).render({id: map.id, name: map.get('name')}) : map.get('name') + obj['desc'] = canEdit ? Hogan.compile(self.descHTML).render({id: map.id, desc: map.get('desc')}) : map.get('desc') + obj['map_creator_tip'] = isCreator ? self.changePermissionText : '' + obj['contributors_class'] = Metamaps.Mappers.length > 1 ? 'multiple' : '' + obj['contributors_class'] += Metamaps.Mappers.length === 2 ? ' mTwo' : '' + obj['contributor_image'] = Metamaps.Mappers.length > 0 ? Metamaps.Mappers.models[0].get('image') : "<%= asset_path('user.png') %>" + obj['contributor_list'] = self.createContributorList() + obj['user_name'] = isCreator ? 'You' : map.get('user_name') + obj['created_at'] = map.get('created_at_clean') + obj['updated_at'] = map.get('updated_at_clean') + + var classes = isCreator ? 'yourMap' : '' + classes += canEdit ? ' canEdit' : '' + classes += shareable ? ' shareable' : '' + $('.mapInfoBox').removeClass('shareable yourMap canEdit') + .addClass(classes) + .html(self.generateBoxHTML.render(obj)) + + self.attachEventListeners() + }, + attachEventListeners: function () { + var self = Metamaps.Map.InfoBox + + $('.mapInfoBox.canEdit .best_in_place').best_in_place() + + // because anyone who can edit the map can change the map title + var bipName = $('.mapInfoBox .best_in_place_name') + bipName.unbind('best_in_place:activate').bind('best_in_place:activate', function () { + var $el = bipName.find('textarea') + var el = $el[0] + + $el.attr('maxlength', '140') + + $('.mapInfoName').append('
') + + var callback = function (data) { + $('.nameCounter.forMap').html(data.all + '/140') + } + Countable.live(el, callback) + }) + bipName.unbind('best_in_place:deactivate').bind('best_in_place:deactivate', function () { + $('.nameCounter.forMap').remove() + }) + + $('.mapInfoName .best_in_place_name').unbind('ajax:success').bind('ajax:success', function () { + var name = $(this).html() + Metamaps.Active.Map.set('name', name) + Metamaps.Active.Map.trigger('saved') + }) + + $('.mapInfoDesc .best_in_place_desc').unbind('ajax:success').bind('ajax:success', function () { + var desc = $(this).html() + Metamaps.Active.Map.set('desc', desc) + Metamaps.Active.Map.trigger('saved') + }) + + $('.yourMap .mapPermission').unbind().click(self.onPermissionClick) + // .yourMap in the unbind/bind is just a namespace for the events + // not a reference to the class .yourMap on the .mapInfoBox + $('.mapInfoBox.yourMap').unbind('.yourMap').bind('click.yourMap', self.hidePermissionSelect) + + $('.yourMap .mapInfoDelete').unbind().click(self.deleteActiveMap) + + $('.mapContributors span, #mapContribs').unbind().click(function (event) { + $('.mapContributors .tip').toggle() + event.stopPropagation() + }) + $('.mapContributors .tip').unbind().click(function (event) { + event.stopPropagation() + }) + $('.mapContributors .tip li a').click(Metamaps.Router.intercept) + + $('.mapInfoBox').unbind('.hideTip').bind('click.hideTip', function () { + $('.mapContributors .tip').hide() + }) + }, + updateNameDescPerm: function (name, desc, perm) { + $('.mapInfoName .best_in_place_name').html(name) + $('.mapInfoDesc .best_in_place_desc').html(desc) + $('.mapInfoBox .mapPermission').removeClass('commons public private').addClass(perm) + }, + createContributorList: function () { + var self = Metamaps.Map.InfoBox + + var string = '' + string += '' + return string + }, + updateNumbers: function () { + var self = Metamaps.Map.InfoBox + var mapper = Metamaps.Active.Mapper + + var contributors_class = '' + if (Metamaps.Mappers.length === 2) contributors_class = 'multiple mTwo' + else if (Metamaps.Mappers.length > 2) contributors_class = 'multiple' + + var contributors_image = "<%= asset_path('user.png') %>" + if (Metamaps.Mappers.length > 0) { + // get the first contributor and use their image + contributors_image = Metamaps.Mappers.models[0].get('image') + } + $('.mapContributors img').attr('src', contributors_image).removeClass('multiple mTwo').addClass(contributors_class) + $('.mapContributors span').text(Metamaps.Mappers.length) + $('.mapContributors .tip').html(self.createContributorList()) + $('.mapTopics').text(Metamaps.Topics.length) + $('.mapSynapses').text(Metamaps.Synapses.length) + + $('.mapEditedAt').html('Last edited: ' + Metamaps.Util.nowDateFormatted()) + }, + onPermissionClick: function (event) { + var self = Metamaps.Map.InfoBox + + if (!self.selectingPermission) { + self.selectingPermission = true + $(this).addClass('minimize') // this line flips the drop down arrow to a pull up arrow + if ($(this).hasClass('commons')) { + $(this).append('') + } else if ($(this).hasClass('public')) { + $(this).append('') + } else if ($(this).hasClass('private')) { + $(this).append('') + } + $('.mapPermission .permissionSelect li').click(self.selectPermission) + event.stopPropagation() + } + }, + hidePermissionSelect: function () { + var self = Metamaps.Map.InfoBox + + self.selectingPermission = false + $('.mapPermission').removeClass('minimize') // this line flips the pull up arrow to a drop down arrow + $('.mapPermission .permissionSelect').remove() + }, + selectPermission: function (event) { + var self = Metamaps.Map.InfoBox + + self.selectingPermission = false + var permission = $(this).attr('class') + var permBefore = Metamaps.Active.Map.get('permission') + Metamaps.Active.Map.save({ + permission: permission + }) + Metamaps.Active.Map.updateMapWrapper() + if (permBefore !== 'commons' && permission === 'commons') { + Metamaps.Realtime.setupSocket() + Metamaps.Realtime.turnOn() + } + else if (permBefore === 'commons' && permission === 'public') { + Metamaps.Realtime.turnOff(true); // true is to 'silence' + // the notification that would otherwise be sent + } + shareable = permission === 'private' ? '' : 'shareable' + $('.mapPermission').removeClass('commons public private minimize').addClass(permission) + $('.mapPermission .permissionSelect').remove() + $('.mapInfoBox').removeClass('shareable').addClass(shareable) + event.stopPropagation() + }, + deleteActiveMap: function () { + var confirmString = 'Are you sure you want to delete this map? ' + confirmString += 'This action is irreversible. It will not delete the topics and synapses on the map.' + + var doIt = confirm(confirmString) + var map = Metamaps.Active.Map + var mapper = Metamaps.Active.Mapper + var authorized = map.authorizePermissionChange(mapper) + + if (doIt && authorized) { + Metamaps.Map.InfoBox.close() + Metamaps.Maps.Active.remove(map) + Metamaps.Maps.Featured.remove(map) + Metamaps.Maps.Mine.remove(map) + map.destroy() + Metamaps.Router.home() + Metamaps.GlobalUI.notifyUser('Map eliminated!') + } + else if (!authorized) { + alert("Hey now. We can't just go around willy nilly deleting other people's maps now can we? Run off and find something constructive to do, eh?") + } + } +}; // end Metamaps.Map.InfoBox diff --git a/app/assets/javascripts/src/Metamaps.Mapper.js b/app/assets/javascripts/src/Metamaps.Mapper.js new file mode 100644 index 00000000..7d565479 --- /dev/null +++ b/app/assets/javascripts/src/Metamaps.Mapper.js @@ -0,0 +1,20 @@ +/* global Metamaps, $ */ + +/* + * Metamaps.Mapper.js.erb + * + * Dependencies: none! + */ + +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) { + return $.ajax({ + url: '/users/' + id + '.json', + success: function (data) { + callback(new Metamaps.Backbone.Mapper(data)) + } + }) + } +}; // end Metamaps.Mapper diff --git a/app/assets/javascripts/src/Metamaps.Organize.js b/app/assets/javascripts/src/Metamaps.Organize.js new file mode 100644 index 00000000..b2463280 --- /dev/null +++ b/app/assets/javascripts/src/Metamaps.Organize.js @@ -0,0 +1,117 @@ +/* global Metamaps, $ */ + +/* + * Metamaps.Organize.js.erb + * + * Dependencies: + * - Metamaps.Visualize + */ +Metamaps.Organize = { + init: function () {}, + arrange: function (layout, 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') { + // this will always be an integer, the # of nodes on your graph visualization + var numNodes = _.size(Metamaps.Visualize.mGraph.graph.nodes) + // 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 + 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) + } 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 + } else alert('please call function with a valid layout dammit!') + } +}; // end Metamaps.Organize diff --git a/app/assets/javascripts/src/Metamaps.Router.js.erb b/app/assets/javascripts/src/Metamaps.Router.js similarity index 100% rename from app/assets/javascripts/src/Metamaps.Router.js.erb rename to app/assets/javascripts/src/Metamaps.Router.js diff --git a/app/assets/javascripts/src/Metamaps.Synapse.js b/app/assets/javascripts/src/Metamaps.Synapse.js new file mode 100644 index 00000000..ceed219d --- /dev/null +++ b/app/assets/javascripts/src/Metamaps.Synapse.js @@ -0,0 +1,169 @@ +/* global Metamaps, $ */ + +/* + * Metamaps.Synapse.js.erb + * + * Dependencies: + * - Metamaps.Backbone + * - Metamaps.Control + * - Metamaps.Create + * - Metamaps.JIT + * - Metamaps.Map + * - Metamaps.Mappings + * - Metamaps.Selected + * - Metamaps.Settings + * - Metamaps.Synapses + * - Metamaps.Topics + * - Metamaps.Visualize + */ + +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 = synapse.createEdge(mapping) + + Metamaps.Visualize.mGraph.graph.addAdjacence(node1, node2, newedge.data) + edgeOnViz = Metamaps.Visualize.mGraph.graph.getAdjacence(node1.id, node2.id) + synapse.set('edge', edgeOnViz) + synapse.updateEdge() // links the synapse and the mapping to the edge + + Metamaps.Control.selectEdge(edgeOnViz) + + var mappingSuccessCallback = function (mappingModel, response) { + var newSynapseData = { + mappingid: mappingModel.id, + mappableid: mappingModel.get('mappable_id') + } + + $(document).trigger(Metamaps.JIT.events.newSynapse, [newSynapseData]) + } + var synapseSuccessCallback = function (synapseModel, response) { + if (Metamaps.Active.Map) { + mapping.save({ mappable_id: synapseModel.id }, { + success: mappingSuccessCallback + }) + } + } + + if (!Metamaps.Settings.sandbox && createNewInDB) { + if (synapse.isNew()) { + synapse.save(null, { + success: synapseSuccessCallback, + error: function (model, response) { + console.log('error saving synapse to database') + } + }) + } else if (!synapse.isNew() && Metamaps.Active.Map) { + mapping.save(null, { + success: mappingSuccessCallback + }) + } + } + }, + createSynapseLocally: function () { + var self = Metamaps.Synapse, + topic1, + topic2, + node1, + node2, + synapse, + mapping + + $(document).trigger(Metamaps.Map.events.editedByActiveMapper) + + // for each node in this array we will create a synapse going to the position2 node. + var synapsesToCreate = [] + + topic2 = Metamaps.Topics.get(Metamaps.Create.newSynapse.topic2id) + node2 = topic2.get('node') + + var len = Metamaps.Selected.Nodes.length + if (len == 0) { + topic1 = Metamaps.Topics.get(Metamaps.Create.newSynapse.topic1id) + synapsesToCreate[0] = topic1.get('node') + } else if (len > 0) { + synapsesToCreate = Metamaps.Selected.Nodes + } + + 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({ + mappable_type: 'Synapse', + mappable_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, + topic1, + topic2, + node1, + node2 + + var synapse = self.get(id) + + var mapping = new Metamaps.Backbone.Mapping({ + mappable_type: 'Synapse', + mappable_id: synapse.id, + }) + Metamaps.Mappings.add(mapping) + + topic1 = Metamaps.Topics.get(Metamaps.Create.newSynapse.topic1id) + node1 = topic1.get('node') + topic2 = Metamaps.Topics.get(Metamaps.Create.newSynapse.topic2id) + node2 = topic2.get('node') + Metamaps.Create.newSynapse.hide() + + self.renderSynapse(mapping, synapse, node1, node2, true) + } +}; // end Metamaps.Synapse diff --git a/app/assets/javascripts/src/Metamaps.Topic.js b/app/assets/javascripts/src/Metamaps.Topic.js new file mode 100644 index 00000000..dc242f7d --- /dev/null +++ b/app/assets/javascripts/src/Metamaps.Topic.js @@ -0,0 +1,362 @@ +/* global Metamaps, $ */ + +/* + * Metamaps.Topic.js.erb + * + * Dependencies: + * - Metamaps.Active + * - Metamaps.Backbone + * - Metamaps.Backbone + * - Metamaps.Create + * - Metamaps.Creators + * - Metamaps.Famous + * - Metamaps.Filter + * - Metamaps.GlobalUI + * - Metamaps.JIT + * - Metamaps.Mappings + * - Metamaps.Selected + * - Metamaps.Settings + * - Metamaps.SynapseCard + * - Metamaps.Synapses + * - Metamaps.TopicCard + * - Metamaps.Topics + * - Metamaps.Util + * - Metamaps.Visualize + * - Metamaps.tempInit + * - Metamaps.tempNode + * - Metamaps.tempNode2 + */ + +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)) + } + } + }, + launch: function (id) { + var bb = Metamaps.Backbone + var start = function (data) { + Metamaps.Active.Topic = new bb.Topic(data.topic) + Metamaps.Creators = new bb.MapperCollection(data.creators) + Metamaps.Topics = new bb.TopicCollection([data.topic].concat(data.relatives)) + Metamaps.Synapses = new bb.SynapseCollection(data.synapses) + Metamaps.Backbone.attachCollectionEvents() + + // set filter mapper H3 text + $('#filter_by_mapper h3').html('CREATORS') + + // build and render the visualization + Metamaps.Visualize.type = 'RGraph' + Metamaps.JIT.prepareVizData() + + // update filters + Metamaps.Filter.reset() + + // reset selected arrays + Metamaps.Selected.reset() + + // these three update the actual filter box with the right list items + Metamaps.Filter.checkMetacodes() + Metamaps.Filter.checkSynapses() + Metamaps.Filter.checkMappers() + } + + $.ajax({ + url: '/topics/' + id + '/network.json', + success: start + }) + }, + end: function () { + if (Metamaps.Active.Topic) { + $('.rightclickmenu').remove() + Metamaps.TopicCard.hideCard() + Metamaps.SynapseCard.hideCard() + Metamaps.Filter.close() + } + }, + centerOn: function (nodeid) { + if (!Metamaps.Visualize.mGraph.busy) { + Metamaps.Visualize.mGraph.onClick(nodeid, { + hideLabels: false, + duration: 1000, + onComplete: function () {} + }) + } + }, + fetchRelatives: function (node, metacode_id) { + var topics = Metamaps.Topics.map(function (t) { return t.id }) + var topics_string = topics.join() + + var creators = Metamaps.Creators.map(function (t) { return t.id }) + var creators_string = creators.join() + + var topic = node.getData('topic') + + var successCallback = function (data) { + if (data.creators.length > 0) Metamaps.Creators.add(data.creators) + if (data.topics.length > 0) Metamaps.Topics.add(data.topics) + if (data.synapses.length > 0) Metamaps.Synapses.add(data.synapses) + + var topicColl = new Metamaps.Backbone.TopicCollection(data.topics) + topicColl.add(topic) + var synapseColl = new Metamaps.Backbone.SynapseCollection(data.synapses) + + var graph = Metamaps.JIT.convertModelsToJIT(topicColl, synapseColl)[0] + Metamaps.Visualize.mGraph.op.sum(graph, { + type: 'fade', + duration: 500, + hideLabels: false + }) + + var i, l, t, s + + Metamaps.Visualize.mGraph.graph.eachNode(function (n) { + t = Metamaps.Topics.get(n.id) + t.set({ node: n }, { silent: true }) + t.updateNode() + + n.eachAdjacency(function (edge) { + if (!edge.getData('init')) { + edge.setData('init', true) + + l = edge.getData('synapseIDs').length + for (i = 0; i < l; i++) { + s = Metamaps.Synapses.get(edge.getData('synapseIDs')[i]) + s.set({ edge: edge }, { silent: true }) + s.updateEdge() + } + } + }) + }) + } + + var paramsString = metacode_id ? 'metacode=' + metacode_id + '&' : '' + paramsString += 'network=' + topics_string + '&creators=' + creators_string + + $.ajax({ + type: 'Get', + url: '/topics/' + topic.id + '/relatives.json?' + paramsString, + success: successCallback, + error: function () {} + }) + }, + /* + * + * + */ + renderTopic: function (mapping, topic, createNewInDB, permitCreateSynapseAfter) { + var self = Metamaps.Topic + + var nodeOnViz, tempPos + + var newnode = topic.createNode() + + var midpoint = {}, pixelPos + + if (!$.isEmptyObject(Metamaps.Visualize.mGraph.graph.nodes)) { + Metamaps.Visualize.mGraph.graph.addNode(newnode) + nodeOnViz = Metamaps.Visualize.mGraph.graph.getNode(newnode.id) + topic.set('node', nodeOnViz, {silent: true}) + topic.updateNode() // links the topic and the mapping to the node + + nodeOnViz.setData('dim', 1, 'start') + nodeOnViz.setData('dim', 25, '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 && permitCreateSynapseAfter) { + Metamaps.Create.newSynapse.topic1id = Metamaps.tempNode.getData('topic').id + + // position the form + midpoint.x = Metamaps.tempNode.pos.getc().x + (nodeOnViz.pos.getc().x - Metamaps.tempNode.pos.getc().x) / 2 + midpoint.y = Metamaps.tempNode.pos.getc().y + (nodeOnViz.pos.getc().y - Metamaps.tempNode.pos.getc().y) / 2 + pixelPos = Metamaps.Util.coordsToPixels(midpoint) + $('#new_synapse').css('left', pixelPos.x + 'px') + $('#new_synapse').css('top', pixelPos.y + 'px') + // show the form + Metamaps.Create.newSynapse.open() + Metamaps.Visualize.mGraph.fx.animate({ + modes: ['node-property:dim'], + duration: 500, + onComplete: function () { + Metamaps.tempNode = null + Metamaps.tempNode2 = null + Metamaps.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) + topic.set('node', nodeOnViz, {silent: true}) + topic.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 () {} + }) + } + + var mappingSuccessCallback = function (mappingModel, response) { + var newTopicData = { + mappingid: mappingModel.id, + mappableid: mappingModel.get('mappable_id') + } + + $(document).trigger(Metamaps.JIT.events.newTopic, [newTopicData]) + } + var topicSuccessCallback = function (topicModel, response) { + if (Metamaps.Active.Map) { + mapping.save({ mappable_id: topicModel.id }, { + success: mappingSuccessCallback, + error: function (model, response) { + console.log('error saving mapping to database') + } + }) + } + + if (Metamaps.Create.newTopic.addSynapse) { + Metamaps.Create.newSynapse.topic2id = topicModel.id + } + } + + if (!Metamaps.Settings.sandbox && createNewInDB) { + if (topic.isNew()) { + topic.save(null, { + success: topicSuccessCallback, + error: function (model, response) { + console.log('error saving topic to database') + } + }) + } else if (!topic.isNew() && Metamaps.Active.Map) { + mapping.save(null, { + success: mappingSuccessCallback + }) + } + } + }, + createTopicLocally: function () { + var self = Metamaps.Topic + + if (Metamaps.Create.newTopic.name === '') { + Metamaps.GlobalUI.notifyUser('Please enter a topic title...') + return + } + + // hide the 'double-click to add a topic' message + Metamaps.Famous.viz.hideInstructions() + + $(document).trigger(Metamaps.Map.events.editedByActiveMapper) + + var metacode = Metamaps.Metacodes.get(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({ + xloc: Metamaps.Create.newTopic.x, + yloc: Metamaps.Create.newTopic.y, + mappable_id: topic.cid, + mappable_type: 'Topic', + }) + Metamaps.Mappings.add(mapping) + + // these can't happen until the value is retrieved, which happens in the line above + Metamaps.Create.newTopic.hide() + + self.renderTopic(mapping, topic, true, true) // this function also includes the creation of the topic in the database + }, + getTopicFromAutocomplete: function (id) { + var self = Metamaps.Topic + + $(document).trigger(Metamaps.Map.events.editedByActiveMapper) + + Metamaps.Create.newTopic.hide() + + var topic = self.get(id) + + var mapping = new Metamaps.Backbone.Mapping({ + xloc: Metamaps.Create.newTopic.x, + yloc: Metamaps.Create.newTopic.y, + mappable_type: 'Topic', + mappable_id: topic.id, + }) + Metamaps.Mappings.add(mapping) + + self.renderTopic(mapping, topic, true, true) + }, + getTopicFromSearch: function (event, id) { + var self = Metamaps.Topic + + $(document).trigger(Metamaps.Map.events.editedByActiveMapper) + + var topic = self.get(id) + + var nextCoords = Metamaps.Map.getNextCoord() + var mapping = new Metamaps.Backbone.Mapping({ + xloc: nextCoords.x, + yloc: nextCoords.y, + mappable_type: 'Topic', + mappable_id: topic.id, + }) + Metamaps.Mappings.add(mapping) + + self.renderTopic(mapping, topic, true, true) + + Metamaps.GlobalUI.notifyUser('Topic was added to your map!') + + event.stopPropagation() + event.preventDefault() + return false + } +}; // end Metamaps.Topic diff --git a/app/assets/javascripts/src/Metamaps.Views.js.erb b/app/assets/javascripts/src/Metamaps.Views.js similarity index 100% rename from app/assets/javascripts/src/Metamaps.Views.js.erb rename to app/assets/javascripts/src/Metamaps.Views.js diff --git a/app/assets/javascripts/src/Metamaps.js.erb b/app/assets/javascripts/src/Metamaps.js.erb index 70252ea8..592c7990 100644 --- a/app/assets/javascripts/src/Metamaps.js.erb +++ b/app/assets/javascripts/src/Metamaps.js.erb @@ -3121,2399 +3121,3 @@ Metamaps.Realtime = { } }, }; // end Metamaps.Realtime - - -/* - * - * CONTROL - * - */ -Metamaps.Control = { - init: function () { - - }, - selectNode: function (node,e) { - var filtered = node.getData('alpha') === 0; - - if (filtered || Metamaps.Selected.Nodes.indexOf(node) != -1) return; - node.selected = true; - node.setData('dim', 30, 'current'); - Metamaps.Selected.Nodes.push(node); - }, - deselectAllNodes: function () { - var l = Metamaps.Selected.Nodes.length; - for (var i = l - 1; i >= 0; i -= 1) { - var node = Metamaps.Selected.Nodes[i]; - Metamaps.Control.deselectNode(node); - } - Metamaps.Visualize.mGraph.plot(); - }, - deselectNode: function (node) { - delete node.selected; - node.setData('dim', 25, 'current'); - - //remove the node - Metamaps.Selected.Nodes.splice( - Metamaps.Selected.Nodes.indexOf(node), 1); - }, - deleteSelected: function () { - - if (!Metamaps.Active.Map) return; - - var n = Metamaps.Selected.Nodes.length; - var e = Metamaps.Selected.Edges.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 authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); - - if (!authorized) { - Metamaps.GlobalUI.notifyUser("Cannot edit Public map."); - return; - } - - 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(); - } - }, - deleteSelectedNodes: function () { // refers to deleting topics permanently - - if (!Metamaps.Active.Map) return; - - var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); - - if (!authorized) { - Metamaps.GlobalUI.notifyUser("Cannot edit Public map."); - return; - } - - var l = Metamaps.Selected.Nodes.length; - for (var i = l - 1; i >= 0; i -= 1) { - var node = Metamaps.Selected.Nodes[i]; - Metamaps.Control.deleteNode(node.id); - } - }, - deleteNode: function (nodeid) { // refers to deleting topics permanently - - if (!Metamaps.Active.Map) return; - - var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); - - if (!authorized) { - Metamaps.GlobalUI.notifyUser("Cannot edit Public map."); - return; - } - - var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid); - var topic = node.getData('topic'); - - var permToDelete = Metamaps.Active.Mapper.id === topic.get('user_id') || Metamaps.Active.Mapper.get('admin'); - if (permToDelete) { - var mappableid = topic.id; - var mapping = node.getData('mapping'); - topic.destroy(); - Metamaps.Mappings.remove(mapping); - $(document).trigger(Metamaps.JIT.events.deleteTopic, [{ - mappableid: mappableid - }]); - Metamaps.Control.hideNode(nodeid); - } else { - Metamaps.GlobalUI.notifyUser('Only topics you created can be deleted'); - } - }, - removeSelectedNodes: function () { // refers to removing topics permanently from a map - - if (!Metamaps.Active.Map) return; - - var l = Metamaps.Selected.Nodes.length, - i, - node, - authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); - - if (!authorized) { - Metamaps.GlobalUI.notifyUser("Cannot edit Public map."); - return; - } - - for (i = l - 1; i >= 0; i -= 1) { - node = Metamaps.Selected.Nodes[i]; - Metamaps.Control.removeNode(node.id); - } - }, - removeNode: function (nodeid) { // refers to removing topics permanently from a map - - if (!Metamaps.Active.Map) return; - - var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); - var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid); - - if (!authorized) { - Metamaps.GlobalUI.notifyUser("Cannot edit Public map."); - return; - } - - var topic = node.getData('topic'); - var mappableid = topic.id; - var mapping = node.getData('mapping'); - mapping.destroy(); - Metamaps.Topics.remove(topic); - $(document).trigger(Metamaps.JIT.events.removeTopic, [{ - mappableid: mappableid - }]); - Metamaps.Control.hideNode(nodeid); - }, - hideSelectedNodes: function () { - var l = Metamaps.Selected.Nodes.length, - i, - node; - - for (i = l - 1; i >= 0; i -= 1) { - node = Metamaps.Selected.Nodes[i]; - Metamaps.Control.hideNode(node.id); - } - }, - hideNode: function (nodeid) { - var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid); - var graph = Metamaps.Visualize.mGraph; - - 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 () { - if (nodeid == Metamaps.Visualize.mGraph.root) { // && Metamaps.Visualize.type === "RGraph" - var newroot = _.find(graph.graph.nodes, function(n){ return n.id !== nodeid; }); - graph.root = newroot ? newroot.id : null; - } - Metamaps.Visualize.mGraph.graph.removeNode(nodeid); - }, 500); - Metamaps.Filter.checkMetacodes(); - Metamaps.Filter.checkMappers(); - }, - selectEdge: function (edge) { - var filtered = edge.getData('alpha') === 0; // don't select if the edge is filtered - - if (filtered || Metamaps.Selected.Edges.indexOf(edge) != -1) return; - - var width = Metamaps.Mouse.edgeHoveringOver === edge ? 4 : 2; - edge.setDataset('current', { - showDesc: true, - lineWidth: width, - color: Metamaps.Settings.colors.synapses.selected - }); - Metamaps.Visualize.mGraph.plot(); - - Metamaps.Selected.Edges.push(edge); - }, - deselectAllEdges: function () { - var l = Metamaps.Selected.Edges.length; - for (var i = l - 1; i >= 0; i -= 1) { - var edge = Metamaps.Selected.Edges[i]; - Metamaps.Control.deselectEdge(edge); - } - Metamaps.Visualize.mGraph.plot(); - }, - deselectEdge: function (edge) { - edge.setData('showDesc', false, 'current'); - - edge.setDataset('current', { - lineWidth: 2, - color: Metamaps.Settings.colors.synapses.normal - }); - - if (Metamaps.Mouse.edgeHoveringOver == edge) { - edge.setDataset('current', { - showDesc: true, - lineWidth: 4 - }); - } - - Metamaps.Visualize.mGraph.plot(); - - //remove the edge - Metamaps.Selected.Edges.splice( - Metamaps.Selected.Edges.indexOf(edge), 1); - }, - deleteSelectedEdges: function () { // refers to deleting topics permanently - var edge, - l = Metamaps.Selected.Edges.length; - - if (!Metamaps.Active.Map) return; - - var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); - - if (!authorized) { - Metamaps.GlobalUI.notifyUser("Cannot edit Public map."); - return; - } - - for (var i = l - 1; i >= 0; i -= 1) { - edge = Metamaps.Selected.Edges[i]; - Metamaps.Control.deleteEdge(edge); - } - }, - deleteEdge: function (edge) { - - if (!Metamaps.Active.Map) return; - - var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); - - if (!authorized) { - Metamaps.GlobalUI.notifyUser("Cannot edit Public map."); - return; - } - - var index = edge.getData("displayIndex") ? edge.getData("displayIndex") : 0; - - var synapse = edge.getData("synapses")[index]; - var mapping = edge.getData("mappings")[index]; - - var permToDelete = Metamaps.Active.Mapper.id === synapse.get('user_id') || Metamaps.Active.Mapper.get('admin'); - if (permToDelete) { - if (edge.getData("synapses").length - 1 === 0) { - Metamaps.Control.hideEdge(edge); - } - var mappableid = synapse.id; - synapse.destroy(); - - // the server will destroy the mapping, we just need to remove it here - Metamaps.Mappings.remove(mapping); - edge.getData("mappings").splice(index, 1); - edge.getData("synapses").splice(index, 1); - if (edge.getData("displayIndex")) { - delete edge.data.$displayIndex; - } - $(document).trigger(Metamaps.JIT.events.deleteSynapse, [{ - mappableid: mappableid - }]); - } else { - Metamaps.GlobalUI.notifyUser('Only synapses you created can be deleted'); - } - }, - removeSelectedEdges: function () { - var l = Metamaps.Selected.Edges.length, - i, - edge; - - if (!Metamaps.Active.Map) return; - - var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); - - if (!authorized) { - Metamaps.GlobalUI.notifyUser("Cannot edit Public map."); - return; - } - - for (i = l - 1; i >= 0; i -= 1) { - edge = Metamaps.Selected.Edges[i]; - Metamaps.Control.removeEdge(edge); - } - Metamaps.Selected.Edges = new Array(); - }, - removeEdge: function (edge) { - - if (!Metamaps.Active.Map) return; - - var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); - - if (!authorized) { - Metamaps.GlobalUI.notifyUser("Cannot edit Public map."); - return; - } - - if (edge.getData("mappings").length - 1 === 0) { - Metamaps.Control.hideEdge(edge); - } - - var index = edge.getData("displayIndex") ? edge.getData("displayIndex") : 0; - - var synapse = edge.getData("synapses")[index]; - var mapping = edge.getData("mappings")[index]; - var mappableid = synapse.id; - mapping.destroy(); - - Metamaps.Synapses.remove(synapse); - - edge.getData("mappings").splice(index, 1); - edge.getData("synapses").splice(index, 1); - if (edge.getData("displayIndex")) { - delete edge.data.$displayIndex; - } - $(document).trigger(Metamaps.JIT.events.removeSynapse, [{ - mappableid: mappableid - }]); - }, - hideSelectedEdges: function () { - var edge, - l = Metamaps.Selected.Edges.length, - i; - for (i = l - 1; i >= 0; i -= 1) { - edge = Metamaps.Selected.Edges[i]; - Metamaps.Control.hideEdge(edge); - } - Metamaps.Selected.Edges = new Array(); - }, - hideEdge: function (edge) { - var from = edge.nodeFrom.id; - var to = edge.nodeTo.id; - edge.setData('alpha', 0, 'end'); - Metamaps.Control.deselectEdge(edge); - Metamaps.Visualize.mGraph.fx.animate({ - modes: ['edge-property:alpha'], - duration: 500 - }); - setTimeout(function () { - Metamaps.Visualize.mGraph.graph.removeAdjacence(from, to); - }, 500); - Metamaps.Filter.checkSynapses(); - Metamaps.Filter.checkMappers(); - }, - updateSelectedPermissions: function (permission) { - - var edge, synapse, node, topic; - - Metamaps.GlobalUI.notifyUser('Working...'); - - // 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.Edges.length; - for (var i = l - 1; i >= 0; i -= 1) { - edge = Metamaps.Selected.Edges[i]; - synapse = edge.getData('synapses')[0]; - - if (synapse.authorizePermissionChange(Metamaps.Active.Mapper)) { - synapse.save({ - permission: permission - }); - sCount++; - } - } - - // change the permission of the selected topics, if logged in user is the original creator - var l = Metamaps.Selected.Nodes.length; - for (var i = l - 1; i >= 0; i -= 1) { - node = Metamaps.Selected.Nodes[i]; - topic = node.getData('topic'); - - if (topic.authorizePermissionChange(Metamaps.Active.Mapper)) { - topic.save({ - permission: permission - }); - nCount++; - } - } - - var nString = nCount == 1 ? (nCount.toString() + ' topic and ') : (nCount.toString() + ' topics and '); - var sString = sCount == 1 ? (sCount.toString() + ' synapse') : (sCount.toString() + ' synapses'); - - var message = nString + sString + ' you created updated to ' + permission; - Metamaps.GlobalUI.notifyUser(message); - }, - updateSelectedMetacodes: function (metacode_id) { - - var node, topic; - - Metamaps.GlobalUI.notifyUser('Working...'); - - var metacode = Metamaps.Metacodes.get(metacode_id); - - // variables to keep track of how many nodes and synapses you had the ability to change the permission of - var nCount = 0; - - // change the permission of the selected topics, if logged in user is the original creator - var l = Metamaps.Selected.Nodes.length; - for (var i = l - 1; i >= 0; i -= 1) { - node = Metamaps.Selected.Nodes[i]; - topic = node.getData('topic'); - - if (topic.authorizeToEdit(Metamaps.Active.Mapper)) { - topic.save({ - 'metacode_id': metacode_id - }); - nCount++; - } - } - - var nString = nCount == 1 ? (nCount.toString() + ' topic') : (nCount.toString() + ' topics'); - - var message = nString + ' you can edit updated to ' + metacode.get('name'); - Metamaps.GlobalUI.notifyUser(message); - Metamaps.Visualize.mGraph.plot(); - }, -}; // end Metamaps.Control - - -/* - * - * FILTER - * - */ -Metamaps.Filter = { - filters: { - name: "", - metacodes: [], - mappers: [], - synapses: [] - }, - visible: { - metacodes: [], - mappers: [], - synapses: [] - }, - isOpen: false, - changing: false, - init: function () { - var self = Metamaps.Filter; - - $('.sidebarFilterIcon').click(self.toggleBox); - - $('.sidebarFilterBox .showAllMetacodes').click(self.filterNoMetacodes); - $('.sidebarFilterBox .showAllSynapses').click(self.filterNoSynapses); - $('.sidebarFilterBox .showAllMappers').click(self.filterNoMappers); - $('.sidebarFilterBox .hideAllMetacodes').click(self.filterAllMetacodes); - $('.sidebarFilterBox .hideAllSynapses').click(self.filterAllSynapses); - $('.sidebarFilterBox .hideAllMappers').click(self.filterAllMappers); - - self.bindLiClicks(); - self.getFilterData(); - }, - toggleBox: function (event) { - var self = Metamaps.Filter; - - if (self.isOpen) self.close(); - else self.open(); - - event.stopPropagation(); - }, - open: function () { - var self = Metamaps.Filter; - - Metamaps.GlobalUI.Account.close(); - $('.sidebarFilterIcon div').addClass('hide'); - - - if (!self.isOpen && !self.changing) { - self.changing = true; - - var height = $(document).height() - 108; - $('.sidebarFilterBox').css('max-height', height + 'px').fadeIn(200, function () { - self.changing = false; - self.isOpen = true; - }); - } - }, - close: function () { - var self = Metamaps.Filter; - $('.sidebarFilterIcon div').removeClass('hide'); - - - if (!self.changing) { - self.changing = true; - - $('.sidebarFilterBox').fadeOut(200, function () { - self.changing = false; - self.isOpen = false; - }); - } - }, - reset: function () { - var self = Metamaps.Filter; - - self.filters.metacodes = []; - self.filters.mappers = []; - self.filters.synapses = []; - self.visible.metacodes = []; - self.visible.mappers = []; - self.visible.synapses = []; - - $('#filter_by_metacode ul').empty(); - $('#filter_by_mapper ul').empty(); - $('#filter_by_synapse ul').empty(); - - $('.filterBox .showAll').addClass('active'); - }, - /* - Most of this data essentially depends on the ruby function which are happening for filter inside view filterBox - But what these function do is load this data into three accessible array within java : metacodes, mappers and synapses - */ - getFilterData: function () { - var self = Metamaps.Filter; - - var metacode, mapper, synapse; - - $('#filter_by_metacode li').each(function() { - metacode = $( this ).attr('data-id'); - self.filters.metacodes.push(metacode); - self.visible.metacodes.push(metacode); - }); - - $('#filter_by_mapper li').each(function() { - mapper = ($( this ).attr('data-id')); - self.filters.mappers.push(mapper); - self.visible.mappers.push(mapper); - }); - - $('#filter_by_synapse li').each(function() { - synapse = ($( this ).attr('data-id')); - self.filters.synapses.push(synapse); - self.visible.synapses.push(synapse); - }); - }, - bindLiClicks: function () { - var self = Metamaps.Filter; - $('#filter_by_metacode ul li').unbind().click(self.toggleMetacode); - $('#filter_by_mapper ul li').unbind().click(self.toggleMapper); - $('#filter_by_synapse ul li').unbind().click(self.toggleSynapse); - }, - // an abstraction function for checkMetacodes, checkMappers, checkSynapses to reduce - // code redundancy - /* - @param - */ - updateFilters: function (collection, propertyToCheck, correlatedModel, filtersToUse, listToModify) { - var self = Metamaps.Filter; - - var newList = []; - var removed = []; - var added = []; - - // the first option enables us to accept - // ['Topics', 'Synapses'] as 'collection' - if (typeof collection === "object") { - Metamaps[collection[0]].each(function(model) { - var prop = model.get(propertyToCheck); - if (prop !== null) { - prop = prop.toString(); - if (newList.indexOf(prop) === -1) { - newList.push(prop); - } - } - }); - Metamaps[collection[1]].each(function(model) { - var prop = model.get(propertyToCheck); - if (prop !== null) { - prop = prop.toString(); - if (newList.indexOf(prop) === -1) { - newList.push(prop); - } - } - }); - } else if (typeof collection === "string") { - Metamaps[collection].each(function(model) { - var prop = model.get(propertyToCheck); - if (prop !== null) { - prop = prop.toString(); - if (newList.indexOf(prop) === -1) { - newList.push(prop); - } - } - }); - } - - removed = _.difference(self.filters[filtersToUse], newList); - added = _.difference(newList, self.filters[filtersToUse]); - - // remove the list items for things no longer present on the map - _.each(removed, function(identifier) { - $('#filter_by_' + listToModify + ' li[data-id="' + identifier + '"]').fadeOut('fast',function(){ - $(this).remove(); - }); - index = self.visible[filtersToUse].indexOf(identifier); - self.visible[filtersToUse].splice(index, 1); - }); - - var model, li, jQueryLi; - function sortAlpha(a,b){ - return a.childNodes[1].innerHTML.toLowerCase() > b.childNodes[1].innerHTML.toLowerCase() ? 1 : -1; - } - // for each new filter to be added, create a list item for it and fade it in - _.each(added, function (identifier) { - model = Metamaps[correlatedModel].get(identifier) || - Metamaps[correlatedModel].find(function (model) { - return model.get(propertyToCheck) === identifier; - }); - li = model.prepareLiForFilter(); - jQueryLi = $(li).hide(); - $('li', '#filter_by_' + listToModify + ' ul').add(jQueryLi.fadeIn("fast")) - .sort(sortAlpha).appendTo('#filter_by_' + listToModify + ' ul'); - self.visible[filtersToUse].push(identifier); - }); - - // update the list of filters with the new list we just generated - self.filters[filtersToUse] = newList; - - // make sure clicks on list items still trigger the right events - self.bindLiClicks(); - }, - checkMetacodes: function () { - var self = Metamaps.Filter; - self.updateFilters('Topics', 'metacode_id', 'Metacodes', 'metacodes', 'metacode'); - }, - checkMappers: function () { - var self = Metamaps.Filter; - var onMap = Metamaps.Active.Map ? true : false; - if (onMap) { - self.updateFilters('Mappings', 'user_id', 'Mappers', 'mappers', 'mapper'); - } - else { - // on topic view - self.updateFilters(['Topics', 'Synapses'], 'user_id', 'Creators', 'mappers', 'mapper'); - } - }, - checkSynapses: function () { - var self = Metamaps.Filter; - self.updateFilters('Synapses', 'desc', 'Synapses', 'synapses', 'synapse'); - }, - filterAllMetacodes: function (e) { - var self = Metamaps.Filter; - $('#filter_by_metacode ul li').addClass('toggledOff'); - $('.showAllMetacodes').removeClass('active'); - $('.hideAllMetacodes').addClass('active'); - self.visible.metacodes = []; - self.passFilters(); - }, - filterNoMetacodes: function (e) { - var self = Metamaps.Filter; - $('#filter_by_metacode ul li').removeClass('toggledOff'); - $('.showAllMetacodes').addClass('active'); - $('.hideAllMetacodes').removeClass('active'); - self.visible.metacodes = self.filters.metacodes.slice(); - self.passFilters(); - }, - filterAllMappers: function (e) { - var self = Metamaps.Filter; - $('#filter_by_mapper ul li').addClass('toggledOff'); - $('.showAllMappers').removeClass('active'); - $('.hideAllMappers').addClass('active'); - self.visible.mappers = []; - self.passFilters(); - }, - filterNoMappers: function (e) { - var self = Metamaps.Filter; - $('#filter_by_mapper ul li').removeClass('toggledOff'); - $('.showAllMappers').addClass('active'); - $('.hideAllMappers').removeClass('active'); - self.visible.mappers = self.filters.mappers.slice(); - self.passFilters(); - }, - filterAllSynapses: function (e) { - var self = Metamaps.Filter; - $('#filter_by_synapse ul li').addClass('toggledOff'); - $('.showAllSynapses').removeClass('active'); - $('.hideAllSynapses').addClass('active'); - self.visible.synapses = []; - self.passFilters(); - }, - filterNoSynapses: function (e) { - var self = Metamaps.Filter; - $('#filter_by_synapse ul li').removeClass('toggledOff'); - $('.showAllSynapses').addClass('active'); - $('.hideAllSynapses').removeClass('active'); - self.visible.synapses = self.filters.synapses.slice(); - self.passFilters(); - }, - // an abstraction function for toggleMetacode, toggleMapper, toggleSynapse - // to reduce code redundancy - // gets called in the context of a list item in a filter box - toggleLi: function (whichToFilter) { - var self = Metamaps.Filter, index; - var id = $(this).attr("data-id"); - if (self.visible[whichToFilter].indexOf(id) == -1) { - self.visible[whichToFilter].push(id); - $(this).removeClass('toggledOff'); - } - else { - index = self.visible[whichToFilter].indexOf(id); - self.visible[whichToFilter].splice(index, 1); - $(this).addClass('toggledOff'); - } - self.passFilters(); - }, - toggleMetacode: function () { - var self = Metamaps.Filter; - self.toggleLi.call(this, 'metacodes'); - - if (self.visible.metacodes.length === self.filters.metacodes.length) { - $('.showAllMetacodes').addClass('active'); - $('.hideAllMetacodes').removeClass('active'); - } - else if (self.visible.metacodes.length === 0) { - $('.showAllMetacodes').removeClass('active'); - $('.hideAllMetacodes').addClass('active'); - } - else { - $('.showAllMetacodes').removeClass('active'); - $('.hideAllMetacodes').removeClass('active'); - } - }, - toggleMapper: function () { - var self = Metamaps.Filter; - self.toggleLi.call(this, 'mappers'); - - if (self.visible.mappers.length === self.filters.mappers.length) { - $('.showAllMappers').addClass('active'); - $('.hideAllMappers').removeClass('active'); - } - else if (self.visible.mappers.length === 0) { - $('.showAllMappers').removeClass('active'); - $('.hideAllMappers').addClass('active'); - } - else { - $('.showAllMappers').removeClass('active'); - $('.hideAllMappers').removeClass('active'); - } - }, - toggleSynapse: function () { - var self = Metamaps.Filter; - self.toggleLi.call(this, 'synapses'); - - if (self.visible.synapses.length === self.filters.synapses.length) { - $('.showAllSynapses').addClass('active'); - $('.hideAllSynapses').removeClass('active'); - } - else if (self.visible.synapses.length === 0) { - $('.showAllSynapses').removeClass('active'); - $('.hideAllSynapses').addClass('active'); - } - else { - $('.showAllSynapses').removeClass('active'); - $('.hideAllSynapses').removeClass('active'); - } - }, - passFilters: function () { - var self = Metamaps.Filter; - var visible = self.visible; - - var passesMetacode, passesMapper, passesSynapse; - var onMap; - - if (Metamaps.Active.Map) { - onMap = true; - } - else if (Metamaps.Active.Topic) { - onMap = false; - } - - var opacityForFilter = onMap ? 0 : 0.4; - - Metamaps.Topics.each(function(topic) { - var n = topic.get('node'); - var metacode_id = topic.get("metacode_id").toString(); - - if (visible.metacodes.indexOf(metacode_id) == -1) passesMetacode = false; - else passesMetacode = true; - - if (onMap) { - // when on a map, - // we filter by mapper according to the person who added the - // topic or synapse to the map - var user_id = topic.getMapping().get("user_id").toString(); - if (visible.mappers.indexOf(user_id) == -1) passesMapper = false; - else passesMapper = true; - } - else { - // when on a topic view, - // we filter by mapper according to the person who created the - // topic or synapse - var user_id = topic.get("user_id").toString(); - if (visible.mappers.indexOf(user_id) == -1) passesMapper = false; - else passesMapper = true; - } - - if (passesMetacode && passesMapper) { - if (n) { - n.setData('alpha', 1, 'end'); - } - else console.log(topic); - } - else { - if (n) { - Metamaps.Control.deselectNode(n, true); - n.setData('alpha', opacityForFilter, 'end'); - n.eachAdjacency(function(e){ - Metamaps.Control.deselectEdge(e, true); - }); - } - else console.log(topic); - } - }); - - // flag all the edges back to 'untouched' - Metamaps.Synapses.each(function(synapse) { - var e = synapse.get('edge'); - e.setData('touched', false); - }); - Metamaps.Synapses.each(function(synapse) { - var e = synapse.get('edge'); - var desc; - var user_id = synapse.get("user_id").toString(); - - if (e && !e.getData('touched')) { - - var synapses = e.getData('synapses'); - - // if any of the synapses represent by the edge are still unfiltered - // leave the edge visible - passesSynapse = false; - for (var i = 0; i < synapses.length; i++) { - desc = synapses[i].get("desc"); - if (visible.synapses.indexOf(desc) > -1) passesSynapse = true; - } - - // if the synapse description being displayed is now being - // filtered, set the displayIndex to the first unfiltered synapse if there is one - var displayIndex = e.getData("displayIndex") ? e.getData("displayIndex") : 0; - var displayedSynapse = synapses[displayIndex]; - desc = displayedSynapse.get("desc"); - if (passesSynapse && visible.synapses.indexOf(desc) == -1) { - // iterate and find an unfiltered one - for (var i = 0; i < synapses.length; i++) { - desc = synapses[i].get("desc"); - if (visible.synapses.indexOf(desc) > -1) { - e.setData('displayIndex', i); - break; - } - } - } - - if (onMap) { - // when on a map, - // we filter by mapper according to the person who added the - // topic or synapse to the map - user_id = synapse.getMapping().get("user_id").toString(); - } - if (visible.mappers.indexOf(user_id) == -1) passesMapper = false; - else passesMapper = true; - - var color = Metamaps.Settings.colors.synapses.normal; - if (passesSynapse && passesMapper) { - e.setData('alpha', 1, 'end'); - e.setData('color', color, 'end'); - } - else { - Metamaps.Control.deselectEdge(e, true); - e.setData('alpha', opacityForFilter, 'end'); - } - - e.setData('touched', true); - } - else if (!e) console.log(synapse); - }); - - // run the animation - Metamaps.Visualize.mGraph.fx.animate({ - modes: ['node-property:alpha', - 'edge-property:alpha'], - duration: 200 - }); - } -}; // end Metamaps.Filter - - -/* - * - * LISTENERS - * - */ -Metamaps.Listeners = { - - init: function () { - - $(document).on('keydown', function (e) { - if (!(Metamaps.Active.Map || Metamaps.Active.Topic)) return; - - switch (e.which) { - case 13: // if enter key is pressed - Metamaps.JIT.enterKeyHandler(); - e.preventDefault(); - break; - case 27: // if esc key is pressed - Metamaps.JIT.escKeyHandler(); - break; - case 65: //if a or A is pressed - if (e.ctrlKey){ - Metamaps.Control.deselectAllNodes(); - Metamaps.Control.deselectAllEdges(); - - e.preventDefault(); - Metamaps.Visualize.mGraph.graph.eachNode(function (n) { - Metamaps.Control.selectNode(n,e); - }); - - Metamaps.Visualize.mGraph.plot(); - } - - break; - case 69: //if e or E is pressed - if (e.ctrlKey){ - e.preventDefault(); - if (Metamaps.Active.Map) { - Metamaps.JIT.zoomExtents(null, Metamaps.Visualize.mGraph.canvas); - } - } - break; - case 77: //if m or M is pressed - if (e.ctrlKey){ - e.preventDefault(); - Metamaps.Control.removeSelectedNodes(); - Metamaps.Control.removeSelectedEdges(); - } - break; - case 68: //if d or D is pressed - if (e.ctrlKey){ - e.preventDefault(); - Metamaps.Control.deleteSelected(); - } - break; - case 72: //if h or H is pressed - if (e.ctrlKey){ - e.preventDefault(); - Metamaps.Control.hideSelectedNodes(); - Metamaps.Control.hideSelectedEdges(); - } - break; - default: - break; //alert(e.which); - } - }); - - $(window).resize(function () { - if (Metamaps.Visualize && Metamaps.Visualize.mGraph) Metamaps.Visualize.mGraph.canvas.resize($(window).width(), $(window).height()); - if ((Metamaps.Active.Map || Metamaps.Active.Topic) && Metamaps.Famous && Metamaps.Famous.maps.surf) Metamaps.Famous.maps.reposition(); - if (Metamaps.Active.Map && Metamaps.Realtime.inConversation) Metamaps.Realtime.positionVideos(); - }); - } -}; // end Metamaps.Listeners - - -/* - * - * ORGANIZE - * - */ -Metamaps.Organize = { - init: function () { - - }, - arrange: function (layout, 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') { - - // this will always be an integer, the # of nodes on your graph visualization - var numNodes = _.size(Metamaps.Visualize.mGraph.graph.nodes); - //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; - 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); - - } 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; - - } else alert('please call function with a valid layout dammit!'); - } -}; // end Metamaps.Organize - - -/* - * - * TOPIC - * - */ -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)); - } - } - }, - launch: function (id) { - var bb = Metamaps.Backbone; - var start = function (data) { - Metamaps.Active.Topic = new bb.Topic(data.topic); - Metamaps.Creators = new bb.MapperCollection(data.creators); - Metamaps.Topics = new bb.TopicCollection([data.topic].concat(data.relatives)); - Metamaps.Synapses = new bb.SynapseCollection(data.synapses); - Metamaps.Backbone.attachCollectionEvents(); - - // set filter mapper H3 text - $('#filter_by_mapper h3').html('CREATORS'); - - // build and render the visualization - Metamaps.Visualize.type = "RGraph"; - Metamaps.JIT.prepareVizData(); - - // update filters - Metamaps.Filter.reset(); - - // reset selected arrays - Metamaps.Selected.reset(); - - // these three update the actual filter box with the right list items - Metamaps.Filter.checkMetacodes(); - Metamaps.Filter.checkSynapses(); - Metamaps.Filter.checkMappers(); - } - - $.ajax({ - url: "/topics/" + id + "/network.json", - success: start - }); - }, - end: function () { - if (Metamaps.Active.Topic) { - $('.rightclickmenu').remove(); - Metamaps.TopicCard.hideCard(); - Metamaps.SynapseCard.hideCard(); - Metamaps.Filter.close(); - } - }, - centerOn: function (nodeid) { - if (!Metamaps.Visualize.mGraph.busy) { - Metamaps.Visualize.mGraph.onClick(nodeid, { - hideLabels: false, - duration: 1000, - onComplete: function () { - - } - }); - } - }, - fetchRelatives: function(node, metacode_id) { - - var topics = Metamaps.Topics.map(function(t){ return t.id }); - var topics_string = topics.join(); - - var creators = Metamaps.Creators.map(function(t){ return t.id }); - var creators_string = creators.join(); - - var topic = node.getData('topic'); - - var successCallback = function(data) { - if (data.creators.length > 0) Metamaps.Creators.add(data.creators); - if (data.topics.length > 0) Metamaps.Topics.add(data.topics); - if (data.synapses.length > 0) Metamaps.Synapses.add(data.synapses); - - var topicColl = new Metamaps.Backbone.TopicCollection(data.topics); - topicColl.add(topic); - var synapseColl = new Metamaps.Backbone.SynapseCollection(data.synapses); - - var graph = Metamaps.JIT.convertModelsToJIT(topicColl, synapseColl)[0]; - Metamaps.Visualize.mGraph.op.sum(graph, { - type: 'fade', - duration: 500, - hideLabels: false - }); - - var i, l, t, s; - - Metamaps.Visualize.mGraph.graph.eachNode(function (n) { - t = Metamaps.Topics.get(n.id); - t.set({ node: n }, { silent: true }); - t.updateNode(); - - n.eachAdjacency(function (edge) { - if(!edge.getData('init')) { - edge.setData('init', true); - - l = edge.getData('synapseIDs').length; - for (i = 0; i < l; i++) { - s = Metamaps.Synapses.get(edge.getData('synapseIDs')[i]); - s.set({ edge: edge }, { silent: true }); - s.updateEdge(); - } - } - }); - }); - }; - - var paramsString = metacode_id ? "metacode=" + metacode_id + "&" : ""; - paramsString += "network=" + topics_string + "&creators=" + creators_string; - - $.ajax({ - type: "Get", - url: "/topics/" + topic.id + "/relatives.json?" + paramsString, - success: successCallback, - error: function () { - - } - }); - }, - /* - * - * - */ - renderTopic: function (mapping, topic, createNewInDB, permitCreateSynapseAfter) { - var self = Metamaps.Topic; - - var nodeOnViz, tempPos; - - var newnode = topic.createNode(); - - var midpoint = {}, pixelPos; - - if (!$.isEmptyObject(Metamaps.Visualize.mGraph.graph.nodes)) { - Metamaps.Visualize.mGraph.graph.addNode(newnode); - nodeOnViz = Metamaps.Visualize.mGraph.graph.getNode(newnode.id); - topic.set('node', nodeOnViz, {silent: true}); - topic.updateNode(); // links the topic and the mapping to the node - - nodeOnViz.setData("dim", 1, "start"); - nodeOnViz.setData("dim", 25, "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 && permitCreateSynapseAfter) { - Metamaps.Create.newSynapse.topic1id = Metamaps.tempNode.getData('topic').id; - - // position the form - midpoint.x = Metamaps.tempNode.pos.getc().x + (nodeOnViz.pos.getc().x - Metamaps.tempNode.pos.getc().x) / 2; - midpoint.y = Metamaps.tempNode.pos.getc().y + (nodeOnViz.pos.getc().y - Metamaps.tempNode.pos.getc().y) / 2; - pixelPos = Metamaps.Util.coordsToPixels(midpoint); - $('#new_synapse').css('left', pixelPos.x + "px"); - $('#new_synapse').css('top', pixelPos.y + "px"); - // show the form - Metamaps.Create.newSynapse.open(); - Metamaps.Visualize.mGraph.fx.animate({ - modes: ["node-property:dim"], - duration: 500, - onComplete: function () { - Metamaps.tempNode = null; - Metamaps.tempNode2 = null; - Metamaps.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); - topic.set('node', nodeOnViz, {silent: true}); - topic.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 () { - - } - }); - } - - var mappingSuccessCallback = function (mappingModel, response) { - var newTopicData = { - mappingid: mappingModel.id, - mappableid: mappingModel.get('mappable_id') - }; - - $(document).trigger(Metamaps.JIT.events.newTopic, [newTopicData]); - }; - var topicSuccessCallback = function (topicModel, response) { - if (Metamaps.Active.Map) { - mapping.save({ mappable_id: topicModel.id }, { - success: mappingSuccessCallback, - error: function (model, response) { - console.log('error saving mapping to database'); - } - }); - } - - if (Metamaps.Create.newTopic.addSynapse) { - Metamaps.Create.newSynapse.topic2id = topicModel.id; - } - }; - - if (!Metamaps.Settings.sandbox && createNewInDB) { - if (topic.isNew()) { - topic.save(null, { - success: topicSuccessCallback, - error: function (model, response) { - console.log('error saving topic to database'); - } - }); - } else if (!topic.isNew() && Metamaps.Active.Map) { - mapping.save(null, { - success: mappingSuccessCallback - }); - } - } - }, - createTopicLocally: function () { - var self = Metamaps.Topic; - - if (Metamaps.Create.newTopic.name === "") { - Metamaps.GlobalUI.notifyUser("Please enter a topic title..."); - return; - } - - // hide the 'double-click to add a topic' message - Metamaps.Famous.viz.hideInstructions(); - - $(document).trigger(Metamaps.Map.events.editedByActiveMapper); - - var metacode = Metamaps.Metacodes.get(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({ - xloc: Metamaps.Create.newTopic.x, - yloc: Metamaps.Create.newTopic.y, - mappable_id: topic.cid, - mappable_type: "Topic", - }); - Metamaps.Mappings.add(mapping); - - //these can't happen until the value is retrieved, which happens in the line above - Metamaps.Create.newTopic.hide(); - - self.renderTopic(mapping, topic, true, true); // this function also includes the creation of the topic in the database - }, - getTopicFromAutocomplete: function (id) { - var self = Metamaps.Topic; - - $(document).trigger(Metamaps.Map.events.editedByActiveMapper); - - Metamaps.Create.newTopic.hide(); - - var topic = self.get(id); - - var mapping = new Metamaps.Backbone.Mapping({ - xloc: Metamaps.Create.newTopic.x, - yloc: Metamaps.Create.newTopic.y, - mappable_type: "Topic", - mappable_id: topic.id, - }); - Metamaps.Mappings.add(mapping); - - self.renderTopic(mapping, topic, true, true); - }, - getTopicFromSearch: function (event, id) { - var self = Metamaps.Topic; - - $(document).trigger(Metamaps.Map.events.editedByActiveMapper); - - var topic = self.get(id); - - var nextCoords = Metamaps.Map.getNextCoord(); - var mapping = new Metamaps.Backbone.Mapping({ - xloc: nextCoords.x, - yloc: nextCoords.y, - mappable_type: "Topic", - mappable_id: topic.id, - }); - Metamaps.Mappings.add(mapping); - - self.renderTopic(mapping, topic, true, true); - - Metamaps.GlobalUI.notifyUser('Topic was added to your map!'); - - event.stopPropagation(); - event.preventDefault(); - return false; - } -}; // end Metamaps.Topic - - -/* - * - * SYNAPSE - * - */ -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 = synapse.createEdge(mapping); - - Metamaps.Visualize.mGraph.graph.addAdjacence(node1, node2, newedge.data); - edgeOnViz = Metamaps.Visualize.mGraph.graph.getAdjacence(node1.id, node2.id); - synapse.set('edge', edgeOnViz); - synapse.updateEdge(); // links the synapse and the mapping to the edge - - Metamaps.Control.selectEdge(edgeOnViz); - - var mappingSuccessCallback = function (mappingModel, response) { - var newSynapseData = { - mappingid: mappingModel.id, - mappableid: mappingModel.get('mappable_id') - }; - - $(document).trigger(Metamaps.JIT.events.newSynapse, [newSynapseData]); - }; - var synapseSuccessCallback = function (synapseModel, response) { - if (Metamaps.Active.Map) { - mapping.save({ mappable_id: synapseModel.id }, { - success: mappingSuccessCallback - }); - } - }; - - if (!Metamaps.Settings.sandbox && createNewInDB) { - if (synapse.isNew()) { - synapse.save(null, { - success: synapseSuccessCallback, - error: function (model, response) { - console.log('error saving synapse to database'); - } - }); - } else if (!synapse.isNew() && Metamaps.Active.Map) { - mapping.save(null, { - success: mappingSuccessCallback - }); - } - } - }, - createSynapseLocally: function () { - var self = Metamaps.Synapse, - topic1, - topic2, - node1, - node2, - synapse, - mapping; - - $(document).trigger(Metamaps.Map.events.editedByActiveMapper); - - //for each node in this array we will create a synapse going to the position2 node. - var synapsesToCreate = []; - - topic2 = Metamaps.Topics.get(Metamaps.Create.newSynapse.topic2id); - node2 = topic2.get('node'); - - var len = Metamaps.Selected.Nodes.length; - if (len == 0) { - topic1 = Metamaps.Topics.get(Metamaps.Create.newSynapse.topic1id); - synapsesToCreate[0] = topic1.get('node'); - } else if (len > 0) { - synapsesToCreate = Metamaps.Selected.Nodes; - } - - 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({ - mappable_type: "Synapse", - mappable_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, - topic1, - topic2, - node1, - node2; - - var synapse = self.get(id); - - var mapping = new Metamaps.Backbone.Mapping({ - mappable_type: "Synapse", - mappable_id: synapse.id, - }); - Metamaps.Mappings.add(mapping); - - topic1 = Metamaps.Topics.get(Metamaps.Create.newSynapse.topic1id); - node1 = topic1.get('node'); - topic2 = Metamaps.Topics.get(Metamaps.Create.newSynapse.topic2id); - node2 = topic2.get('node'); - Metamaps.Create.newSynapse.hide(); - - self.renderSynapse(mapping, synapse, node1, node2, true); - } -}; // end Metamaps.Synapse - - -/* - * - * MAP - * - */ -Metamaps.Map = { - events: { - editedByActiveMapper: "Metamaps:Map:events:editedByActiveMapper" - }, - nextX: 0, - nextY: 0, - sideLength: 1, - turnCount: 0, - nextXshift: 1, - nextYshift: 0, - timeToTurn: 0, - init: function () { - var self = Metamaps.Map; - - // prevent right clicks on the main canvas, so as to not get in the way of our right clicks - $('#center-container').bind('contextmenu', function (e) { - return false; - }); - - $('.sidebarFork').click(function () { - self.fork(); - }); - - Metamaps.GlobalUI.CreateMap.emptyForkMapForm = $('#fork_map').html(); - - self.InfoBox.init(); - self.CheatSheet.init(); - - $(document).on(Metamaps.Map.events.editedByActiveMapper, self.editedByActiveMapper); - }, - launch: function (id) { - var bb = Metamaps.Backbone; - var start = function (data) { - Metamaps.Active.Map = new bb.Map(data.map); - Metamaps.Mappers = new bb.MapperCollection(data.mappers); - Metamaps.Topics = new bb.TopicCollection(data.topics); - Metamaps.Synapses = new bb.SynapseCollection(data.synapses); - Metamaps.Mappings = new bb.MappingCollection(data.mappings); - Metamaps.Messages = data.messages; - Metamaps.Backbone.attachCollectionEvents(); - - var map = Metamaps.Active.Map; - var mapper = Metamaps.Active.Mapper; - - // add class to .wrapper for specifying whether you can edit the map - if (map.authorizeToEdit(mapper)) { - $('.wrapper').addClass('canEditMap'); - } - - // add class to .wrapper for specifying if the map can - // be collaborated on - if (map.get('permission') === 'commons') { - $('.wrapper').addClass('commonsMap'); - } - - // set filter mapper H3 text - $('#filter_by_mapper h3').html('MAPPERS'); - - // build and render the visualization - Metamaps.Visualize.type = "ForceDirected"; - Metamaps.JIT.prepareVizData(); - - // update filters - Metamaps.Filter.reset(); - - // reset selected arrays - Metamaps.Selected.reset(); - - // set the proper mapinfobox content - Metamaps.Map.InfoBox.load(); - - // these three update the actual filter box with the right list items - Metamaps.Filter.checkMetacodes(); - Metamaps.Filter.checkSynapses(); - Metamaps.Filter.checkMappers(); - - Metamaps.Realtime.startActiveMap(); - Metamaps.Loading.hide(); - } - - $.ajax({ - url: "/maps/" + id + "/contains.json", - success: start - }); - }, - end: function () { - if (Metamaps.Active.Map) { - - $('.wrapper').removeClass('canEditMap commonsMap'); - Metamaps.Map.resetSpiral(); - - $('.rightclickmenu').remove(); - Metamaps.TopicCard.hideCard(); - Metamaps.SynapseCard.hideCard(); - Metamaps.Create.newTopic.hide(); - Metamaps.Create.newSynapse.hide(); - Metamaps.Filter.close(); - Metamaps.Map.InfoBox.close(); - Metamaps.Realtime.endActiveMap(); - } - }, - fork: function () { - Metamaps.GlobalUI.openLightbox('forkmap'); - - var nodes_data = "", - synapses_data = ""; - var nodes_array = []; - var synapses_array = []; - // collect the unfiltered topics - Metamaps.Visualize.mGraph.graph.eachNode(function (n) { - // if the opacity is less than 1 then it's filtered - if (n.getData('alpha') === 1) { - var id = n.getData('topic').id; - nodes_array.push(id); - var x, y; - if (n.pos.x && n.pos.y) { - x = n.pos.x; - y = n.pos.y; - } else { - var x = Math.cos(n.pos.theta) * n.pos.rho; - var y = Math.sin(n.pos.theta) * n.pos.rho; - } - nodes_data += id + '/' + x + '/' + y + ','; - } - }); - // collect the unfiltered synapses - Metamaps.Synapses.each(function(synapse){ - var desc = synapse.get("desc"); - - var descNotFiltered = Metamaps.Filter.visible.synapses.indexOf(desc) > -1; - // make sure that both topics are being added, otherwise, it - // doesn't make sense to add the synapse - var topicsNotFiltered = nodes_array.indexOf(synapse.get('node1_id')) > -1; - topicsNotFiltered = topicsNotFiltered && nodes_array.indexOf(synapse.get('node2_id')) > -1; - if (descNotFiltered && topicsNotFiltered) { - synapses_array.push(synapse.id); - } - }); - - synapses_data = synapses_array.join(); - nodes_data = nodes_data.slice(0, -1); - - Metamaps.GlobalUI.CreateMap.topicsToMap = nodes_data; - Metamaps.GlobalUI.CreateMap.synapsesToMap = synapses_data; - - }, - leavePrivateMap: function(){ - var map = Metamaps.Active.Map; - Metamaps.Maps.Active.remove(map); - Metamaps.Maps.Featured.remove(map); - Metamaps.Router.home(); - Metamaps.GlobalUI.notifyUser('Sorry! That map has been changed to Private.'); - }, - commonsToPublic: function(){ - Metamaps.Realtime.turnOff(true); // true is for 'silence' - Metamaps.GlobalUI.notifyUser('Map was changed to Public. Editing is disabled.'); - Metamaps.Active.Map.trigger('changeByOther'); - }, - publicToCommons: function(){ - var confirmString = "This map permission has been changed to Commons! "; - confirmString += "Do you want to reload and enable realtime collaboration?"; - var c = confirm(confirmString); - if (c) { - Metamaps.Router.maps(Metamaps.Active.Map.id); - } - }, - editedByActiveMapper: function () { - if (Metamaps.Active.Mapper) { - Metamaps.Mappers.add(Metamaps.Active.Mapper); - } - }, - getNextCoord: function() { - var self = Metamaps.Map; - var nextX = self.nextX; - var nextY = self.nextY; - - var DISTANCE_BETWEEN = 120; - - self.nextX = self.nextX + DISTANCE_BETWEEN * self.nextXshift; - self.nextY = self.nextY + DISTANCE_BETWEEN * self.nextYshift; - - self.timeToTurn += 1; - // if true, it's time to turn - if (self.timeToTurn === self.sideLength) { - - self.turnCount += 1; - // if true, it's time to increase side length - if (self.turnCount % 2 === 0) { - self.sideLength += 1; - } - self.timeToTurn = 0; - - // going right? turn down - if (self.nextXshift == 1 && self.nextYshift == 0) { - self.nextXshift = 0; - self.nextYshift = 1; - } - // going down? turn left - else if (self.nextXshift == 0 && self.nextYshift == 1) { - self.nextXshift = -1; - self.nextYshift = 0; - } - // going left? turn up - else if (self.nextXshift == -1 && self.nextYshift == 0) { - self.nextXshift = 0; - self.nextYshift = -1; - } - // going up? turn right - else if (self.nextXshift == 0 && self.nextYshift == -1) { - self.nextXshift = 1; - self.nextYshift = 0; - } - } - - return { - x: nextX, - y: nextY - } - }, - resetSpiral: function() { - Metamaps.Map.nextX = 0; - Metamaps.Map.nextY = 0; - Metamaps.Map.nextXshift = 1; - Metamaps.Map.nextYshift = 0; - Metamaps.Map.sideLength = 1; - Metamaps.Map.timeToTurn = 0; - Metamaps.Map.turnCount = 0; - }, - exportImage: function() { - - var canvas = {}; - - canvas.canvas = document.createElement("canvas"); - canvas.canvas.width = 1880; // 960; - canvas.canvas.height = 1260; // 630 - - canvas.scaleOffsetX = 1; - canvas.scaleOffsetY = 1; - canvas.translateOffsetY = 0; - canvas.translateOffsetX = 0; - canvas.denySelected = true; - - canvas.getSize = function() { - if(this.size) return this.size; - var canvas = this.canvas; - return this.size = { - width: canvas.width, - height: canvas.height - }; - }; - canvas.scale = function(x, y) { - var px = this.scaleOffsetX * x, - py = this.scaleOffsetY * y; - var dx = this.translateOffsetX * (x -1) / px, - dy = this.translateOffsetY * (y -1) / py; - this.scaleOffsetX = px; - this.scaleOffsetY = py; - this.getCtx().scale(x, y); - this.translate(dx, dy); - }; - canvas.translate = function(x, y) { - var sx = this.scaleOffsetX, - sy = this.scaleOffsetY; - this.translateOffsetX += x*sx; - this.translateOffsetY += y*sy; - this.getCtx().translate(x, y); - }; - canvas.getCtx = function() { - return this.canvas.getContext("2d"); - }; - // center it - canvas.getCtx().translate(1880/2, 1260/2); - - var mGraph = Metamaps.Visualize.mGraph; - - var id = mGraph.root; - var root = mGraph.graph.getNode(id); - var T = !!root.visited; - - // pass true to avoid basing it on a selection - Metamaps.JIT.zoomExtents(null, canvas, true); - - var c = canvas.canvas, - ctx = canvas.getCtx(), - scale = canvas.scaleOffsetX; - - // draw a grey background - ctx.fillStyle = '#d8d9da'; - var xPoint = (-(c.width/scale)/2) - (canvas.translateOffsetX/scale), - yPoint = (-(c.height/scale)/2) - (canvas.translateOffsetY/scale); - ctx.fillRect(xPoint,yPoint,c.width/scale,c.height/scale); - - // draw the graph - mGraph.graph.eachNode(function(node) { - var nodeAlpha = node.getData('alpha'); - node.eachAdjacency(function(adj) { - var nodeTo = adj.nodeTo; - if(!!nodeTo.visited === T && node.drawn && nodeTo.drawn) { - mGraph.fx.plotLine(adj, canvas); - } - }); - if(node.drawn) { - mGraph.fx.plotNode(node, canvas); - } - if(!mGraph.labelsHidden) { - if(node.drawn && nodeAlpha >= 0.95) { - mGraph.labels.plotLabel(canvas, node); - } else { - mGraph.labels.hideLabel(node, false); - } - } - node.visited = !T; - }); - - var imageData = { - encoded_image: canvas.canvas.toDataURL() - }; - - var map = Metamaps.Active.Map; - - var today = new Date(); - var dd = today.getDate(); - var mm = today.getMonth()+1; //January is 0! - var yyyy = today.getFullYear(); - if(dd<10) { - dd='0'+dd - } - if(mm<10) { - mm='0'+mm - } - today = mm+'/'+dd+'/'+yyyy; - - var mapName = map.get("name").split(" ").join([separator = '-']); - var downloadMessage = ""; - downloadMessage += "Captured map screenshot! "; - downloadMessage += "DOWNLOAD"; - Metamaps.GlobalUI.notifyUser(downloadMessage); - - $.ajax({ - type: "POST", - dataType: 'json', - url: "/maps/" + Metamaps.Active.Map.id + "/upload_screenshot", - data: imageData, - success: function (data) { - console.log('successfully uploaded map screenshot'); - }, - error: function () { - console.log('failed to save map screenshot'); - } - }); - } -}; - - -/* - * - * CHEATSHEET - * - */ -Metamaps.Map.CheatSheet = { - init: function () { - // tab the cheatsheet - $('#cheatSheet').tabs(); - $('#quickReference').tabs().addClass("ui-tabs-vertical ui-helper-clearfix"); - $("#quickReference .ui-tabs-nav li").removeClass("ui-corner-top").addClass("ui-corner-left"); - - // id = the id of a vimeo video - var switchVideo = function (element, id) { - $('.tutorialItem').removeClass("active"); - $(element).addClass("active"); - $('#tutorialVideo').attr('src','//player.vimeo.com/video/'+id); - }; - - $('#gettingStarted').click(function() { - //switchVideo(this,'88334167'); - }); - $('#upYourSkillz').click(function() { - //switchVideo(this,'100118167'); - }); - $('#advancedMapping').click(function() { - //switchVideo(this,'88334167'); - }); - } -}; // end Metamaps.Map.CheatSheet - - -/* - * - * INFOBOX - * - */ -Metamaps.Map.InfoBox = { - isOpen: false, - changing: false, - selectingPermission: false, - changePermissionText: "
As the creator, you can change the permission of this map, but the permissions of the topics and synapses on it must be changed independently.
", - nameHTML: '{{name}}', - descHTML: '{{desc}}', - init: function () { - var self = Metamaps.Map.InfoBox; - - $('.mapInfoIcon').click(self.toggleBox); - $('.mapInfoBox').click(function(event){ - event.stopPropagation(); - }); - $('body').click(self.close); - - self.attachEventListeners(); - - self.generateBoxHTML = Hogan.compile($('#mapInfoBoxTemplate').html()); - }, - toggleBox: function (event) { - var self = Metamaps.Map.InfoBox; - - if (self.isOpen) self.close(); - else self.open(); - - event.stopPropagation(); - }, - open: function () { - var self = Metamaps.Map.InfoBox; - $('.mapInfoIcon div').addClass('hide'); - if (!self.isOpen && !self.changing) { - self.changing = true; - $('.mapInfoBox').fadeIn(200, function () { - self.changing = false; - self.isOpen = true; - }); - } - }, - close: function () { - var self = Metamaps.Map.InfoBox; - - $('.mapInfoIcon div').removeClass('hide'); - if (!self.changing) { - self.changing = true; - $('.mapInfoBox').fadeOut(200, function () { - self.changing = false; - self.isOpen = false; - self.hidePermissionSelect(); - $('.mapContributors .tip').hide(); - }); - } - }, - load: function () { - var self = Metamaps.Map.InfoBox; - - var map = Metamaps.Active.Map; - - var obj = map.pick("permission","contributor_count","topic_count","synapse_count"); - - var isCreator = map.authorizePermissionChange(Metamaps.Active.Mapper); - var canEdit = map.authorizeToEdit(Metamaps.Active.Mapper); - var shareable = map.get('permission') !== 'private'; - - obj["name"] = canEdit ? Hogan.compile(self.nameHTML).render({id: map.id, name: map.get("name")}) : map.get("name"); - obj["desc"] = canEdit ? Hogan.compile(self.descHTML).render({id: map.id, desc: map.get("desc")}) : map.get("desc"); - obj["map_creator_tip"] = isCreator ? self.changePermissionText : ""; - obj["contributors_class"] = Metamaps.Mappers.length > 1 ? "multiple" : ""; - obj["contributors_class"] += Metamaps.Mappers.length === 2 ? " mTwo" : ""; - obj["contributor_image"] = Metamaps.Mappers.length > 0 ? Metamaps.Mappers.models[0].get("image") : "<%= asset_path('user.png') %>"; - obj["contributor_list"] = self.createContributorList(); - obj["user_name"] = isCreator ? "You" : map.get("user_name"); - obj["created_at"] = map.get("created_at_clean"); - obj["updated_at"] = map.get("updated_at_clean"); - - var classes = isCreator ? "yourMap" : ""; - classes += canEdit ? " canEdit" : ""; - classes += shareable ? " shareable" : ""; - $(".mapInfoBox").removeClass("shareable yourMap canEdit") - .addClass(classes) - .html(self.generateBoxHTML.render(obj)); - - self.attachEventListeners(); - }, - attachEventListeners: function () { - var self = Metamaps.Map.InfoBox; - - $('.mapInfoBox.canEdit .best_in_place').best_in_place(); - - // because anyone who can edit the map can change the map title - var bipName = $('.mapInfoBox .best_in_place_name'); - bipName.unbind("best_in_place:activate").bind("best_in_place:activate", function () { - var $el = bipName.find('textarea'); - var el = $el[0]; - - $el.attr('maxlength', '140'); - - $('.mapInfoName').append('
'); - - var callback = function (data) { - $('.nameCounter.forMap').html(data.all + '/140'); - }; - Countable.live(el, callback); - }); - bipName.unbind("best_in_place:deactivate").bind("best_in_place:deactivate", function () { - $('.nameCounter.forMap').remove(); - }); - - $('.mapInfoName .best_in_place_name').unbind("ajax:success").bind("ajax:success", function () { - var name = $(this).html(); - Metamaps.Active.Map.set('name', name); - Metamaps.Active.Map.trigger('saved'); - }); - - $('.mapInfoDesc .best_in_place_desc').unbind("ajax:success").bind("ajax:success", function () { - var desc = $(this).html(); - Metamaps.Active.Map.set('desc', desc); - Metamaps.Active.Map.trigger('saved'); - }); - - $('.yourMap .mapPermission').unbind().click(self.onPermissionClick); - // .yourMap in the unbind/bind is just a namespace for the events - // not a reference to the class .yourMap on the .mapInfoBox - $('.mapInfoBox.yourMap').unbind('.yourMap').bind('click.yourMap', self.hidePermissionSelect); - - $('.yourMap .mapInfoDelete').unbind().click(self.deleteActiveMap); - - $('.mapContributors span, #mapContribs').unbind().click(function(event){ - $('.mapContributors .tip').toggle(); - event.stopPropagation(); - }); - $('.mapContributors .tip').unbind().click(function(event){ - event.stopPropagation(); - }); - $('.mapContributors .tip li a').click(Metamaps.Router.intercept); - - $('.mapInfoBox').unbind('.hideTip').bind('click.hideTip', function(){ - $('.mapContributors .tip').hide(); - }); - }, - updateNameDescPerm: function(name, desc, perm) { - $('.mapInfoName .best_in_place_name').html(name); - $('.mapInfoDesc .best_in_place_desc').html(desc); - $('.mapInfoBox .mapPermission').removeClass('commons public private').addClass(perm); - }, - createContributorList: function () { - var self = Metamaps.Map.InfoBox; - - var string = ""; - string += ""; - return string; - }, - updateNumbers: function () { - var self = Metamaps.Map.InfoBox; - var mapper = Metamaps.Active.Mapper; - - var contributors_class = ""; - if (Metamaps.Mappers.length === 2) contributors_class = "multiple mTwo"; - else if (Metamaps.Mappers.length > 2) contributors_class = "multiple"; - - var contributors_image = "<%= asset_path('user.png') %>"; - if (Metamaps.Mappers.length > 0) { - // get the first contributor and use their image - contributors_image = Metamaps.Mappers.models[0].get("image"); - } - $('.mapContributors img').attr('src', contributors_image).removeClass('multiple mTwo').addClass(contributors_class); - $('.mapContributors span').text(Metamaps.Mappers.length) - $('.mapContributors .tip').html(self.createContributorList()); - $('.mapTopics').text(Metamaps.Topics.length); - $('.mapSynapses').text(Metamaps.Synapses.length); - - $('.mapEditedAt').html('Last edited: ' + Metamaps.Util.nowDateFormatted()); - }, - onPermissionClick: function (event) { - var self = Metamaps.Map.InfoBox; - - if (!self.selectingPermission) { - self.selectingPermission = true; - $(this).addClass('minimize'); // this line flips the drop down arrow to a pull up arrow - if ($(this).hasClass('commons')) { - $(this).append(''); - } else if ($(this).hasClass('public')) { - $(this).append(''); - } else if ($(this).hasClass('private')) { - $(this).append(''); - } - $('.mapPermission .permissionSelect li').click(self.selectPermission); - event.stopPropagation(); - } - }, - hidePermissionSelect: function () { - var self = Metamaps.Map.InfoBox; - - self.selectingPermission = false; - $('.mapPermission').removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow - $('.mapPermission .permissionSelect').remove(); - }, - selectPermission: function (event) { - var self = Metamaps.Map.InfoBox; - - self.selectingPermission = false; - var permission = $(this).attr('class'); - var permBefore = Metamaps.Active.Map.get('permission'); - Metamaps.Active.Map.save({ - permission: permission - }); - Metamaps.Active.Map.updateMapWrapper(); - if (permBefore !== 'commons' && permission === 'commons') { - Metamaps.Realtime.setupSocket(); - Metamaps.Realtime.turnOn(); - } - else if (permBefore === 'commons' && permission === 'public') { - Metamaps.Realtime.turnOff(true); // true is to 'silence' - // the notification that would otherwise be sent - } - shareable = permission === 'private' ? '' : 'shareable'; - $('.mapPermission').removeClass('commons public private minimize').addClass(permission); - $('.mapPermission .permissionSelect').remove(); - $('.mapInfoBox').removeClass('shareable').addClass(shareable); - event.stopPropagation(); - }, - deleteActiveMap: function () { - var confirmString = 'Are you sure you want to delete this map? '; - confirmString += 'This action is irreversible. It will not delete the topics and synapses on the map.'; - - var doIt = confirm(confirmString); - var map = Metamaps.Active.Map; - var mapper = Metamaps.Active.Mapper; - var authorized = map.authorizePermissionChange(mapper); - - if (doIt && authorized) { - Metamaps.Map.InfoBox.close(); - Metamaps.Maps.Active.remove(map); - Metamaps.Maps.Featured.remove(map); - Metamaps.Maps.Mine.remove(map); - map.destroy(); - Metamaps.Router.home(); - Metamaps.GlobalUI.notifyUser('Map eliminated!'); - } - else if (!authorized) { - alert('Hey now. We can\'t just go around willy nilly deleting other people\'s maps now can we? Run off and find something constructive to do, eh?'); - } - } -}; // end Metamaps.Map.InfoBox - -/* -* -* Account Settings -* -*/ -Metamaps.Account = { - listenersInitialized: false, - init: function () { - var self = Metamaps.Account; - - - }, - initListeners: function(){ - var self = Metamaps.Account; - - $('#user_image').change(self.showImagePreview); - self.listenersInitialized = true; - }, - toggleChangePicture: function(){ - var self = Metamaps.Account; - - $('.userImageMenu').toggle(); - if (!self.listenersInitialized) self.initListeners(); - }, - openChangePicture: function(){ - var self = Metamaps.Account; - - $('.userImageMenu').show(); - if (!self.listenersInitialized) self.initListeners(); - }, - closeChangePicture: function(){ - var self = Metamaps.Account; - - $('.userImageMenu').hide(); - }, - showLoading: function(){ - var self = Metamaps.Account; - - var loader = new CanvasLoader('accountPageLoading'); - loader.setColor('#4FC059'); // default is '#000000' - loader.setDiameter(28); // default is 40 - loader.setDensity(41); // default is 40 - loader.setRange(0.9); // default is 1.3 - loader.show(); // Hidden by default - $('#accountPageLoading').show(); - }, - showImagePreview: function(){ - var self = Metamaps.Account; - - var file = $('#user_image')[0].files[0]; - - var reader = new FileReader(); - - reader.onload = function(e) { - var $canvas = $('').attr({ - width: 84, - height: 84 - }); - var context = $canvas[0].getContext('2d'); - var imageObj = new Image(); - - imageObj.onload = function() { - $('.userImageDiv canvas').remove(); - $('.userImageDiv img').hide(); - - var imgWidth = imageObj.width; - var imgHeight = imageObj.height; - - var dimensionToMatch = imgWidth > imgHeight ? imgHeight : imgWidth; - // draw cropped image - var nonZero = Math.abs(imgHeight - imgWidth) / 2; - var sourceX = dimensionToMatch === imgWidth ? 0 : nonZero; - var sourceY = dimensionToMatch === imgHeight ? 0 : nonZero; - var sourceWidth = dimensionToMatch; - var sourceHeight = dimensionToMatch; - var destX = 0; - var destY = 0; - var destWidth = 84; - var destHeight = 84; - - context.drawImage(imageObj, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight); - $('.userImageDiv').prepend($canvas); - }; - imageObj.src = reader.result; - }; - - if (file) { - reader.readAsDataURL(file); - $('.userImageMenu').hide(); - $('#remove_image').val('0'); - } - }, - removePicture: function(){ - var self = Metamaps.Account; - - $('.userImageDiv canvas').remove(); - $('.userImageDiv img').attr('src', '<%= asset_path('user.png') %>').show(); - $('.userImageMenu').hide(); - - var input = $('#user_image'); - input.replaceWith(input.val('').clone(true)); - $('#remove_image').val('1'); - }, - changeName: function(){ - $('.accountName').hide(); - $('.changeName').show(); - }, - showPass: function(){ - $(".toHide").show(); - $(".changePass").hide(); - }, - hidePass: function(){ - $(".toHide").hide(); - $(".changePass").show(); - - $('#current_password').val(''); - $('#user_password').val(''); - $('#user_password_confirmation').val(''); - } -}; - -/* - * - * MAPPER - * - */ -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) { - return $.ajax({ - url: "/users/" + id + ".json", - success: function (data) { - callback(new Metamaps.Backbone.Mapper(data)); - } - }); - } -}; // end Metamaps.Mapper