Skip to content
Snippets Groups Projects
display.tsx 5.05 KiB
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>&#10530;</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;