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 Palette from "./Palette.vue"
|
||||
import InfoLinkEdge from "./edges/InfoLinkEdge.vue"
|
||||
import PipeEdge from "./edges/PipeEdge.vue"
|
||||
import CloudNode from "./nodes/CloudNode.vue"
|
||||
import ConverterNode from "./nodes/ConverterNode.vue"
|
||||
import FlowNode from "./nodes/FlowNode.vue"
|
||||
@@ -405,6 +406,9 @@ onBeforeUnmount(() => {
|
||||
<CloudNode v-bind="nodeProps" />
|
||||
</template>
|
||||
|
||||
<template #edge-pipe="edgeProps">
|
||||
<PipeEdge v-bind="edgeProps" />
|
||||
</template>
|
||||
<template #edge-info="edgeProps">
|
||||
<InfoLinkEdge v-bind="edgeProps" />
|
||||
</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.
|
||||
edges.push({
|
||||
id: `${node.id}::in`,
|
||||
type: "pipe",
|
||||
source: node.source,
|
||||
sourceHandle: HANDLE_OUT,
|
||||
target: node.id,
|
||||
@@ -64,13 +65,14 @@ export function projectEdges(model: Model): FlowGraphEdge[] {
|
||||
// Pipe out: valve → target. Arrowhead carries the flow direction.
|
||||
edges.push({
|
||||
id: `${node.id}::out`,
|
||||
type: "pipe",
|
||||
source: node.id,
|
||||
sourceHandle: HANDLE_OUT,
|
||||
target: node.target,
|
||||
targetHandle: HANDLE_IN,
|
||||
data: { kind: "pipe" },
|
||||
style: { strokeWidth: "2.5px" },
|
||||
markerEnd: "arrowclosed",
|
||||
markerEnd: "arrow",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -86,7 +88,7 @@ export function projectEdges(model: Model): FlowGraphEdge[] {
|
||||
targetHandle: HANDLE_IN,
|
||||
data: { kind: "info", polarity: link.polarity },
|
||||
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
|
||||
* cup faster the hotter it is (Coffee → [+] → heat loss) but slower the warmer
|
||||
* the room (room temperature → [−] → heat loss). The loop Coffee → heat loss →
|
||||
* (outflow) → Coffee has one `−` → Balancing: it settles toward room temperature.
|
||||
* Coffee cooling — the simplest Balancing loop, plus a Converter. The cup cools
|
||||
* faster the hotter it is (Coffee → [+] → cooling) but slower the warmer the room
|
||||
* (room temperature → [−] → cooling). The loop Coffee → cooling → (outflow) →
|
||||
* Coffee has one `−` → Balancing: it settles toward room temperature.
|
||||
*/
|
||||
function coffee(): Model {
|
||||
const coffee = makeStock({ x: -200, y: 0 }, "Coffee")
|
||||
const sink = makeCloud({ x: 200, y: 0 })
|
||||
const heatLoss = makeFlow(
|
||||
midpoint(coffee.position, sink.position),
|
||||
"heat loss",
|
||||
coffee.id,
|
||||
sink.id,
|
||||
)
|
||||
const cooling = makeFlow(midpoint(coffee.position, sink.position), "cooling", coffee.id, sink.id)
|
||||
const room = makeConverter({ x: 0, y: -160 }, "room temperature")
|
||||
return model(
|
||||
"Coffee cooling",
|
||||
[coffee, sink, heatLoss, room],
|
||||
[link(coffee, heatLoss, "+"), link(room, heatLoss, "-")],
|
||||
[coffee, sink, cooling, room],
|
||||
[link(coffee, cooling, "+"), link(room, cooling, "-")],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -166,10 +161,10 @@ function limitsToGrowth(): Model {
|
||||
const source = makeCloud({ x: -280, y: 0 })
|
||||
const yeast = makeStock({ x: 40, y: 0 }, "Yeast")
|
||||
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
|
||||
// `capacity → crowding` link is a short horizontal hop along the top.
|
||||
// crowding rides above the pipe; carrying capacity stacks above the Source on the
|
||||
// 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 capacity = makeConverter({ x: 160, y: -160 }, "carrying capacity")
|
||||
return model(
|
||||
"Limits to growth",
|
||||
[source, yeast, growth, crowding, capacity],
|
||||
|
||||
Reference in New Issue
Block a user