From 9ace95e66b6f242232d87e72888046b251d22aa8 Mon Sep 17 00:00:00 2001 From: Matthias Konitzny <konitzny@ibr.cs.tu-bs.de> Date: Wed, 4 Aug 2021 17:02:21 +0200 Subject: [PATCH] More refactoring on the overlays. Beginning to write code comments. --- display/graph.js | 8 +- display/helpers.js | 11 +- display/infooverlay.js | 187 ------------------ .../linkselection.js} | 23 ++- display/overlays/neighbors.js | 120 +++++++++++ display/overlays/nodeinfo.js | 107 ++++++++++ 6 files changed, 261 insertions(+), 195 deletions(-) delete mode 100644 display/infooverlay.js rename display/{linkoverlay.js => overlays/linkselection.js} (70%) create mode 100644 display/overlays/neighbors.js create mode 100644 display/overlays/nodeinfo.js diff --git a/display/graph.js b/display/graph.js index dfa2183..f10a9ba 100644 --- a/display/graph.js +++ b/display/graph.js @@ -1,7 +1,7 @@ import * as Config from "../config"; import * as Helpers from "./helpers"; -import { InfoOverlay } from "./infooverlay"; -import { LinkOverlay } from "./linkoverlay"; +import { NodeInfoOverlay } from "./overlays/nodeinfo"; +import { LinkSelectionOverlay } from "./overlays/linkselection"; import * as THREE from "three"; import ForceGraph3D from "3d-force-graph"; @@ -357,6 +357,6 @@ function createFullScreenButton() { const dataUrl = Config.PLUGIN_PATH + "datasets/aud1.json"; const G = new Graph(dataUrl); -const linkoverlay = new LinkOverlay(G); -const infooverlay = new InfoOverlay(G); +const linkoverlay = new LinkSelectionOverlay(G); +const infooverlay = new NodeInfoOverlay(G); G.infooverlay = infooverlay; diff --git a/display/helpers.js b/display/helpers.js index 4469d40..459ec16 100644 --- a/display/helpers.js +++ b/display/helpers.js @@ -1,4 +1,4 @@ -export { getWidth, getHeight, getCanvasDivNode }; +export { getWidth, getHeight, getCanvasDivNode, createDiv }; function getWidth() { return document.getElementById("3d-graph").offsetWidth; @@ -12,3 +12,12 @@ function getCanvasDivNode() { const domNode = document.getElementById("3d-graph"); return domNode.firstChild.firstChild.firstChild; } + +function createDiv(className, parent) { + const node = document.createElement("div"); + node.className = className; + if (typeof parent !== "undefined") { + parent.appendChild(node); + } + return node; +} diff --git a/display/infooverlay.js b/display/infooverlay.js deleted file mode 100644 index a821327..0000000 --- a/display/infooverlay.js +++ /dev/null @@ -1,187 +0,0 @@ -import * as Helpers from "./helpers"; -import jQuery from "jquery"; -import * as Config from "../config"; - -export { InfoOverlay }; - -class InfoOverlay { - constructor(graph) { - this.graph = graph; - - this.activeTabNav = null; - this.activeTabContent = null; - - this.tabContentPages = {}; - } - - create() { - const overlayDiv = this.createOverlayMainDiv(); - this.createOverlayElements(overlayDiv); - this.createBottomMenu(overlayDiv); - - jQuery("#infoOverlayCloseButton").click(function () { - jQuery("#infoOverlay").slideUp("fast"); - }); - } - - createBottomMenu(overlayNode) { - const bottomContainerDiv = document.createElement("div"); - bottomContainerDiv.className = "bottom-container"; - overlayNode.appendChild(bottomContainerDiv); - - const bottomContainerNavDiv = document.createElement("div"); - bottomContainerNavDiv.className = "bottom-container-nav"; - bottomContainerDiv.appendChild(bottomContainerNavDiv); - - const bottomContainerLinkDiv = document.createElement("div"); - bottomContainerLinkDiv.className = "bottom-container-links"; - bottomContainerDiv.appendChild(bottomContainerLinkDiv); - - for (const [cls, color] of Object.entries(this.graph.edgeColors)) { - const navTab = document.createElement("div"); - navTab.className = "bottom-container-nav-tab"; - navTab.innerText = cls.slice(0, 3); - navTab.style = "background-color: " + color; - navTab.edgeType = cls; - bottomContainerNavDiv.appendChild(navTab); - jQuery(navTab).click((event) => this.openTab(event)); - - const tabContent = document.createElement("div"); - tabContent.className = "bottom-container-tab-content"; - tabContent.id = cls + "_id_content"; - tabContent.style = "background-color: " + color; - bottomContainerLinkDiv.appendChild(tabContent); - this.tabContentPages[cls] = tabContent; - } - - this.activeTabNav = bottomContainerNavDiv.firstChild; - this.activeTabContent = bottomContainerLinkDiv.firstChild; - - this.activeTabContent.classList.add("active-tab-content"); - this.activeTabNav.classList.add("active-tab-nav"); - this.activeTabNav.innerText = this.activeTabNav.edgeType; - } - - openTab(event) { - const navTab = event.target; - const cls = navTab.edgeType; - - this.activeTabNav.classList.remove("active-tab-nav"); - this.activeTabNav.innerText = this.activeTabNav.innerText.slice(0, 3); - navTab.classList.add("active-tab-nav"); - navTab.innerText = cls; - - this.activeTabContent.classList.remove("active-tab-content"); - this.tabContentPages[cls].classList.add("active-tab-content"); - - this.activeTabNav = navTab; - this.activeTabContent = this.tabContentPages[cls]; - } - - createOverlayMainDiv() { - const sceneNode = Helpers.getCanvasDivNode(); - const overlayNode = document.createElement("div"); - overlayNode.id = "infoOverlay"; - overlayNode.className = "detail-view"; - //overlayNode.innerText = 'Hello there!'; - sceneNode.insertBefore(overlayNode, sceneNode.childNodes[2]); - return overlayNode; - } - - createOverlayElements(overlayNode) { - const close = document.createElement("div"); - close.innerHTML = "<p>✖</p>"; - close.id = "infoOverlayCloseButton"; - close.className = "close-button"; - overlayNode.appendChild(close); - - const infoArea = document.createElement("div"); - infoArea.className = "detail-view-info-area"; - overlayNode.appendChild(infoArea); - - const topArea = document.createElement("div"); - topArea.className = "detail-view-top-area"; - infoArea.appendChild(topArea); - - const nodeImage = document.createElement("img"); - nodeImage.id = "infoOverlayImage"; - nodeImage.src = Config.PLUGIN_PATH + "datasets/images/default.jpg"; - nodeImage.className = "detail-view-image"; - topArea.appendChild(nodeImage); - - const headline = document.createElement("h2"); - headline.id = "infoOverlayHeadline"; - headline.innerText = "Default Text"; - headline.className = "detail-view-headline"; - topArea.appendChild(headline); - - const textArea = document.createElement("div"); - textArea.className = "detail-view-text-area"; - infoArea.appendChild(textArea); - - const description = document.createElement("p"); - description.id = "infoOverlayDescription"; - description.innerText = "Default Text"; - description.setAttribute("overflow-y", "scroll"); - textArea.appendChild(description); - } - - updateInfoOverlay(node) { - jQuery("#infoOverlayHeadline").text(node.name); - if ("image" in node) { - jQuery("#infoOverlayImage").attr( - "src", - Config.PLUGIN_PATH + "datasets/images/" + node.image - ); - } else { - jQuery("#infoOverlayImage").attr( - "src", - Config.PLUGIN_PATH + "datasets/images/default.jpg" - ); - } - - if ("description" in node) { - jQuery("#infoOverlayDescription").text(node.description); - } else { - jQuery("#infoOverlayDescription").text("Default Text"); - } - - this.updateTabs(node); - - jQuery("#infoOverlay").slideDown("fast"); - } - - clearTabContentPages() { - for (const page of Object.values(this.tabContentPages)) { - jQuery(page).empty(); - } - } - - createReference(target) { - const linkDiv = document.createElement("div"); - linkDiv.className = "link-img"; - - if ("image" in target) { - const linkImage = document.createElement("img"); - linkImage.src = - Config.PLUGIN_PATH + "datasets/images/" + target.image; - linkDiv.appendChild(linkImage); - } - - jQuery(linkDiv).on("click", () => { - this.graph.focusOnNode(target); - this.updateInfoOverlay(target); - }); - return linkDiv; - } - - updateTabs(node) { - this.clearTabContentPages(); - - for (const link of node.links) { - const target = link.source == node ? link.target : link.source; - const reference = this.createReference(target); - this.tabContentPages[link.type].appendChild(reference); - } - } -} diff --git a/display/linkoverlay.js b/display/overlays/linkselection.js similarity index 70% rename from display/linkoverlay.js rename to display/overlays/linkselection.js index 912089a..1a27d21 100644 --- a/display/linkoverlay.js +++ b/display/overlays/linkselection.js @@ -1,13 +1,25 @@ -import * as Helpers from "./helpers"; +import * as Helpers from "../helpers"; import jQuery from "jquery"; -export { LinkOverlay }; +export { LinkSelectionOverlay }; -class LinkOverlay { +/** + * Represents an overlay showing the meaning of different edge colors. + * Offers the ability to toggle certain edge types. + */ +class LinkSelectionOverlay { + /** + * Creates the link overlay for a given graph object. + * @param {Graph} graph The graph object. + */ constructor(graph) { this.graph = graph; } + /** + * 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"); @@ -39,6 +51,11 @@ class LinkOverlay { } } + /** + * Event handler for the click event of the link type divs. + * Toggles the visibility of certain edge types. + * @param {MouseEvent} event + */ toggleLinkVisibility(event) { const target = event.currentTarget; this.graph.toggleLinkVisibility(target.edgeType); diff --git a/display/overlays/neighbors.js b/display/overlays/neighbors.js new file mode 100644 index 0000000..0499788 --- /dev/null +++ b/display/overlays/neighbors.js @@ -0,0 +1,120 @@ +import * as Helpers from "../helpers"; +import jQuery from "jquery"; +import * as Config from "../../config"; + +export { NodeNeighborOverlay }; + +class NodeNeighborOverlay { + constructor(graph, parentNode, infoOverlay) { + this.graph = graph; + this.parentNode = parentNode; + this.infoOverlay = infoOverlay; + + this.activeTabNav = null; + this.activeTabContent = null; + + this.tabContentPages = {}; + } + + create() { + const bottomContainerDiv = Helpers.createDiv( + "bottom-container", + this.parentNode + ); + const bottomContainerNavDiv = Helpers.createDiv( + "bottom-container-nav", + bottomContainerDiv + ); + const bottomContainerLinkDiv = Helpers.createDiv( + "bottom-container-links", + bottomContainerDiv + ); + + for (const [cls, color] of Object.entries(this.graph.edgeColors)) { + const navTab = Helpers.createDiv( + "bottom-container-nav-tab", + bottomContainerNavDiv + ); + navTab.innerText = cls.slice(0, 3); + navTab.style.backgroundColor = color; + navTab.edgeType = cls; // Attach the edge type to the DOM object to retrieve it during click events + jQuery(navTab).click((event) => this.openTab(event)); + + const tabContent = Helpers.createDiv( + "bottom-container-tab-content", + bottomContainerLinkDiv + ); + tabContent.style.backgroundColor = color; + this.tabContentPages[cls] = tabContent; + } + this.initializeActive(bottomContainerNavDiv, bottomContainerLinkDiv); + } + + /** + * Initializes the activeTabNav and activeTabContent variables to a random edge type. + * @param {Element} bottomContainerNavDiv + * @param {Element} bottomContainerLinkDiv + */ + initializeActive(bottomContainerNavDiv, bottomContainerLinkDiv) { + this.activeTabNav = bottomContainerNavDiv.firstChild; + this.activeTabContent = bottomContainerLinkDiv.firstChild; + + this.activeTabContent.classList.add("active-tab-content"); + this.activeTabNav.classList.add("active-tab-nav"); + this.activeTabNav.innerText = this.activeTabNav.edgeType; + } + + /** + * Click event handler for the tab headers of the bottom menu. + * @param event + */ + openTab(event) { + const navTab = event.target; + const cls = navTab.edgeType; + + this.activeTabNav.classList.remove("active-tab-nav"); + this.activeTabNav.innerText = this.activeTabNav.innerText.slice(0, 3); + navTab.classList.add("active-tab-nav"); + navTab.innerText = cls; + + this.activeTabContent.classList.remove("active-tab-content"); + this.tabContentPages[cls].classList.add("active-tab-content"); + + this.activeTabNav = navTab; + this.activeTabContent = this.tabContentPages[cls]; + } + + clearTabContentPages() { + for (const page of Object.values(this.tabContentPages)) { + jQuery(page).empty(); + } + } + + createReference(target) { + const linkDiv = document.createElement("div"); + linkDiv.className = "link-img"; + + if ("image" in target) { + const linkImage = document.createElement("img"); + linkImage.src = + Config.PLUGIN_PATH + "datasets/images/" + target.image; + linkDiv.appendChild(linkImage); + } + + jQuery(linkDiv).on("click", () => { + this.graph.focusOnNode(target); + this.infoOverlay.updateInfoOverlay(target); + }); + return linkDiv; + } + + updateTabs(node) { + this.clearTabContentPages(); + + for (const link of node.links) { + const target = link.source == node ? link.target : link.source; + const reference = this.createReference(target); + this.tabContentPages[link.type].appendChild(reference); + } + } +} diff --git a/display/overlays/nodeinfo.js b/display/overlays/nodeinfo.js new file mode 100644 index 0000000..18bd679 --- /dev/null +++ b/display/overlays/nodeinfo.js @@ -0,0 +1,107 @@ +import jQuery from "jquery"; + +import { NodeNeighborOverlay } from "./neighbors"; +import * as Helpers from "../helpers"; +import * as Config from "../../config"; + +export { NodeInfoOverlay }; + +/** + * An overlay displaying the information which is connected with each node. + */ +class NodeInfoOverlay { + /** + * Constructs a new info overlay for the given graph object. + * @param {Graph} graph + */ + constructor(graph) { + this.graph = graph; + this.bottomMenu = null; + } + + /** + * Creates the visible elements of the overlay. + * Must be called after the graph has been initialized. + */ + create() { + const overlayDiv = this.createOverlayMainDiv(); + this.createOverlayElements(overlayDiv); + this.bottomMenu = new NodeNeighborOverlay(this.graph, overlayDiv, this); + this.bottomMenu.create(); + + jQuery("#infoOverlayCloseButton").click(function () { + jQuery("#infoOverlay").slideUp("fast"); + }); + } + + createOverlayMainDiv() { + const sceneNode = Helpers.getCanvasDivNode(); + const overlayNode = document.createElement("div"); + overlayNode.id = "infoOverlay"; + overlayNode.className = "detail-view"; + sceneNode.insertBefore(overlayNode, sceneNode.childNodes[2]); + return overlayNode; + } + + createOverlayElements(overlayNode) { + const close = document.createElement("div"); + close.innerHTML = "<p>✖</p>"; + close.id = "infoOverlayCloseButton"; + close.className = "close-button"; + overlayNode.appendChild(close); + + const infoArea = document.createElement("div"); + infoArea.className = "detail-view-info-area"; + overlayNode.appendChild(infoArea); + + const topArea = document.createElement("div"); + topArea.className = "detail-view-top-area"; + infoArea.appendChild(topArea); + + const nodeImage = document.createElement("img"); + nodeImage.id = "infoOverlayImage"; + nodeImage.src = Config.PLUGIN_PATH + "datasets/images/default.jpg"; + nodeImage.className = "detail-view-image"; + topArea.appendChild(nodeImage); + + const headline = document.createElement("h2"); + headline.id = "infoOverlayHeadline"; + headline.innerText = "Default Text"; + headline.className = "detail-view-headline"; + topArea.appendChild(headline); + + const textArea = document.createElement("div"); + textArea.className = "detail-view-text-area"; + infoArea.appendChild(textArea); + + const description = document.createElement("p"); + description.id = "infoOverlayDescription"; + description.innerText = "Default Text"; + description.setAttribute("overflow-y", "scroll"); + textArea.appendChild(description); + } + + updateInfoOverlay(node) { + jQuery("#infoOverlayHeadline").text(node.name); + if ("image" in node) { + jQuery("#infoOverlayImage").attr( + "src", + Config.PLUGIN_PATH + "datasets/images/" + node.image + ); + } else { + jQuery("#infoOverlayImage").attr( + "src", + Config.PLUGIN_PATH + "datasets/images/default.jpg" + ); + } + + if ("description" in node) { + jQuery("#infoOverlayDescription").text(node.description); + } else { + jQuery("#infoOverlayDescription").text("Default Text"); + } + + this.bottomMenu.updateTabs(node); + jQuery("#infoOverlay").slideDown("fast"); + } +} -- GitLab