showcases/graph/graph-layer/graph-layout-layer.js (100 lines of code) (raw):
import {CompositeLayer, COORDINATE_SYSTEM} from 'deck.gl';
import GraphLayer from './graph-layer';
import LayoutD3 from './layout/layout-d3';
const defaultProps = {
width: 640,
height: 640,
data: null,
opacity: 1.0,
layout: LayoutD3,
coordinateSystem: COORDINATE_SYSTEM.IDENTITY,
nodeIconAccessors: {}
};
/**
* GraphLayoutLayer displays a force-directed network graph in deck.gl.
* It accepts a list of nodes and a list of links,
* processes them with a graph layout engine (default: LayoutD3),
* and passes props and transformed data on to GraphLayer for rendering.
*/
export default class GraphLayoutLayer extends CompositeLayer {
initializeState() {
const {data, layoutAccessors} = this.props;
const Layout = this.props.layout;
this.state.layout = new Layout(Object.assign({data}, layoutAccessors));
}
updateState({oldProps, props, changeFlags}) {
const {layout} = this.state;
const data = props.data ? props.data[props.data.length - 1] : null;
let layoutData;
// If the data have changed, send to layout;
// else just update layout in its current state.
if (changeFlags.dataChanged) {
layoutData = data;
}
const {layoutProps} = this.props;
const {nodes, isUpdating} = layout.update(layoutData, layoutProps);
if (isUpdating) {
// update state only if layout is still running
this.state.nodes = nodes;
this.state.links = data ? data.links : undefined;
// update timestamp for updateTriggers diff
this.state.layoutTime = Date.now();
}
}
getPickingInfo({info}) {
const pickingInfo = ['index', 'layer', 'object', 'picked', 'x', 'y'].reduce((acc, k) => {
acc[k] = info[k];
return acc;
}, {});
// determine object type (node or link) via duck-typing
const {object} = pickingInfo;
if (!object) {
pickingInfo.objectType = '';
} else if (info.object.source) {
pickingInfo.objectType = 'link';
} else {
pickingInfo.objectType = 'node';
}
// find "related objects":
// nodes at either end of picked link,
// or all links and nodes connected to picked node
const {nodes, links} = this.state;
let relatedObjects = [];
if (object) {
if (pickingInfo.objectType === 'link') {
relatedObjects = nodes.filter(n => n.id === object.source.id || n.id === object.target.id);
} else if (pickingInfo.objectType === 'node') {
relatedObjects = links.filter(l => l.source.id === object.id || l.target.id === object.id);
const oneDegreeNodes = relatedObjects.map(related => {
return related.source === object ? related.target : related.source;
});
relatedObjects = relatedObjects.concat(oneDegreeNodes);
}
}
pickingInfo.relatedObjects = relatedObjects;
return pickingInfo;
}
finalizeLayer() {
if (this.state.layout) {
this.state.layout.dispose();
}
}
renderLayers() {
const {id} = this.props;
const {nodes, links, layoutTime} = this.state;
// base layer props
const {opacity, visible} = this.props;
// base layer handlers
const {onHover, onClick} = this.props;
const pickable = Boolean(this.props.onHover || this.props.onClick);
// viewport props
const {coordinateSystem} = this.props;
// base layer accessors
const {linkAccessors, nodeAccessors, nodeIconAccessors} = this.props;
return new GraphLayer(
Object.assign(
{
id: `${id}-graph`,
data: {
nodes,
links
},
layoutTime,
opacity,
pickable,
visible,
coordinateSystem,
onHover,
onClick
},
// deconstruct these
linkAccessors,
nodeAccessors,
// leave these accessors bundled in an object
{nodeIconAccessors}
)
);
}
}
GraphLayoutLayer.layerName = 'GraphLayoutLayer';
GraphLayoutLayer.defaultProps = defaultProps;