import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { darkTheme, GraphCanvas, GraphCanvasRef, GraphEdge, GraphNode, LayoutTypes, lightTheme, Sphere, useSelection } from 'reagraph'; import Button from '../../components/Common/Button'; import Checkbox from '../../components/Common/Checkbox'; import ConceptSelect from '../../components/Common/ConceptSelect'; import ConceptTooltip from '../../components/Common/ConceptTooltip'; import Divider from '../../components/Common/Divider'; import MiniButton from '../../components/Common/MiniButton'; import ConstituentaInfo from '../../components/Help/ConstituentaInfo'; import CstClassInfo from '../../components/Help/CstClassInfo'; import CstStatusInfo from '../../components/Help/CstStatusInfo'; import { ArrowsRotateIcon, FilterCogIcon, HelpIcon } from '../../components/Icons'; import { useRSForm } from '../../context/RSFormContext'; import { useConceptTheme } from '../../context/ThemeContext'; import useLocalStorage from '../../hooks/useLocalStorage'; import { prefixes, resources } from '../../utils/constants'; import { Graph } from '../../utils/Graph'; import { CstType, IConstituenta } from '../../utils/models'; import { getCstClassColor, getCstStatusColor, GraphColoringSelector, GraphLayoutSelector, mapColoringLabels, mapLayoutLabels } from '../../utils/staticUI'; import DlgGraphOptions from './DlgGraphOptions'; import ConstituentaTooltip from './elements/ConstituentaTooltip'; export type ColoringScheme = 'none' | 'status' | 'type'; const TREE_SIZE_MILESTONE = 50; function getCstNodeColor(cst: IConstituenta, coloringScheme: ColoringScheme, darkMode: boolean): string { if (coloringScheme === 'type') { return getCstClassColor(cst.cstClass, darkMode); } if (coloringScheme === 'status') { return getCstStatusColor(cst.status, darkMode); } return (darkMode ? '#7a8c9e' :'#7ca0ab'); } export interface GraphEditorParams { noHermits: boolean noTransitive: boolean noTemplates: boolean allowBase: boolean allowStruct: boolean allowTerm: boolean allowAxiom: boolean allowFunction: boolean allowPredicate: boolean allowConstant: boolean allowTheorem: boolean } interface EditorTermGraphProps { onOpenEdit: (cstID: number) => void // onCreateCst: (selectedID: number | undefined, type: CstType | undefined, skipDialog?: boolean) => void // onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void } function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) { const { schema } = useRSForm(); const { darkMode, noNavigation } = useConceptTheme(); const [ layout, setLayout ] = useLocalStorage('graph_layout', 'treeTd2d'); const [ coloringScheme, setColoringScheme ] = useLocalStorage('graph_coloring', 'none'); const [ orbit, setOrbit ] = useState(false); const [ noHermits, setNoHermits ] = useLocalStorage('graph_no_hermits', true); const [ noTransitive, setNoTransitive ] = useLocalStorage('graph_no_transitive', false); const [ noTemplates, setNoTemplates ] = useLocalStorage('graph_no_templates', false); const [ allowBase, setAllowBase ] = useLocalStorage('graph_allow_base', true); const [ allowStruct, setAllowStruct ] = useLocalStorage('graph_allow_struct', true); const [ allowTerm, setAllowTerm ] = useLocalStorage('graph_allow_term', true); const [ allowAxiom, setAllowAxiom ] = useLocalStorage('graph_allow_axiom', true); const [ allowFunction, setAllowFunction ] = useLocalStorage('function', true); const [ allowPredicate, setAllowPredicate ] = useLocalStorage('graph_allow_predicate', true); const [ allowConstant, setAllowConstant ] = useLocalStorage('graph_allow_constant', true); const [ allowTheorem, setAllowTheorem ] = useLocalStorage('graph_allow_theorem', true); const [ filtered, setFiltered ] = useState(new Graph()); const [ dismissed, setDismissed ] = useState([]); const [ selectedDismissed, setSelectedDismissed ] = useState([]); const graphRef = useRef(null); const [showOptions, setShowOptions] = useState(false); const [hoverID, setHoverID] = useState(undefined); const hoverCst = useMemo( () => { return schema?.items.find(cst => String(cst.id) == hoverID); }, [schema?.items, hoverID]); const is3D = useMemo(() => layout.includes('3d'), [layout]); const allowedTypes: CstType[] = useMemo( () => { const result: CstType[] = []; if (allowBase) result.push(CstType.BASE); if (allowStruct) result.push(CstType.STRUCTURED); if (allowTerm) result.push(CstType.TERM); if (allowAxiom) result.push(CstType.AXIOM); if (allowFunction) result.push(CstType.FUNCTION); if (allowPredicate) result.push(CstType.PREDICATE); if (allowConstant) result.push(CstType.CONSTANT); if (allowTheorem) result.push(CstType.THEOREM); return result; }, [allowBase, allowStruct, allowTerm, allowAxiom, allowFunction, allowPredicate, allowConstant, allowTheorem]); useEffect( () => { if (!schema) { setFiltered(new Graph()); return; } const graph = schema.graph.clone(); if (noHermits) { graph.removeIsolated(); } if (noTransitive) { graph.transitiveReduction(); } if (noTemplates) { schema.items.forEach(cst => { if (cst.isTemplate) { graph.foldNode(cst.id); } }); } if (allowedTypes.length < Object.values(CstType).length) { schema.items.forEach(cst => { if (!allowedTypes.includes(cst.cstType)) { graph.foldNode(cst.id); } }); } const newDismissed: number[] = []; schema.items.forEach(cst => { if (!graph.nodes.has(cst.id)) { newDismissed.push(cst.id); } }); setFiltered(graph); setDismissed(newDismissed); setSelectedDismissed([]); setHoverID(undefined); }, [schema, noHermits, noTransitive, noTemplates, allowedTypes]); function toggleDismissed(cstID: number) { setSelectedDismissed(prev => { const index = prev.findIndex(id => cstID == id); if (index !== -1) { prev.splice(index, 1); } else { prev.push(cstID); } return [... prev]; }); } const nodes: GraphNode[] = useMemo( () => { const result: GraphNode[] = []; if (!schema) { return result; } filtered.nodes.forEach(node => { const cst = schema.items.find(cst => cst.id === node.id); if (cst) { result.push({ id: String(node.id), fill: getCstNodeColor(cst, coloringScheme, darkMode), label: cst.term.resolved ? `${cst.alias}: ${cst.term.resolved}` : cst.alias }); } }); return result; }, [schema, coloringScheme, filtered.nodes, darkMode]); const edges: GraphEdge[] = useMemo( () => { const result: GraphEdge[] = []; let edgeID = 1; filtered.nodes.forEach(source => { source.outputs.forEach(target => { result.push({ id: String(edgeID), source: String(source.id), target: String(target) }); edgeID += 1; }); }); return result; }, [filtered.nodes]); const { selections, actives, onNodeClick, onCanvasClick, onNodePointerOver, onNodePointerOut } = useSelection({ ref: graphRef, nodes, edges, type: 'multi', // 'single' | 'multi' | 'multiModifier' pathSelectionType: 'all', focusOnSelect: false }); const handleRecreate = useCallback( () => { graphRef.current?.resetControls(); graphRef.current?.centerGraph(); }, []); const handleHoverIn = useCallback( (node: GraphNode) => { setHoverID(node.id); if (onNodePointerOver) onNodePointerOver(node); }, [onNodePointerOver]); const handleHoverOut = useCallback( (node: GraphNode) => { setHoverID(undefined); if (onNodePointerOut) onNodePointerOut(node); }, [onNodePointerOut]); const handleNodeClick = useCallback( (node: GraphNode) => { if (selections.includes(node.id)) { onOpenEdit(Number(node.id)); return; } if (onNodeClick) onNodeClick(node); }, [onNodeClick, selections, onOpenEdit]); function getOptions() { return { noHermits: noHermits, noTemplates: noTemplates, noTransitive: noTransitive, allowBase: allowBase, allowStruct: allowStruct, allowTerm: allowTerm, allowAxiom: allowAxiom, allowFunction: allowFunction, allowPredicate: allowPredicate, allowConstant: allowConstant, allowTheorem: allowTheorem } } const handleChangeOptions = useCallback( (params: GraphEditorParams) => { setNoHermits(params.noHermits); setNoTransitive(params.noTransitive); setNoTemplates(params.noTemplates); setAllowBase(params.allowBase); setAllowStruct(params.allowStruct); setAllowTerm(params.allowTerm); setAllowAxiom(params.allowAxiom); setAllowFunction(params.allowFunction); setAllowPredicate(params.allowPredicate); setAllowConstant(params.allowConstant); setAllowTheorem(params.allowTheorem); }, [setNoHermits, setNoTransitive, setNoTemplates, setAllowBase, setAllowStruct, setAllowTerm, setAllowAxiom, setAllowFunction, setAllowPredicate, setAllowConstant, setAllowTheorem]); const canvasWidth = useMemo( () => { return 'calc(100vw - 14.6rem)'; }, []); const canvasHeight = useMemo( () => { return !noNavigation ? 'calc(100vh - 13rem)' : 'calc(100vh - 2rem)'; }, [noNavigation]); const dismissedStyle = useCallback( (cstID: number) => { return selectedDismissed.includes(cstID) ? {outlineWidth: '2px', outlineStyle: 'solid'}: {}; }, [selectedDismissed]); return (
{showOptions && setShowOptions(false)} initial={getOptions()} onConfirm={handleChangeOptions} />}
{hoverCst &&
}
{ setLayout(data.length > 0 ? data[0].value : GraphLayoutSelector[0].value); }} /> setOrbit(event.target.checked) } /> setNoTransitive(event.target.checked) } />

Скрытые конституенты

{dismissed.map(cstID => { const cst = schema!.items.find(cst => cst.id === cstID)!; const adjustedColoring = coloringScheme === 'none' ? 'status': coloringScheme; return (<>
toggleDismissed(cstID)} onDoubleClick={() => onOpenEdit(cstID)} > {cst.alias}
); })}
} tooltip='Пересоздать граф' onClick={handleRecreate} />

Настройка графа

Цвет - выбор правила покраски узлов

Граф - выбор модели расположения узлов

Удалить несвязанные - в графе не отображаются одинокие вершины

Транзитивная редукция - в графе устраняются транзитивные пути

( )} />
); } export default EditorTermGraph;