Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion demo/Demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
DepthFirstIteratorPage,
EquivalentNodesPage,
IndexPage,
LeidenPage,
PlanarizationPage,
TopologicalIteratorPage,
} from '@demo/pages';
Expand All @@ -21,7 +22,11 @@ export const Demo = () => {
<Route path="/iterator" element={<TopologicalIteratorPage />} />
<Route path="/iterator/bfs" element={<BreadthFirstIteratorPage />} />
<Route path="/iterator/dfs" element={<DepthFirstIteratorPage />} />
<Route path="/nodes/equivalent" element={<EquivalentNodesPage />} />
<Route
path="/groupings/equivalent"
element={<EquivalentNodesPage />}
/>
<Route path="/groupings/leiden" element={<LeidenPage />} />
<Route path="/layout/planarization" element={<PlanarizationPage />} />
</Routes>
</Router>
Expand Down
11 changes: 7 additions & 4 deletions demo/src/contexts/DirectedAcyclicGraph/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,35 @@ import React, {
useEffect,
} from 'react';

import { NodeWithData, useDAG } from '@demo/hooks';
import { Identified, useDAG } from '@demo/hooks';

import * as T from './types';

export const DirectedAcyclicGraphContext = createContext<
ReturnType<typeof useDAG<T.NodeData>>
ReturnType<typeof useDAG<T.NodeData, T.EdgeData>>
>([
new DirectedAcyclicGraph<NodeWithData<T.NodeData>>(),
new DirectedAcyclicGraph<Identified<T.NodeData>, Identified<T.EdgeData>>(),
{
has: () => false,
node: () => undefined,
edge: () => undefined,
data: () => undefined,
clear: () => {},
add: () => '',
delete: () => {},
replace: () => {},
connect: () => {},
disconnect: () => {},
replaceEdge: () => {},
batch: () => {},
},
]);

export const DirectedAcyclicGraphProvider = ({
children,
}: PropsWithChildren) => {
const [instance, actions] = useDAG<T.NodeData>();
const [instance, actions] = useDAG<T.NodeData, T.EdgeData>();

useEffect(() => {
if (instance.size.nodes === 0) {
actions.add({ position: { x: 0, y: 0 }, data: {} });
Expand Down
16 changes: 13 additions & 3 deletions demo/src/contexts/DirectedAcyclicGraph/types.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {
Node as ReactFlowNode,
NodeProps as ReactFlowNodeProps,
Edge as ReactFlowEdge,
EdgeProps as ReactFlowEdgeProps,
} from '@xyflow/react';

type Data = {
type Node = {
/** General */
loading?: boolean;
active?: boolean;
Expand All @@ -17,6 +19,14 @@ type Data = {
equivalenceClassNumber?: number;
};

export type NodeData = Omit<ReactFlowNode<Data>, 'id'>;
export type NodeData = Omit<ReactFlowNode<Node>, 'id'>;

export type NodeProps = ReactFlowNodeProps<ReactFlowNode<Data>>;
export type NodeProps = ReactFlowNodeProps<ReactFlowNode<Node>>;

type Edge = {
weight?: number;
};

export type EdgeData = Omit<ReactFlowEdge<Edge>, 'id'>;

export type EdgeProps = ReactFlowEdgeProps<ReactFlowEdge<Edge>>;
27 changes: 14 additions & 13 deletions demo/src/contexts/DirectedAcyclicGraph/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { ReadonlyDirectedAcyclicGraph } from '@self/dag';
import { DirectedAcyclicGraphActions, NodeWithData } from '@demo/hooks';
import { DirectedAcyclicGraphActions, Identified } from '@demo/hooks';

import * as T from './types';

type Instance = ReadonlyDirectedAcyclicGraph<
Identified<T.NodeData>,
Identified<T.EdgeData>
>;

export function getNode(
instance: ReadonlyDirectedAcyclicGraph<NodeWithData<T.NodeData>>,
predicate: (node: NodeWithData<T.NodeData>) => boolean,
instance: Instance,
predicate: (node: Identified<T.NodeData>) => boolean,
) {
for (const node of instance.nodes) {
if (predicate(node)) return node;
Expand All @@ -14,8 +19,8 @@ export function getNode(
}

export function getNodes(
instance: ReadonlyDirectedAcyclicGraph<NodeWithData<T.NodeData>>,
predicate: (node: NodeWithData<T.NodeData>) => boolean,
instance: Instance,
predicate: (node: Identified<T.NodeData>) => boolean,
) {
const nodes = [];
for (const node of instance.nodes) {
Expand All @@ -24,22 +29,18 @@ export function getNodes(
return nodes;
}

export function getRootNode(
instance: ReadonlyDirectedAcyclicGraph<NodeWithData<T.NodeData>>,
) {
export function getRootNode(instance: Instance) {
return getNode(instance, (node) => node.data.data.root === true);
}

export function getIgnored(
instance: ReadonlyDirectedAcyclicGraph<NodeWithData<T.NodeData>>,
) {
export function getIgnored(instance: Instance) {
return getNodes(instance, (node) => node.data.data.ignored === true);
}

export function groupNodes(
nodes: Iterable<string>,
group: string,
dag: DirectedAcyclicGraphActions<T.NodeData>,
dag: DirectedAcyclicGraphActions<T.NodeData, T.EdgeData>,
) {
dag.batch(() => {
for (const node of nodes) {
Expand All @@ -53,7 +54,7 @@ export function groupNodes(

export function ungroupNodes(
nodes: Iterable<string>,
dag: DirectedAcyclicGraphActions<T.NodeData>,
dag: DirectedAcyclicGraphActions<T.NodeData, T.EdgeData>,
) {
dag.batch(() => {
for (const node of nodes) {
Expand Down
16 changes: 12 additions & 4 deletions demo/src/contexts/Group/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,32 @@ import React, {
useMemo,
} from 'react';

import { NodeWithData, useDAG } from '@demo/hooks';
import { Identified, useDAG } from '@demo/hooks';

import * as T from './types';

export const GroupContext = createContext<
[ReturnType<typeof useDAG<T.GroupData>>[0], T.GroupContextActions]
[
ReturnType<typeof useDAG<T.GroupData, T.GroupEdgeData>>[0],
T.GroupContextActions,
]
>([
new DirectedAcyclicGraph<NodeWithData<T.GroupData>>(),
new DirectedAcyclicGraph<
Identified<T.GroupData>,
Identified<T.GroupEdgeData>
>(),
{
has: () => false,
node: () => undefined,
edge: () => undefined,
data: () => undefined,
clear: () => {},
add: () => '',
delete: () => {},
replace: () => {},
connect: () => {},
disconnect: () => {},
replaceEdge: () => {},
addMember: () => {},
deleteMember: () => {},
setMembers: () => new Set<string>(),
Expand All @@ -33,7 +41,7 @@ export const GroupContext = createContext<
]);

export const GroupProvider = ({ children }: PropsWithChildren) => {
const [instance, actions] = useDAG<T.GroupData>();
const [instance, actions] = useDAG<T.GroupData, T.GroupEdgeData>();
useEffect(() => {
if (instance.size.nodes === 0) {
actions.add({ position: { x: 0, y: 0 }, data: {} });
Expand Down
16 changes: 12 additions & 4 deletions demo/src/contexts/Group/types.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
import {
NodeProps as ReactFlowNodeProps,
Node as ReactFlowNode,
EdgeProps as ReactFlowEdgeProps,
Edge as ReactFlowEdge,
} from '@xyflow/react';

import { DirectedAcyclicGraphActions } from '@demo/hooks';

type Data = {
type Node = {
disabled?: boolean;
members?: ReadonlySet<string>;
};

export type GroupData = Omit<ReactFlowNode<Data>, 'id'>;
export type GroupData = Omit<ReactFlowNode<Node>, 'id'>;

export type GroupProps = ReactFlowNodeProps<ReactFlowNode<Data>>;
export type GroupProps = ReactFlowNodeProps<ReactFlowNode<Node>>;

type Edge = Record<string, unknown>;

export type GroupEdgeData = Omit<ReactFlowEdge<Edge>, 'id'>;

export type GroupEdgeProps = ReactFlowEdgeProps<ReactFlowEdge<Edge>>;

export interface GroupContextActions
extends DirectedAcyclicGraphActions<GroupData> {
extends DirectedAcyclicGraphActions<GroupData, GroupEdgeData> {
addMember: (group: string, node: string) => void;
deleteMember: (group: string, node: string) => void;
setMembers: (group: string, members: ReadonlySet<string>) => Set<string>;
Expand Down
107 changes: 3 additions & 104 deletions demo/src/edges/DefaultEdge/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,128 +2,27 @@ import React from 'react';
import {
BaseEdge,
ConnectionLineComponentProps,
Edge,
EdgeLabelRenderer,
EdgeProps,
getStraightPath,
InternalNode,
useInternalNode,
useReactFlow,
XYPosition,
} from '@xyflow/react';
import cn from 'classnames';

import { nodeShapes, Shape } from '@demo/nodes';
import { getConnectionParams, getEdgeParams } from '../utils';

import S from './index.module.css';

export function getCenter(node: InternalNode) {
return {
x: node.internals.positionAbsolute.x + node.measured.width! / 2,
y: node.internals.positionAbsolute.y + node.measured.height! / 2,
};
}

export function getCircleDelta(
node: InternalNode,
vec: XYPosition,
gap: number,
) {
const size = Math.sqrt(vec.x * vec.x + vec.y * vec.y);
return {
x: ((node.measured.width! / 2 + gap) / size) * vec.x,
y: ((node.measured.height! / 2 + gap) / size) * vec.y,
};
}

export function getRectangleDelta(
node: InternalNode,
a: XYPosition,
gap: number,
) {
const treshold =
node.measured.width !== 0
? node.measured.height! / node.measured.width!
: 0;
const sin = a.x !== 0 ? Math.abs(a.y) / Math.abs(a.x) : 0;
const intersectingTopBottom = sin >= treshold;
const intersectingLeftRight = sin <= treshold;
const totalX = node.measured.width! / 2 + gap;
const totalY = node.measured.height! / 2 + gap;

const delta = {
x: intersectingLeftRight ? totalX : sin !== 0 ? totalY / sin : 0,
y: intersectingTopBottom ? totalY : totalX * sin,
};
delta.x *= a.x > 0 ? 1 : -1;
delta.y *= a.y > 0 ? 1 : -1;

return delta;
}

export function getDelta(node: InternalNode, a: XYPosition, gap: number = 8) {
if (node.type === undefined) return { x: 0, y: 0 };
switch (nodeShapes[node.type]) {
case Shape.CIRCLE:
return getCircleDelta(node, a, gap);
case Shape.RECTANGLE:
return getRectangleDelta(node, a, gap);
default:
return { x: 0, y: 0 };
}
}

export function getConnectionParams(
source: InternalNode,
targetCenter: XYPosition,
options: { gap: number } = { gap: 8 },
) {
const sourceCenter = getCenter(source);
const a = {
x: targetCenter.x - sourceCenter.x,
y: targetCenter.y - sourceCenter.y,
};

const sourceDelta = getDelta(source, a, options.gap);

return {
sourceX: sourceCenter.x + sourceDelta.x,
sourceY: sourceCenter.y + sourceDelta.y,
targetX: targetCenter.x,
targetY: targetCenter.y,
};
}

export function getEdgeParams(
source: InternalNode,
target: InternalNode,
options: { gap: number } = { gap: 8 },
) {
const sourceCenter = getCenter(source);
const targetCenter = getCenter(target);
const a = {
x: targetCenter.x - sourceCenter.x,
y: targetCenter.y - sourceCenter.y,
};

const sourceDelta = getDelta(source, a, options.gap);
const targetDelta = getDelta(target, a, options.gap);

return {
sourceX: sourceCenter.x + sourceDelta.x,
sourceY: sourceCenter.y + sourceDelta.y,
targetX: targetCenter.x - targetDelta.x,
targetY: targetCenter.y - targetDelta.y,
};
}

export const DefaultEdge = ({
id,
source,
target,
style = {},
selected,
data,
}: EdgeProps) => {
}: EdgeProps<Edge<{ active: boolean; inactive: boolean }>>) => {
const { deleteElements } = useReactFlow();
const sourceNode = useInternalNode(source);
const targetNode = useInternalNode(target);
Expand Down
25 changes: 25 additions & 0 deletions demo/src/edges/WeightedEdge/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.toolbar {
position: absolute;
pointer-events: all;
z-index: 999;
}

.edge {
stroke: gray !important;

&.selected {
stroke: black !important;
}

&.active {
stroke: blue !important;
}

&.inactive {
stroke: lightgray !important;
}
}

.weight {
width: 64px;
}
Loading
Loading