From 0ea29f544e1de6f8665e366b8246c31f92529121 Mon Sep 17 00:00:00 2001 From: Matthias Konitzny <konitzny@ibr.cs.tu-bs.de> Date: Mon, 28 Mar 2022 15:58:13 +0200 Subject: [PATCH] The FilterOverlay is now a React component. Renamed the overlays directory to components. Typescript now compiles to es6 instead of es5 (es6 content gets translated by babel) --- src/display/components/filteroverlay.tsx | 140 ++++++++++++++++++ .../{overlays => components}/neighbors.js | 0 .../{overlays => components}/nodeinfo.js | 0 src/display/display.css | 1 + src/display/display.tsx | 27 +++- src/display/overlays/filteroverlay.js | 79 ---------- tsconfig.json | 2 +- 7 files changed, 164 insertions(+), 85 deletions(-) create mode 100644 src/display/components/filteroverlay.tsx rename src/display/{overlays => components}/neighbors.js (100%) rename src/display/{overlays => components}/nodeinfo.js (100%) delete mode 100644 src/display/overlays/filteroverlay.js diff --git a/src/display/components/filteroverlay.tsx b/src/display/components/filteroverlay.tsx new file mode 100644 index 0000000..c087fd6 --- /dev/null +++ b/src/display/components/filteroverlay.tsx @@ -0,0 +1,140 @@ +import React from "react"; +import Graph from "../graph"; +import PropTypes, { InferType } from "prop-types"; + +export { FilterOverlay }; + +class Link extends React.Component< + InferType<typeof Link.propTypes>, + InferType<typeof Link.stateTypes> +> { + static propTypes = { + type: PropTypes.string.isRequired, + color: PropTypes.string.isRequired, + width: PropTypes.number.isRequired, + toggledCallback: PropTypes.func, + }; + + static stateTypes = { + visible: PropTypes.bool, + }; + + constructor(props: InferType<typeof Link.propTypes>) { + super(props); + this.state = { visible: true }; + this.handleClick = this.handleClick.bind(this); + } + + handleClick() { + const callback = this.props.toggledCallback || null; + if (callback) { + callback(this.props.type); + } + this.setState( + ( + state: InferType<typeof Link.stateTypes>, + props: InferType<typeof Link.propTypes> + ) => ({ visible: !state.visible }) + ); + } + + render() { + const opacity = this.state.visible ? 1.0 : 0.4; + return ( + <div + className={"relation"} + style={{ opacity: opacity }} + onClick={this.handleClick} + > + <p>{this.props.type}</p> + <div + className={"rel-container"} + style={{ + width: this.props.width + "px", + backgroundColor: this.props.color, + }} + /> + </div> + ); + } +} + +/** + * Represents an overlay showing the meaning of different link/node colors. + * Offers the ability to toggle certain types. + */ +class FilterOverlay extends React.Component< + InferType<typeof FilterOverlay.propTypes>, + unknown +> { + static propTypes = { + graph: PropTypes.instanceOf(Graph).isRequired, + type: PropTypes.string.isRequired, + }; + + /** + * Callback which gets called when the visibility of a category has changed. + */ + filterChangedCallback: (type: string) => void; + + constructor(props: InferType<typeof FilterOverlay.propTypes>) { + super(props); + this.filterChangedCallback = (type) => void type; + this.toggleVisibility = this.toggleVisibility.bind(this); + } + + /** + * Renders an element for each category, which enables the user to toggle + * the corresponding visibility of assigned nodes. + */ + renderLinks() { + const classes = + this.props.type === "link" + ? this.props.graph.getLinkClasses() + : this.props.graph.getNodeClasses(); + + const chars = Math.max( + ...classes.map(function (c: string) { + return c.length; + }) + ); + + const links = []; + for (const link of classes) { + links.push( + <Link + type={link} + color={ + this.props.type == "link" + ? this.props.graph.edgeColors[link] + : this.props.graph.nodeColors[link] + } + width={10 * chars} + toggledCallback={this.toggleVisibility} + key={link} + /> + ); + } + return links; + } + + render() { + return <div className={"link-overlay"}>{this.renderLinks()}</div>; + } + + /** + * Event handler for the click event of the link type divs. + * Toggles the visibility of certain edge types. + */ + toggleVisibility(type: string) { + if (this.props.type === "link") { + this.props.graph.toggleLinkVisibility(type); + } else { + this.props.graph.toggleNodeVisibility(type); + } + this.filterChangedCallback(type); + + // TODO: This is really ugly. + this.props.graph.infoOverlay.bottomMenu.toggleCategory(type); + } +} diff --git a/src/display/overlays/neighbors.js b/src/display/components/neighbors.js similarity index 100% rename from src/display/overlays/neighbors.js rename to src/display/components/neighbors.js diff --git a/src/display/overlays/nodeinfo.js b/src/display/components/nodeinfo.js similarity index 100% rename from src/display/overlays/nodeinfo.js rename to src/display/components/nodeinfo.js diff --git a/src/display/display.css b/src/display/display.css index 21d8b9d..7aeadfc 100644 --- a/src/display/display.css +++ b/src/display/display.css @@ -153,6 +153,7 @@ left: 0; display: block; background-color: rgba(0, 0, 0, 0.6); + z-index: 1; } .relation { diff --git a/src/display/display.tsx b/src/display/display.tsx index 60bf7aa..c7a33aa 100644 --- a/src/display/display.tsx +++ b/src/display/display.tsx @@ -1,13 +1,12 @@ import * as Config from "../config"; -import { FilterOverlay } from "./overlays/filteroverlay"; -import { NodeInfoOverlay } from "./overlays/nodeinfo"; +import { FilterOverlay } from "./components/filteroverlay"; +import { NodeInfoOverlay } from "./components/nodeinfo"; import Graph from "./graph"; import React from "react"; import screenfull from "screenfull"; class Display extends React.PureComponent { graph: Graph; - filterOverlay: FilterOverlay; infoOverlay: NodeInfoOverlay; sceneNode: HTMLElement; @@ -16,16 +15,30 @@ class Display extends React.PureComponent { Config.SPACE, this.loadGraphComponents.bind(this) ); - this.filterOverlay = new FilterOverlay(this.graph, "node"); this.infoOverlay = new NodeInfoOverlay(this.graph); this.graph.infoOverlay = this.infoOverlay; } + /** + * Callback which is called when the underlying graph has finished loading + * its content + */ loadGraphComponents() { - this.filterOverlay.create(); this.infoOverlay.create(); this.sceneNode = document.getElementById("kg-display"); + + /* + * This call is necessary since the graph can only load after the + * component has mounted (the graph requires a fixed <div> element), + * but some components rely on the graph being loaded. + * The fix is to exclude all components from rendering until the graph + * finished loading. + * TODO: Once all components have been transformed to react components + * this call could be substituted by changing the object state (which + * causes a re-render) + */ + this.forceUpdate(); // filterOverlay.filterChangedCallback = (cls) => // infoOverlay.bottomMenu.toggleTabVisibility(cls); // createFullScreenButton(); @@ -50,6 +63,10 @@ class Display extends React.PureComponent { > <p>⤢</p> </div> + {this.graph && ( + <FilterOverlay graph={this.graph} type={"node"} /> + )} + <div id="3d-graph" /> </div> ); diff --git a/src/display/overlays/filteroverlay.js b/src/display/overlays/filteroverlay.js deleted file mode 100644 index 06d6804..0000000 --- a/src/display/overlays/filteroverlay.js +++ /dev/null @@ -1,79 +0,0 @@ -import * as Helpers from "../helpers"; -import jQuery from "jquery"; - -export { FilterOverlay }; - -/** - * Represents an overlay showing the meaning of different link/node colors. - * Offers the ability to toggle certain types. - */ -class FilterOverlay { - /** - * Creates the overlay for a given graph object. - * @param {Graph} graph The graph object. - * @param {String} type The selection type. Can be "link" or "node". - */ - constructor(graph, type = "link") { - this.graph = graph; - this.type = type; - this.filterChangedCallback = (type) => void type; - } - - /** - * Creates the overlay based on the edges and nodes of the graph object. - * Must be called after the graph has been initialized. - */ - create() { - const sceneNode = Helpers.getCanvasDivNode(); - const overlayNode = document.createElement("div"); - overlayNode.className = "link-overlay"; - sceneNode.insertBefore(overlayNode, sceneNode.childNodes[2]); - - const classes = - this.type === "link" - ? this.graph.getLinkClasses() - : this.graph.getNodeClasses(); - const chars = Math.max.apply( - Math, - classes.map(function (c) { - return c.length; - }) - ); - - for (const link of classes) { - const relation = Helpers.createDiv("relation", overlayNode, { - type: link, // Attach the link type to the relation div object - innerHTML: "<p>" + link + "</p>", - }); - jQuery(relation).click((event) => this.toggleVisibility(event)); - - const colorStrip = Helpers.createDiv("rel-container", relation); - colorStrip.style.backgroundColor = - this.type === "link" - ? this.graph.edgeColors[link] - : this.graph.nodeColors[link]; - colorStrip.style.width = 10 * chars + "px"; - } - } - - /** - * Event handler for the click event of the link type divs. - * Toggles the visibility of certain edge types. - * @param {MouseEvent} event - */ - toggleVisibility(event) { - const target = event.currentTarget; - if (this.type === "link") { - this.graph.toggleLinkVisibility(target.type); - } else { - this.graph.toggleNodeVisibility(target.type); - } - this.filterChangedCallback(target.type); - if (getComputedStyle(target).opacity == 1.0) { - target.style.opacity = 0.4; - } else { - target.style.opacity = 1.0; - } - this.graph.infoOverlay.bottomMenu.toggleCategory(target.type); - } -} diff --git a/tsconfig.json b/tsconfig.json index 00c7971..f187ed7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "noImplicitAny": true, "strictNullChecks": false, "module": "es6", - "target": "es5", + "target": "es6", "jsx": "preserve", "allowJs": true, "moduleResolution": "node", -- GitLab