Implemented sorted graph

This commit is contained in:
Sebastian Seedorf
2022-08-15 23:24:58 +02:00
parent 183396f599
commit 9082dcdd26
15 changed files with 464 additions and 71 deletions

View File

@@ -1,15 +1,17 @@
.plane {
border: 1px solid red;
box-shadow: 0 0 0 1px red; /* Border left */
padding: 2em;
width: fit-content;
display: flex;
flex-direction: column;
gap: 5em;
gap: 10em;
position: relative;
}
.row {
display: flex;
gap: 2em;
justify-content: space-evenly;
}
.node {
@@ -18,3 +20,7 @@
border: 1px solid #DDDDDD;
background-color: #EEE;
}
.hidden {
width: 0;
}

View File

@@ -1,16 +1,10 @@
import {FC, HTMLProps, PropsWithChildren, useMemo} from "react";
import {FC, HTMLProps, PropsWithChildren, useEffect, useRef, useState} from "react";
import styles from './ProducingGraph.module.css'
import {EntityIcon} from "../../home/EntityIcon/EntityIcon";
import {sortByProperty} from "../../../src/utils";
import {EnrichedEntity, Recipe} from "../../../src/types";
interface GraphNodeBase {
inputs: string[]
outputs: string[]
name: string
}
export type GraphNode<T extends Record<string, unknown>> = GraphNodeBase & T
import {createPath, drawLine} from "../../../src/svg";
import {AdditionalNode, GraphNode, GraphNodeWithIds, isAdditionalNode} from "../../../src/graph-untangle/types";
import {graphUntangled} from "../../../src/graph-untangle";
import {Dict} from "../../../src/types";
interface Props<T extends {}> {
nodes: GraphNode<T>[]
@@ -19,53 +13,116 @@ interface Props<T extends {}> {
childType: FC<HTMLProps<HTMLDivElement> & {node: GraphNode<T>}>
}
export const ProducingGraph = <T extends Record<string, unknown>,>({nodes, inputs, outputs, childType: ChildType}: PropsWithChildren<Props<T>>) => {
const rows: GraphNode<T>[][] = useMemo(() => {
const available = new Set(inputs)
let todo = [...nodes]
const result: GraphNode<T>[][] = []
while (todo.length) {
const amount = todo.length
const thisRow: string[] = []
result.push([])
export const ProducingGraph = <T extends Dict<unknown>,>({nodes, inputs, outputs, childType: ChildType}: PropsWithChildren<Props<T>>) => {
const planeRef = useRef<HTMLDivElement>(null)
const [[rows, nodeMap], setGraph] = useState<[string[][], Dict<GraphNodeWithIds<T|AdditionalNode>>]>([[], {}])
useEffect(() => {
setGraph(graphUntangled(nodes, inputs, outputs ?? []))
setTimeout(() => setGraph(graphUntangled(nodes, inputs, outputs ?? [], 5000)), 0)
}, [inputs, nodes, outputs])
todo = todo.filter((node) => {
if (node.inputs.every(input => available.has(input))) {
result[result.length - 1].push(node)
thisRow.push(...node.outputs)
return false
}
return true
})
thisRow.map(uid => available.add(uid))
result[result.length - 1].sort(sortByProperty(val => -val.outputs.length * 1000 + -val.inputs.length))
if (amount === todo.length) {
console.warn("Loop detected! Left over:", todo)
result.pop()
break
/*const rowsWithInOut: (GraphNode<T>|AdditionalNode)[][] = useMemo(() => {
const addedInputs = new Set<string>()
const res: (GraphNode<T>|AdditionalNode)[][] = deepcopy([[], ...rows, []])
for (let i = 0; i < rows.length; i++) {
const rowOutputs = uniquify(rows[i].flatMap(row => row.outputs))
for (let rowOutput of rowOutputs) {
outputs?.includes(rowOutput) && res[i+2].push({___type: 'o', value: rowOutput})
}
const rowInputs = uniquify(rows[i].flatMap(row => row.inputs))
for (let rowInput of rowInputs) {
if (addedInputs.has(rowInput))
!res[i].some(node => isAdditionalNode(node) && node.value === rowInput) && res[i].push({___type: 'h', value: rowInput})
else if (inputs.includes(rowInput))
res[i].push({___type: 'i', value: rowInput})
addedInputs.add(rowInput)
}
}
return result
}, [inputs, nodes])
return res
}, [rows, inputs, outputs])*/
return <div className={styles.plane}>
<div className={styles.row}>
{inputs.map((input, idx) => <EntityIcon className={styles.input} key={input} value={input} />)}
</div>
{rows.map((row, colIdx) => {
return <div className={styles.row} key={colIdx}>
useEffect(() => {
if (!planeRef.current) return
const plane = planeRef.current
function createSvgElement() {
const elem = document.createElementNS("http://www.w3.org/2000/svg", "svg")
elem.id = 'arrows'
elem.setAttribute('style', 'position: absolute; inset: 0 0 0 0; pointer-events: none; z-index: -10;')
elem.setAttributeNS(null, 'stroke', 'black')
elem.setAttributeNS(null, 'fill', 'none')
plane.appendChild(elem)
return elem
}
const svg = document.getElementById('arrows') ?? createSvgElement()
const width = Math.round(plane.getBoundingClientRect().width)
const height = Math.round(plane.getBoundingClientRect().height)
svg.setAttribute('width', `${width}`)
svg.setAttribute('height', `${height}`)
svg.setAttributeNS(null, 'viewBox', `0 0 ${width} ${height}`)
svg.replaceChildren()
plane.querySelectorAll('[data-outputs]').forEach(startNode => {
if (!(startNode instanceof HTMLElement)) return
(startNode.dataset.outputs?.split(' ') ?? []).forEach(output => {
const endNode = plane.querySelector(`[data-name="${output}"]`)
if (!(endNode instanceof HTMLElement)) return
const path = document.createElementNS('http://www.w3.org/2000/svg',"path")
path.setAttributeNS(null, 'd', createPath(plane.getBoundingClientRect(), startNode.getBoundingClientRect(), endNode.getBoundingClientRect()))
svg.appendChild(path)
})
})
plane.querySelectorAll('[data-hidden][data-outputs]').forEach(elem => {
if (!(elem instanceof HTMLElement)) return
const path = document.createElementNS('http://www.w3.org/2000/svg',"path")
path.setAttributeNS(null, 'd', drawLine(plane.getBoundingClientRect(), elem.getBoundingClientRect()))
svg.appendChild(path)
})
})
return <div className={styles.plane} ref={planeRef}>
{rows.map((row, colIdx) => (
<div className={styles.row} key={colIdx} data-row={true}>
{
row.map((node) => <ChildType
className={styles.node}
key={node.name}
node={node}
/>)
row.map((uid) => {
const node = nodeMap[uid]
return (
!isAdditionalNode(node)
? <ChildType
data-name={node.___uid}
data-inputs={node.___uidInputs.join(' ')}
data-outputs={node.___uidOutputs.join(' ')}
className={styles.node}
key={node.name}
node={node}
/>
: node.___type === 'h'
? <span
className={styles.hidden}
key={node.___uid}
data-name={node.___uid}
data-inputs={node.___uidInputs.join(' ')}
data-outputs={node.___uidOutputs.join(' ')}
data-hidden={true}></span>
: node.___type === 'i'
? <span
className={styles.input}
key={node.___uid}
data-name={node.___uid}
data-outputs={node.___uidOutputs.join(' ')}
data-hidden={true}>
<EntityIcon value={node.name}/>
</span>
: <EntityIcon
className={styles.input}
key={node.___uid}
value={node.name}
data-name={node.___uid}
data-inputs={node.___uidInputs.join(' ')}
/>
);
})
}
</div>
})}
{outputs ? <div className={styles.row}>
{outputs.map((input, idx) => <EntityIcon className={styles.input} key={input} value={input} />)}
</div> : null }
))}
</div>
}