diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 403441bb..cfa9af29 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -25,6 +25,7 @@ //= require ./src/views/room //= require ./src/JIT //= require ./src/Metamaps +//= require ./src/Metamaps.Visualize //= require ./src/Metamaps.Util //= require ./src/Metamaps.Realtime //= require ./src/Metamaps.Control diff --git a/app/assets/javascripts/src/Metamaps.Visualize.js b/app/assets/javascripts/src/Metamaps.Visualize.js new file mode 100644 index 00000000..2b8231a8 --- /dev/null +++ b/app/assets/javascripts/src/Metamaps.Visualize.js @@ -0,0 +1,207 @@ +/* global Metamaps, $ */ +/* + * Metamaps.Visualize + * + * Dependencies: + * - Metamaps.Active + * - Metamaps.JIT + * - Metamaps.Loading + * - Metamaps.Metacodes + * - Metamaps.Router + * - Metamaps.Synapses + * - Metamaps.TopicCard + * - Metamaps.Topics + * - Metamaps.Touch + * - Metamaps.Visualize + */ + +Metamaps.Visualize = { + mGraph: null, // a reference to the graph object. + cameraPosition: null, // stores the camera position when using a 3D visualization + type: 'ForceDirected', // the type of graph we're building, could be "RGraph", "ForceDirected", or "ForceDirected3D" + loadLater: false, // indicates whether there is JSON that should be loaded right in the offset, or whether to wait till the first topic is created + init: function () { + var self = Metamaps.Visualize + // disable awkward dragging of the canvas element that would sometimes happen + $('#infovis-canvas').on('dragstart', function (event) { + event.preventDefault() + }) + + // prevent touch events on the canvas from default behaviour + $('#infovis-canvas').bind('touchstart', function (event) { + event.preventDefault() + self.mGraph.events.touched = true + }) + + // prevent touch events on the canvas from default behaviour + $('#infovis-canvas').bind('touchmove', function (event) { + // Metamaps.JIT.touchPanZoomHandler(event) + }) + + // prevent touch events on the canvas from default behaviour + $('#infovis-canvas').bind('touchend touchcancel', function (event) { + lastDist = 0 + if (!self.mGraph.events.touchMoved && !Metamaps.Touch.touchDragNode) Metamaps.TopicCard.hideCurrentCard() + self.mGraph.events.touched = self.mGraph.events.touchMoved = false + Metamaps.Touch.touchDragNode = false + }) + }, + computePositions: function () { + var self = Metamaps.Visualize, + mapping + + if (self.type == 'RGraph') { + var i, l, startPos, endPos, topic, synapse + + self.mGraph.graph.eachNode(function (n) { + topic = Metamaps.Topics.get(n.id) + topic.set({ node: n }, { silent: true }) + topic.updateNode() + + n.eachAdjacency(function (edge) { + if (!edge.getData('init')) { + edge.setData('init', true) + + l = edge.getData('synapseIDs').length + for (i = 0; i < l; i++) { + synapse = Metamaps.Synapses.get(edge.getData('synapseIDs')[i]) + synapse.set({ edge: edge }, { silent: true }) + synapse.updateEdge() + } + } + }) + + var pos = n.getPos() + pos.setc(-200, -200) + }) + self.mGraph.compute('end') + } else if (self.type == 'ForceDirected') { + var i, l, startPos, endPos, topic, synapse + + self.mGraph.graph.eachNode(function (n) { + topic = Metamaps.Topics.get(n.id) + topic.set({ node: n }, { silent: true }) + topic.updateNode() + mapping = topic.getMapping() + + n.eachAdjacency(function (edge) { + if (!edge.getData('init')) { + edge.setData('init', true) + + l = edge.getData('synapseIDs').length + for (i = 0; i < l; i++) { + synapse = Metamaps.Synapses.get(edge.getData('synapseIDs')[i]) + synapse.set({ edge: edge }, { silent: true }) + synapse.updateEdge() + } + } + }) + + startPos = new $jit.Complex(0, 0) + endPos = new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')) + n.setPos(startPos, 'start') + n.setPos(endPos, 'end') + }) + } else if (self.type == 'ForceDirected3D') { + self.mGraph.compute() + } + }, + /** + * render does the heavy lifting of creating the engine that renders the graph with the properties we desire + * + */ + render: function () { + var self = Metamaps.Visualize, RGraphSettings, FDSettings + + if (self.type == 'RGraph' && (!self.mGraph || self.mGraph instanceof $jit.ForceDirected)) { + RGraphSettings = $.extend(true, {}, Metamaps.JIT.ForceDirected.graphSettings) + + $jit.RGraph.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings) + $jit.RGraph.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings) + + RGraphSettings.width = $(document).width() + RGraphSettings.height = $(document).height() + RGraphSettings.background = Metamaps.JIT.RGraph.background + RGraphSettings.levelDistance = Metamaps.JIT.RGraph.levelDistance + + self.mGraph = new $jit.RGraph(RGraphSettings) + } else if (self.type == 'ForceDirected' && (!self.mGraph || self.mGraph instanceof $jit.RGraph)) { + FDSettings = $.extend(true, {}, Metamaps.JIT.ForceDirected.graphSettings) + + $jit.ForceDirected.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings) + $jit.ForceDirected.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings) + + FDSettings.width = $('body').width() + FDSettings.height = $('body').height() + + self.mGraph = new $jit.ForceDirected(FDSettings) + } else if (self.type == 'ForceDirected3D' && !self.mGraph) { + // init ForceDirected3D + self.mGraph = new $jit.ForceDirected3D(Metamaps.JIT.ForceDirected3D.graphSettings) + self.cameraPosition = self.mGraph.canvas.canvases[0].camera.position + } else { + self.mGraph.graph.empty() + } + + function runAnimation () { + Metamaps.Loading.hide() + // load JSON data, if it's not empty + if (!self.loadLater) { + // load JSON data. + var rootIndex = 0 + if (Metamaps.Active.Topic) { + var node = _.find(Metamaps.JIT.vizData, function (node) { + return node.id === Metamaps.Active.Topic.id + }) + rootIndex = _.indexOf(Metamaps.JIT.vizData, node) + } + self.mGraph.loadJSON(Metamaps.JIT.vizData, rootIndex) + // compute positions and plot. + self.computePositions() + self.mGraph.busy = true + if (self.type == 'RGraph') { + self.mGraph.fx.animate(Metamaps.JIT.RGraph.animate) + } else if (self.type == 'ForceDirected') { + self.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout) + } else if (self.type == 'ForceDirected3D') { + self.mGraph.animate(Metamaps.JIT.ForceDirected.animateFDLayout) + } + } + } + // hold until all the needed metacode images are loaded + // hold for a maximum of 80 passes, or 4 seconds of waiting time + var tries = 0 + function hold () { + var unique = _.uniq(Metamaps.Topics.models, function (metacode) { return metacode.get('metacode_id'); }), + requiredMetacodes = _.map(unique, function (metacode) { return metacode.get('metacode_id'); }), + loadedCount = 0 + + _.each(requiredMetacodes, function (metacode_id) { + var metacode = Metamaps.Metacodes.get(metacode_id), + img = metacode ? metacode.get('image') : false + + if (img && (img.complete || (typeof img.naturalWidth !== 'undefined' && img.naturalWidth !== 0))) { + loadedCount += 1 + } + }) + + if (loadedCount === requiredMetacodes.length || tries > 80) runAnimation() + else setTimeout(function () { tries++; hold() }, 50) + } + hold() + + // update the url now that the map is ready + clearTimeout(Metamaps.Router.timeoutId) + Metamaps.Router.timeoutId = setTimeout(function () { + var m = Metamaps.Active.Map + var t = Metamaps.Active.Topic + + if (m && window.location.pathname !== '/maps/' + m.id) { + Metamaps.Router.navigate('/maps/' + m.id) + } + else if (t && window.location.pathname !== '/topics/' + t.id) { + Metamaps.Router.navigate('/topics/' + t.id) + } + }, 800) + } +}; // end Metamaps.Visualize diff --git a/app/assets/javascripts/src/Metamaps.js.erb b/app/assets/javascripts/src/Metamaps.js.erb index 0bd30932..6ac29af3 100644 --- a/app/assets/javascripts/src/Metamaps.js.erb +++ b/app/assets/javascripts/src/Metamaps.js.erb @@ -853,10 +853,6 @@ Metamaps.Create = { } }; // end Metamaps.Create - -////////////////// TOPIC AND SYNAPSE CARDS ////////////////////////// - - /* * * TOPICCARD @@ -1592,209 +1588,3 @@ Metamaps.SynapseCard = { } // if } //add_direction_form }; // end Metamaps.SynapseCard - - -////////////////////// END TOPIC AND SYNAPSE CARDS ////////////////////////////////// - - -/* - * - * VISUALIZE - * - */ -Metamaps.Visualize = { - mGraph: null, // a reference to the graph object. - cameraPosition: null, // stores the camera position when using a 3D visualization - type: "ForceDirected", // the type of graph we're building, could be "RGraph", "ForceDirected", or "ForceDirected3D" - loadLater: false, // indicates whether there is JSON that should be loaded right in the offset, or whether to wait till the first topic is created - init: function () { - var self = Metamaps.Visualize; - // disable awkward dragging of the canvas element that would sometimes happen - $('#infovis-canvas').on('dragstart', function (event) { - event.preventDefault(); - }); - - // prevent touch events on the canvas from default behaviour - $("#infovis-canvas").bind('touchstart', function (event) { - event.preventDefault(); - self.mGraph.events.touched = true; - }); - - // prevent touch events on the canvas from default behaviour - $("#infovis-canvas").bind('touchmove', function (event) { - //Metamaps.JIT.touchPanZoomHandler(event); - }); - - // prevent touch events on the canvas from default behaviour - $("#infovis-canvas").bind('touchend touchcancel', function (event) { - lastDist = 0; - if (!self.mGraph.events.touchMoved && !Metamaps.Touch.touchDragNode) Metamaps.TopicCard.hideCurrentCard(); - self.mGraph.events.touched = self.mGraph.events.touchMoved = false; - Metamaps.Touch.touchDragNode = false; - }); - }, - computePositions: function () { - var self = Metamaps.Visualize, - mapping; - - if (self.type == "RGraph") { - var i, l, startPos, endPos, topic, synapse; - - self.mGraph.graph.eachNode(function (n) { - topic = Metamaps.Topics.get(n.id); - topic.set({ node: n }, { silent: true }); - topic.updateNode(); - - n.eachAdjacency(function (edge) { - if(!edge.getData('init')) { - edge.setData('init', true); - - l = edge.getData('synapseIDs').length; - for (i = 0; i < l; i++) { - synapse = Metamaps.Synapses.get(edge.getData('synapseIDs')[i]); - synapse.set({ edge: edge }, { silent: true }); - synapse.updateEdge(); - } - } - }); - - var pos = n.getPos(); - pos.setc(-200, -200); - }); - self.mGraph.compute('end'); - } else if (self.type == "ForceDirected") { - var i, l, startPos, endPos, topic, synapse; - - self.mGraph.graph.eachNode(function (n) { - topic = Metamaps.Topics.get(n.id); - topic.set({ node: n }, { silent: true }); - topic.updateNode(); - mapping = topic.getMapping(); - - n.eachAdjacency(function (edge) { - if(!edge.getData('init')) { - edge.setData('init', true); - - l = edge.getData('synapseIDs').length; - for (i = 0; i < l; i++) { - synapse = Metamaps.Synapses.get(edge.getData('synapseIDs')[i]); - synapse.set({ edge: edge }, { silent: true }); - synapse.updateEdge(); - } - } - }); - - startPos = new $jit.Complex(0, 0); - endPos = new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')); - n.setPos(startPos, 'start'); - n.setPos(endPos, 'end'); - }); - } else if (self.type == "ForceDirected3D") { - self.mGraph.compute(); - } - }, - /** - * render does the heavy lifting of creating the engine that renders the graph with the properties we desire - * - */ - render: function () { - var self = Metamaps.Visualize, RGraphSettings, FDSettings; - - if (self.type == "RGraph" && (!self.mGraph || self.mGraph instanceof $jit.ForceDirected)) { - - RGraphSettings = $.extend(true, {}, Metamaps.JIT.ForceDirected.graphSettings); - - $jit.RGraph.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings); - $jit.RGraph.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings); - - RGraphSettings.width = $(document).width(); - RGraphSettings.height = $(document).height(); - RGraphSettings.background = Metamaps.JIT.RGraph.background; - RGraphSettings.levelDistance = Metamaps.JIT.RGraph.levelDistance; - - self.mGraph = new $jit.RGraph(RGraphSettings); - - } else if (self.type == "ForceDirected" && (!self.mGraph || self.mGraph instanceof $jit.RGraph)) { - - FDSettings = $.extend(true, {}, Metamaps.JIT.ForceDirected.graphSettings); - - $jit.ForceDirected.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings); - $jit.ForceDirected.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings); - - FDSettings.width = $('body').width(); - FDSettings.height = $('body').height(); - - self.mGraph = new $jit.ForceDirected(FDSettings); - - } else if (self.type == "ForceDirected3D" && !self.mGraph) { - // init ForceDirected3D - self.mGraph = new $jit.ForceDirected3D(Metamaps.JIT.ForceDirected3D.graphSettings); - self.cameraPosition = self.mGraph.canvas.canvases[0].camera.position; - } - else { - self.mGraph.graph.empty(); - } - - function runAnimation() { - Metamaps.Loading.hide(); - // load JSON data, if it's not empty - if (!self.loadLater) { - //load JSON data. - var rootIndex = 0; - if (Metamaps.Active.Topic) { - var node = _.find(Metamaps.JIT.vizData, function(node){ - return node.id === Metamaps.Active.Topic.id; - }); - rootIndex = _.indexOf(Metamaps.JIT.vizData, node); - } - self.mGraph.loadJSON(Metamaps.JIT.vizData, rootIndex); - //compute positions and plot. - self.computePositions(); - self.mGraph.busy = true; - if (self.type == "RGraph") { - self.mGraph.fx.animate(Metamaps.JIT.RGraph.animate); - } else if (self.type == "ForceDirected") { - self.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout); - } else if (self.type == "ForceDirected3D") { - self.mGraph.animate(Metamaps.JIT.ForceDirected.animateFDLayout); - } - } - } - // hold until all the needed metacode images are loaded - // hold for a maximum of 80 passes, or 4 seconds of waiting time - var tries = 0; - function hold() { - var unique = _.uniq(Metamaps.Topics.models, function (metacode) { return metacode.get('metacode_id'); }), - requiredMetacodes = _.map(unique, function (metacode) { return metacode.get('metacode_id'); }), - loadedCount = 0; - - _.each(requiredMetacodes, function (metacode_id) { - var metacode = Metamaps.Metacodes.get(metacode_id), - img = metacode ? metacode.get('image') : false; - - if (img && (img.complete || (typeof img.naturalWidth !== "undefined" && img.naturalWidth !== 0))) { - loadedCount += 1; - } - }); - - if (loadedCount === requiredMetacodes.length || tries > 80) runAnimation(); - else setTimeout(function(){ tries++; hold() }, 50); - } - hold(); - - // update the url now that the map is ready - clearTimeout(Metamaps.Router.timeoutId); - Metamaps.Router.timeoutId = setTimeout(function(){ - var m = Metamaps.Active.Map; - var t = Metamaps.Active.Topic; - - if (m && window.location.pathname !== "/maps/" + m.id) { - Metamaps.Router.navigate("/maps/" + m.id); - } - else if (t && window.location.pathname !== "/topics/" + t.id) { - Metamaps.Router.navigate("/topics/" + t.id); - } - }, 800); - - } -}; // end Metamaps.Visualize