Improve graph UI

This commit is contained in:
IRBorisov 2024-04-10 17:58:44 +03:00
parent 849ec42bdf
commit 12dfd1c3cb
6 changed files with 88 additions and 70 deletions

View File

@ -20,7 +20,7 @@
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.13",
"react-icons": "^4.12.0",
"react-intl": "^6.6.4",
"react-intl": "^6.6.5",
"react-loader-spinner": "^5.4.5",
"react-pdf": "^7.7.1",
"react-router-dom": "^6.22.3",
@ -28,14 +28,14 @@
"react-tabs": "^6.0.2",
"react-toastify": "^9.1.3",
"react-tooltip": "^5.26.3",
"reagraph": "^4.15.27",
"reagraph": "^4.16.0",
"use-debounce": "^10.0.0"
},
"devDependencies": {
"@lezer/generator": "^1.7.0",
"@types/jest": "^29.5.12",
"@types/node": "^20.12.4",
"@types/react": "^18.2.74",
"@types/node": "^20.12.7",
"@types/react": "^18.2.75",
"@types/react-dom": "^18.2.24",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
@ -50,7 +50,7 @@
"postcss": "^8.4.38",
"tailwindcss": "^3.4.3",
"ts-jest": "^29.1.2",
"typescript": "^5.4.3",
"typescript": "^5.4.4",
"vite": "^4.5.3"
}
},
@ -691,9 +691,9 @@
}
},
"node_modules/@codemirror/view": {
"version": "6.26.1",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.1.tgz",
"integrity": "sha512-wLw0t3R9AwOSQThdZ5Onw8QQtem5asE7+bPlnzc57eubPqiuJKIzwjMZ+C42vQett+iva+J8VgFV4RYWDBh5FA==",
"version": "6.26.2",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.2.tgz",
"integrity": "sha512-j6V48PlFC/O7ERAR5vRW5QKDdchzmyyfojDdt+zPsB0YXoWgcjlC1IWjmlYfx08aQZ3HN5BtALcgGgtSKGMe7A==",
"dependencies": {
"@codemirror/state": "^6.4.0",
"style-mod": "^4.1.0",
@ -2804,9 +2804,9 @@
"dev": true
},
"node_modules/@types/node": {
"version": "20.12.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.4.tgz",
"integrity": "sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==",
"version": "20.12.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz",
"integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
@ -2828,9 +2828,9 @@
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
},
"node_modules/@types/react": {
"version": "18.2.74",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.74.tgz",
"integrity": "sha512-9AEqNZZyBx8OdZpxzQlaFEVCSFUM2YXJH46yPOiOpm078k6ZLOCcuAzGum/zK8YBwY+dbahVNbHrbgrAwIRlqw==",
"version": "18.2.75",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.75.tgz",
"integrity": "sha512-+DNnF7yc5y0bHkBTiLKqXFe+L4B3nvOphiMY3tuA5X10esmjqk7smyBZzbGTy2vsiy/Bnzj8yFIBL8xhRacoOg==",
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
@ -2891,9 +2891,9 @@
}
},
"node_modules/@types/webxr": {
"version": "0.5.14",
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.14.tgz",
"integrity": "sha512-UEMMm/Xn3DtEa+gpzUrOcDj+SJS1tk5YodjwOxcqStNhCfPcwgyC5Srg2ToVKyg2Fhq16Ffpb0UWUQHqoT9AMA=="
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.15.tgz",
"integrity": "sha512-nC9116Gd4N+CqTxqo6gvCfhAMAzgRcfS8ZsciNodHq8uwW4JCVKwhagw8yN0XmC7mHrLnWqniJpoVEiR+72Drw=="
},
"node_modules/@types/yargs": {
"version": "17.0.32",
@ -3779,9 +3779,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001605",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001605.tgz",
"integrity": "sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ==",
"version": "1.0.30001608",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001608.tgz",
"integrity": "sha512-cjUJTQkk9fQlJR2s4HMuPMvTiRggl0rAVMtthQuyOlDWuqHXqN8azLq+pi8B2TjwKJ32diHjUqRIKeFX4z1FoA==",
"funding": [
{
"type": "opencollective",
@ -4511,9 +4511,9 @@
"dev": true
},
"node_modules/electron-to-chromium": {
"version": "1.4.726",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.726.tgz",
"integrity": "sha512-xtjfBXn53RORwkbyKvDfTajtnTp0OJoPOIBzXvkNbb7+YYvCHJflba3L7Txyx/6Fov3ov2bGPr/n5MTixmPhdQ=="
"version": "1.4.731",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.731.tgz",
"integrity": "sha512-+TqVfZjpRz2V/5SPpmJxq9qK620SC5SqCnxQIOi7i/U08ZDcTpKbT7Xjj9FU5CbXTMUb4fywbIr8C7cGv4hcjw=="
},
"node_modules/ellipsize": {
"version": "0.5.1",
@ -5929,22 +5929,15 @@
}
},
"node_modules/its-fine": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.1.3.tgz",
"integrity": "sha512-mncCA+yb6tuh5zK26cHqKlsSyxm4zdm4YgJpxycyx6p9fgxgK5PLu3iDVpKhzTn57Yrv3jk/r0aK0RFTT1OjFw==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.2.0.tgz",
"integrity": "sha512-518vLgHK/dgGxyZj4MdLrDRryziqR1M+JbVtjw1tmdgvZQYsJvB2Leoe2qFOHPalZ5KiAOK18wTmIC0XszYc0w==",
"dependencies": {
"@types/react-reconciler": "^0.28.0"
"@types/react": "*",
"@types/react-reconciler": "*"
},
"peerDependencies": {
"react": ">=18.0"
}
},
"node_modules/its-fine/node_modules/@types/react-reconciler": {
"version": "0.28.8",
"resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.8.tgz",
"integrity": "sha512-SN9c4kxXZonFhbX4hJrZy37yw9e7EIxcpHCxQv5JUS18wDE5ovkQKlqQEkufdJCCMfuI9BnjUJvhYeJ9x5Ra7g==",
"dependencies": {
"@types/react": "*"
"react": ">=16.8"
}
},
"node_modules/jackspeak": {
@ -7622,9 +7615,9 @@
}
},
"node_modules/just-types/node_modules/@types/node": {
"version": "18.19.29",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.29.tgz",
"integrity": "sha512-5pAX7ggTmWZdhUrhRWLPf+5oM7F80bcKVCBbr0zwEkTNzTJL2CWQjznpFgHYy6GrzkYi2Yjy7DHKoynFxqPV8g==",
"version": "18.19.31",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.31.tgz",
"integrity": "sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==",
"dependencies": {
"undici-types": "~5.26.4"
}
@ -8753,9 +8746,9 @@
}
},
"node_modules/react-intl": {
"version": "6.6.4",
"resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.6.4.tgz",
"integrity": "sha512-bD+7hVTX3zBFI3z/ffIVZrNkCVK0/sQguQ/DqW5uZ6JFWsuiwOieVcLnmtFiUgMQZLdmwNl2Ur5c10RaO7NWBQ==",
"version": "6.6.5",
"resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.6.5.tgz",
"integrity": "sha512-OErDPbGqus0QKVj77MGCC9Plbnys3CDQrq6Lw41c60pmeTdn41AhoS1SIzXG6SUlyF7qNN2AVqfrrIvHUgSyLQ==",
"dependencies": {
"@formatjs/ecma402-abstract": "1.18.2",
"@formatjs/icu-messageformat-parser": "2.7.6",
@ -9034,9 +9027,9 @@
}
},
"node_modules/reagraph": {
"version": "4.15.27",
"resolved": "https://registry.npmjs.org/reagraph/-/reagraph-4.15.27.tgz",
"integrity": "sha512-BoYWSFdbxeLkEL4lAM9Y/ey0L+liY1is/3+dxZ4pvhlVj4f9RIWZh1IqILY+7t2mRYts5RInsgz0ZAV4t4tIJw==",
"version": "4.16.0",
"resolved": "https://registry.npmjs.org/reagraph/-/reagraph-4.16.0.tgz",
"integrity": "sha512-PCmEfq/UOPies6EqXJQGGi7N1n+687rCQ/WsdhuuglWHgkEhgSVGmtpxWVMOqkCs2P7fp0UzAQOyPy4YhdbQtw==",
"dependencies": {
"@react-spring/three": "9.6.1",
"@react-three/fiber": "8.13.5",
@ -10023,9 +10016,9 @@
}
},
"node_modules/typescript": {
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz",
"integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==",
"version": "5.4.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz",
"integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"

View File

@ -24,7 +24,7 @@
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.13",
"react-icons": "^4.12.0",
"react-intl": "^6.6.4",
"react-intl": "^6.6.5",
"react-loader-spinner": "^5.4.5",
"react-pdf": "^7.7.1",
"react-router-dom": "^6.22.3",
@ -32,14 +32,14 @@
"react-tabs": "^6.0.2",
"react-toastify": "^9.1.3",
"react-tooltip": "^5.26.3",
"reagraph": "^4.15.27",
"reagraph": "^4.16.0",
"use-debounce": "^10.0.0"
},
"devDependencies": {
"@lezer/generator": "^1.7.0",
"@types/jest": "^29.5.12",
"@types/node": "^20.12.4",
"@types/react": "^18.2.74",
"@types/node": "^20.12.7",
"@types/react": "^18.2.75",
"@types/react-dom": "^18.2.24",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
@ -54,7 +54,7 @@
"postcss": "^8.4.38",
"tailwindcss": "^3.4.3",
"ts-jest": "^29.1.2",
"typescript": "^5.4.3",
"typescript": "^5.4.4",
"vite": "^4.5.3"
},
"jest": {

View File

@ -3,7 +3,19 @@
import { GraphCanvas as GraphUI } from 'reagraph';
export { type GraphEdge, type GraphNode, type GraphCanvasRef, Sphere, useSelection } from 'reagraph';
export {
type GraphEdge,
type GraphNode,
type GraphCanvasRef,
Sphere,
useSelection,
type CollapseProps
} from 'reagraph';
export { type LayoutTypes as GraphLayout } from 'reagraph';
import { ThreeEvent } from '@react-three/fiber';
export type GraphMouseEvent = ThreeEvent<MouseEvent>;
export type GraphPointerEvent = ThreeEvent<PointerEvent>;
export default GraphUI;

View File

@ -75,6 +75,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
return hoverID && controller.schema?.cstByID.get(hoverID);
}, [controller.schema?.cstByID, hoverID]);
const [hoverCstDebounced] = useDebounce(hoverCst, PARAMETER.graphPopupDelay);
const [hoverLeft, setHoverLeft] = useState(true);
const [toggleResetView, setToggleResetView] = useState(false);
@ -227,6 +228,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
onEdit={onOpenEdit}
onSelectCentral={handleSetFocus}
toggleResetView={toggleResetView}
setHoverLeft={setHoverLeft}
/>
),
[
@ -320,7 +322,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
{hoverCst && hoverCstDebounced && hoverCst === hoverCstDebounced ? (
<Overlay
layer='z-tooltip'
position='top-[1.6rem] left-[2.6rem]'
position={clsx('top-[1.6rem]', { 'left-[2.6rem]': hoverLeft, 'right-[2.6rem]': !hoverLeft })}
className={clsx('w-[25rem]', 'px-3', 'overflow-y-auto', 'border shadow-md', 'clr-app')}
>
<InfoConstituenta className='pt-1 pb-2' data={hoverCstDebounced} />

View File

@ -2,7 +2,16 @@
import { RefObject, useCallback, useLayoutEffect, useMemo } from 'react';
import GraphUI, { GraphCanvasRef, GraphEdge, GraphLayout, GraphNode, useSelection } from '@/components/ui/GraphUI';
import GraphUI, {
CollapseProps,
GraphCanvasRef,
GraphEdge,
GraphLayout,
GraphMouseEvent,
GraphNode,
GraphPointerEvent,
useSelection
} from '@/components/ui/GraphUI';
import { useConceptOptions } from '@/context/OptionsContext';
import { ConstituentaID } from '@/models/rsform';
import { graphDarkT, graphLightT } from '@/styling/color';
@ -19,6 +28,7 @@ interface TermGraphProps {
orbit: boolean;
setHoverID: (newID: ConstituentaID | undefined) => void;
setHoverLeft: (value: boolean) => void;
onEdit: (cstID: ConstituentaID) => void;
onSelectCentral: (selectedID: ConstituentaID) => void;
onSelect: (newID: ConstituentaID) => void;
@ -37,12 +47,12 @@ function TermGraph({
orbit,
toggleResetView,
setHoverID,
setHoverLeft,
onEdit,
onSelectCentral,
onSelect,
onDeselect
}: TermGraphProps) {
let ctrlKey: boolean = false;
const { calculateHeight, darkMode } = useConceptOptions();
const { selections, setSelections } = useSelection({
@ -54,10 +64,14 @@ function TermGraph({
});
const handleHoverIn = useCallback(
(node: GraphNode) => {
(node: GraphNode, event: GraphPointerEvent) => {
setHoverID(Number(node.id));
setHoverLeft(
event.clientX / window.innerWidth >= PARAMETER.graphHoverXLimit ||
event.clientY / window.innerHeight >= PARAMETER.graphHoverYLimit
);
},
[setHoverID]
[setHoverID, setHoverLeft]
);
const handleHoverOut = useCallback(() => {
@ -65,8 +79,8 @@ function TermGraph({
}, [setHoverID]);
const handleNodeClick = useCallback(
(node: GraphNode) => {
if (ctrlKey) {
(node: GraphNode, _?: CollapseProps, event?: GraphMouseEvent) => {
if (event?.ctrlKey) {
onSelectCentral(Number(node.id));
} else if (selections.includes(node.id)) {
onDeselect(Number(node.id));
@ -74,7 +88,7 @@ function TermGraph({
onSelect(Number(node.id));
}
},
[onSelect, selections, onDeselect, onSelectCentral, ctrlKey]
[onSelect, selections, onDeselect, onSelectCentral]
);
const handleNodeDoubleClick = useCallback(
@ -101,13 +115,7 @@ function TermGraph({
const canvasHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
return (
<div
className='outline-none'
tabIndex={-1}
// TODO: fix hacky way of tracking CTRL. Expose event from onNodeClick instead
onKeyUp={event => (ctrlKey = event.ctrlKey)}
onKeyDown={event => (ctrlKey = event.ctrlKey)}
>
<div className='outline-none'>
<div className='relative' style={{ width: canvasWidth, height: canvasHeight }}>
<GraphUI
nodes={nodes}

View File

@ -15,9 +15,12 @@ export const buildConstants = {
export const PARAMETER = {
smallScreen: 640, // == tailwind:xs
smallTreeNodes: 50, // amount of nodes threshold for size increase for large graphs
graphPopupDelay: 500, // milliseconds delay for graph popup selections
refreshTimeout: 100, // milliseconds delay for post-refresh actions
minimalTimeout: 10, // milliseconds delay for fast updates
graphHoverXLimit: 0.4, // ratio to clientWidth used to determine which side of screen popup should be
graphHoverYLimit: 0.6, // ratio to clientHeight used to determine which side of screen popup should be
graphPopupDelay: 500, // milliseconds delay for graph popup selections
graphRefreshDelay: 10, // milliseconds delay for graph viewpoint reset
logicLabel: 'LOGIC'