Setup term graph experiments

This commit is contained in:
IRBorisov 2023-07-30 00:47:07 +03:00
parent 37c052f73d
commit 29239590ba
7 changed files with 105 additions and 23 deletions

View File

@ -5,7 +5,7 @@ function ConceptTab({ children, className, ...otherProps }: TabProps) {
return ( return (
<Tab <Tab
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
className={`px-2 py-1 text-sm hover:cursor-pointer clr-tab ${className} whitespace-nowrap`} className={`px-2 py-1 border-r-2 text-sm hover:cursor-pointer clr-tab whitespace-nowrap ${className}`}
{...otherProps} {...otherProps}
> >
{children} {children}

View File

@ -0,0 +1,29 @@
import { Ref } from 'react';
import { darkTheme, GraphCanvas, GraphCanvasProps, GraphCanvasRef, lightTheme } from 'reagraph';
import { useConceptTheme } from '../../context/ThemeContext';
import { resources } from '../../utils/constants';
interface GraphThemedProps
extends Omit<GraphCanvasProps, 'theme' | 'labelFontUrl'> {
ref?: Ref<GraphCanvasRef>
sizeClass: string
}
function GraphThemed({ sizeClass, ...props }: GraphThemedProps) {
const { darkMode } = useConceptTheme();
return (
<div className='flex-wrap w-full h-full overflow-auto'>
<div className={`relative border ${sizeClass}`}>
<GraphCanvas
theme={darkMode ? darkTheme : lightTheme}
labelFontUrl={resources.graph_font}
{...props}
/>
</div>
</div>
);
}
export default GraphThemed;

View File

@ -52,7 +52,7 @@
} }
.clr-tab { .clr-tab {
@apply text-gray-600 dark:text-zinc-200 hover:bg-gray-300 dark:hover:bg-gray-400 @apply clr-border text-gray-600 dark:text-zinc-200 hover:bg-gray-300 dark:hover:bg-gray-400
} }
.clr-hover { .clr-hover {

View File

@ -1,13 +1,16 @@
import { useMemo } from 'react'; import { useMemo, useRef } from 'react';
import { darkTheme, GraphCanvas, GraphEdge, GraphNode, lightTheme } from 'reagraph'; import Select from 'react-select';
import { GraphCanvasRef, GraphEdge, GraphNode, LayoutTypes, useSelection } from 'reagraph';
import GraphThemed from '../../components/Common/GraphThemed';
import { useRSForm } from '../../context/RSFormContext'; import { useRSForm } from '../../context/RSFormContext';
import { useConceptTheme } from '../../context/ThemeContext'; import useLocalStorage from '../../hooks/useLocalStorage';
import { resources } from '../../utils/constants'; import { GraphLayoutSelector } from '../../utils/staticUI';
function EditorTermGraph() { function EditorTermGraph() {
const { schema } = useRSForm(); const { schema } = useRSForm();
const { darkMode } = useConceptTheme(); const [ layout, setLayout ] = useLocalStorage<LayoutTypes>('graph_layout', 'forceatlas2');
const graphRef = useRef<GraphCanvasRef | null>(null);
const nodes: GraphNode[] = useMemo(() => { const nodes: GraphNode[] = useMemo(() => {
return schema?.items.map(cst => { return schema?.items.map(cst => {
@ -18,7 +21,7 @@ function EditorTermGraph() {
) ?? []; ) ?? [];
}, [schema?.items]); }, [schema?.items]);
const edges = useMemo(() => { const edges: GraphEdge[] = useMemo(() => {
const result: GraphEdge[] = []; const result: GraphEdge[] = [];
let edgeID = 1; let edgeID = 1;
schema?.graph.nodes.forEach(source => { schema?.graph.nodes.forEach(source => {
@ -34,17 +37,49 @@ function EditorTermGraph() {
return result; return result;
}, [schema?.graph]); }, [schema?.graph]);
const {
selections, actives,
onNodeClick,
onCanvasClick,
onNodePointerOver,
onNodePointerOut
} = useSelection({
type: 'multi', // 'single' | 'multi' | 'multiModifier'
ref: graphRef,
nodes,
edges,
pathSelectionType: 'all',
focusOnSelect: 'singleOnly'
});
return ( return (
<div className='flex-wrap w-full h-full overflow-auto'> <div>
<div className='relative border w-[1240px] h-[800px] 2xl:w-[1880px] 2xl:h-[800px]'> <div className='relative w-full'>
<GraphCanvas <div className='absolute top-0 left-0 z-20 px-3 py-2'>
nodes={nodes} <Select
edges={edges} className='w-[10rem]'
draggable options={GraphLayoutSelector}
theme={darkMode ? darkTheme : lightTheme} placeholder='Выберите тип'
labelFontUrl={resources.graph_font} value={layout && { value: layout, label: String(layout) }}
onChange={data => { data && setLayout(data.value); }}
/>
</div>
</div>
<GraphThemed ref={graphRef}
sizeClass='w-[1240px] h-[800px] 2xl:w-[1880px] 2xl:h-[800px]'
nodes={nodes}
edges={edges}
draggable
layoutType={layout}
selections={selections}
actives={actives}
onCanvasClick={onCanvasClick}
onNodeClick={onNodeClick}
onNodePointerOver={onNodePointerOver}
onNodePointerOut={onNodePointerOut}
// sizingType="default" // 'none' | 'pagerank' | 'centrality' | 'attribute' | 'default';
cameraMode={ layout.includes('3d') ? 'rotate' : 'pan'}
/> />
</div>
</div> </div>
); );
} }

View File

@ -165,12 +165,12 @@ function RSTabs() {
showCloneDialog={() => setShowClone(true)} showCloneDialog={() => setShowClone(true)}
showUploadDialog={() => setShowUpload(true)} showUploadDialog={() => setShowUpload(true)}
/> />
<ConceptTab>Паспорт схемы</ConceptTab> <ConceptTab className='border-r-2'>Паспорт схемы</ConceptTab>
<ConceptTab className='border-x-2 clr-border min-w-[10rem] flex justify-between gap-2'> <ConceptTab className='border-r-2 min-w-[10rem] flex justify-between gap-2'>
<span>Конституенты</span> <span>Конституенты</span>
<span>{`${schema.stats?.count_errors ?? 0} | ${schema.stats?.count_all ?? 0}`}</span> <span>{`${schema.stats?.count_errors ?? 0} | ${schema.stats?.count_all ?? 0}`}</span>
</ConceptTab> </ConceptTab>
<ConceptTab>Редактор</ConceptTab> <ConceptTab className='border-r-2'>Редактор</ConceptTab>
<ConceptTab>Граф термов</ConceptTab> <ConceptTab>Граф термов</ConceptTab>
</TabList> </TabList>

View File

@ -179,8 +179,8 @@ export interface IRSForm {
graph: Graph graph: Graph
} }
export interface IRSFormData extends Omit<IRSForm, 'stats' > {} export interface IRSFormData extends Omit<IRSForm, 'stats' | 'graph'> {}
export interface IRSFormMeta extends Omit<IRSForm, 'items' | 'stats'> {} export interface IRSFormMeta extends Omit<IRSForm, 'items' | 'stats' | 'graph'> {}
export interface IRSFormUpdateData export interface IRSFormUpdateData
extends Omit<IRSFormMeta, 'time_create' | 'time_update' | 'id' | 'owner'> {} extends Omit<IRSFormMeta, 'time_create' | 'time_update' | 'id' | 'owner'> {}

View File

@ -1,3 +1,5 @@
import { LayoutTypes } from 'reagraph';
import { resolveErrorClass,RSErrorClass, RSErrorType, TokenID } from './enums'; import { resolveErrorClass,RSErrorClass, RSErrorType, TokenID } from './enums';
import { CstType, ExpressionStatus, type IConstituenta, IRSErrorDescription,type IRSForm, ParsingStatus, ValueClass } from './models'; import { CstType, ExpressionStatus, type IConstituenta, IRSErrorDescription,type IRSForm, ParsingStatus, ValueClass } from './models';
@ -214,7 +216,23 @@ export const CstTypeSelector = (Object.values(CstType)).map(
(typeStr) => { (typeStr) => {
const type = typeStr as CstType; const type = typeStr as CstType;
return { value: type, label: getCstTypeLabel(type) }; return { value: type, label: getCstTypeLabel(type) };
}); });
export const GraphLayoutSelector: {value: LayoutTypes, label: string}[] = [
{ value: 'forceatlas2', label: 'forceatlas2'},
{ value: 'nooverlap', label: 'nooverlap'},
{ value: 'forceDirected2d', label: 'forceDirected2d'},
{ value: 'forceDirected3d', label: 'forceDirected3d'},
{ value: 'circular2d', label: 'circular2d'},
{ value: 'treeTd2d', label: 'treeTd2d'},
{ value: 'treeTd3d', label: 'treeTd3d'},
{ value: 'treeLr2d', label: 'treeLr2d'},
{ value: 'treeLr3d', label: 'treeLr3d'},
{ value: 'radialOut2d', label: 'radialOut2d'},
{ value: 'radialOut3d', label: 'radialOut3d'},
// { value: 'hierarchicalTd', label: 'hierarchicalTd'},
// { value: 'hierarchicalLr', label: 'hierarchicalLr'}
];
export function getCstTypePrefix(type: CstType) { export function getCstTypePrefix(type: CstType) {
switch (type) { switch (type) {