mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Setup term graph experiments
This commit is contained in:
parent
37c052f73d
commit
29239590ba
|
@ -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}
|
||||||
|
|
29
rsconcept/frontend/src/components/Common/GraphThemed.tsx
Normal file
29
rsconcept/frontend/src/components/Common/GraphThemed.tsx
Normal 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;
|
|
@ -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 {
|
||||||
|
|
|
@ -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,18 +37,50 @@ 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'>
|
||||||
|
<Select
|
||||||
|
className='w-[10rem]'
|
||||||
|
options={GraphLayoutSelector}
|
||||||
|
placeholder='Выберите тип'
|
||||||
|
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}
|
nodes={nodes}
|
||||||
edges={edges}
|
edges={edges}
|
||||||
draggable
|
draggable
|
||||||
theme={darkMode ? darkTheme : lightTheme}
|
layoutType={layout}
|
||||||
labelFontUrl={resources.graph_font}
|
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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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'> {}
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
@ -216,6 +218,22 @@ export const CstTypeSelector = (Object.values(CstType)).map(
|
||||||
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) {
|
||||||
case CstType.BASE: return 'X';
|
case CstType.BASE: return 'X';
|
||||||
|
|
Loading…
Reference in New Issue
Block a user