import React from "react"; import screenfull from "screenfull"; import PropTypes, { InferType } from "prop-types"; import "./display.css"; import { GraphNode, GraphRenderer } from "./renderer"; import * as Helpers from "./helpers"; import Graph, { NodeData } from "./graph"; import { loadGraphJson } from "../datasets"; import NodeInfoBar from "./components/nodeinfo/nodeinfobar"; import FilterMenu from "./components/nodefilter/filtermenu"; /** * This component manages and renders a 3d-force-graph with additional menus to navigate, filter and view information on nodes. */ class Display extends React.Component< InferType<typeof Display.propTypes>, InferType<typeof Display.stateTypes> > { props: InferType<typeof Display.propTypes>; state: InferType<typeof Display.stateTypes>; fullscreenRef: React.RefObject<HTMLDivElement>; rendererRef: React.RefObject<GraphRenderer>; graph: Graph; static propTypes = { spaceId: PropTypes.string.isRequired, }; static stateTypes = { graph: PropTypes.instanceOf(Graph), currentNode: PropTypes.object, nodeActive: PropTypes.bool, width: PropTypes.number, height: PropTypes.number, }; constructor(props: InferType<typeof Display.propTypes>) { super(props); this.fullscreenRef = React.createRef(); this.rendererRef = React.createRef(); this.state = { graph: null, currentNode: null, nodeActive: false, width: 0, height: 0, }; this.handleNodeClicked = this.handleNodeClicked.bind(this); this.handleNodeClose = this.handleNodeClose.bind(this); this.handleNodeChangeRequest = this.handleNodeChangeRequest.bind(this); this.handleNodeFilter = this.handleNodeFilter.bind(this); } componentDidMount() { this.setState({ width: Helpers.getWidth(), height: Helpers.getHeight(), }); const fetchGraph = async () => { const graphData = await loadGraphJson(this.props.spaceId); this.graph = new Graph(graphData.nodes, graphData.links); this.setState({ graph: this.graph }); }; fetchGraph(); } handleNodeClicked(node: NodeData) { this.setState({ currentNode: node, nodeActive: true }); } handleNodeChangeRequest(node: NodeData) { this.rendererRef.current.focusOnNode(node as GraphNode); this.handleNodeClicked(node); } handleNodeClose() { this.setState({ nodeActive: false }); } handleNodeFilter(visibility: Map<string, boolean>) { const graph = this.graph.view(visibility); this.setState({ graph: graph }); } toggleFullscreen() { if (screenfull.isEnabled) { if (!screenfull.isFullscreen) { this.setState({ width: screen.width, height: screen.height, }); } else { this.setState({ width: Helpers.getWidth(), height: Helpers.getHeight(), }); } screenfull.toggle(this.fullscreenRef.current); } else { console.log("No fullscreen mode available :("); } } render() { return ( <div id="kg-display" style={{ position: "relative" }} ref={this.fullscreenRef} > <div className={"display-fullscreen-button no-select"} title={"Vollbild"} onClick={this.toggleFullscreen.bind(this)} > <p>⤢</p> </div> {/*{this.graph && (*/} {/* <FilterOverlay graph={this.graph} type={"node"} />*/} {/*)}*/} {this.state.currentNode && ( <NodeInfoBar node={this.state.currentNode} nodeColors={this.state.graph.nodeColors} height={this.state.nodeActive ? this.state.height : 0} onClose={this.handleNodeClose} nodeClickedCallback={this.handleNodeChangeRequest} ></NodeInfoBar> )} {this.state.graph && ( <FilterMenu classes={this.graph.nodeColors} onVisibilityChange={this.handleNodeFilter} /> )} <div id="3d-graph"> {this.state.graph && ( <GraphRenderer ref={this.rendererRef} graph={this.state.graph} width={this.state.width} height={this.state.height} onNodeClicked={this.handleNodeClicked} /> )} </div> </div> ); } } export default Display;