diff --git a/README.md b/README.md index 6d304af7..06e0cdf9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ GLIMPSE is a graph-based web application to visualize and update GridLAB-D power grid models. The tool can be used to search and highlight power grid model objects. Additionally, it also update the model attributes and export the modified model future simulations. The application is developed using React.js, Node.js, and Python. -## Build Installation Instructions +## Build Instructions **Download Node and Nim** - [Node.js](https://nodejs.org/en) - [Nim](https://nim-lang.org/install.html) @@ -21,7 +21,6 @@ cd /GLIMPSE/glimpse/local-server/ python -m venv venv ``` -### If on `Windows` go to `/venv/Scripts/` and rename **json2glm** to **json2glm.exe** ### In `/GLIMPSE/glimpse/local-server/` Activate Virtual Environment using the following command for your system | Platform | Shell | Command to activate virtual environment | diff --git a/glimpse/main.js b/glimpse/main.js index ae4c721e..0e549d4f 100644 --- a/glimpse/main.js +++ b/glimpse/main.js @@ -8,9 +8,9 @@ const Ajv = require("ajv"); // const log = require('electron-log'); // const { autoUpdater } = require("electron-updater"); -require("electron-reload")(__dirname, { - electron: path.join(__dirname, "node_modules", ".bin", "electron"), -}); +// require("electron-reload")(__dirname, { +// electron: path.join(__dirname, "node_modules", ".bin", "electron"), +// }); const jsonUploadSchema = require("./schemas/json_upload.schema.json"); const themeUploadSchema = require("./schemas/theme_upload.schema.json"); @@ -19,8 +19,8 @@ const isMac = process.platform === "darwin"; let mainWindow = null; let splashWindow = null; -const rootDir = __dirname; -// const rootDir = process.resourcesPath; +// const rootDir = __dirname; +const rootDir = process.resourcesPath; //------------------ for debugging ------------------ // autoUpdater.logger = log; diff --git a/glimpse/package.json b/glimpse/package.json index 371d0572..5b2f7a13 100644 --- a/glimpse/package.json +++ b/glimpse/package.json @@ -58,6 +58,7 @@ "fs": "^0.0.1-security", "react": "^18.3.1", "react-dom": "^18.2.0", + "react-draggable": "^4.4.6", "socket.io-client": "^4.7.4", "tree-kill": "^1.2.2", "vis-data": "^7.1.7", diff --git a/glimpse/renderer/src/components/ActionBar.js b/glimpse/renderer/src/components/ActionBar.js deleted file mode 100644 index 02840c7a..00000000 --- a/glimpse/renderer/src/components/ActionBar.js +++ /dev/null @@ -1,222 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { - Box, - Stack, - Button, - Switch, - TextField, - FormGroup, - IconButton, - createTheme, - ButtonGroup, - Autocomplete, - ThemeProvider, - FormControlLabel, -} from "@mui/material"; -import OverlayUpload from "./OverlayUpload.js"; -import PlotModal from "./PlotModal.js"; -import SearchIcon from "@mui/icons-material/Search"; -import StatsTableModal from "./StatsTableModal.js"; - -const { appOptions } = JSON.parse(await window.glimpseAPI.getConfig()); - -const ActionBar = ({ - showLegendStateRef, - addGraphOverlay, - toggleLegendRef, - physicsToggle, - nodesDataObj, - graphDataObj, - getNodeIds, - removeOverlay, - onFind, - reset, - prev, - next, -}) => { - const nodes = nodesDataObj; - const [node, setNode] = useState(null); - const [stats, setStats] = useState(null); - const [imgUrl, setImgUrl] = useState(null); - const [checked, setChecked] = useState(false); - const [showPlot, setShowPlot] = useState(false); - const [showTable, setShowTable] = useState(false); - const [showUpload, setShowUpload] = useState(false); - const [displayRemoveOverlayBtn, setDisplayRemoveOverlayBtn] = useState("none"); - - const theme = createTheme({ - palette: { - primary: { - main: "#333333", - }, - secondary: { - main: "#b25a00", - }, - }, - }); - - /** - * Hide/Show the Legend Component - * @param {Event} e - */ - const toggleLegend = (e) => { - e.preventDefault(); - - const graph = document.getElementById("graph"); - const circularProgress = document.getElementById("circularProgress"); - const layoutForm = document.getElementById("layout-form"); - const rotateBtns = document.getElementById("rt-btns-wrapper"); - - if (showLegendStateRef.current) { - toggleLegendRef.current(false); - rotateBtns.style.left = "96%"; - graph.style.width = "100%"; - circularProgress.style.left = "50%"; - } else { - if (layoutForm.style.display === "flex") { - layoutForm.style.display = "none"; - } - rotateBtns.style.left = "68%"; - graph.style.width = "72%"; - circularProgress.style.left = "36%"; - toggleLegendRef.current(true); - } - }; - - /** - * Trigger the find function from the Graph component to focus on the selected node ID - * @param {Event} e - */ - const handleSubmit = (e) => { - e.preventDefault(); - - if (nodes.get(node)) onFind(node); - else alert(`${node} is not in the graph.`); - }; - - const autoLayout = (e) => { - physicsToggle(e.target.checked); - setChecked(e.target.checked); - }; - - /** - * Communicate With the main process to get a buffer of the plot and display it - * @param {Event} e - */ - - /** - * Send the entire graph data object to the main process and extract statistic using networkx - */ - const showStats = async () => { - if (stats === null) { - const statsObj = await window.glimpseAPI.getStats(JSON.stringify(graphDataObj)); - setStats(statsObj); - setShowTable(true); - } else { - setShowTable(true); - } - }; - - const getPlotImg = ({ buffer }) => { - const imgUrl = URL.createObjectURL(new Blob([buffer], { type: "image/png" })); - - setImgUrl(imgUrl); - setShowPlot(true); - }; - - const handleRemoveOverlay = () => { - removeOverlay(); - setDisplayRemoveOverlayBtn("none"); - }; - - useEffect(() => { - const removeListenersArr = []; - removeListenersArr.push(window.glimpseAPI.getEmbeddingsPlot(getPlotImg)); - removeListenersArr.push(window.glimpseAPI.onGetMetrics(showStats)); - - return () => { - for (let removeListener of removeListenersArr) { - removeListener(); - } - }; - }, []); - - return ( - <> - - - - - - - - - } - label={appOptions.buttons.layoutLbl} - /> - - - setNode(ID)} - renderInput={(params) => ( - - )} - /> - - - - - - - - - - - - - - - - - - setShowTable(false)} /> - setShowUpload(false)} - /> - setShowPlot(false)} /> - - ); -}; - -export default ActionBar; diff --git a/glimpse/renderer/src/components/ActionDrawer.js b/glimpse/renderer/src/components/ActionDrawer.js index 03d5ebd7..2b813746 100644 --- a/glimpse/renderer/src/components/ActionDrawer.js +++ b/glimpse/renderer/src/components/ActionDrawer.js @@ -27,6 +27,7 @@ const ActionDrawer = ({ attachOverlay, removeOverlay, reset, + graphDataObj, }) => { const nodes = getNodeIds(); const [nodeID, setNodeID] = useState(""); diff --git a/glimpse/renderer/src/components/Graph.js b/glimpse/renderer/src/components/Graph.js index 75a733ed..9c7cfa66 100644 --- a/glimpse/renderer/src/components/Graph.js +++ b/glimpse/renderer/src/components/Graph.js @@ -537,7 +537,7 @@ const Graph = ({ dataToVis, theme, isGlm }) => { }, }, }); - }, []); + }); // a d3.js example // useEffect(() => { @@ -641,22 +641,7 @@ const Graph = ({ dataToVis, theme, isGlm }) => { // }); return ( - <> - {/* NodeFocus(id, glmNetwork)} - prev={() => Prev(glmNetwork, highlightedNodes, counter)} - next={() => Next(glmNetwork, highlightedNodes, counter)} - addGraphOverlay={setCommunicationNetwork} - getNodeIds={() => graphData.nodes.getIds()} - toggleLegendRef={toggleLegendRef} - showLegendStateRef={showLegendStateRef} - removeOverlay={removeOverlay} - /> */} - + { attachOverlay={attachOverlay} removeOverlay={removeOverlay} reset={Reset} + graphDataObj={GLIMPSE_OBJECT} /> - - - - -
- - - HighlightGroup(nodeType, graphData, highlightedNodes, highlightedEdges) - } - highlightEdges={(edgeType) => - HighlightEdges(edgeType, highlightedNodes, graphData, edgeOptions, highlightedEdges) - } - hideObjects={(objType, type) => hideObjects(objType, type, graphData)} - legendData={getLegendData(objectTypeCount, theme, edgeOptions)} - onMount={legendMount} - setShowLegendRef={toggleLegendRef} - legendStateRef={showLegendStateRef} - /> - - - rotateCW(graphData, glmNetwork, ANGLE)} - rotateCCW={() => rotateCCW(graphData, glmNetwork, ANGLE)} - prev={() => Prev(glmNetwork, highlightedNodes, counter)} - next={() => Next(glmNetwork, highlightedNodes, counter)} + + + +
+ + + HighlightGroup(nodeType, graphData, highlightedNodes, highlightedEdges) + } + highlightEdges={(edgeType) => + HighlightEdges(edgeType, highlightedNodes, graphData, edgeOptions, highlightedEdges) + } + hideObjects={(objType, type) => hideObjects(objType, type, graphData)} + legendData={getLegendData(objectTypeCount, theme, edgeOptions)} + onMount={legendMount} + setShowLegendRef={toggleLegendRef} + legendStateRef={showLegendStateRef} /> - - + + + rotateCW(graphData, glmNetwork, ANGLE)} + rotateCCW={() => rotateCCW(graphData, glmNetwork, ANGLE)} + prev={() => Prev(glmNetwork, highlightedNodes, counter)} + next={() => Next(glmNetwork, highlightedNodes, counter)} + /> + ); }; diff --git a/glimpse/renderer/src/components/Legend.js b/glimpse/renderer/src/components/Legend.js index ea722508..790121eb 100644 --- a/glimpse/renderer/src/components/Legend.js +++ b/glimpse/renderer/src/components/Legend.js @@ -3,6 +3,7 @@ import { Network } from "vis-network"; import { Box } from "@mui/material"; import LegendContextMenu from "./LegendContextMenu"; import "../styles/vis-network.css"; +// import ThemeBuilder from "./ThemeBuilder"; const { legendGraphOptions } = JSON.parse(await window.glimpseAPI.getConfig()); const Legend = ({ @@ -17,6 +18,7 @@ const Legend = ({ const container = useRef(null); const [data, setData] = useState(legendData); const [showLegend, setShowLegend] = useState(true); + // const [openThemeBuilder, setOpenThemeBuilder] = useState(false); // set the current state as refs from the Graph component useEffect(() => { @@ -68,6 +70,10 @@ const Legend = ({ network.fit(); }); + // network.on("click", () => { + // setOpenThemeBuilder(true); + // }); + // set the context menu data with either a node or edge ID so that type can be hidden in the main network network.on("oncontext", (params) => { if (network.getNodeAt(params.pointer.DOM)) { @@ -105,7 +111,14 @@ const Legend = ({ ref={container} onContextMenu={handleContext} /> - + {/* "group" in node) + .map((node) => node.group)} + open={openThemeBuilder} + close={() => setOpenThemeBuilder(false)} + /> */} ); diff --git a/glimpse/renderer/src/components/ThemeBuilder.js b/glimpse/renderer/src/components/ThemeBuilder.js new file mode 100644 index 00000000..4872e6f0 --- /dev/null +++ b/glimpse/renderer/src/components/ThemeBuilder.js @@ -0,0 +1,68 @@ +import React, { useState } from "react"; +import ReactDom from "react-dom"; +import Draggable from "react-draggable"; +import { Button, Card, CardActions, CardContent, CardHeader, TextField, MenuItem } from "@mui/material"; + +const ThemeBuilder = ({ close, open, nodeTypes }) => { + const [color, setColor] = useState("#FFFFFF"); + const [objTypeValue, setObjTypeValue] = useState(nodeTypes[0]); + + // console.log(objTypeValue); + + if (!open) return null; + + return ReactDom.createPortal( + + + + + { + setObjTypeValue(e.target.value); + }} + > + {nodeTypes.map((type, index) => { + return ( + + {type} + + ); + })} + + setColor(e.target.value)} /> + + + + + + + , + document.getElementById("portal") + ); +}; + +export default ThemeBuilder; diff --git a/glimpse/renderer/src/utils/appUtils.js b/glimpse/renderer/src/utils/appUtils.js index fc5196fa..2e94b453 100644 --- a/glimpse/renderer/src/utils/appUtils.js +++ b/glimpse/renderer/src/utils/appUtils.js @@ -34,12 +34,10 @@ export const handleFileUpload = async (paths, setFileData, setFilesUploaded) => themeData = await window.glimpseAPI.getThemeJsonData("LevelTheme.json"); break; case "custom-theme": - if (paths.length > 1) themeData = await getCustomTheme(paths); - - if ("error" in themeData) { - alert(themeData.error); - return; - } else if (themeData === null) { + if (paths.length > 1) { + themeData = await getCustomTheme(paths); + if ("error" in themeData) alert(themeData.error); + } else { themeData = { groups: {}, edgeOptions: {} }; }