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
2 changes: 2 additions & 0 deletions demo/Demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Markers } from '@demo/edges';
import {
BreadthFirstIteratorPage,
DepthFirstIteratorPage,
EquivalentNodesPage,
IndexPage,
PlanarizationPage,
TopologicalIteratorPage,
Expand All @@ -20,6 +21,7 @@ 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="/layout/planarization" element={<PlanarizationPage />} />
</Routes>
</Router>
Expand Down
18 changes: 18 additions & 0 deletions demo/src/assets/equivalent.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 6 additions & 2 deletions demo/src/contexts/DirectedAcyclicGraph/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import {
} from '@xyflow/react';

type Data = {
root?: boolean;
ignored?: boolean;
/** General */
loading?: boolean;
active?: boolean;
inactive?: boolean;
disabled?: boolean;
group?: string;

/** Demo case specific */
root?: boolean;
ignored?: boolean;
equivalenceClassNumber?: number;
};

export type NodeData = Omit<ReactFlowNode<Data>, 'id'>;
Expand Down
5 changes: 5 additions & 0 deletions demo/src/nodes/ClassifiedNode/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.marker {
position: absolute;
width: 60%;
height: 60%;
}
42 changes: 42 additions & 0 deletions demo/src/nodes/ClassifiedNode/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import { NodeToolbar, Position, useReactFlow } from '@xyflow/react';

import { NodeProps } from '@demo/contexts';

import { BaseNode } from '../BaseNode';

import { EquivalenceClassMarker } from './marker';

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

export const ClassifiedNode = ({ id, selected, data }: NodeProps) => {
const { deleteElements } = useReactFlow();

return (
<>
<NodeToolbar isVisible={selected} position={Position.Top}>
<button
disabled={data.disabled}
onClick={() => deleteElements({ nodes: [{ id }] })}
>
×
</button>
</NodeToolbar>
<BaseNode
selected={selected}
loading={data.loading}
active={data.active}
inactive={data.inactive}
>
{data.equivalenceClassNumber !== undefined ? (
<EquivalenceClassMarker
num={data.equivalenceClassNumber}
className={S.marker}
/>
) : (
id
)}
</BaseNode>
</>
);
};
61 changes: 61 additions & 0 deletions demo/src/nodes/ClassifiedNode/marker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React, { memo } from 'react';

import * as T from './types';

/** Variant count of each feature should be unique prime number */
/** 2 */
const stroke = [0, 64];

/** 5 */
const shapes = [
<svg viewBox="0 0 800 800" xmlns="http://www.w3.org/2000/svg">
<rect x="200" y="200" width="400" height="400" />
</svg>,
<svg viewBox="0 0 800 800" xmlns="http://www.w3.org/2000/svg">
<circle cx="400" cy="400" r="200" />
</svg>,
<svg viewBox="0 0 800 800" xmlns="http://www.w3.org/2000/svg">
<rect
transform="translate(0 0) rotate(45) skewX(0) skewY(0) scale(1)"
transform-origin="400 400"
x="200"
y="200"
width="400"
height="400"
/>
</svg>,
<svg viewBox="0 0 800 800" xmlns="http://www.w3.org/2000/svg">
<polygon points="120,600 400,120 680,600" />
</svg>,
<svg viewBox="0 0 800 800" xmlns="http://www.w3.org/2000/svg">
<polygon points="150,535 150,255 400,100 650,255 650,535 400,700" />
</svg>,
];

/** 7 */
const colors = [
'#ff3f3f',
'#ffcc06',
'#0a9ad7',
'#9ad70a',
'#d70a9a',
'#3a329f',
'#111111',
];

export const EquivalenceClassMarker = memo(
({ num, className }: T.EquivalenceClassMarkerProps) => {
return (
<div
style={{
fill: num % 2 === 0 ? colors[num % colors.length] : 'transparent',
strokeWidth: stroke[num % 2],
stroke: colors[num % colors.length],
}}
className={className}
>
{shapes[num % shapes.length]}
</div>
);
},
);
4 changes: 4 additions & 0 deletions demo/src/nodes/ClassifiedNode/types.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface EquivalenceClassMarkerProps {
num: number;
className?: string;
}
8 changes: 6 additions & 2 deletions demo/src/nodes/index.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
import { DeletableNode } from './DeletableNode';
import { AnyFirstIteratorNode } from './AnyFirstIteratorNode';
import { PartitionNode } from './PartitionNode';
import { ClassifiedNode } from './ClassifiedNode';

export enum Shape {
CIRCLE = 'circle',
RECTANGLE = 'rectangle',
}

export const nodeTypes = {
Deletable: DeletableNode,
AnyFirstIterator: AnyFirstIteratorNode,
Classified: ClassifiedNode,
Deletable: DeletableNode,
Partition: PartitionNode,
};

export const nodeShapes: Record<string, Shape> = {
Deletable: Shape.CIRCLE,
AnyFirstIterator: Shape.CIRCLE,
Classified: Shape.CIRCLE,
Deletable: Shape.CIRCLE,
Partition: Shape.RECTANGLE,
};

export * from './AnyFirstIteratorNode';
export * from './ClassifiedNode';
export * from './DeletableNode';
export * from './PartitionNode';
7 changes: 6 additions & 1 deletion demo/src/pages/DepthFirstIterator/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import React, {
} from 'react';

import { Card, Radio } from '@demo/components';
import { NodeData, getIgnored, getRootNode, useDAGContext } from '@demo/contexts';
import {
NodeData,
getIgnored,
getRootNode,
useDAGContext,
} from '@demo/contexts';
import { NodeWithData } from '@demo/hooks';

import S from './index.module.css';
Expand Down
111 changes: 111 additions & 0 deletions demo/src/pages/EquivalentNodes/card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import {
getEquivalentNodesByParents,
getEquivalentNodesByChildren,
getEquivalentNodes,
} from '@self/dag';
import React, { useCallback, useEffect, useState } from 'react';

import { Card, Radio } from '@demo/components';
import { NodeData, useDAGContext } from '@demo/contexts';

import * as T from './types';
import S from './index.module.css';
import { NodeWithData } from '@demo/hooks';

const availableEquivalenceBy = [
T.EquivalenceBy.PARENTS,
T.EquivalenceBy.CHILDREN,
T.EquivalenceBy.BOTH,
];

export const EquivalentNodesCard = () => {
const [instance, dag] = useDAGContext();

const [equivalenceBy, setEquivalenceBy] = useState<
T.EquivalenceBy | undefined
>();

const groupByEquivalenceClass = useCallback(
() =>
dag.batch(() => {
if (equivalenceBy == undefined) return;

let projection;
switch (equivalenceBy) {
case T.EquivalenceBy.PARENTS:
projection = getEquivalentNodesByParents(
instance,
new Set(instance.nodes),
);
break;
case T.EquivalenceBy.CHILDREN:
projection = getEquivalentNodesByChildren(
instance,
new Set(instance.nodes),
);
break;
case T.EquivalenceBy.BOTH:
projection = getEquivalentNodes(instance, new Set(instance.nodes));
break;
}

let classNumber = 0;
const classified = new Set<Set<NodeWithData<NodeData>>>();
for (const [, cls] of projection.entries()) {
if (classified.has(cls)) continue;

for (const node of cls) {
dag.replace(node.id, {
...node.data,
data: { ...node.data, equivalenceClassNumber: classNumber },
});
}
classified.add(cls);
++classNumber;
}
}),
[instance, equivalenceBy],
);

useEffect(
() => () =>
[...instance.nodes].forEach((node) =>
dag.replace(node.id, (data) => ({
...data,
data: { ...data.data, equivalenceClassNumber: undefined },
})),
),
[],
);

return (
<Card>
<Card.Title>Equivalent Nodes</Card.Title>
<Card.DemoSection
status={'Group by equivalence class'}
disabled={equivalenceBy === undefined}
onClick={groupByEquivalenceClass}
>
<Card.Params>
<Card.Param required done={equivalenceBy !== undefined}>
Choose node equivalence type:
<div className={S.equivalenceBy}>
{availableEquivalenceBy.map((value) => (
<Radio
key={`equivalenceBy:${value}`}
name="equivalenceBy"
value={value}
defaultChecked={equivalenceBy === value}
onChange={() => setEquivalenceBy(value)}
style={{ pointerEvents: 'all' }}
>
{value}
</Radio>
))}
</div>
</Card.Param>
</Card.Params>
</Card.DemoSection>
</Card>
);
};
5 changes: 5 additions & 0 deletions demo/src/pages/EquivalentNodes/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.equivalence-by {
display: flex;
flex-flow: row wrap;
gap: 4px;
}
Loading
Loading