Compare commits

...

1 commit

Author SHA1 Message Date
Connor Turland a763484235 move more event handling into native jit, and decouple 2017-10-25 14:15:55 -04:00
8 changed files with 1588 additions and 3412 deletions

View file

@ -23,8 +23,6 @@ import TopicCard from './Views/TopicCard'
import Util from './Util' import Util from './Util'
import Visualize from './Visualize' import Visualize from './Visualize'
let panningInt
const JIT = { const JIT = {
tempInit: false, tempInit: false,
tempNode: null, tempNode: null,
@ -272,14 +270,27 @@ const JIT = {
graphSettings: { graphSettings: {
// id of the visualization container // id of the visualization container
injectInto: 'infovis', injectInto: 'infovis',
// Enable zooming and panning // Number of iterations for the FD algorithm
// by scrolling and DnD iterations: 200,
// Edge length
levelDistance: 200,
Navigation: { Navigation: {
enable: true, enable: true,
// Enable panning events only if we're dragging the empty
// canvas (and not a node).
panning: 'avoid nodes', panning: 'avoid nodes',
zooming: 28 // zoom speed. higher is more sensible zooming: 28, // zoom speed. higher is more sensible
onZoom: function (event) {
$(document).trigger(Metamaps.JIT.events.zoom, [event]);
},
onPan: function () {
$(document).trigger(Metamaps.JIT.events.pan);
}
},
Selection: {
enable: true,
type: 'Native',
onDrawSelectBox: function (e, corner, oppositeCorner) {
JIT.selectWithBox(e, corner, oppositeCorner);
}
}, },
// Change node and edge styles such as // Change node and edge styles such as
// color and width. // color and width.
@ -299,81 +310,36 @@ const JIT = {
lineWidth: 2, lineWidth: 2,
alpha: 1 alpha: 1
}, },
// Native canvas text styling
Label: { Label: {
type: 'Native', // Native or HTML type: 'Native',
size: 20, size: 20,
family: 'arial', family: 'arial',
textBaseline: 'alphabetic', textBaseline: 'alphabetic',
color: Settings.colors.labels.text color: Settings.colors.labels.text
}, },
// Add Tips // this is events for clicking on edges and nodes in particular
Tips: {
enable: false,
onShow: function(tip, node) {}
},
// Add node events
Events: { Events: {
enable: true, enable: true,
enableForEdges: true, enableForEdges: true,
onMouseMove: function(node, eventInfo, e) { onMouseMove: function(node, eventInfo, e) {
JIT.onMouseMoveHandler(node, eventInfo, e) JIT.onMouseMoveHandler(node, eventInfo, e)
// console.log('called mouse move handler')
}, },
// Update node positions when dragged
onDragMove: function(node, eventInfo, e) { onDragMove: function(node, eventInfo, e) {
JIT.onDragMoveTopicHandler(node, eventInfo, e) JIT.onDragMoveTopicHandler(node, eventInfo, e)
// console.log('called drag move handler')
}, },
onDragEnd: function(node, eventInfo, e) { onDragEnd: function(node, eventInfo, e) {
JIT.onDragEndTopicHandler(node, eventInfo, e, false) JIT.onDragEndTopicHandler(node, eventInfo, e, false)
// console.log('called drag end handler')
}, },
onDragCancel: function(node, eventInfo, e) { onDragCancel: function(node, eventInfo, e) {
JIT.onDragCancelHandler(node, eventInfo, e, false) JIT.onDragCancelHandler(node, eventInfo, e, false)
}, },
// Implement the same handler for touchscreens
onTouchStart: function(node, eventInfo, e) {},
// Implement the same handler for touchscreens
onTouchMove: function(node, eventInfo, e) { onTouchMove: function(node, eventInfo, e) {
JIT.onDragMoveTopicHandler(node, eventInfo, e) JIT.onDragMoveTopicHandler(node, 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) { onClick: function(node, eventInfo, e) {
// remove the rightclickmenu // remove the rightclickmenu
ContextMenu.reset(ReactApp.render) ContextMenu.reset(ReactApp.render)
if (Mouse.boxStartCoordinates) {
if (e.ctrlKey) {
Visualize.mGraph.busy = false
Mouse.boxEndCoordinates = eventInfo.getPos()
const bS = Mouse.boxStartCoordinates
const bE = Mouse.boxEndCoordinates
if (Math.abs(bS.x - bE.x) > 20 && Math.abs(bS.y - bE.y) > 20) {
JIT.zoomToBox(e)
return
} else {
Mouse.boxStartCoordinates = null
Mouse.boxEndCoordinates = null
}
}
if (e.shiftKey) {
Visualize.mGraph.busy = false
Mouse.boxEndCoordinates = eventInfo.getPos()
JIT.selectWithBox(e)
return
}
}
if (e.target.id !== 'infovis-canvas') return false if (e.target.id !== 'infovis-canvas') return false
// clicking on a edge, node, or clicking on blank part of canvas? // clicking on a edge, node, or clicking on blank part of canvas?
if (node.nodeFrom) { if (node.nodeFrom) {
JIT.selectEdgeOnClickHandler(node, e) JIT.selectEdgeOnClickHandler(node, e)
@ -387,34 +353,15 @@ const JIT = {
onRightClick: function(node, eventInfo, e) { onRightClick: function(node, eventInfo, e) {
// remove the rightclickmenu // remove the rightclickmenu
ContextMenu.reset(ReactApp.render) ContextMenu.reset(ReactApp.render)
if (Mouse.boxStartCoordinates) {
Create.newSynapse.hide()
Create.newTopic.hide()
Visualize.mGraph.busy = false
Mouse.boxEndCoordinates = eventInfo.getPos()
JIT.selectWithBox(e)
return
}
if (e.target.id !== 'infovis-canvas') return false if (e.target.id !== 'infovis-canvas') return false
// clicking on a edge, node, or clicking on blank part of canvas? // clicking on a edge, node, or clicking on blank part of canvas?
if (node.nodeFrom) { if (node.nodeFrom) {
JIT.selectEdgeOnRightClickHandler(node, e) JIT.selectEdgeOnRightClickHandler(node, e)
} else if (node && !node.nodeFrom) { } else if (node && !node.nodeFrom) {
JIT.selectNodeOnRightClickHandler(node, e) JIT.selectNodeOnRightClickHandler(node, e)
} else {
// right click open space
Create.newSynapse.hide()
Create.newTopic.hide()
} }
} }
}, }
// Number of iterations for the FD algorithm
iterations: 200,
// Edge length
levelDistance: 200
}, },
nodeSettings: { nodeSettings: {
'customNode': { 'customNode': {
@ -509,97 +456,7 @@ const JIT = {
} }
} }
} }
}, // ForceDirected },
ForceDirected3D: {
animate: {
modes: ['linear'],
// TODO fix tests so we don't need _.get
transition: _.get($jit, 'Trans.Elastic.easeOut'),
duration: 2500,
onComplete: function() {
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
const pos = eventInfo.getPos()
Visualize.cameraPosition.x += (pos.x - Visualize.cameraPosition.x) * 0.5
Visualize.cameraPosition.y += (-pos.y - Visualize.cameraPosition.y) * 0.5
Visualize.mGraph.plot()
},
onMouseWheel: function(delta) {
Visualize.cameraPosition.z += -delta * 20
Visualize.mGraph.plot()
},
onClick: function() {}
},
// Number of iterations for the FD algorithm
iterations: 200,
// Edge length
levelDistance: 100
},
nodeSettings: {
},
edgeSettings: {
}
}, // ForceDirected3D
RGraph: { RGraph: {
animate: { animate: {
modes: ['polar'], modes: ['polar'],
@ -1103,12 +960,12 @@ const JIT = {
return {} return {}
} }
}, },
selectWithBox: function(e) { selectWithBox: function(e, corner, oppositeCorner) {
const self = this const self = this
let sX = Mouse.boxStartCoordinates.x let sX = corner.x
let sY = Mouse.boxStartCoordinates.y let sY = corner.y
let eX = Mouse.boxEndCoordinates.x let eX = oppositeCorner.x
let eY = Mouse.boxEndCoordinates.y let eY = oppositeCorner.y
if (!e.shiftKey) { if (!e.shiftKey) {
Control.deselectAllNodes() Control.deselectAllNodes()
@ -1245,30 +1102,7 @@ const JIT = {
} }
} }
}) })
Mouse.boxStartCoordinates = false
Mouse.boxEndCoordinates = false
Visualize.mGraph.plot()
}, // selectWithBox }, // selectWithBox
drawSelectBox: function(eventInfo, e) {
const ctx = Visualize.mGraph.canvas.getCtx()
const startX = Mouse.boxStartCoordinates.x
const startY = Mouse.boxStartCoordinates.y
const currX = eventInfo.getPos().x
const currY = eventInfo.getPos().y
Visualize.mGraph.canvas.clear()
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) { selectNodeOnClickHandler: function(node, e) {
if (Visualize.mGraph.busy) return if (Visualize.mGraph.busy) return
@ -1398,26 +1232,6 @@ const JIT = {
Control.selectEdge(adj) Control.selectEdge(adj)
ContextMenu.selectEdge(ReactApp.render, adj, {x: e.clientX, y: e.clientY}) ContextMenu.selectEdge(ReactApp.render, adj, {x: e.clientX, y: e.clientY})
}, // selectEdgeOnRightClickHandler }, // selectEdgeOnRightClickHandler
SmoothPanning: function() {
const sx = Visualize.mGraph.canvas.scaleOffsetX
const sy = Visualize.mGraph.canvas.scaleOffsetY
const yVelocity = Mouse.changeInY // initial y velocity
const xVelocity = Mouse.changeInX // initial x velocity
let easing = 1 // frictional value
window.clearInterval(panningInt)
panningInt = setInterval(function() {
myTimer()
}, 1)
function myTimer() {
Visualize.mGraph.canvas.translate(xVelocity * easing * 1 / sx, yVelocity * easing * 1 / sy)
$(document).trigger(JIT.events.pan)
easing = easing * 0.75
if (easing < 0.1) window.clearInterval(panningInt)
}
}, // SmoothPanning
renderMidArrow: function(from, to, dim, swap, canvas, placement, newSynapse) { renderMidArrow: function(from, to, dim, swap, canvas, placement, newSynapse) {
const ctx = canvas.getCtx() const ctx = canvas.getCtx()
// invert edge direction // invert edge direction
@ -1522,53 +1336,11 @@ const JIT = {
}, },
centerMap: function(canvas) { centerMap: function(canvas) {
const offsetScale = canvas.scaleOffsetX const offsetScale = canvas.scaleOffsetX
canvas.scale(1 / offsetScale, 1 / offsetScale)
const offsetX = canvas.translateOffsetX const offsetX = canvas.translateOffsetX
const offsetY = canvas.translateOffsetY const offsetY = canvas.translateOffsetY
canvas.scale(1 / offsetScale, 1 / offsetScale)
canvas.translate(-1 * offsetX, -1 * offsetY) canvas.translate(-1 * offsetX, -1 * offsetY)
}, },
zoomToBox: function(event) {
const sX = Mouse.boxStartCoordinates.x
const sY = Mouse.boxStartCoordinates.y
const eX = Mouse.boxEndCoordinates.x
const eY = Mouse.boxEndCoordinates.y
let canvas = Visualize.mGraph.canvas
JIT.centerMap(canvas)
let height = $(document).height()
let width = $(document).width()
let spanX = Math.abs(sX - eX)
let spanY = Math.abs(sY - eY)
let ratioX = width / spanX
let ratioY = height / spanY
let 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)
}
const cogX = (sX + eX) / 2
const cogY = (sY + eY) / 2
canvas.translate(-1 * cogX, -1 * cogY)
$(document).trigger(JIT.events.zoom, [event])
Mouse.boxStartCoordinates = false
Mouse.boxEndCoordinates = false
Visualize.mGraph.plot()
},
zoomExtents: function(event, canvas, denySelected) { zoomExtents: function(event, canvas, denySelected) {
JIT.centerMap(canvas) JIT.centerMap(canvas)
let height = canvas.getSize().height let height = canvas.getSize().height

View file

@ -0,0 +1,6 @@
import $jit from '../patched/JIT'
const mJit = {}
$jit(mJit)
export default mJit

View file

@ -1,11 +1,5 @@
const Mouse = { const Mouse = {
didPan: false,
didBoxZoom: false,
changeInX: 0,
changeInY: 0,
edgeHoveringOver: false, edgeHoveringOver: false,
boxStartCoordinates: false,
boxEndCoordinates: false,
synapseStartCoordinates: [], synapseStartCoordinates: [],
synapseEndCoordinates: null, synapseEndCoordinates: null,
lastNodeClick: 0, lastNodeClick: 0,

View file

@ -201,34 +201,6 @@ const Util = {
}, },
isTester: function(currentUser) { isTester: function(currentUser) {
return ['connorturland@gmail.com', 'devin@callysto.com', 'chessscholar@gmail.com', 'solaureum@gmail.com', 'ishanshapiro@gmail.com'].indexOf(currentUser.get('email')) > -1 return ['connorturland@gmail.com', 'devin@callysto.com', 'chessscholar@gmail.com', 'solaureum@gmail.com', 'ishanshapiro@gmail.com'].indexOf(currentUser.get('email')) > -1
},
zoomOnPoint: function(graph, ans, zoomPoint) {
var s = graph.canvas.getSize(),
p = graph.canvas.getPos(),
ox = graph.canvas.translateOffsetX,
oy = graph.canvas.translateOffsetY,
sx = graph.canvas.scaleOffsetX,
sy = graph.canvas.scaleOffsetY
var pointerCoordX = (zoomPoint.x - p.x - s.width / 2 - ox) * (1 / sx),
pointerCoordY = (zoomPoint.y - p.y - s.height / 2 - oy) * (1 / sy)
// This translates the canvas to be centred over the zoomPoint, then the canvas is zoomed as intended.
graph.canvas.translate(-pointerCoordX, -pointerCoordY)
graph.canvas.scale(ans, ans)
// Get the canvas attributes again now that is has changed
s = graph.canvas.getSize(),
p = graph.canvas.getPos(),
ox = graph.canvas.translateOffsetX,
oy = graph.canvas.translateOffsetY,
sx = graph.canvas.scaleOffsetX,
sy = graph.canvas.scaleOffsetY
var newX = (zoomPoint.x - p.x - s.width / 2 - ox) * (1 / sx),
newY = (zoomPoint.y - p.y - s.height / 2 - oy) * (1 / sy)
// Translate the canvas to put the pointer back over top the same coordinate it was over before
graph.canvas.translate(newX - pointerCoordX, newY - pointerCoordY)
} }
} }

View file

@ -12,10 +12,8 @@ import TopicCard from './Views/TopicCard'
const Visualize = { const Visualize = {
mGraph: null, // a reference to the graph object. 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"
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 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
touchDragNode: null,
init: function(serverData) { init: function(serverData) {
var self = Visualize var self = Visualize
@ -32,16 +30,10 @@ const Visualize = {
self.mGraph.events.touched = true self.mGraph.events.touched = true
}) })
// prevent touch events on the canvas from default behaviour
$('#infovis-canvas').bind('touchmove', function(event) {
// JIT.touchPanZoomHandler(event)
})
// prevent touch events on the canvas from default behaviour // prevent touch events on the canvas from default behaviour
$('#infovis-canvas').bind('touchend touchcancel', function(event) { $('#infovis-canvas').bind('touchend touchcancel', function(event) {
if (!self.mGraph.events.touchMoved && !Visualize.touchDragNode) TopicCard.hideCurrentCard() if (!self.mGraph.events.touchMoved) TopicCard.hideCurrentCard()
self.mGraph.events.touched = self.mGraph.events.touchMoved = false self.mGraph.events.touched = self.mGraph.events.touchMoved = false
Visualize.touchDragNode = false
}) })
}, },
computePositions: function() { computePositions: function() {
@ -98,8 +90,6 @@ const Visualize = {
n.setPos(startPos, 'start') n.setPos(startPos, 'start')
n.setPos(endPos, 'end') n.setPos(endPos, 'end')
}) })
} else if (self.type === 'ForceDirected3D') {
self.mGraph.compute()
} }
}, },
/** /**
@ -137,13 +127,6 @@ const Visualize = {
FDSettings.height = $('body').height() FDSettings.height = $('body').height()
self.mGraph = new $jit.ForceDirected(FDSettings) self.mGraph = new $jit.ForceDirected(FDSettings)
} else if (self.type === 'ForceDirected3D' && !self.mGraph) {
// clear the previous canvas from #infovis
$('#infovis').empty()
// init ForceDirected3D
self.mGraph = new $jit.ForceDirected3D(JIT.ForceDirected3D.graphSettings)
self.cameraPosition = self.mGraph.canvas.canvases[0].camera.position
} else { } else {
self.mGraph.graph.empty() self.mGraph.graph.empty()
} }
@ -168,8 +151,6 @@ const Visualize = {
self.mGraph.fx.animate(JIT.RGraph.animate) self.mGraph.fx.animate(JIT.RGraph.animate)
} else if (self.type === 'ForceDirected') { } else if (self.type === 'ForceDirected') {
self.mGraph.animate(JIT.ForceDirected.animateSavedLayout) self.mGraph.animate(JIT.ForceDirected.animateSavedLayout)
} else if (self.type === 'ForceDirected3D') {
self.mGraph.animate(JIT.ForceDirected.animateFDLayout)
} }
} }
} }

View file

@ -13,6 +13,7 @@ import GlobalUI, {
} from './GlobalUI' } from './GlobalUI'
import Import from './Import' import Import from './Import'
import JIT from './JIT' import JIT from './JIT'
import JitExtended from './JitExtended'
import Listeners from './Listeners' import Listeners from './Listeners'
import Loading from './Loading' import Loading from './Loading'
import Map, { CheatSheet, InfoBox } from './Map' import Map, { CheatSheet, InfoBox } from './Map'
@ -49,6 +50,7 @@ Metamaps.GlobalUI.CreateMap = CreateMap
Metamaps.GlobalUI.ImportDialog = ImportDialog Metamaps.GlobalUI.ImportDialog = ImportDialog
Metamaps.Import = Import Metamaps.Import = Import
Metamaps.JIT = JIT Metamaps.JIT = JIT
Metamaps.JitExtended = JitExtended
Metamaps.Listeners = Listeners Metamaps.Listeners = Listeners
Metamaps.Loading = Loading Metamaps.Loading = Loading
Metamaps.Map = Map Metamaps.Map = Map

View file

@ -5,7 +5,9 @@ Backbone.ajax = (opts) => window.$.ajaxq('backbone-ajaxq', opts)
import _ from 'lodash' import _ from 'lodash'
import Metamaps from './Metamaps' import Metamaps from './Metamaps'
import $jit from './patched/JIT'
// create global references // create global references
window._ = _ window._ = _
window.Metamaps = Metamaps window.Metamaps = Metamaps
window.$jit = $jit

File diff suppressed because it is too large Load diff