|
|
@ -1,11 +1,13 @@ |
|
|
|
import React, { Component } from 'react' |
|
|
|
import PropTypes from 'prop-types' |
|
|
|
import * as d3Force from 'd3-force'; |
|
|
|
import * as d3Selection from 'd3-selection'; |
|
|
|
import * as d3Zoom from 'd3-zoom'; |
|
|
|
const d3 = Object.assign({}, d3Force, d3Selection, d3Zoom) |
|
|
|
import * as d3Force from 'd3-force' |
|
|
|
import * as d3Selection from 'd3-selection' |
|
|
|
import * as d3Zoom from 'd3-zoom' |
|
|
|
|
|
|
|
import styles from './CanvasNetworkGraph.scss' |
|
|
|
|
|
|
|
const d3 = Object.assign({}, d3Force, d3Selection, d3Zoom) |
|
|
|
|
|
|
|
function generateSimulationData(nodes, edges) { |
|
|
|
const resNodes = nodes.map(node => Object.assign(node, { id: node.pub_key })) |
|
|
|
const resEdges = edges.map(node => Object.assign(node, { source: node.node1_pub, target: node.node2_pub })) |
|
|
@ -39,21 +41,6 @@ class CanvasNetworkGraph extends Component { |
|
|
|
this.restart = this.restart.bind(this) |
|
|
|
} |
|
|
|
|
|
|
|
componentWillReceiveProps(nextProps) { |
|
|
|
const { network } = nextProps |
|
|
|
const { simulationData: { nodes, links } } = this.state |
|
|
|
|
|
|
|
const simulationDataEmpty = !nodes.length && !links.length |
|
|
|
const networkDataLoaded = network.nodes.length || network.edges.length |
|
|
|
|
|
|
|
// if the simulationData is empty and we have network data
|
|
|
|
if (simulationDataEmpty && networkDataLoaded) { |
|
|
|
this.setState({ |
|
|
|
simulationData: generateSimulationData(network.nodes, network.edges) |
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
componentDidMount() { |
|
|
|
// wait for the svg to be in the DOM before we start the simulation
|
|
|
|
const svgInterval = setInterval(() => { |
|
|
@ -71,24 +58,33 @@ class CanvasNetworkGraph extends Component { |
|
|
|
}, 1000) |
|
|
|
} |
|
|
|
|
|
|
|
componentWillReceiveProps(nextProps) { |
|
|
|
const { network } = nextProps |
|
|
|
const { simulationData: { nodes, links } } = this.state |
|
|
|
|
|
|
|
const simulationDataEmpty = !nodes.length && !links.length |
|
|
|
const networkDataLoaded = network.nodes.length || network.edges.length |
|
|
|
const prevNetwork = this.props.network |
|
|
|
|
|
|
|
if ( |
|
|
|
// update the simulationData only if
|
|
|
|
// the simulationData is empty and we have network data
|
|
|
|
(simulationDataEmpty && networkDataLoaded) || |
|
|
|
// the nodes or edges have changed
|
|
|
|
(prevNetwork.nodes.length !== network.nodes.length || prevNetwork.edges.length !== network.edges.length)) { |
|
|
|
this.setState({ |
|
|
|
simulationData: generateSimulationData(network.nodes, network.edges) |
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
componentDidUpdate(prevProps) { |
|
|
|
const { |
|
|
|
network: { nodes, edges }, |
|
|
|
selectedPeerPubkeys, |
|
|
|
selectedChannelIds, |
|
|
|
currentRouteChanIds |
|
|
|
} = this.props |
|
|
|
|
|
|
|
const prevNodes = prevProps.network.nodes |
|
|
|
const prevEdges = prevProps.network.edges |
|
|
|
|
|
|
|
// update the simulationData only if the nodes or edges have changed
|
|
|
|
if (prevNodes.length !== nodes.length || prevEdges.length !== edges.length) { |
|
|
|
this.setState({ |
|
|
|
simulationData: generateSimulationData(nodes, edges) |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
if (prevProps.selectedPeerPubkeys.length !== selectedPeerPubkeys.length) { |
|
|
|
this.updateSelectedPeers() |
|
|
|
} |
|
|
@ -111,10 +107,7 @@ class CanvasNetworkGraph extends Component { |
|
|
|
const { selectedPeerPubkeys } = this.props |
|
|
|
|
|
|
|
// remove active class
|
|
|
|
d3.selectAll('.active-peer') |
|
|
|
.each(function () { |
|
|
|
d3.select(this).classed('active-peer', false) |
|
|
|
}) |
|
|
|
d3.selectAll('.active-peer').classed('active-peer', false) |
|
|
|
|
|
|
|
// add active class to all selected peers
|
|
|
|
selectedPeerPubkeys.forEach((pubkey) => { |
|
|
@ -126,10 +119,7 @@ class CanvasNetworkGraph extends Component { |
|
|
|
const { selectedChannelIds } = this.props |
|
|
|
|
|
|
|
// remove active class
|
|
|
|
d3.selectAll('.active-channel') |
|
|
|
.each(function () { |
|
|
|
d3.select(this).classed('active-channel', false) |
|
|
|
}) |
|
|
|
d3.selectAll('.active-channel').classed('active-channel', false) |
|
|
|
|
|
|
|
// add active class to all selected peers
|
|
|
|
selectedChannelIds.forEach((chanid) => { |
|
|
@ -137,53 +127,6 @@ class CanvasNetworkGraph extends Component { |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
renderSelectedRoute() { |
|
|
|
const { currentRouteChanIds } = this.props |
|
|
|
|
|
|
|
// remove all route animations before rendering new ones
|
|
|
|
d3.selectAll('.animated-route-circle') |
|
|
|
.each(function () { |
|
|
|
d3.select(this).remove() |
|
|
|
}) |
|
|
|
|
|
|
|
currentRouteChanIds.forEach((chanId) => { |
|
|
|
const link = document.getElementById(`link-${chanId}`) |
|
|
|
|
|
|
|
if (!link) { return } |
|
|
|
const x1 = link.x1.baseVal.value |
|
|
|
const x2 = link.x2.baseVal.value |
|
|
|
const y1 = link.y1.baseVal.value |
|
|
|
const y2 = link.y2.baseVal.value |
|
|
|
|
|
|
|
// create the circle that represent btc traveling through a channel
|
|
|
|
this.g |
|
|
|
.append('circle') |
|
|
|
.attr('id', `circle-${chanId}`) |
|
|
|
.attr('class', 'animated-route-circle') |
|
|
|
.attr('r', 50) |
|
|
|
.attr('cx', x1) |
|
|
|
.attr('cy', y1) |
|
|
|
.attr('fill', '#FFDC53') |
|
|
|
|
|
|
|
// we want the animation to repeat back and forth, this function executes that visually
|
|
|
|
const repeat = () => { |
|
|
|
d3.select(`#circle-${chanId}`) |
|
|
|
.transition() |
|
|
|
.attr('cx', x2) |
|
|
|
.attr('cy', y2) |
|
|
|
.duration(1000) |
|
|
|
.transition() |
|
|
|
.duration(1000) |
|
|
|
.attr('cx', x1) |
|
|
|
.attr('cy', y1) |
|
|
|
.on('end', repeat) |
|
|
|
} |
|
|
|
|
|
|
|
// call repeat to animate the circle
|
|
|
|
repeat() |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
startSimulation() { |
|
|
|
const { simulationData: { nodes, links } } = this.state |
|
|
|
|
|
|
@ -235,7 +178,7 @@ class CanvasNetworkGraph extends Component { |
|
|
|
this.node = this.node.enter() |
|
|
|
.append('circle') |
|
|
|
.attr('stroke', () => 'silver') |
|
|
|
.attr('fill', d => d.pub_key === identity_pubkey ? '#FFF' : '#353535') |
|
|
|
.attr('fill', d => (d.pub_key === identity_pubkey ? '#FFF' : '#353535')) |
|
|
|
.attr('r', () => 100) |
|
|
|
.attr('id', d => `node-${d.pub_key}`) |
|
|
|
.attr('class', 'network-node') |
|
|
@ -257,6 +200,50 @@ class CanvasNetworkGraph extends Component { |
|
|
|
this.simulation.restart() |
|
|
|
} |
|
|
|
|
|
|
|
renderSelectedRoute() { |
|
|
|
const { currentRouteChanIds } = this.props |
|
|
|
|
|
|
|
// remove all route animations before rendering new ones
|
|
|
|
d3.selectAll('.animated-route-circle').remove() |
|
|
|
|
|
|
|
currentRouteChanIds.forEach((chanId) => { |
|
|
|
const link = document.getElementById(`link-${chanId}`) |
|
|
|
|
|
|
|
if (!link) { return } |
|
|
|
const x1 = link.x1.baseVal.value |
|
|
|
const x2 = link.x2.baseVal.value |
|
|
|
const y1 = link.y1.baseVal.value |
|
|
|
const y2 = link.y2.baseVal.value |
|
|
|
|
|
|
|
// create the circle that represent btc traveling through a channel
|
|
|
|
this.g |
|
|
|
.append('circle') |
|
|
|
.attr('id', `circle-${chanId}`) |
|
|
|
.attr('class', 'animated-route-circle') |
|
|
|
.attr('r', 50) |
|
|
|
.attr('cx', x1) |
|
|
|
.attr('cy', y1) |
|
|
|
.attr('fill', '#FFDC53') |
|
|
|
|
|
|
|
// we want the animation to repeat back and forth, this function executes that visually
|
|
|
|
const repeat = () => { |
|
|
|
d3.select(`#circle-${chanId}`) |
|
|
|
.transition() |
|
|
|
.attr('cx', x2) |
|
|
|
.attr('cy', y2) |
|
|
|
.duration(1000) |
|
|
|
.transition() |
|
|
|
.duration(1000) |
|
|
|
.attr('cx', x1) |
|
|
|
.attr('cy', y1) |
|
|
|
.on('end', repeat) |
|
|
|
} |
|
|
|
|
|
|
|
// call repeat to animate the circle
|
|
|
|
repeat() |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
render() { |
|
|
|
const { svgLoaded } = this.state |
|
|
|
|
|
|
|