move jit stuff to react
This commit is contained in:
parent
a763484235
commit
12ef63b20d
9 changed files with 262 additions and 186 deletions
|
@ -98,6 +98,7 @@ const ReactApp = {
|
|||
getProps: function() {
|
||||
const self = ReactApp
|
||||
return merge({
|
||||
DataModel: DataModel,
|
||||
unreadNotificationsCount: Notifications.unreadNotificationsCount,
|
||||
currentUser: Active.Mapper,
|
||||
toast: self.toast,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
class DataVis extends Component {
|
||||
static propTypes = {
|
||||
}
|
||||
|
||||
render () {
|
||||
return <div id="infovis" />
|
||||
}
|
||||
}
|
||||
|
||||
export default DataVis
|
154
frontend/src/components/MapVis.js
Normal file
154
frontend/src/components/MapVis.js
Normal file
|
@ -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 <div id="infovis" ref={this.divMounted} />
|
||||
}
|
||||
}
|
||||
|
||||
export default MapVis
|
49
frontend/src/components/TopicVis.js
Normal file
49
frontend/src/components/TopicVis.js
Normal file
|
@ -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 <div id="infovis" />
|
||||
}
|
||||
}
|
||||
|
||||
export default MapVis
|
|
@ -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} />
|
||||
<DataVis />
|
||||
<MapVis map={map} DataModel={DataModel} />
|
||||
{openTopic && <TopicCard {...this.props} />}
|
||||
{contextMenu && <ContextMenu {...this.props} />}
|
||||
{currentUser && <Instructions mobile={mobile} hasLearnedTopicCreation={hasLearnedTopicCreation} />}
|
||||
|
|
|
@ -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} />
|
||||
<DataVis />
|
||||
<TopicVis />
|
||||
<TopicCard {...this.props} />
|
||||
{contextMenu && <ContextMenu {...this.props} />}
|
||||
<VisualizationControls onClickZoomIn={onZoomIn}
|
||||
|
|
Loading…
Reference in a new issue