metamaps--metamaps/frontend/src/Metamaps/Visualize.js

224 lines
7.5 KiB
JavaScript
Raw Normal View History

/* global $ */
2016-09-22 09:36:47 +00:00
2016-09-22 15:51:33 +00:00
import _ from 'lodash'
import $jit from '../patched/JIT'
2016-09-22 09:36:47 +00:00
import Active from './Active'
import DataModel from './DataModel'
2016-09-22 09:36:47 +00:00
import JIT from './JIT'
import Loading from './Loading'
2016-09-22 10:31:56 +00:00
import Router from './Router'
Into master: two finger pan/zoom, map and topic follows (for internal testing) on the UI, map activity emails (#1084) * fix topic spec * fix synapse/mapping spec * brakeman csrf warning suppressed :| * follows for maps in the ui for internal testing only still (#1072) * follows for maps in the ui for testers * require user for these actions * match how map follow works * include ability to unfollow from email * fixup templates * add unfollow_from_email to the policies * Update _cheatsheet.html.erb Clean up text, clarify, and bring in line with current functionality * topicsRegex and synapsesRegex should allow commas (#1073) * even better import csv regexes * prevent double prompt on file drop import * topic card in react (#1031) * its coming along * links bar * scssify a bunch * metacode image working a bit better * metacode selector in react topic card * riek editing for name field on topic card * riek submit on enter * factor out Title and Links from Topic Card component, but not the listeners * create working Desc editor * styling is much better now * textarea min height for desc * disallow images in topic card markdown * shift enter is linebreak, enter is save * attachments split out, but it's pretty buggy * move listeners into Links.js * slightly wider metacodeTitle * fix positioning on metacode selector * fix metacode selection * move metacode and permissions into subcomponents * fixes * prevent editing on desc/title if not authorized to edit * fix topic card draggability * fix embedly * fix md test * remove the removed link card manually with jquery * fix test syntax * eslint * more eslin * reuse authorizedToEdit * convert metacode sets to a json object for react * add the html in react whoop * fix metacode styling * sort wasn't working * finishing metacode select * readd the above link input border * fix syntax * multiline title editable textarea * more portable metacode selector component * factor out #metacodeOptions into one react component with a callback :D:D:D * render metacodeOptions in right click menu with react * render metacodeOptions in right click menu with react * fix up right click menu's metacode editing * fix topic card title character counter * ignore metamaps secret bundle in ag * simplify Attachments props * factor out embedly card into its own component; it seems to help * link resetter * fix edit icon on title in topic card * move mapCount and synapseCount hover/click logic to react * fix up the showMore control * metacode selection tweaks * tweak links bar spacing in topic card * rubocop * remove TODOs * more badass permissions selector * close permission selector when you click outside * fix overeager metacode selector * more modular attachments component * fix bug in Desc.js * fix right click styling * permission changes are different than edit rights * bad module ref * ensure maxLength on topic titles * hellz yeah (#1074) * fix drop from two touches to one * don't commit activity service * ability to select/unselect all metacodes in custom set with keyboard shortcut (fix #390) (#1078) * ability to select/unselect all metacodes in custom set with keyboard shortcut * select all button * nicer all/none buttons * set up react testing (#1080) * install mocha-webpack. also switch hark to npm version instead of github version * well, mocha-webpack runs * add jsdom for tests * upgrade to webpack 2 * fix npm run test errors * ImportDialogBox component tests * Fixes bug where pressing delete key while editing text will suggest... (#1083) * Fixes bug where pressing delete key while editing text will suggest the deletion of selected map entities * Changed the DEL key to remove entities instead of delete them * temporarily disable code climate duplication engine * add topic following for internal testing * daily map activity emails (#1081) * data prepared, task setup * add the basics of the email template * cover granular permissions * unfollow this map * break out permissions tests better * rename so test runs
2017-03-07 03:49:46 +00:00
import TopicCard from './Views/TopicCard'
2016-09-22 09:36:47 +00:00
const 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
2016-09-22 09:14:34 +00:00
touchDragNode: null,
2016-11-07 20:25:08 +00:00
init: function(serverData) {
var self = Visualize
if (serverData.VisualizeType) self.type = serverData.VisualizeType
// disable awkward dragging of the canvas element that would sometimes happen
2016-11-07 20:25:08 +00:00
$('#infovis-canvas').on('dragstart', function(event) {
event.preventDefault()
})
// prevent touch events on the canvas from default behaviour
2016-11-07 20:25:08 +00:00
$('#infovis-canvas').bind('touchstart', function(event) {
event.preventDefault()
self.mGraph.events.touched = true
})
// prevent touch events on the canvas from default behaviour
2016-11-07 20:25:08 +00:00
$('#infovis-canvas').bind('touchmove', function(event) {
2016-09-22 09:36:47 +00:00
// JIT.touchPanZoomHandler(event)
})
// prevent touch events on the canvas from default behaviour
2016-11-07 20:25:08 +00:00
$('#infovis-canvas').bind('touchend touchcancel', function(event) {
2016-09-22 10:31:56 +00:00
if (!self.mGraph.events.touchMoved && !Visualize.touchDragNode) TopicCard.hideCurrentCard()
self.mGraph.events.touched = self.mGraph.events.touchMoved = false
2016-09-22 09:14:34 +00:00
Visualize.touchDragNode = false
})
},
2016-11-07 20:25:08 +00:00
computePositions: function() {
const self = Visualize
2016-11-07 20:25:08 +00:00
if (self.type === 'RGraph') {
let i
let l
2016-11-07 20:25:08 +00:00
self.mGraph.graph.eachNode(function(n) {
const topic = DataModel.Topics.get(n.id)
topic.set({ node: n }, { silent: true })
topic.updateNode()
2016-11-07 20:25:08 +00:00
n.eachAdjacency(function(edge) {
if (!edge.getData('init')) {
edge.setData('init', true)
l = edge.getData('synapseIDs').length
for (i = 0; i < l; i++) {
2016-11-07 20:25:08 +00:00
const synapse = DataModel.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')
2016-11-07 20:25:08 +00:00
} else if (self.type === 'ForceDirected') {
self.mGraph.graph.eachNode(function(n) {
const topic = DataModel.Topics.get(n.id)
topic.set({ node: n }, { silent: true })
topic.updateNode()
2016-11-07 20:25:08 +00:00
const mapping = topic.getMapping()
2016-11-07 20:25:08 +00:00
n.eachAdjacency(function(edge) {
if (!edge.getData('init')) {
edge.setData('init', true)
2016-11-07 20:25:08 +00:00
const l = edge.getData('synapseIDs').length
for (let i = 0; i < l; i++) {
const synapse = DataModel.Synapses.get(edge.getData('synapseIDs')[i])
synapse.set({ edge: edge }, { silent: true })
synapse.updateEdge()
}
}
})
2016-11-07 20:25:08 +00:00
const startPos = new $jit.Complex(0, 0)
const endPos = new $jit.Complex(mapping.get('xloc'), mapping.get('yloc'))
n.setPos(startPos, 'start')
n.setPos(endPos, 'end')
})
2016-11-07 20:25:08 +00:00
} 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
*
*/
2016-11-07 20:25:08 +00:00
render: function() {
const self = Visualize
2016-11-07 20:25:08 +00:00
if (self.type === 'RGraph') {
// clear the previous canvas from #infovis
$('#infovis').empty()
2016-11-07 20:25:08 +00:00
const RGraphSettings = $.extend(true, {}, JIT.ForceDirected.graphSettings)
2016-09-22 09:36:47 +00:00
$jit.RGraph.Plot.NodeTypes.implement(JIT.ForceDirected.nodeSettings)
$jit.RGraph.Plot.EdgeTypes.implement(JIT.ForceDirected.edgeSettings)
RGraphSettings.width = $(document).width()
RGraphSettings.height = $(document).height()
2016-09-22 09:36:47 +00:00
RGraphSettings.background = JIT.RGraph.background
RGraphSettings.levelDistance = JIT.RGraph.levelDistance
self.mGraph = new $jit.RGraph(RGraphSettings)
2016-11-07 20:25:08 +00:00
} else if (self.type === 'ForceDirected') {
// clear the previous canvas from #infovis
$('#infovis').empty()
2016-11-07 20:25:08 +00:00
const FDSettings = $.extend(true, {}, JIT.ForceDirected.graphSettings)
2016-09-22 09:36:47 +00:00
$jit.ForceDirected.Plot.NodeTypes.implement(JIT.ForceDirected.nodeSettings)
$jit.ForceDirected.Plot.EdgeTypes.implement(JIT.ForceDirected.edgeSettings)
FDSettings.width = $('body').width()
FDSettings.height = $('body').height()
self.mGraph = new $jit.ForceDirected(FDSettings)
2016-11-07 20:25:08 +00:00
} else if (self.type === 'ForceDirected3D' && !self.mGraph) {
// clear the previous canvas from #infovis
$('#infovis').empty()
2016-11-07 20:25:08 +00:00
// init ForceDirected3D
2016-09-22 09:36:47 +00:00
self.mGraph = new $jit.ForceDirected3D(JIT.ForceDirected3D.graphSettings)
self.cameraPosition = self.mGraph.canvas.canvases[0].camera.position
} else {
self.mGraph.graph.empty()
}
2016-11-07 20:25:08 +00:00
function runAnimation() {
Loading.hide()
// load JSON data, if it's not empty
if (!self.loadLater) {
// load JSON data.
var rootIndex = 0
2016-09-22 09:36:47 +00:00
if (Active.Topic) {
2016-11-07 20:25:08 +00:00
var node = _.find(JIT.vizData, function(node) {
2016-09-22 09:36:47 +00:00
return node.id === Active.Topic.id
})
2016-09-22 09:36:47 +00:00
rootIndex = _.indexOf(JIT.vizData, node)
}
2016-09-22 09:36:47 +00:00
self.mGraph.loadJSON(JIT.vizData, rootIndex)
// compute positions and plot.
self.computePositions()
self.mGraph.busy = true
2016-11-07 20:25:08 +00:00
if (self.type === 'RGraph') {
2016-09-22 09:36:47 +00:00
self.mGraph.fx.animate(JIT.RGraph.animate)
2016-11-07 20:25:08 +00:00
} else if (self.type === 'ForceDirected') {
2016-09-22 09:36:47 +00:00
self.mGraph.animate(JIT.ForceDirected.animateSavedLayout)
2016-11-07 20:25:08 +00:00
} else if (self.type === 'ForceDirected3D') {
2016-09-22 09:36:47 +00:00
self.mGraph.animate(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
2016-11-07 20:25:08 +00:00
function hold() {
const unique = _.uniq(DataModel.Topics.models, function(metacode) { return metacode.get('metacode_id') })
const requiredMetacodes = _.map(unique, function(metacode) { return metacode.get('metacode_id') })
let loadedCount = 0
2016-11-07 20:25:08 +00:00
_.each(requiredMetacodes, function(metacodeId) {
const metacode = DataModel.Metacodes.get(metacodeId)
const img = metacode ? metacode.get('image') : false
if (img && (img.complete || (typeof img.naturalWidth !== 'undefined' && img.naturalWidth !== 0))) {
loadedCount += 1
}
})
2016-11-07 20:25:08 +00:00
if (loadedCount === requiredMetacodes.length || tries > 80) {
runAnimation()
} else {
setTimeout(function() { tries++; hold() }, 50)
}
}
hold()
// update the url now that the map is ready
2016-09-22 10:31:56 +00:00
clearTimeout(Router.timeoutId)
2016-11-07 20:25:08 +00:00
Router.timeoutId = setTimeout(function() {
2016-09-22 09:36:47 +00:00
var m = Active.Map
var t = Active.Topic
if (m && window.location.pathname !== '/maps/' + m.id) {
Router.navigateAndTrack('/maps/' + m.id)
2016-11-07 20:25:08 +00:00
} else if (t && window.location.pathname !== '/topics/' + t.id) {
Router.navigateAndTrack('/topics/' + t.id)
}
}, 800)
},
clearVisualization: function() {
Visualize.mGraph.graph.empty()
Visualize.mGraph.plot()
JIT.centerMap(Visualize.mGraph.canvas)
$('#infovis').empty()
2016-11-07 20:25:08 +00:00
}
}
export default Visualize