forked from visgl/deck.gl-community
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(graph-layers): Add source code for additional examples (visgl#169)
- Loading branch information
Showing
7 changed files
with
979 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import React, {Component} from 'react'; | ||
import {scaleOrdinal} from 'd3-scale'; | ||
import {schemeAccent} from 'd3-scale-chromatic'; | ||
import {extent} from 'd3-array'; | ||
import Color from 'color'; | ||
import {fetchJSONFromS3} from '../../utils/data/io'; | ||
|
||
// graph.gl | ||
import GraphGL, {JSONLoader, NODE_TYPE} from '../../src'; | ||
import HivePlot from './hive-plot-layout'; | ||
|
||
const DEFAULT_NODE_SIZE = 3; | ||
const DEFAULT_EDGE_COLOR = 'rgba(80, 80, 80, 0.3)'; | ||
const DEFAULT_EDGE_WIDTH = 1; | ||
const DEFAULT_WIDTH = 1000; | ||
|
||
export default class HivePlotExample extends Component { | ||
state = {graph: null}; | ||
|
||
componentDidMount() { | ||
fetchJSONFromS3(['wits.json']).then(([sampleGraph]) => { | ||
const {nodes} = sampleGraph; | ||
const nodeIndexMap = nodes.reduce((res, node, idx) => { | ||
res[idx] = node.name; | ||
return res; | ||
}, {}); | ||
const graph = JSONLoader({ | ||
json: sampleGraph, | ||
nodeParser: node => ({id: node.name}), | ||
edgeParser: edge => ({ | ||
id: `${edge.source}-${edge.target}`, | ||
sourceId: nodeIndexMap[edge.source], | ||
targetId: nodeIndexMap[edge.target], | ||
directed: true, | ||
}), | ||
}); | ||
this.setState({graph}); | ||
const groupExtent = extent(nodes, n => n.group); | ||
this._nodeColorScale = scaleOrdinal(schemeAccent).domain(groupExtent); | ||
}); | ||
} | ||
|
||
// node accessors | ||
getNodeColor = node => { | ||
const hex = this._nodeColorScale(node.getPropertyValue('group')); | ||
return Color(hex).array(); | ||
}; | ||
|
||
render() { | ||
if (!this.state.graph) { | ||
return null; | ||
} | ||
return ( | ||
<GraphGL | ||
graph={this.state.graph} | ||
layout={ | ||
new HivePlot({ | ||
innerRadius: DEFAULT_WIDTH * 0.05, | ||
outerRadius: DEFAULT_WIDTH * 0.3, | ||
getNodeAxis: node => node.getPropertyValue('group'), | ||
}) | ||
} | ||
nodeStyle={[ | ||
{ | ||
type: NODE_TYPE.CIRCLE, | ||
radius: DEFAULT_NODE_SIZE, | ||
fill: this.getNodeColor, | ||
}, | ||
]} | ||
edgeStyle={{ | ||
stroke: DEFAULT_EDGE_COLOR, | ||
strokeWidth: DEFAULT_EDGE_WIDTH, | ||
}} | ||
/> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
import {BaseLayout, EDGE_TYPE} from '../../src'; | ||
|
||
const defaultOptions = { | ||
innerRadius: 100, | ||
outerRadius: 500, | ||
getNodeAxis: node => node.getPropertyValue('group'), | ||
}; | ||
|
||
const computeControlPoint = ({ | ||
sourcePosition, | ||
sourceNodeAxis, | ||
targetPosition, | ||
targetNodeAxis, | ||
totalAxis, | ||
}) => { | ||
const halfAxis = (totalAxis - 1) / 2; | ||
// check whether the source/target are at the same side. | ||
const sameSide = | ||
(sourceNodeAxis <= halfAxis && targetNodeAxis <= halfAxis) || | ||
(sourceNodeAxis > halfAxis && targetNodeAxis > halfAxis); | ||
// curve direction | ||
const direction = | ||
sameSide && (sourceNodeAxis <= halfAxis && targetNodeAxis <= halfAxis) | ||
? 1 | ||
: -1; | ||
|
||
// flip the source/target to follow the clockwise diretion | ||
const source = | ||
sourceNodeAxis < targetNodeAxis && sameSide | ||
? sourcePosition | ||
: targetPosition; | ||
const target = | ||
sourceNodeAxis < targetNodeAxis && sameSide | ||
? targetPosition | ||
: sourcePosition; | ||
|
||
// calculate offset | ||
const distance = Math.hypot(source[0] - target[0], source[1] - target[1]); | ||
const offset = distance * 0.2; | ||
|
||
const midPoint = [(source[0] + target[0]) / 2, (source[1] + target[1]) / 2]; | ||
const dx = target[0] - source[0]; | ||
const dy = target[1] - source[1]; | ||
const normal = [dy, -dx]; | ||
const length = Math.hypot(dy, -dx); | ||
const normalized = [normal[0] / length, normal[1] / length]; | ||
return [ | ||
midPoint[0] + normalized[0] * offset * direction, | ||
midPoint[1] + normalized[1] * offset * direction, | ||
]; | ||
}; | ||
|
||
export default class HivePlot extends BaseLayout { | ||
constructor(options) { | ||
super(options); | ||
this._name = 'HivePlot'; | ||
this._options = { | ||
...defaultOptions, | ||
...options, | ||
}; | ||
this._nodePositionMap = {}; | ||
} | ||
|
||
initializeGraph(graph) { | ||
this.updateGraph(graph); | ||
} | ||
|
||
updateGraph(graph) { | ||
const {getNodeAxis, innerRadius, outerRadius} = this._options; | ||
this._graph = graph; | ||
this._nodeMap = graph.getNodes().reduce((res, node) => { | ||
res[node.getId()] = node; | ||
return res; | ||
}, {}); | ||
|
||
// bucket nodes into few axis | ||
|
||
this._axis = graph.getNodes().reduce((res, node) => { | ||
const axis = getNodeAxis(node); | ||
if (!res[axis]) { | ||
res[axis] = []; | ||
} | ||
res[axis].push(node); | ||
return res; | ||
}, {}); | ||
|
||
// sort nodes along the same axis by degree | ||
this._axis = Object.keys(this._axis).reduce((res, axis) => { | ||
const bucketedNodes = this._axis[axis]; | ||
const sortedNodes = bucketedNodes.sort((a, b) => { | ||
if (a.getDegree() > b.getDegree()) { | ||
return 1; | ||
} | ||
if (a.getDegree() === b.getDegree()) { | ||
return 0; | ||
} | ||
return -1; | ||
}); | ||
res[axis] = sortedNodes; | ||
return res; | ||
}, {}); | ||
this._totalAxis = Object.keys(this._axis).length; | ||
const center = [0, 0]; | ||
const angleInterval = 360 / Object.keys(this._axis).length; | ||
|
||
// calculate positions | ||
this._nodePositionMap = Object.keys(this._axis).reduce( | ||
(res, axis, axisIdx) => { | ||
const axisAngle = angleInterval * axisIdx; | ||
const bucketedNodes = this._axis[axis]; | ||
const interval = (outerRadius - innerRadius) / bucketedNodes.length; | ||
|
||
bucketedNodes.forEach((node, idx) => { | ||
const radius = innerRadius + idx * interval; | ||
const x = Math.cos((axisAngle / 180) * Math.PI) * radius + center[0]; | ||
const y = Math.sin((axisAngle / 180) * Math.PI) * radius + center[1]; | ||
res[node.getId()] = [x, y]; | ||
}); | ||
return res; | ||
}, | ||
{} | ||
); | ||
} | ||
|
||
start() { | ||
this._callbacks.onLayoutChange(); | ||
this._callbacks.onLayoutDone(); | ||
} | ||
|
||
getNodePosition = node => this._nodePositionMap[node.getId()]; | ||
|
||
getEdgePosition = edge => { | ||
const {getNodeAxis} = this._options; | ||
const sourceNodeId = edge.getSourceNodeId(); | ||
const targetNodeId = edge.getTargetNodeId(); | ||
|
||
const sourcePosition = this._nodePositionMap[sourceNodeId]; | ||
const targetPosition = this._nodePositionMap[targetNodeId]; | ||
|
||
const sourceNode = this._nodeMap[sourceNodeId]; | ||
const targetNode = this._nodeMap[targetNodeId]; | ||
|
||
const sourceNodeAxis = getNodeAxis(sourceNode); | ||
const targetNodeAxis = getNodeAxis(targetNode); | ||
|
||
if (sourceNodeAxis === targetNodeAxis) { | ||
return { | ||
type: EDGE_TYPE.LINE, | ||
sourcePosition, | ||
targetPosition, | ||
controlPoints: [], | ||
}; | ||
} | ||
|
||
const controlPoint = computeControlPoint({ | ||
sourcePosition, | ||
sourceNodeAxis, | ||
targetPosition, | ||
targetNodeAxis, | ||
totalAxis: this._totalAxis, | ||
}); | ||
|
||
return { | ||
type: EDGE_TYPE.SPLINE_CURVE, | ||
sourcePosition, | ||
targetPosition, | ||
controlPoints: [controlPoint], | ||
}; | ||
}; | ||
|
||
lockNodePosition = (node, x, y) => { | ||
this._nodePositionMap[node.id] = [x, y]; | ||
this._callbacks.onLayoutChange(); | ||
this._callbacks.onLayoutDone(); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
iimport React, {Component} from 'react'; | ||
import Color from 'color'; | ||
|
||
// data | ||
import sampleGraph from './sample-graph.json'; | ||
|
||
// graph.gl | ||
import GraphGL, {EDGE_DECORATOR_TYPE, JSONLoader, NODE_TYPE} from '../../src'; | ||
import MultiGraphLayout from './multi-graph-layout'; | ||
|
||
const DEFAULT_NODE_SIZE = 30; | ||
const DEFAULT_NODE_PLACEHOLDER_SIZE = 40; | ||
const DEFAULT_NODE_PLACEHOLDER_COLOR = 'rgb(240, 240, 240)'; | ||
|
||
export default class MultiGraphExample extends Component { | ||
static defaultProps = { | ||
showNodePlaceholder: true, | ||
showNodeCircle: true, | ||
nodeColor: '#cf4569', | ||
showNodeLabel: true, | ||
nodeLabelColor: '#ffffff', | ||
nodeLabelSize: 14, | ||
edgeColor: '#cf4569', | ||
edgeWidth: 2, | ||
showEdgeLabel: true, | ||
edgeLabelColor: '#000000', | ||
edgeLabelSize: 14, | ||
}; | ||
|
||
render() { | ||
return ( | ||
<GraphGL | ||
graph={JSONLoader({json: sampleGraph})} | ||
layout={ | ||
new MultiGraphLayout({ | ||
nBodyStrength: -8000, | ||
}) | ||
} | ||
nodeStyle={[ | ||
this.props.showNodePlaceholder && { | ||
type: NODE_TYPE.CIRCLE, | ||
radius: DEFAULT_NODE_PLACEHOLDER_SIZE, | ||
fill: DEFAULT_NODE_PLACEHOLDER_COLOR, | ||
}, | ||
this.props.showNodeCircle && { | ||
type: NODE_TYPE.CIRCLE, | ||
radius: DEFAULT_NODE_SIZE, | ||
fill: this.props.nodeColor, | ||
}, | ||
{ | ||
type: NODE_TYPE.CIRCLE, | ||
radius: node => (node.getPropertyValue('star') ? 6 : 0), | ||
fill: [255, 255, 0], | ||
offset: [18, -18], | ||
}, | ||
this.props.showNodeLabel && { | ||
type: NODE_TYPE.LABEL, | ||
text: node => node.getId(), | ||
color: Color(this.props.nodeLabelColor).array(), | ||
fontSize: this.props.nodeLabelSize, | ||
}, | ||
]} | ||
edgeStyle={{ | ||
stroke: this.props.edgeColor, | ||
strokeWidth: this.props.edgeWidth, | ||
decorators: [ | ||
this.props.showEdgeLabel && { | ||
type: EDGE_DECORATOR_TYPE.LABEL, | ||
text: edge => edge.getPropertyValue('type'), | ||
color: Color(this.props.edgeLabelColor).array(), | ||
fontSize: this.props.edgeLabelSize, | ||
}, | ||
], | ||
}} | ||
/> | ||
); | ||
} | ||
} |
Oops, something went wrong.