From 12ef63b20d0ac51c6f1f6599fff3d7ace815af23 Mon Sep 17 00:00:00 2001 From: Connor Turland Date: Wed, 25 Oct 2017 16:38:23 -0400 Subject: [PATCH] move jit stuff to react --- frontend/src/Metamaps/GlobalUI/ReactApp.js | 1 + frontend/src/Metamaps/JIT.js | 32 +--- frontend/src/Metamaps/Map/index.js | 24 ++- frontend/src/Metamaps/Visualize.js | 165 ++++----------------- frontend/src/components/DataVis.js | 13 -- frontend/src/components/MapVis.js | 154 +++++++++++++++++++ frontend/src/components/TopicVis.js | 49 ++++++ frontend/src/routes/MapView/index.js | 6 +- frontend/src/routes/TopicView.js | 4 +- 9 files changed, 262 insertions(+), 186 deletions(-) delete mode 100644 frontend/src/components/DataVis.js create mode 100644 frontend/src/components/MapVis.js create mode 100644 frontend/src/components/TopicVis.js diff --git a/frontend/src/Metamaps/GlobalUI/ReactApp.js b/frontend/src/Metamaps/GlobalUI/ReactApp.js index 6237f08a..7a744433 100644 --- a/frontend/src/Metamaps/GlobalUI/ReactApp.js +++ b/frontend/src/Metamaps/GlobalUI/ReactApp.js @@ -98,6 +98,7 @@ const ReactApp = { getProps: function() { const self = ReactApp return merge({ + DataModel: DataModel, unreadNotificationsCount: Notifications.unreadNotificationsCount, currentUser: Active.Mapper, toast: self.toast, diff --git a/frontend/src/Metamaps/JIT.js b/frontend/src/Metamaps/JIT.js index 1c50256c..02162d15 100644 --- a/frontend/src/Metamaps/JIT.js +++ b/frontend/src/Metamaps/JIT.js @@ -38,7 +38,6 @@ const JIT = { 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. */ @@ -55,8 +54,6 @@ const JIT = { */ convertModelsToJIT: function(topics, synapses) { const jitReady = [] - - const synapsesToRemove = [] let mapping let node const nodes = {} @@ -70,11 +67,7 @@ const JIT = { }) synapses.each(function(s) { edge = s.createEdge() - - if (topics.get(s.get('topic1_id')) === undefined || topics.get(s.get('topic2_id')) === undefined) { - // this means it's an invalid synapse - synapsesToRemove.push(s) - } else if (nodes[edge.nodeFrom] && nodes[edge.nodeTo]) { + if (nodes[edge.nodeFrom] && nodes[edge.nodeTo]) { existingEdge = _.find(edges, { nodeFrom: edge.nodeFrom, nodeTo: edge.nodeTo @@ -103,29 +96,8 @@ const JIT = { jitReady.push(node) }) - return [jitReady, synapsesToRemove] + return jitReady }, - prepareVizData: function() { - const self = JIT - let mapping - self.vizData = [] - Visualize.loadLater = false - const results = self.convertModelsToJIT(DataModel.Topics, DataModel.Synapses) - self.vizData = results[0] - // clean up the synapses array in case of any faulty data - _.each(results[1], function(synapse) { - mapping = synapse.getMapping() - DataModel.Synapses.remove(synapse) - if (DataModel.Mappings) DataModel.Mappings.remove(mapping) - }) - if (self.vizData.length === 0) { - Map.setHasLearnedTopicCreation(false) - Visualize.loadLater = true - } else { - Map.setHasLearnedTopicCreation(true) - } - Visualize.render() - }, // prepareVizData edgeRender: function(adj, canvas) { // get nodes cartesian coordinates const pos = adj.nodeFrom.pos.getc(true) diff --git a/frontend/src/Metamaps/Map/index.js b/frontend/src/Metamaps/Map/index.js index f337d1d0..43af6b2a 100644 --- a/frontend/src/Metamaps/Map/index.js +++ b/frontend/src/Metamaps/Map/index.js @@ -89,12 +89,31 @@ const Map = { } ReactApp.render() }, + cleanUpSynapses: function () { + const synapsesToRemove = [] + const topics = DataModel.Topics + DataModel.Synapses.each(function(s) { + if (topics.get(s.get('topic1_id')) === undefined || topics.get(s.get('topic2_id')) === undefined) { + // this means it's an invalid synapse + synapsesToRemove.push(s) + } + }) + // clean up the synapses array in case of any faulty data + _.each(synapsesToRemove, function(synapse) { + mapping = synapse.getMapping() + DataModel.Synapses.remove(synapse) + if (DataModel.Mappings) DataModel.Mappings.remove(mapping) + }) + }, launch: function(id) { const self = Map var dataIsReadySetupMap = function() { + if (DataModel.Topics.length === 0) { + Map.setHasLearnedTopicCreation(false) + } else { + Map.setHasLearnedTopicCreation(true) + } Map.setAccessRequest() - Visualize.type = 'ForceDirected' - JIT.prepareVizData() Selected.reset() InfoBox.load() Filter.reset() @@ -127,6 +146,7 @@ const Map = { DataModel.Mappings = new DataModel.MappingCollection(data.mappings) DataModel.Messages = data.messages DataModel.Stars = data.stars + Map.cleanUpSynapses() DataModel.attachCollectionEvents() self.requests = data.requests isLoaded() diff --git a/frontend/src/Metamaps/Visualize.js b/frontend/src/Metamaps/Visualize.js index d8d10aa9..bc93c2ec 100644 --- a/frontend/src/Metamaps/Visualize.js +++ b/frontend/src/Metamaps/Visualize.js @@ -11,13 +11,12 @@ import Loading from './Loading' import TopicCard from './Views/TopicCard' const Visualize = { - mGraph: null, // a reference to the graph object. - type: 'ForceDirected', // the type of graph we're building, could be "RGraph", "ForceDirected" - 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(serverData) { var self = Visualize - if (serverData.VisualizeType) self.type = serverData.VisualizeType + $jit.RGraph.Plot.NodeTypes.implement(JIT.ForceDirected.nodeSettings) + $jit.RGraph.Plot.EdgeTypes.implement(JIT.ForceDirected.edgeSettings) + // disable awkward dragging of the canvas element that would sometimes happen $('#infovis-canvas').on('dragstart', function(event) { @@ -39,58 +38,32 @@ const Visualize = { computePositions: function() { const self = Visualize - if (self.type === 'RGraph') { - let i - let l + // for RGraph + let i + let l - self.mGraph.graph.eachNode(function(n) { - const topic = DataModel.Topics.get(n.id) - topic.set({ node: n }, { silent: true }) - topic.updateNode() + self.mGraph.graph.eachNode(function(n) { + const topic = DataModel.Topics.get(n.id) + topic.set({ node: n }, { silent: true }) + topic.updateNode() - n.eachAdjacency(function(edge) { - if (!edge.getData('init')) { - edge.setData('init', true) + n.eachAdjacency(function(edge) { + if (!edge.getData('init')) { + edge.setData('init', true) - l = edge.getData('synapseIDs').length - for (i = 0; i < l; i++) { - const synapse = DataModel.Synapses.get(edge.getData('synapseIDs')[i]) - synapse.set({ edge: edge }, { silent: true }) - synapse.updateEdge() - } + l = edge.getData('synapseIDs').length + for (i = 0; i < l; i++) { + 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') - } 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() - const mapping = topic.getMapping() - n.eachAdjacency(function(edge) { - if (!edge.getData('init')) { - edge.setData('init', true) - - 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() - } - } - }) - - 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') - }) - } + var pos = n.getPos() + pos.setc(-200, -200) + }) + self.mGraph.compute('end') }, /** * render does the heavy lifting of creating the engine that renders the graph with the properties we desire @@ -98,92 +71,12 @@ const Visualize = { */ render: function() { const self = Visualize - - if (self.type === 'RGraph') { - // clear the previous canvas from #infovis - $('#infovis').empty() - - const RGraphSettings = $.extend(true, {}, JIT.ForceDirected.graphSettings) - - $jit.RGraph.Plot.NodeTypes.implement(JIT.ForceDirected.nodeSettings) - $jit.RGraph.Plot.EdgeTypes.implement(JIT.ForceDirected.edgeSettings) - - RGraphSettings.width = $(document).width() - RGraphSettings.height = $(document).height() - RGraphSettings.background = JIT.RGraph.background - RGraphSettings.levelDistance = JIT.RGraph.levelDistance - - self.mGraph = new $jit.RGraph(RGraphSettings) - } else if (self.type === 'ForceDirected') { - // clear the previous canvas from #infovis - $('#infovis').empty() - - const FDSettings = $.extend(true, {}, JIT.ForceDirected.graphSettings) - - $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) - } else { - self.mGraph.graph.empty() - } - - function runAnimation() { - Loading.hide() - // load JSON data, if it's not empty - if (!self.loadLater) { - // load JSON data. - var rootIndex = 0 - if (Active.Topic) { - var node = _.find(JIT.vizData, function(node) { - return node.id === Active.Topic.id - }) - rootIndex = _.indexOf(JIT.vizData, node) - } - self.mGraph.loadJSON(JIT.vizData, rootIndex) - // compute positions and plot. - self.computePositions() - self.mGraph.busy = true - if (self.type === 'RGraph') { - self.mGraph.fx.animate(JIT.RGraph.animate) - } else if (self.type === 'ForceDirected') { - self.mGraph.animate(JIT.ForceDirected.animateSavedLayout) - } - } - } - // 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() { - 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 - - _.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 - } - }) - - if (loadedCount === requiredMetacodes.length || tries > 80) { - runAnimation() - } else { - setTimeout(function() { tries++; hold() }, 50) - } - } - hold() - }, - clearVisualization: function() { - Visualize.mGraph.graph.empty() - Visualize.mGraph.plot() - JIT.centerMap(Visualize.mGraph.canvas) - $('#infovis').empty() + const RGraphSettings = $.extend(true, {}, JIT.ForceDirected.graphSettings) + RGraphSettings.width = $(document).width() + RGraphSettings.height = $(document).height() + RGraphSettings.background = JIT.RGraph.background + RGraphSettings.levelDistance = JIT.RGraph.levelDistance + self.mGraph = new $jit.RGraph(RGraphSettings) } } diff --git a/frontend/src/components/DataVis.js b/frontend/src/components/DataVis.js deleted file mode 100644 index f39bdd7f..00000000 --- a/frontend/src/components/DataVis.js +++ /dev/null @@ -1,13 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' - -class DataVis extends Component { - static propTypes = { - } - - render () { - return
- } -} - -export default DataVis diff --git a/frontend/src/components/MapVis.js b/frontend/src/components/MapVis.js new file mode 100644 index 00000000..1bb8d2e8 --- /dev/null +++ b/frontend/src/components/MapVis.js @@ -0,0 +1,154 @@ +/* global $ */ + +import _ from 'lodash' +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import $jit from '../patched/JIT' +import JIT from '../Metamaps/JIT' + +// There would be separate one of these for mapview and topicview + +/* +it could use the diffing intelligently to know when you +update the visualization + +// JIT MORPH to move between states? + +use componentDidUpdate to check for differences in +- topic list, synapse list, mapping list, etc +- use that info to intelligently update and animate the viz. + +basically port everything from VISUALIZE module over into here + +it should dynamically generate and pass in callbacks to the visualization +*/ + +class MapVis extends Component { + static propTypes = { + DataModel: PropTypes.object, + filters: PropTypes.array, + selectedNodes: PropTypes.array, + selectedEdges: PropTypes.array, + onSelect: PropTypes.func, + onPan: PropTypes.func, + onZoom: PropTypes.func, + onDrawSelectBox: PropTypes.func + } + + constructor(props) { + super(props) + this.jitGraph = null + this.vizData = null + this.state = { + loading: true + } + } + + componentDidMount() { + $jit.ForceDirected.Plot.NodeTypes.implement(JIT.ForceDirected.nodeSettings) + $jit.ForceDirected.Plot.EdgeTypes.implement(JIT.ForceDirected.edgeSettings) + } + + componentDidUpdate(prevProps) { + const { map, DataModel } = this.props + const prevMap = prevProps.map + const prevDataModel = prevProps.DataModel + if (DataModel) { + if (map !== prevMap) { + this.initialize() + } + } + } + + initialize() { + const { DataModel } = this.props + this.createJitGraph() + this.vizData = JIT.convertModelsToJIT(DataModel.Topics, DataModel.Synapses) + this.waitForMetacodesThenLoad() + } + + divMounted(div) { + console.log(div) + } + + createJitGraph() { + const FDSettings = $.extend(true, {}, JIT.ForceDirected.graphSettings) + FDSettings.width = $('body').width() + FDSettings.height = $('body').height() + this.jitGraph = new $jit.ForceDirected(FDSettings) + } + + waitForMetacodesThenLoad() { + const { DataModel } = this.props + // 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 + const 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 + + _.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 + } + }) + + if (loadedCount === requiredMetacodes.length || tries > 80) { + this.runAnimation() + } else { + setTimeout(function() { tries++; hold() }, 50) + } + } + hold() + } + + computePositions() { + const { DataModel } = this.props + this.jitGraph.graph.eachNode(function(n) { + const topic = DataModel.Topics.get(n.id) + topic.set({ node: n }, { silent: true }) + topic.updateNode() + const mapping = topic.getMapping() + n.eachAdjacency(function(edge) { + if (!edge.getData('init')) { + edge.setData('init', true) + 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() + } + } + }) + 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') + }) + } + + runAnimation() { + // load JSON data, if it's not empty + if (this.vizData) { + // load JSON data. + var rootIndex = 0 + // 0 is rootIndex + this.jitGraph.loadJSON(this.vizData, 0) + // compute positions and plot. + this.computePositions() + this.jitGraph.animate(JIT.ForceDirected.animateSavedLayout) + } + } + + render() { + const { loading } = this.state + // display loading while loading + return
+ } +} + +export default MapVis diff --git a/frontend/src/components/TopicVis.js b/frontend/src/components/TopicVis.js new file mode 100644 index 00000000..f753b5a5 --- /dev/null +++ b/frontend/src/components/TopicVis.js @@ -0,0 +1,49 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' + +// There would be separate one of these for mapview and topicview + +/* +it could use the diffing intelligently to know when you +update the visualization + +use componentDidUpdate to check for differences in +- topic list, synapse list, mapping list, etc +- use that info to intelligently update and animate the viz. + +basically port everything from VISUALIZE module over into here + +it should dynamically generate and pass in callbacks to the visualization +*/ + +class MapVis extends Component { + static propTypes = { + topics: PropTypes.array, + synapses: PropTypes.array, + mappings: PropTypes.array, + filters: PropTypes.array, + selectedNodes: PropTypes.array, + selectedEdges: PropTypes.array, + onSelect: PropTypes.func, + onPan: PropTypes.func, + onZoom: PropTypes.func, + onDrawSelectBox: PropTypes.func + } + + constructor(props) { + super(props) + this.state = { + mGraph: null + } + } + + componentDidMount() { + + } + + render () { + return
+ } +} + +export default MapVis diff --git a/frontend/src/routes/MapView/index.js b/frontend/src/routes/MapView/index.js index 027b395d..0c15448e 100644 --- a/frontend/src/routes/MapView/index.js +++ b/frontend/src/routes/MapView/index.js @@ -2,7 +2,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import ContextMenu from '../../components/ContextMenu' -import DataVis from '../../components/DataVis' +import MapVis from '../../components/MapVis' import UpperOptions from '../../components/UpperOptions' import InfoAndHelp from '../../components/InfoAndHelp' import Instructions from './Instructions' @@ -82,7 +82,7 @@ export default class MapView extends Component { openImportLightbox, forkMap, openHelpLightbox, mapIsStarred, onMapStar, onMapUnstar, openTopic, onZoomExtents, onZoomIn, onZoomOut, hasLearnedTopicCreation, - contextMenu } = this.props + contextMenu, DataModel } = this.props const { chatOpen } = this.state const onChatOpen = () => { this.setState({chatOpen: true}) @@ -110,7 +110,7 @@ export default class MapView extends Component { filterAllMetacodes={filterAllMetacodes} filterAllMappers={filterAllMappers} filterAllSynapses={filterAllSynapses} /> - + {openTopic && } {contextMenu && } {currentUser && } diff --git a/frontend/src/routes/TopicView.js b/frontend/src/routes/TopicView.js index cc8e54ca..577a877e 100644 --- a/frontend/src/routes/TopicView.js +++ b/frontend/src/routes/TopicView.js @@ -2,7 +2,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import ContextMenu from '../components/ContextMenu' -import DataVis from '../components/DataVis' +import TopicVis from '../components/TopicVis' import UpperOptions from '../components/UpperOptions' import InfoAndHelp from '../components/InfoAndHelp' import VisualizationControls from '../components/VisualizationControls' @@ -73,7 +73,7 @@ export default class TopicView extends Component { filterAllMetacodes={filterAllMetacodes} filterAllMappers={filterAllMappers} filterAllSynapses={filterAllSynapses} /> - + {contextMenu && }