Compare commits
2 Commits
df03012c8b
...
528fc97c7f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
528fc97c7f | ||
|
|
21a459256a |
@@ -36,6 +36,7 @@ import GlossPanel from "./GlossPanel.vue"
|
|||||||
import LoopOverlay from "./LoopOverlay.vue"
|
import LoopOverlay from "./LoopOverlay.vue"
|
||||||
import Palette from "./Palette.vue"
|
import Palette from "./Palette.vue"
|
||||||
import InfoLinkEdge from "./edges/InfoLinkEdge.vue"
|
import InfoLinkEdge from "./edges/InfoLinkEdge.vue"
|
||||||
|
import PipeEdge from "./edges/PipeEdge.vue"
|
||||||
import CloudNode from "./nodes/CloudNode.vue"
|
import CloudNode from "./nodes/CloudNode.vue"
|
||||||
import ConverterNode from "./nodes/ConverterNode.vue"
|
import ConverterNode from "./nodes/ConverterNode.vue"
|
||||||
import FlowNode from "./nodes/FlowNode.vue"
|
import FlowNode from "./nodes/FlowNode.vue"
|
||||||
@@ -405,6 +406,9 @@ onBeforeUnmount(() => {
|
|||||||
<CloudNode v-bind="nodeProps" />
|
<CloudNode v-bind="nodeProps" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #edge-pipe="edgeProps">
|
||||||
|
<PipeEdge v-bind="edgeProps" />
|
||||||
|
</template>
|
||||||
<template #edge-info="edgeProps">
|
<template #edge-info="edgeProps">
|
||||||
<InfoLinkEdge v-bind="edgeProps" />
|
<InfoLinkEdge v-bind="edgeProps" />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
35
src/components/edges/PipeEdge.vue
Normal file
35
src/components/edges/PipeEdge.vue
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* Flow pipe edge (C3) — the source→valve and valve→target segments of a Flow.
|
||||||
|
*
|
||||||
|
* The pipe leaves the source on the right and docks square into the target's
|
||||||
|
* left edge (horizontal tangents both ends), so the arrowhead always enters the
|
||||||
|
* stock straight-on. Vue Flow's default bezier does the same — but it sizes the
|
||||||
|
* horizontal "flatten" arm from the *horizontal* gap alone, so when the valve
|
||||||
|
* sits nearly above the stock that arm collapses to ~zero: the curve stays steep
|
||||||
|
* and snaps flat only in its final pixels, leaving the arrowhead on a stub of
|
||||||
|
* horizontal line while the visible stroke arrives steep — the head detaches.
|
||||||
|
*
|
||||||
|
* Sizing the arm from the vertical drop too keeps a real horizontal run before
|
||||||
|
* the stock at any approach angle, so the arrowhead sits on the line and still
|
||||||
|
* docks square. BaseEdge draws the stroke (same .vue-flow__edge-path class, so
|
||||||
|
* theme stroke/width are unchanged) and carries the markerEnd through.
|
||||||
|
*/
|
||||||
|
import { BaseEdge, type EdgeProps } from "@vue-flow/core"
|
||||||
|
import { computed } from "vue"
|
||||||
|
import type { EdgeData } from "@/model/projection"
|
||||||
|
|
||||||
|
const props = defineProps<EdgeProps<EdgeData>>()
|
||||||
|
|
||||||
|
const path = computed(() => {
|
||||||
|
const { sourceX: sx, sourceY: sy, targetX: tx, targetY: ty } = props
|
||||||
|
const arm = Math.min(120, Math.max(30, Math.abs(tx - sx) * 0.5, Math.abs(ty - sy) * 0.45))
|
||||||
|
// Cubic with horizontal control points: out the source's right, into the
|
||||||
|
// target's left. `arm` is the run over which each end stays horizontal.
|
||||||
|
return `M ${sx},${sy} C ${sx + arm},${sy} ${tx - arm},${ty} ${tx},${ty}`
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<BaseEdge :id="props.id" :path="path" :marker-end="props.markerEnd" :style="props.style" />
|
||||||
|
</template>
|
||||||
@@ -54,6 +54,7 @@ export function projectEdges(model: Model): FlowGraphEdge[] {
|
|||||||
// Pipe in: source → valve. No arrowhead — the valve is the visual midpoint.
|
// Pipe in: source → valve. No arrowhead — the valve is the visual midpoint.
|
||||||
edges.push({
|
edges.push({
|
||||||
id: `${node.id}::in`,
|
id: `${node.id}::in`,
|
||||||
|
type: "pipe",
|
||||||
source: node.source,
|
source: node.source,
|
||||||
sourceHandle: HANDLE_OUT,
|
sourceHandle: HANDLE_OUT,
|
||||||
target: node.id,
|
target: node.id,
|
||||||
@@ -64,13 +65,14 @@ export function projectEdges(model: Model): FlowGraphEdge[] {
|
|||||||
// Pipe out: valve → target. Arrowhead carries the flow direction.
|
// Pipe out: valve → target. Arrowhead carries the flow direction.
|
||||||
edges.push({
|
edges.push({
|
||||||
id: `${node.id}::out`,
|
id: `${node.id}::out`,
|
||||||
|
type: "pipe",
|
||||||
source: node.id,
|
source: node.id,
|
||||||
sourceHandle: HANDLE_OUT,
|
sourceHandle: HANDLE_OUT,
|
||||||
target: node.target,
|
target: node.target,
|
||||||
targetHandle: HANDLE_IN,
|
targetHandle: HANDLE_IN,
|
||||||
data: { kind: "pipe" },
|
data: { kind: "pipe" },
|
||||||
style: { strokeWidth: "2.5px" },
|
style: { strokeWidth: "2.5px" },
|
||||||
markerEnd: "arrowclosed",
|
markerEnd: "arrow",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +88,7 @@ export function projectEdges(model: Model): FlowGraphEdge[] {
|
|||||||
targetHandle: HANDLE_IN,
|
targetHandle: HANDLE_IN,
|
||||||
data: { kind: "info", polarity: link.polarity },
|
data: { kind: "info", polarity: link.polarity },
|
||||||
style: { strokeDasharray: "5 4" },
|
style: { strokeDasharray: "5 4" },
|
||||||
markerEnd: "arrowclosed",
|
markerEnd: "arrow",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,25 +102,20 @@ function savings(): Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Coffee cooling — the simplest Balancing loop, plus a Converter. Heat leaves the
|
* Coffee cooling — the simplest Balancing loop, plus a Converter. The cup cools
|
||||||
* cup faster the hotter it is (Coffee → [+] → heat loss) but slower the warmer
|
* faster the hotter it is (Coffee → [+] → cooling) but slower the warmer the room
|
||||||
* the room (room temperature → [−] → heat loss). The loop Coffee → heat loss →
|
* (room temperature → [−] → cooling). The loop Coffee → cooling → (outflow) →
|
||||||
* (outflow) → Coffee has one `−` → Balancing: it settles toward room temperature.
|
* Coffee has one `−` → Balancing: it settles toward room temperature.
|
||||||
*/
|
*/
|
||||||
function coffee(): Model {
|
function coffee(): Model {
|
||||||
const coffee = makeStock({ x: -200, y: 0 }, "Coffee")
|
const coffee = makeStock({ x: -200, y: 0 }, "Coffee")
|
||||||
const sink = makeCloud({ x: 200, y: 0 })
|
const sink = makeCloud({ x: 200, y: 0 })
|
||||||
const heatLoss = makeFlow(
|
const cooling = makeFlow(midpoint(coffee.position, sink.position), "cooling", coffee.id, sink.id)
|
||||||
midpoint(coffee.position, sink.position),
|
|
||||||
"heat loss",
|
|
||||||
coffee.id,
|
|
||||||
sink.id,
|
|
||||||
)
|
|
||||||
const room = makeConverter({ x: 0, y: -160 }, "room temperature")
|
const room = makeConverter({ x: 0, y: -160 }, "room temperature")
|
||||||
return model(
|
return model(
|
||||||
"Coffee cooling",
|
"Coffee cooling",
|
||||||
[coffee, sink, heatLoss, room],
|
[coffee, sink, cooling, room],
|
||||||
[link(coffee, heatLoss, "+"), link(room, heatLoss, "-")],
|
[link(coffee, cooling, "+"), link(room, cooling, "-")],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,10 +161,10 @@ function limitsToGrowth(): Model {
|
|||||||
const source = makeCloud({ x: -280, y: 0 })
|
const source = makeCloud({ x: -280, y: 0 })
|
||||||
const yeast = makeStock({ x: 40, y: 0 }, "Yeast")
|
const yeast = makeStock({ x: 40, y: 0 }, "Yeast")
|
||||||
const growth = makeFlow(midpoint(source.position, yeast.position), "growth", source.id, yeast.id)
|
const growth = makeFlow(midpoint(source.position, yeast.position), "growth", source.id, yeast.id)
|
||||||
// crowding rides above the pipe; carrying capacity sits to its right so the
|
// crowding rides above the pipe; carrying capacity stacks above the Source on the
|
||||||
// `capacity → crowding` link is a short horizontal hop along the top.
|
// left, so the `capacity → crowding` link is a clean horizontal hop along the top.
|
||||||
|
const capacity = makeConverter({ x: -280, y: -160 }, "carrying capacity")
|
||||||
const crowding = makeConverter({ x: -40, y: -160 }, "crowding")
|
const crowding = makeConverter({ x: -40, y: -160 }, "crowding")
|
||||||
const capacity = makeConverter({ x: 160, y: -160 }, "carrying capacity")
|
|
||||||
return model(
|
return model(
|
||||||
"Limits to growth",
|
"Limits to growth",
|
||||||
[source, yeast, growth, crowding, capacity],
|
[source, yeast, growth, crowding, capacity],
|
||||||
|
|||||||
Reference in New Issue
Block a user