This is the GUI for the GraphEditor project. It supports users to work with different databases.
Build out the project to the designs provided.
Our (WebGL) graph provides multiple GUI functionalities such as dragging a node, selecting multiple nodes, etc..., which are all described below. Please note, currently most of the graph GUI functionalities are not optimized for touch devices.
To select nodes, please hold down the Shift key on your keyboard, click on the graph, and start moving your mouse to select multiple nodes.
If you already selected nodes, simply click and drag one of the selected nodes. If there are no previously selected nodes, simply click and drag a node you want to move.
By holding down Ctrl (Strg) + left-click on a node and moving your mouse, you can quickly create a node, which will be connected to the node you clicked on. On the left side of your screen you can choose which labels to assign to new nodes (default: none).
You can also connect two existing nodes by doing Ctrl (Strg) + left-click on a node and dragging your cursor to an existing node. On the left side of your screen you can choose which relation type to assign to new relations. If not chosen, we will assign one for you (default: auto-generated by our backend).
In this mode, on mouse-up we will try and store data on server and sync the data with the graph. You might see a semi-transparent overlay, this indicates the data between server and frontend are being synced.
NOTE: If you create a new node this way, you'll notice your mouse click/hover events are not working on it unless you move your mouse. This is due to the fact we programmatically remove the temporary node (the "moving" node) and create a new node based on the server data. Since this is done programmatically (without any user interaction), Sigma isn't registering that your mouse is hovering the newly created node. There are some technical solutions to resolve this, but none of them are safe enough to be implemented, and moving your mouse isn't that troublesome.
In order to quickly create a non-connected empty node using our GUI, please hold down Ctrl (Strg) and do left-click on canvas (not node or relation). On the left side of your screen you can choose which labels to assign to new nodes (default: none).
To move camera around, simply do a left-click on graph (not nodes or relations) and move your mouse.
Both node and relation labels are auto-scaling with the graph, meaning, when you zoom in or zoom out, those labels will auto-scale with the graph. This enables more fluid user experience, rather than rendering labels with a constant size, regardless of your current zoom level.
You can still manually adjust the (linear) label scaling factor by holding down Shift and scrolling up or down ( depending on if you want to scale the factor up or down) while over canvas (not node or relation).
If you would like to scale up or down your nodes, you can do that by holding down Alt and scrolling up or down ( depending on if you want to scale the factor up or down) while over canvas (not node or relation).
Graph zoom factor can be easily adjusted to your needs. We offer two ways to change the zoom factor. The first way is via your keyboard by holding down the Ctrl (Strg) key and scrolling up or down (depending on if you want to increase od decrease the zoom factor). The second way is via GUI elements on the left side of the graph. There you'll see a slider which you can use to set your desired zoom factor value. Close to the slider you will also see a reset button which will reset the zoom factor to its default (hardcoded) value.
In order to simplify and enable single-flow of events executed, an event state manager based on the FSM (Finite State Machine) pattern is implemented.
For more technical information, please check the StateManager class.
For an event-flow diagram, please execute next block into http://www.webgraphviz.com/:
digraph G {
"IDLE" -> "NODE_ENTER"
"IDLE" -> "NODE_DOWN"
"IDLE" -> "RELATION_ENTER"
"IDLE" -> "STAGE_DOWN"
"IDLE" -> "SCALE"
"STAGE_DOWN" -> "NODE_SELECTION"
"STAGE_DOWN" -> "NODE_QUICK"
"NODE_DOWN" -> "NODE_DRAG"
"NODE_DOWN" -> "NODE_AUTO_CONNECT"
"NODE_DOWN" -> "NODE_CLICK"
"NODE_ENTER" -> "NODE_DOWN"
"NODE_ENTER" -> "NODE_LEAVE"
"NODE_LEAVE" -> "IDLE"
"NODE_LEAVE" -> "NODE_ENTER"
"NODE_LEAVE" -> "RELATION_ENTER"
"RELATION_ENTER" -> "RELATION_LEAVE"
"RELATION_ENTER" -> "RELATION_CLICK"
"RELATION_LEAVE" -> "IDLE"
"RELATION_LEAVE" -> "NODE_ENTER"
"RELATION_LEAVE" -> "NODE_DOWN"
"RELATION_LEAVE" -> "RELATION_ENTER"
"SCALE" -> "IDLE"
}
- HTML5
- SCSS
- Typescript
- Internationalization Library react-i18next
- React
- Vite
- Vitest
- UX/UI library DB UI MONO Library
- Form validation React Hook Form
- Node must be installed (version: >=20.18.0)
- Docker must be installed (recommended version: >= 24.0.0)
In order to start local frontend development, we need to do a couple of things.
-
create frontend/.env file
- Copy
frontend/.env.disttofrontend/.env
- Copy
-
run
npm run setup
To reliably protect our project from supply chain attacks via npm, please always use "npm run setup" instead of "npm install"
– this ensures that only reviewed and explicitly allowed installation scripts are executed, while all others are automatically blocked.
This prevents malicious code scripts from being silently installed without restricting normal development.
- run Docker (local backend server):
- Go to the root directory and run
docker-compose up backend neo4j-enterpriseto start local backend server. It is possible you are not required to pass username and password in Neo4J browser GUI in order to connect to the neo4j database, but if you are, please check the backend documentation and/or its environment files
- Go to the root directory and run
4.run frontend: - npm run dev in this directory to start the project locally. For other commands please refer to the package.json file.
5.mock data: - If you need mock data to explore the project, please visit http://localhost:4999/swagger. Under the headline "Dev tools" you can
find /dev/reset and /dev/osm_data endpoints. For a simple set of data, you can use the /dev/reset endpoint, for a more
complex set of data please use the /dev/osm_data endpoint.
If you successfully followed this short tutorial, you should see our homepage in your browser.
To interface with our backend system, we have established two primary methodologies. The first method utilizes API abstraction objects,
which reside in the frontend/src/utils/api directory. These objects encapsulate backend communication logic and are designed to
streamline integration within the frontend architecture.
The alternative strategy involves deploying custom React hooks, accessible within frontend/src/utils/hooks. These hooks are tailored
specifically for React components, offering a seamless way to manage asynchronous data flows in a declarative manner.
Scenarios for employing custom hooks:
- Loading State Management: If the requirement is to monitor the loading state of an HTTP request distinctly, custom hooks provide
built-in state management - Automatic Execution on Component Mount: To initiate HTTP requests automatically upon component initialization without manually
injecting additional useEffect hooks into the component logic, custom hooks offer an elegant solution
Scenarios for utilizing API objects:
- Complex Logic Execution: When your implementation involves comprehensive logic with multiple sequential API calls interspersed
with variable manipulations and function executions, API objects provide structural robustness - Technical Constraints: React hooks are subject to specific invocation constraints, being callable only within other hooks or
functional components. API objects circumvent these restrictions, allowing greater flexibility in execution design - Asynchronous Operations: For scenarios requiring intricate asynchronous operations using async/await syntax, API objects offer
a superior handling mechanism
General Guidance: While API objects are generally the recommended approach due to their flexibility and support for complex operations,
custom API hooks remain a viable option for specific use cases, especially when component-centric concerns are predominant.
Important Considerations: Direct utilization of API functions from frontend/src/utils/fetch is discouraged for several reasons:
- Code Clarity: To maintain a clean and uncluttered codebase, avoiding direct fetch operations can minimize extraneous details
- Unified Communication: By adhering to the established API objects and custom hooks, we leverage extension patterns that ensure consistent
communication protocols with the backend - Contextual Consistency: Uniformly applied methodologies across different contexts enhance code maintainability and readability
For managing global state withing the application, we use Zustand.
We tried keeping API layer out of it, but because sometimes it would fit really well into it, and to reduce code complexity, you
might find API calls inside Zustand states.
When working with global state, please don't mix one state properties or methods with another another state.
@db-ux/react-core-components come with a whitelabel theme. If you want to use another theme, you can generate one via
https://design-system.deutschebahn.com/theme-builder/main/customization or you can manually create one by simply overwriting existing
CSS variables. Generated css can be placed in
file (src/assets/scss/main.scss)
No theme builder export support yet.
In the project, we use SVG icons to generate a woff2 font which is then used for icons. The font is generated via the generate-icon-font
NPM script. SVG icons stored in the src/assets/source-icons directory are used as source of icons. The generated @font-face CSS
file from the generate-icon-font NPM script is forwarded in the src/assets/scss/main.scss file. If you change the generated font
name, please update the --db-icon-font-family variable as well. For more technical information how all of this works, please check
the generate-icon-font NPM script.
Please make sure to name icons as they are named in the @db-ux/react-core-components package. Because of the --db-icon-font-family
CSS variable, our own icons will be automatically picked by the package. If you use SVG icons from other sources, please make sure
to leave the source and the icon name as a class inside the SVG icon. This way we will know the source and original name of the icon.
At the moment, most of our icons come from https://lucide.dev/icons/.
When developing our application, particularly when implementing features with time delays or those that monitor user interactions
like keyboard inputs, it's crucial to consider the possibility of our application loosing its focus. This can occur when a user navigates
away from our application, such as by switching tabs or clicking outside the window. If we don't manage such focus change events effectively,
our application functionalities could remain in an in-between state, potentially preventing users from using our application smoothly
or even accessing it altogether.
It is advisable to attach event listeners to the window's "blur" event rather than relying on the document's Visibility Change API.
The reason for this is the broader scope of application tracking needed, encompassing window-level blur events such as switching tabs
within the same browser window, minimizing the browser, and similar scenarios—rather than just document visibility transitions.
https://react.dev/learn/react-compiler
10.03.2025 Not working as intended, try another time. Tested with the MainVisual.tsx component and a simple component which would do console.log('test') and return
In order to unify error handling and prepare it for a logging service such as Sentry.js, we are observing errors at one place only.
You can find relevant code inside the src/main.tsx file. At the moment, we are observing 2 groups of errors:
- React errors:
- errors during render, gives as a possibility to reload affected blocks/components
- JS errors:
- observe non-React related errors (thrown Errors, failed Promises and similar)
- observe non-React related errors (thrown Errors, failed Promises and similar)
For more information about observing React errors, please visit https://react.dev/reference/react-dom/client/createRoot#parameters.
Please be careful to trigger proper error when catching errors in the code manually.
Promise-based errors should be thrown via Promise.reject or, if you are manually doing a promise, then link this
new Promise((resolve, reject) => { ...reject(...) }).
-
Promise.reject - try and throw new error via the
rejectmethodPromise.reject(new Error('My error message')) OR new Promise((resolve, reject) => { ... reject(new Error('My error message')); })
-
Try/catch - try and throw new error via the catch block
try { ... } catch (error) => { ... throw new Error(parseError(error)); }
The Error object expects a string, so if you are working with caught errors, you can use our parseError function to process the
caught error and return a string from it. Please always create an Error object instance when manually dealing with errors. This will
add an error stack to the caught/rejected (Promise.reject) errors.
Generating SBOM and dependency license check happens during the production build. For more information, please check the vite.plugins.ts file.
Currently, we provide basic test setup and a couple initial unit tests in terms of testing.
We use Vitest in combination with headless Playwright as our testing setup. Ideally, we would run unit tests in a browser-emulated environment but working with WebGL2 (Sigma.js) forced us to switch to real browser environment since we were not able to mock WebGL2. If, at some point, the WebGL2 mocking issue is resolved, we could switch back to a browser-emulated environment.
When testing keyboard events, please see Testing library keyboard documentation.
To run unit tests, please run npm run test. If you want to check unit test coverage, please run npm run test:coverage.
At the moment, it is not completely clear in which order does Vite inject CSS files.
If you look at the frontend/src/App.tsx file and compare CSS files injected in the browser (use development mode for easier
file injection sort tracking), you'll see the order of injected CSS files doesn't match neither the import order nor the render
order of components in the frontend/src/App.tsx file.
If we come to a point where we explicitly want to control the order of CSS files, we could import all our SCSS files in the
frontend/src/assets/scss/main.scss file and sort them as we want.
If we do that, don't forget to remove component's SCSS file from the component definition file, otherwise we will have duplicate CSS
definitions.
WebGL uses the GPU (graphics processing unit) to render 3D graphics and animations in web browsers. In order for some parts of this application to work properly, your browser needs to support WebGL. Please enable the hardware acceleration option in your browser if it is not already enabled.
Team GraphEditor. Responsible frontend developers:
- Mihajlo Lazar (mihajlo.lazar@deutschebahn.com)
- Stefan Seliger (stefan.seliger@deutschebahn.com)
- Resolve all TODOs in the codebase
- Revert
skipLibCheckintsconfig.jsontofalseonce Sigma.js reaches stable v3. - Consider utilizing useEffect's cleanup function to abort ongoing/duplicate API requests
- Consider
subscribeWithSelectorfor granular store changes subscription - Consider switching nodes and relations to classes
- The
useTranslationrenders twice on first component re-render, investigate - Consider increasing Error.stackTraceLimit to infinity (default is 10).
- Change API functions keys to more meaningful names (e.g. instead of "postNode" use something like "createCode")
- Consider using the
exactOptionalPropertyTypesTS option (https://devblogs.microsoft.com/typescript/announcing-typescript-4-4/#exact-optional-property-types-(--exactoptionalpropertytypes)) - Refactor URL parameters workflow (define how we deal with URL parameters and their changes, create a helper function to create clean URLs and similar)
- Add loading states to components when fetching data from backend
- Add e2e tests with Playwright