Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • graphpolaris/frontend-v2
  • rijkheere/frontend-v-2-reordering-paoh
2 results
Show changes
Commits on Source (7)
Showing
with 519 additions and 99 deletions
......@@ -8,4 +8,16 @@ VITE_BACKEND_QUERY=/query
VITE_BACKEND_SCHEMA=/schema
SENTRY_ENABLED=false
SENTRY_URL=
\ No newline at end of file
SENTRY_URL=
WIP_TABLEVIS=false
WIP_NODELINKVIS=false
WIP_RAWJSONVIS=false
WIP_PAOHVIS=true
WIP_MATRIXVIS=true
WIP_SEMANTICSUBSTRATESVIS=true
WIP_MAPVIS=true
WIP_INSIGHT_SHARING=true
WIP_VIEWER_PERMISSIONS=true
WIP_SHARABLE_EXPLORATION=true
\ No newline at end of file
......@@ -7,7 +7,7 @@ import {
useQuerybuilderSettings,
useSessionCache,
} from '@graphpolaris/shared/lib/data-access';
import { setCurrentTheme } from '@graphpolaris/shared/lib/data-access/store/configSlice';
import { addError, setCurrentTheme } from '@graphpolaris/shared/lib/data-access/store/configSlice';
import { resetGraphQueryResults, queryingBackend } from '@graphpolaris/shared/lib/data-access/store/graphQueryResultSlice';
import { Query2BackendQuery, QueryMultiGraph } from '@graphpolaris/shared/lib/querybuilder';
import { Navbar } from '../components/navbar/navbar';
......@@ -24,6 +24,7 @@ import { InspectorPanel } from '@graphpolaris/shared/lib/inspector';
import { SearchBar } from '@graphpolaris/shared/lib/sidebar/search/SearchBar';
import { Schema } from '@graphpolaris/shared/lib/schema/panel';
import { InsightDialog } from '@graphpolaris/shared/lib/insight-sharing';
import { ErrorBoundary } from '@graphpolaris/shared/lib/components/errorBoundary';
export type App = {
load?: string;
......@@ -93,21 +94,44 @@ export function App(props: App) {
<Resizable divisorSize={3} horizontal={true} defaultProportion={0.33}>
{tab !== undefined ? (
<div className="flex flex-col w-full h-full">
{tab === 'Search' && <SearchBar onRemove={() => setTab(undefined)} />}
{tab === 'Schema' && <Schema auth={authCheck} onRemove={() => setTab(undefined)} />}
{tab === 'Search' && (
<ErrorBoundary
fallback={<div>Something went wrong</div>}
onError={() => dispatch(addError('Something went wrong while trying to load the search bar'))}
>
<SearchBar onRemove={() => setTab(undefined)} />
</ErrorBoundary>
)}
{tab === 'Schema' && (
<ErrorBoundary
fallback={<div>Something went wrong</div>}
onError={() => dispatch(addError('Something went wrong while trying to load the schema panel'))}
>
<Schema auth={authCheck} onRemove={() => setTab(undefined)} />
</ErrorBoundary>
)}
</div>
) : null}
<Resizable divisorSize={3} horizontal={false}>
<VisualizationPanel
fullSize={() => {
// setVisFullSize(!visFullSize);
// tab === undefined && setTab('Schema');
// tab !== undefined && setTab(undefined);
}}
/>
<QueryBuilder onRunQuery={runQuery} />
<ErrorBoundary
fallback={<div>Something went wrong</div>}
onError={() => dispatch(addError('Something went wrong while trying to load the visualization panel'))}
>
<VisualizationPanel
fullSize={() => {
// setVisFullSize(!visFullSize);
// tab === undefined && setTab('Schema');
// tab !== undefined && setTab(undefined);
}}
/>
</ErrorBoundary>
<ErrorBoundary
fallback={<div>Something went wrong</div>}
onError={() => dispatch(addError('Something went wrong while trying to load the query builder'))}
>
<QueryBuilder onRunQuery={runQuery} />
</ErrorBoundary>
</Resizable>
</Resizable>
<InspectorPanel />
......
......@@ -16,15 +16,15 @@ import GpLogo from './gp-logo';
import { Popover, PopoverContent, PopoverTrigger } from '@graphpolaris/shared/lib/components/layout/Popover';
import { useDispatch } from 'react-redux';
import { Dialog, DialogContent, DialogTrigger } from '@graphpolaris/shared/lib/components/layout/Dialog';
import { UserManagementContent } from '@graphpolaris/shared/lib/components/userManagementContent/UserManagementContent';
import { UserManagementContent } from '@graphpolaris/shared/lib/components/panels/userManagementContent/UserManagementContent';
import { addInfo } from '@graphpolaris/shared/lib/data-access/store/configSlice';
import { showManagePermissions, showSharableExploration } from 'config';
export const Navbar = () => {
const dropdownRef = useRef<HTMLDivElement>(null);
const auth = useAuth();
const authCache = useAuthorizationCache();
const [menuOpen, setMenuOpen] = useState(false);
const [reportingOpen, setReportingOpen] = useState(false);
const dispatch = useDispatch();
const buildInfo = import.meta.env.GRAPHPOLARIS_VERSION;
......@@ -97,12 +97,14 @@ export const Navbar = () => {
</div>
{authCache.authorized ? (
<>
<DropdownItem
value="Share"
onClick={() => {
auth.newShareRoom();
}}
/>
{showSharableExploration() && (
<DropdownItem
value="Share"
onClick={() => {
auth.newShareRoom();
}}
/>
)}
<DropdownItem value="Settings" onClick={() => {}} />
<DropdownItem value="Log out" onClick={() => {}} />
</>
......@@ -119,7 +121,7 @@ export const Navbar = () => {
<div className="p-2 border-t">
<h3 className="text-xs">Version: {buildInfo}</h3>
</div>
{writeAllowed && (
{showManagePermissions() && writeAllowed && (
<>
<Dialog>
<DialogTrigger className="ml-2 text-sm hover:bg-secondary-200">Manage Viewers Permission</DialogTrigger>
......
// Safely retrieve environment variable values with a default fallback
const getEnvVariable = (key: string, defaultValue: string = 'false'): string => {
return import.meta.env[key] ?? defaultValue;
};
// Check if the environment is production
const isProduction = (): boolean => {
return getEnvVariable('GRAPHPOLARIS_VERSION', 'dev') === 'prod';
};
// Check if the Manage Permissions feature is enabled
const showManagePermissions = (): boolean => {
return !isProduction() || (isProduction() && getEnvVariable('WIP_VIEWER_PERMISSIONS') === 'false');
};
// Check if the Insight Sharing feature is enabled
const showInsightSharing = (): boolean => {
return !isProduction() || (isProduction() && getEnvVariable('WIP_INSIGHT_SHARING') === 'false');
};
// Check if the Insight Sharing feature is enabled
const showSharableExploration = (): boolean => {
return !isProduction() || (isProduction() && getEnvVariable('WIP_SHARABLE_EXPLORATION') === 'false');
};
// Utility to check if a specific visualization is released based on environment variables
const isVisualizationReleased = (visualizationName: string): boolean => {
const visualizationFlag = getEnvVariable(`WIP_${visualizationName.toUpperCase()}`, 'false');
return !isProduction() || (isProduction() && visualizationFlag === 'false');
};
export { isProduction, showManagePermissions, showInsightSharing, showSharableExploration, isVisualizationReleased };
export * from './colors';
export * from './featureFlags';
......@@ -143,7 +143,7 @@ export const Input = (props: InputProps) => {
switch (props.type) {
case 'slider':
return <SliderInput {...(props as SliderProps)} />;
case 'text' || 'password':
case 'text':
return <TextInput {...(props as TextProps)} />;
case 'checkbox':
return <CheckboxInput {...(props as CheckboxProps)} />;
......
// Dialog.stories.tsx
/* eslint-disable react-hooks/rules-of-hooks */
import React, { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { Dialog, DialogTrigger, DialogContent, DialogHeading, DialogDescription, DialogClose } from './Dialog';
const metaDialog: Meta<typeof Dialog> = {
component: Dialog,
title: 'Components/Layout/Dialog',
};
export default metaDialog;
type Story = StoryObj<typeof Dialog>;
export const mainStory: Story = {
render: (args) => {
const [isOpen, setIsOpen] = useState(false);
const handleToggle = () => setIsOpen(!isOpen);
return (
<Dialog {...args} open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<button onClick={handleToggle} className="px-4 py-2 bg-secondary-200 text-white rounded">
Open Dialog
</button>
</DialogTrigger>
<DialogContent>
<DialogHeading>Dialog Title</DialogHeading>
<DialogDescription>This is a description inside the dialog.</DialogDescription>
<DialogClose>Close</DialogClose>
</DialogContent>
</Dialog>
);
},
args: {
initialOpen: false,
onOpenChange: (open: boolean) => console.log(`Dialog is ${open ? 'open' : 'closed'}`),
},
};
// Panel.stories.tsx
/* eslint-disable react-hooks/rules-of-hooks */
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { Panel } from './Panel';
const metaPanel: Meta<typeof Panel> = {
component: Panel,
title: 'Components/Layout/Panel',
};
export default metaPanel;
type Story = StoryObj<typeof Panel>;
export const mainStory: Story = {
render: (args) => (
<Panel {...args}>
<p>This is the content inside the panel.</p>
</Panel>
),
args: {
title: 'Panel Title',
tooltips: <span>Some tooltip</span>,
},
};
......@@ -7,7 +7,7 @@ import { Popover, PopoverTrigger, PopoverContent, PopoverHeading, PopoverDescrip
import { Icon } from '../icon';
const metaPopover: Meta<typeof Popover> = {
component: Popover,
title: 'Components/Popover',
title: 'Components/Layout/Popover',
};
export default metaPopover;
......
import React, { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { useState } from 'react';
import { Pagination } from '.';
const metaPagination: Meta<typeof Pagination> = {
......
......@@ -130,7 +130,7 @@ export const Pill = React.memo((props: PillI) => {
>
{props.children}
</div>
<div className="absolute z-50 pointer-events-auto">{props.handles}</div>
<div className="absolute z-50 pointer-events-auto bg-black">{props.handles}</div>
</div>
);
});
......
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import type { RootState } from './store';
import { AllLayoutAlgorithms, Layouts } from '@graphpolaris/shared/lib/graph-layout';
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { SchemaFromBackend, SchemaGraph, SchemaGraphInference, SchemaGraphology, SchemaGraphStats } from '../../schema';
import { SchemaViewState } from '../../schema/panel/Schema';
import { SchemaUtils } from '../../schema/schema-utils';
import { SchemaGraphStats, SchemaFromBackend, SchemaGraph, SchemaGraphology, SchemaGraphInference } from '../../schema';
import type { RootState } from './store';
/**************************************************************** */
......@@ -13,6 +14,7 @@ export type SchemaSettings = {
layoutName: AllLayoutAlgorithms;
animatedEdges: boolean;
showMinimap: boolean;
schemaViewState: SchemaViewState;
};
type schemaSliceI = {
......@@ -42,9 +44,12 @@ export const initialState: schemaSliceI = {
// layoutName: 'Cytoscape_fcose',
settings: {
connectionType: 'connection',
layoutName: Layouts.DAGRE,
// layoutName: Layouts.DAGRE,
layoutName: Layouts.LISTINTERSECTED,
animatedEdges: false,
showMinimap: true,
// schemaViewState: SchemaViewState.SchemaGraphS,
schemaViewState: SchemaViewState.SchemaListS,
},
};
export const schemaSlice = createSlice({
......
......@@ -3,10 +3,37 @@ import Graph from 'graphology';
import { Attributes } from 'graphology-types';
import { Layout } from './layout';
import { ILayoutFactory } from './layout-creator-usecase';
import { CytoscapeLayoutAlgorithms, LayoutAlgorithm } from './types';
import { LayoutAlgorithm } from './types';
export type CytoscapeProvider = 'Cytoscape';
export type CytoscapeLayoutAlgorithms =
| 'Cytoscape_klay'
| 'Cytoscape_dagre'
| 'Cytoscape_elk'
| 'Cytoscape_fcose'
| 'Cytoscape_cose-bilkent'
| 'Cytoscape_cise'
| 'Cytoscape_cose'
| 'Cytoscape_grid'
| 'Cytoscape_circle'
| 'Cytoscape_concentric'
| 'Cytoscape_breadthfirst';
export enum CytoscapeLayouts {
KLAY = 'Cytoscape_klay',
DAGRE = 'Cytoscape_dagre',
ELK = 'Cytoscape_elk',
FCOSE = 'Cytoscape_fcose',
COSE_BILKENT = 'Cytoscape_cose-bilkent',
CISE = 'Cytoscape_cise',
GRID = 'Cytoscape_grid',
COSE = 'Cytoscape_cose',
CIRCLE = 'Cytoscape_circle',
CONCENTRIC = 'Cytoscape_concentric',
BREATHFIRST = 'Cytoscape_breadthfirst',
}
type CytoNode = {
data: {
id: string;
......@@ -20,8 +47,19 @@ type CytoNode = {
label?: string;
count?: number;
color?: string;
sbgnbbox?: {
x: number;
y: number;
w: number;
h: number;
};
};
};
const DEFAULTWIDTH = 120;
const DEFAULTHEIGHT = 60;
/**
* This is the Cytoscape Factory
*/
......@@ -116,6 +154,13 @@ export abstract class CytoscapeLayout extends Layout<CytoscapeProvider> {
label: 'start',
count: 50,
color: 'green',
sbgnbbox: {
x: 0,
y: 0,
w: DEFAULTWIDTH,
h: DEFAULTHEIGHT,
},
},
});
});
......@@ -167,7 +212,7 @@ export abstract class CytoscapeLayout extends Layout<CytoscapeProvider> {
this.cytoscapeInstance = cytoscape({
elements: cytonodes,
headless: true,
styleEnabled: false,
styleEnabled: true,
});
return this.cytoscapeInstance;
......@@ -284,23 +329,49 @@ class CytoscapeElk extends CytoscapeLayout {
const cy = this.cytoscapeInstance;
if (!cy) return;
// options here https://github.com/cytoscape/cytoscape.js-elk
cy.style().fromJson([
{
selector: 'node',
style: {
shape: 'rectangle',
width: function (n: any) {
return n['_private'].data.sbgnbbox?.w || DEFAULTWIDTH;
},
height: function (n: any) {
return n['_private'].data.sbgnbbox?.h || DEFAULTHEIGHT;
},
},
},
{
selector: 'edge',
style: {
opacity: 0.5,
},
},
]);
// options here https://github.com/cytoscape/cytoscape.js-elk
const layout = cy.layout({
...this.defaultLayoutSettings,
boundingBox: boundingBox,
// boundingBox: boundingBox,
name: 'elk',
fit: true,
ranker: 'longest-path',
animate: false,
padding: 30,
// nodeDimensionsIncludeLabels: true,
// fit: true,
// ranker: 'longest-path',
// animate: false,
// padding: 30,
elk: {
zoomToFit: true,
algorithm: 'layered',
separateConnectedComponents: false,
// zoomToFit: true,
algorithm: 'box',
// separateConnectedComponents: false,
// direction: 'DOWN',
},
} as any);
layout.run();
const layouts = layout.run();
console.log('layouts', layouts);
this.updateNodePositions();
}
......@@ -332,7 +403,7 @@ class CytoscapeDagre extends CytoscapeLayout {
name: 'dagre',
// acyclicer: 'greedy',
ranker: 'longest-path',
spacingFactor: 0.7,
spacingFactor: 0.95,
} as any);
layout.run();
......
......@@ -6,10 +6,25 @@ import noverlap from 'graphology-layout-noverlap';
import { Attributes } from 'graphology-types';
import { Layout } from './layout';
import { ILayoutFactory } from './layout-creator-usecase';
import { GraphologyLayoutAlgorithms, LayoutAlgorithm } from './types';
import { LayoutAlgorithm } from './types';
export type GraphologyProvider = 'Graphology';
export type GraphologyLayoutAlgorithms =
| `Graphology_circular`
| `Graphology_random`
| `Graphology_noverlap`
| `Graphology_forceAtlas2`
| `Graphology_forceAtlas2_webworker`;
export enum GraphologyLayouts {
RANDOM = 'Graphology_random',
CIRCULAR = 'Graphology_circular',
NOVERLAP = 'Graphology_noverlap',
FORCEATLAS2 = 'Graphology_forceAtlas2',
FORCEATLAS2WEBWORKER = 'Graphology_forceAtlas2_webworker',
}
/**
* This is the Graphology Constructor for the main layouts available at
* https://graphology.github.io/
......@@ -73,7 +88,7 @@ export class GraphologyCircular extends GraphologyLayout {
super.layout(graph, boundingBox);
// To directly assign the positions to the nodes:
circular.assign(graph, {
scale: graph.order * 2,
scale: (graph.order * graph.order) / 10,
...this.defaultLayoutSettings,
});
}
......@@ -96,7 +111,7 @@ export class GraphologyRandom extends GraphologyLayout {
// To directly assign the positions to the nodes:
random.assign(graph, {
scale: graph.order * 1.5,
scale: (graph.order * graph.order) / 10,
...this.defaultLayoutSettings,
center: 0,
});
......
import { CytoscapeFactory } from './cytoscape-layouts';
import { GraphologyFactory } from './graphology-layouts';
import { AllLayoutAlgorithms, AlgorithmToLayoutProvider, GraphologyLayoutAlgorithms, CytoscapeLayoutAlgorithms } from './types';
import { CytoscapeFactory, CytoscapeLayoutAlgorithms } from './cytoscape-layouts';
import { GraphologyFactory, GraphologyLayoutAlgorithms } from './graphology-layouts';
import { ListLayoutAlgorithms, ListLayoutFactory } from './list-layouts';
import { AlgorithmToLayoutProvider, AllLayoutAlgorithms } from './types';
export interface ILayoutFactory<Algorithm extends AllLayoutAlgorithms> {
createLayout: (Algorithm: Algorithm) => AlgorithmToLayoutProvider<Algorithm> | null;
......@@ -12,16 +13,19 @@ export interface ILayoutFactory<Algorithm extends AllLayoutAlgorithms> {
export class LayoutFactory implements ILayoutFactory<AllLayoutAlgorithms> {
private graphologyFactory = new GraphologyFactory();
private cytoscapeFactory = new CytoscapeFactory();
private listlayoutFactory = new ListLayoutFactory();
private isSpecificAlgorithm<Algorithm extends AllLayoutAlgorithms>(
LayoutAlgorithm: AllLayoutAlgorithms,
startsWith: string
startsWith: string,
): LayoutAlgorithm is Algorithm {
return LayoutAlgorithm.startsWith(startsWith);
}
// todo make this static
createLayout<Algorithm extends AllLayoutAlgorithms>(layoutAlgorithm: Algorithm): AlgorithmToLayoutProvider<Algorithm> {
createLayout<Algorithm extends AllLayoutAlgorithms = AllLayoutAlgorithms>(
layoutAlgorithm: Algorithm,
): AlgorithmToLayoutProvider<Algorithm> {
if (this.isSpecificAlgorithm<GraphologyLayoutAlgorithms>(layoutAlgorithm, 'Graphology')) {
return this.graphologyFactory.createLayout(layoutAlgorithm) as AlgorithmToLayoutProvider<Algorithm>;
}
......@@ -30,6 +34,10 @@ export class LayoutFactory implements ILayoutFactory<AllLayoutAlgorithms> {
return this.cytoscapeFactory.createLayout(layoutAlgorithm) as AlgorithmToLayoutProvider<Algorithm>;
}
if (this.isSpecificAlgorithm<ListLayoutAlgorithms>(layoutAlgorithm, 'List')) {
return this.listlayoutFactory.createLayout(layoutAlgorithm) as AlgorithmToLayoutProvider<Algorithm>;
}
throw Error('Invalid layout algorithm ' + layoutAlgorithm);
}
}
......@@ -23,6 +23,10 @@ export abstract class Layout<provider extends Providers> {
if (boundingBox !== undefined) {
this.boundingBox = boundingBox;
if (this.verbose) {
console.log(`Setting bounding box to ${JSON.stringify(this.boundingBox)}`);
}
}
if (this.verbose) {
......
import Graph from 'graphology';
import { Attributes } from 'graphology-types';
import { Layout } from './layout';
import { ILayoutFactory } from './layout-creator-usecase';
import { LayoutAlgorithm } from './types';
export type ListLayoutProvider = 'ListLayout';
export type ListLayoutAlgorithms = 'ListLayout_intersected' | 'ListLayout_nodesfirst' | 'ListLayout_edgesfirst';
export enum ListLayouts {
LISTINTERSECTED = 'ListLayout_intersected',
LISTNODEFIRST = 'ListLayout_nodesfirst',
LISTEDGEFIRST = 'ListLayout_edgesfirst',
}
export class ListLayoutFactory implements ILayoutFactory<ListLayoutAlgorithms> {
createLayout(layoutAlgorithm: ListLayoutAlgorithms): ListLayout | null {
switch (layoutAlgorithm) {
case 'ListLayout_intersected':
return new ListIntersectedLayout();
case 'ListLayout_nodesfirst':
return new ListNodesFirstLayout();
case 'ListLayout_edgesfirst':
return new ListEdgesFirstLayout();
default:
return null;
}
}
}
const Y_OFFSET = 50;
const X_RELATION_OFFSET = 50;
export abstract class ListLayout extends Layout<ListLayoutProvider> {
protected defaultLayoutSettings = {
dimensions: ['x', 'y'],
center: 0.5,
};
constructor(public override algorithm: LayoutAlgorithm<ListLayoutProvider>) {
super('ListLayout', algorithm);
}
/**
* Retrieves the position of a node in the graph layout.
* @param nodeId - The ID of the node.
* @returns The position of the node as an object with `x` and `y` coordinates.
* @throws Error if the node is not found in the current graph.
*/
public getNodePosition(nodeId: string) {
if (this.graph === null) {
throw new Error('The graph is not set.');
}
// console.log('Getting position for node:', nodeId, this.graph.getNodeAttributes(nodeId));
return this.graph.getNodeAttributes(nodeId);
}
}
/**
* This is a ConcreteProduct
*/
export class ListNodesFirstLayout extends ListLayout {
constructor() {
super('ListLayout_nodesfirst');
}
public override async layout(
graph: Graph<Attributes, Attributes, Attributes>,
boundingBox?: { x1: number; x2: number; y1: number; y2: number },
): Promise<void> {
super.layout(graph, boundingBox);
if (this.verbose) {
console.log('ListLayout_nodesfirst layouting now', boundingBox);
}
if (boundingBox === undefined) {
boundingBox = { x1: 0, x2: 1000, y1: 0, y2: 1000 };
}
const relationNodes = graph.nodes().filter((node) => node.startsWith('Relation'));
const entityNodes = graph.nodes().filter((node) => !relationNodes.includes(node));
let y = 0;
entityNodes.map((node, index) => {
y = index * Y_OFFSET;
graph.updateNodeAttribute(node, 'x', () => boundingBox.x1);
graph.updateNodeAttribute(node, 'y', () => y);
});
relationNodes.map((node, index) => {
const relationsY = y + Y_OFFSET + index * Y_OFFSET;
graph.updateNodeAttribute(node, 'x', () => boundingBox.x1);
graph.updateNodeAttribute(node, 'y', () => relationsY);
});
if (this.verbose) {
console.log(`ListLayout_nodesfirst layouting finished`);
}
}
}
/**
* This is a ConcreteProduct
*/
export class ListEdgesFirstLayout extends ListLayout {
constructor() {
super('ListLayout_edgesfirst');
}
public override async layout(
graph: Graph<Attributes, Attributes, Attributes>,
boundingBox?: { x1: number; x2: number; y1: number; y2: number },
): Promise<void> {
super.layout(graph, boundingBox);
if (this.verbose) {
console.log('ListLayout_edgesfirst layouting now', boundingBox);
}
if (boundingBox === undefined) {
boundingBox = { x1: 0, x2: 1000, y1: 0, y2: 1000 };
}
const relationNodes = graph.nodes().filter((node) => node.startsWith('Relation'));
const entityNodes = graph.nodes().filter((node) => !relationNodes.includes(node));
let y = 0;
relationNodes.map((node, index) => {
y = index * Y_OFFSET;
graph.updateNodeAttribute(node, 'x', () => boundingBox.x1);
graph.updateNodeAttribute(node, 'y', () => y);
});
entityNodes.map((node, index) => {
const relationsY = y + Y_OFFSET + index * Y_OFFSET;
graph.updateNodeAttribute(node, 'x', () => boundingBox.x1);
graph.updateNodeAttribute(node, 'y', () => relationsY);
});
if (this.verbose) {
console.log(`ListLayout_edgesfirst layouting finished`);
}
}
}
/**
* This is a ConcreteProduct
*/
export class ListIntersectedLayout extends ListLayout {
constructor() {
super('ListLayout_intersected');
}
public override async layout(
graph: Graph<Attributes, Attributes, Attributes>,
boundingBox?: { x1: number; x2: number; y1: number; y2: number },
): Promise<void> {
super.layout(graph, boundingBox);
if (this.verbose) {
console.log('ListLayout_intersected layouting now', boundingBox);
}
if (boundingBox === undefined) {
boundingBox = { x1: 0, x2: 1000, y1: 0, y2: 1000 };
}
const relationNodes = graph.nodes().filter((node) => node.startsWith('Relation'));
const entityNodes = graph.nodes().filter((node) => !relationNodes.includes(node));
const graphAllNodes = graph.nodes();
const intersectedList: string[] = [];
entityNodes.forEach((node) => {
intersectedList.push(node);
graph.forEachNeighbor(node, (neighbor) => {
if (graphAllNodes.includes(neighbor)) {
graphAllNodes.splice(graphAllNodes.indexOf(neighbor), 1);
intersectedList.push(neighbor);
}
});
});
let y = 0;
intersectedList.map((node, index) => {
y = index * Y_OFFSET;
graph.updateNodeAttribute(node, 'x', () => {
if (node.startsWith('Relation')) {
return boundingBox.x1 + X_RELATION_OFFSET;
} else {
return boundingBox.x1;
}
});
graph.updateNodeAttribute(node, 'y', () => y);
});
if (this.verbose) {
console.log(`ListLayout_intersected layouting finished`);
}
}
}
import { CytoscapeLayout, CytoscapeProvider } from './cytoscape-layouts';
import { GraphologyLayout, GraphologyProvider } from './graphology-layouts';
import { CytoscapeLayout, CytoscapeLayoutAlgorithms, CytoscapeLayouts, CytoscapeProvider } from './cytoscape-layouts';
import { GraphologyLayout, GraphologyLayoutAlgorithms, GraphologyLayouts, GraphologyProvider } from './graphology-layouts';
import { ListLayout, ListLayoutAlgorithms, ListLayoutProvider, ListLayouts } from './list-layouts';
export type GraphologyLayoutAlgorithms =
| `Graphology_circular`
| `Graphology_random`
| `Graphology_noverlap`
| `Graphology_forceAtlas2`
| `Graphology_forceAtlas2_webworker`;
export type AllLayoutAlgorithms = GraphologyLayoutAlgorithms | CytoscapeLayoutAlgorithms | ListLayoutAlgorithms;
export type CytoscapeLayoutAlgorithms =
| 'Cytoscape_klay'
| 'Cytoscape_dagre'
| 'Cytoscape_elk'
| 'Cytoscape_fcose'
| 'Cytoscape_cose-bilkent'
| 'Cytoscape_cise'
| 'Cytoscape_cose'
| 'Cytoscape_grid'
| 'Cytoscape_circle'
| 'Cytoscape_concentric'
| 'Cytoscape_breadthfirst';
export type AllLayoutAlgorithms = GraphologyLayoutAlgorithms | CytoscapeLayoutAlgorithms;
export type Providers = GraphologyProvider | CytoscapeProvider;
export type Providers = GraphologyProvider | CytoscapeProvider | ListLayoutProvider;
export type LayoutAlgorithm<Provider extends Providers> = `${Provider}_${string}`;
export type AlgorithmToLayoutProvider<Algorithm extends AllLayoutAlgorithms> = Algorithm extends GraphologyLayoutAlgorithms
? GraphologyLayout
: Algorithm extends CytoscapeLayoutAlgorithms
? CytoscapeLayout
: CytoscapeLayout | GraphologyLayout;
export type AlgorithmToLayoutProvider<Algorithm extends AllLayoutAlgorithms = AllLayoutAlgorithms> =
Algorithm extends GraphologyLayoutAlgorithms
? GraphologyLayout
: Algorithm extends CytoscapeLayoutAlgorithms
? CytoscapeLayout
: ListLayout;
export const Layouts = {
...GraphologyLayouts,
...CytoscapeLayouts,
...ListLayouts,
} as const;
export enum Layouts {
KLAY = 'Cytoscape_klay',
DAGRE = 'Cytoscape_dagre',
ELK = 'Cytoscape_elk',
FCOSE = 'Cytoscape_fcose',
COSE_BILKENT = 'Cytoscape_cose-bilkent',
CISE = 'Cytoscape_cise',
GRID = 'Cytoscape_grid',
COSE = 'Cytoscape_cose',
CIRCLE = 'Cytoscape_circle',
CONCENTRIC = 'Cytoscape_concentric',
BREATHFIRST = 'Cytoscape_breadthfirst',
RANDOM = 'Graphology_random',
CIRCULAR = 'Graphology_circular',
NOVERLAP = 'Graphology_noverlap',
FORCEATLAS2 = 'Graphology_forceAtlas2',
FORCEATLAS2WEBWORKER = 'Graphology_forceAtlas2_webworker',
}
export type LayoutTypes = (typeof Layouts)[keyof typeof Layouts];
......@@ -18,7 +18,7 @@ export function InspectorPanel(props: { children?: React.ReactNode }) {
const { activeVisualizationIndex } = useVisualization();
const inspector = useMemo(() => {
//if (selection) return <SelectionConfig />;
if (selection) return <SelectionConfig />;
// if (!focus) return <ConnectionInspector />;
// if (activeVisualizationIndex !== -1) return <ConnectionInspector />;
return <VisualizationSettings />;
......