agentic-mermaid 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +144 -4
- package/LICENSES/Apache-2.0.txt +202 -0
- package/README.md +25 -32
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/dist/agent-core.d.ts +463 -0
- package/dist/agent-core.js +397 -0
- package/dist/agent.d.ts +73 -1868
- package/dist/agent.js +336 -37325
- package/dist/agentic-mermaid-mcp.js +7 -38399
- package/dist/am.js +33632 -40445
- package/dist/chunk-4QECACIA.js +4702 -0
- package/dist/chunk-CDPKJ2BQ.js +37 -0
- package/dist/chunk-QKDLMOJJ.js +3487 -0
- package/dist/chunk-WI35L7IR.js +615 -0
- package/dist/chunk-WN3YDOBG.js +149 -0
- package/dist/chunk-ZYMMKQ3I.js +62637 -0
- package/dist/index-D54dqSdC.d.ts +6777 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +310 -33014
- package/dist/mcp-cli-SBQSTJFC.js +17 -0
- package/docs/README.md +14 -6
- package/docs/schemas/style-spec.schema.json +1221 -30
- package/examples/styles/catalog.json +54 -0
- package/examples/styles/cloudflare-workers-inspired-prototype.mmd +9 -0
- package/examples/styles/cloudflare-workers-inspired-prototype.style.json +110 -0
- package/examples/styles/cupertino-prototype.mmd +10 -0
- package/examples/styles/cupertino-prototype.style.json +63 -0
- package/examples/styles/mid-century-report.style.json +2 -2
- package/examples/styles/star-chart-atlas.style.json +2 -1
- package/examples/styles/transit-route-map.style.json +2 -2
- package/examples/styles/vercel-inspired-prototype.mmd +6 -0
- package/examples/styles/vercel-inspired-prototype.style.json +87 -0
- package/package.json +68 -43
- package/server.json +2 -2
- package/skills/agentic-mermaid-diagram-workflow/SKILL.md +17 -36
- package/skills/agentic-mermaid-diagram-workflow/references/cli.md +4 -5
- package/skills/agentic-mermaid-diagram-workflow/references/code-mode.md +15 -31
- package/skills/agentic-mermaid-diagram-workflow/references/sequence.md +10 -14
- package/skills/agentic-mermaid-live-editor/SKILL.md +100 -206
- package/AGENT_NATIVE.md +0 -607
- package/Instructions_for_agents.md +0 -89
- package/TODO.md +0 -684
- package/assets/hero.png +0 -0
- package/dist/index-wjRjbsP-.d.ts +0 -1102
- package/docs/agent-api-cookbook.md +0 -308
- package/docs/agent-mutation-policy.md +0 -104
- package/docs/agent-workflow-examples.md +0 -46
- package/docs/api.md +0 -211
- package/docs/ascii.md +0 -103
- package/docs/assets/improvements/box-start-connector.png +0 -0
- package/docs/assets/improvements/comment-dropped-lint.png +0 -0
- package/docs/assets/improvements/er-content-loss.png +0 -0
- package/docs/assets/improvements/fan-in-trunks.png +0 -0
- package/docs/assets/improvements/gantt-family.png +0 -0
- package/docs/assets/improvements/gantt-milestone-clamp.png +0 -0
- package/docs/assets/improvements/gantt-verify-seam.png +0 -0
- package/docs/assets/improvements/subgraph-container-edges.png +0 -0
- package/docs/assets/improvements/wrapper-frontmatter.png +0 -0
- package/docs/assets/improvements/wrapper-init-directive.png +0 -0
- package/docs/assets/improvements/wrapper-leading-comment.png +0 -0
- package/docs/assets/style-cookbook/mid-century-report.png +0 -0
- package/docs/assets/style-cookbook/star-chart-atlas.png +0 -0
- package/docs/assets/style-cookbook/transit-route-map.png +0 -0
- package/docs/comparison.md +0 -94
- package/docs/config.md +0 -105
- package/docs/contributing/adding-diagram-types.md +0 -144
- package/docs/contributing/diagram-family-citizenship.matrix.json +0 -2127
- package/docs/contributing/diagram-family-citizenship.md +0 -82
- package/docs/contributing/harvesting-upstream-tests.md +0 -126
- package/docs/contributing/lessons-learned.md +0 -276
- package/docs/contributing/releasing.md +0 -82
- package/docs/contributing/visual-review-evidence.md +0 -53
- package/docs/custom-style-cookbook.md +0 -154
- package/docs/design/families/README.md +0 -26
- package/docs/design/families/architecture-beta.md +0 -48
- package/docs/design/families/flowchart-parser-conformance.md +0 -22
- package/docs/design/families/gantt-research.md +0 -137
- package/docs/design/families/gantt.md +0 -329
- package/docs/design/families/journey-agentic-cards-before.png +0 -0
- package/docs/design/families/journey-experience-curve-implementation.png +0 -0
- package/docs/design/families/journey-mermaid-classic-implementation.png +0 -0
- package/docs/design/families/journey-mermaid-classic-prototype.png +0 -0
- package/docs/design/families/journey-mermaid-classic-prototype.svg +0 -163
- package/docs/design/families/journey-mermaid-classic-prototype.svg.png +0 -0
- package/docs/design/families/journey-migration-parity.md +0 -439
- package/docs/design/families/journey-section-overlap-after.png +0 -0
- package/docs/design/families/journey-section-overlap-before.png +0 -0
- package/docs/design/families/journey-usage-research.md +0 -423
- package/docs/design/families/journey.md +0 -153
- package/docs/design/families/xychart.md +0 -85
- package/docs/design/system/README.md +0 -45
- package/docs/design/system/abstraction-audit.md +0 -224
- package/docs/design/system/abstraction-recommendations.md +0 -655
- package/docs/design/system/architecture.mmd +0 -29
- package/docs/design/system/architecture.svg +0 -137
- package/docs/design/system/layout-guarantees-and-robustness.md +0 -351
- package/docs/design/system/layout-rubric.md +0 -138
- package/docs/design/system/route-contracts.md +0 -848
- package/docs/design/system/source-preservation-ladder.md +0 -52
- package/docs/design/system/styles-rollout.md +0 -216
- package/docs/design/system/ugly-layouts.md +0 -119
- package/docs/diagram-families.md +0 -193
- package/docs/features.md +0 -147
- package/docs/fork-differences.md +0 -130
- package/docs/getting-started.md +0 -175
- package/docs/issue-derived-test-cases.md +0 -156
- package/docs/layout-characterization/README.md +0 -245
- package/docs/layout-characterization/contact-sheet-families.md +0 -498
- package/docs/layout-characterization/contact-sheet.md +0 -638
- package/docs/layout-characterization/properties.md +0 -281
- package/docs/layout-characterization/visual-quality.md +0 -160
- package/docs/layout-characterization/visual-snapshots/architecture.svg +0 -79
- package/docs/layout-characterization/visual-snapshots/class.svg +0 -60
- package/docs/layout-characterization/visual-snapshots/er.svg +0 -61
- package/docs/layout-characterization/visual-snapshots/flowchart.svg +0 -58
- package/docs/layout-characterization/visual-snapshots/gantt.svg +0 -52
- package/docs/layout-characterization/visual-snapshots/journey.svg +0 -98
- package/docs/layout-characterization/visual-snapshots/pie.svg +0 -36
- package/docs/layout-characterization/visual-snapshots/quadrant.svg +0 -47
- package/docs/layout-characterization/visual-snapshots/sequence.svg +0 -59
- package/docs/layout-characterization/visual-snapshots/state.svg +0 -52
- package/docs/layout-characterization/visual-snapshots/timeline.svg +0 -64
- package/docs/layout-characterization/visual-snapshots/xychart.svg +0 -97
- package/docs/mcp-code-mode-rationale.md +0 -101
- package/docs/mcp-http-transport.md +0 -223
- package/docs/mermaid-layout-complaints.md +0 -837
- package/docs/mutation-testing.md +0 -206
- package/docs/project/divergences.md +0 -98
- package/docs/project/dogfooding-docs-strategy.md +0 -66
- package/docs/project/hosted-mcp-cloudflare-plan.md +0 -338
- package/docs/project/lessons-learned.md +0 -1019
- package/docs/project/remove-role-styling-plan.md +0 -176
- package/docs/quality.md +0 -215
- package/docs/react.md +0 -97
- package/docs/style-authoring.md +0 -133
- package/docs/testing-strategy.md +0 -335
- package/docs/theming.md +0 -162
- package/examples/agent-improve-auth-flow.ts +0 -206
- package/examples/agent-loop.ts +0 -53
- package/examples/mcp-vs-cli-complex-diagrams.ts +0 -133
- package/llms.txt +0 -104
- package/skill-evals/README.md +0 -77
- package/skill-evals/fixtures/architecture-cache/input.mmd +0 -5
- package/skill-evals/fixtures/class-payment/input.mmd +0 -4
- package/skill-evals/fixtures/er-orders/input.mmd +0 -10
- package/skill-evals/fixtures/flowchart-cache/input.mmd +0 -3
- package/skill-evals/fixtures/gantt-release/input.mmd +0 -7
- package/skill-evals/fixtures/journey-checkout/input.mmd +0 -6
- package/skill-evals/fixtures/pie-exports/input.mmd +0 -5
- package/skill-evals/fixtures/quadrant-priorities/input.mmd +0 -11
- package/skill-evals/fixtures/sequence-retry/input.mmd +0 -8
- package/skill-evals/fixtures/state-lifecycle/input.mmd +0 -5
- package/skill-evals/fixtures/timeline-release/input.mmd +0 -6
- package/skill-evals/fixtures/xychart-growth/input.mmd +0 -5
- package/skill-evals/shared-benchmark.json +0 -510
- package/src/agent/analyze.ts +0 -157
- package/src/agent/apply.ts +0 -146
- package/src/agent/architecture-body.ts +0 -458
- package/src/agent/certificates.ts +0 -110
- package/src/agent/class-body.ts +0 -319
- package/src/agent/core.ts +0 -207
- package/src/agent/create.ts +0 -106
- package/src/agent/describe.ts +0 -368
- package/src/agent/er-body.ts +0 -224
- package/src/agent/facts.ts +0 -360
- package/src/agent/families-builtin.ts +0 -920
- package/src/agent/families.ts +0 -232
- package/src/agent/family-layouts.ts +0 -832
- package/src/agent/flowchart-body.ts +0 -372
- package/src/agent/flowchart-unsupported.ts +0 -243
- package/src/agent/font-coverage.ts +0 -288
- package/src/agent/gantt-body.ts +0 -495
- package/src/agent/index.ts +0 -10
- package/src/agent/journey-body.ts +0 -374
- package/src/agent/label-metrics.ts +0 -28
- package/src/agent/layout-to-rendered.ts +0 -63
- package/src/agent/mutate.ts +0 -97
- package/src/agent/mutation-ops.ts +0 -51
- package/src/agent/op-schema.ts +0 -443
- package/src/agent/parse.ts +0 -223
- package/src/agent/pie-body.ts +0 -270
- package/src/agent/png.ts +0 -171
- package/src/agent/quadrant-body.ts +0 -336
- package/src/agent/quality.ts +0 -363
- package/src/agent/readability-audit.ts +0 -92
- package/src/agent/rendered-route-audit.ts +0 -61
- package/src/agent/sequence-body.ts +0 -345
- package/src/agent/serialize.ts +0 -214
- package/src/agent/state-body.ts +0 -589
- package/src/agent/structural-count.ts +0 -150
- package/src/agent/timeline-body.ts +0 -280
- package/src/agent/trace-log.ts +0 -43
- package/src/agent/types.ts +0 -995
- package/src/agent/verify.ts +0 -603
- package/src/agent/xychart-body.ts +0 -472
- package/src/architecture/config.ts +0 -282
- package/src/architecture/layout.ts +0 -496
- package/src/architecture/parser.ts +0 -375
- package/src/architecture/renderer.ts +0 -611
- package/src/architecture/types.ts +0 -113
- package/src/ascii/ansi.ts +0 -423
- package/src/ascii/architecture.ts +0 -210
- package/src/ascii/canvas.ts +0 -443
- package/src/ascii/class-diagram.ts +0 -697
- package/src/ascii/converter.ts +0 -475
- package/src/ascii/draw.ts +0 -1693
- package/src/ascii/edge-bundling.ts +0 -328
- package/src/ascii/edge-routing.ts +0 -330
- package/src/ascii/er-diagram.ts +0 -439
- package/src/ascii/gantt.ts +0 -276
- package/src/ascii/grid.ts +0 -747
- package/src/ascii/index.ts +0 -196
- package/src/ascii/journey.ts +0 -169
- package/src/ascii/meta.ts +0 -244
- package/src/ascii/multiline-utils.ts +0 -80
- package/src/ascii/pathfinder.ts +0 -301
- package/src/ascii/pie.ts +0 -67
- package/src/ascii/quadrant.ts +0 -158
- package/src/ascii/reverse.ts +0 -216
- package/src/ascii/sequence.ts +0 -499
- package/src/ascii/shapes/circle.ts +0 -27
- package/src/ascii/shapes/corners.ts +0 -143
- package/src/ascii/shapes/diamond.ts +0 -27
- package/src/ascii/shapes/hexagon.ts +0 -27
- package/src/ascii/shapes/index.ts +0 -106
- package/src/ascii/shapes/rectangle.ts +0 -168
- package/src/ascii/shapes/rounded.ts +0 -27
- package/src/ascii/shapes/special.ts +0 -332
- package/src/ascii/shapes/stadium.ts +0 -110
- package/src/ascii/shapes/state.ts +0 -192
- package/src/ascii/shapes/types.ts +0 -73
- package/src/ascii/timeline.ts +0 -141
- package/src/ascii/types.ts +0 -312
- package/src/ascii/validate.ts +0 -120
- package/src/ascii/width.ts +0 -81
- package/src/ascii/wrap.ts +0 -131
- package/src/ascii/xychart.ts +0 -919
- package/src/browser.ts +0 -29
- package/src/class/layout.ts +0 -240
- package/src/class/parser.ts +0 -332
- package/src/class/renderer.ts +0 -638
- package/src/class/types.ts +0 -129
- package/src/cli/agent-instructions.ts +0 -1
- package/src/cli/am-bin.ts +0 -6
- package/src/cli/exit-codes.ts +0 -18
- package/src/cli/index.ts +0 -1132
- package/src/cli/init-agent.ts +0 -147
- package/src/cli/run-entrypoint.ts +0 -10
- package/src/color-resolver.ts +0 -152
- package/src/elk-instance.ts +0 -114
- package/src/er/layout.ts +0 -187
- package/src/er/parser.ts +0 -226
- package/src/er/renderer.ts +0 -792
- package/src/er/types.ts +0 -99
- package/src/family-rubric.ts +0 -425
- package/src/font-manifest.ts +0 -31
- package/src/gantt/layout.ts +0 -365
- package/src/gantt/parser.ts +0 -282
- package/src/gantt/pipeline.ts +0 -27
- package/src/gantt/renderer.ts +0 -414
- package/src/gantt/schedule.ts +0 -513
- package/src/gantt/types.ts +0 -255
- package/src/index.ts +0 -368
- package/src/journey/layout.ts +0 -554
- package/src/journey/parse-core.ts +0 -251
- package/src/journey/parser.ts +0 -101
- package/src/journey/renderer.ts +0 -917
- package/src/journey/types.ts +0 -144
- package/src/layout/geometry.ts +0 -182
- package/src/layout/pass.ts +0 -142
- package/src/layout/passes/index.ts +0 -3916
- package/src/layout-engine.ts +0 -1565
- package/src/layout-rubric.ts +0 -659
- package/src/mcp/artifacts.ts +0 -137
- package/src/mcp/dynamic-harness.ts +0 -31
- package/src/mcp/dynamic-modules.d.ts +0 -7
- package/src/mcp/facade.ts +0 -608
- package/src/mcp/harness-runtime.ts +0 -203
- package/src/mcp/hosted-server.ts +0 -468
- package/src/mcp/mcp-bin.ts +0 -3
- package/src/mcp/mcp-cli.ts +0 -82
- package/src/mcp/protocol.ts +0 -13
- package/src/mcp/sandbox.ts +0 -195
- package/src/mcp/sdk-decl.ts +0 -391
- package/src/mcp/server.ts +0 -362
- package/src/mcp/tool-surface.ts +0 -146
- package/src/mermaid-source.ts +0 -655
- package/src/multiline-utils.ts +0 -228
- package/src/parser.ts +0 -1031
- package/src/pie/layout.ts +0 -168
- package/src/pie/parser.ts +0 -136
- package/src/pie/renderer.ts +0 -234
- package/src/pie/types.ts +0 -82
- package/src/quadrant/layout.ts +0 -191
- package/src/quadrant/parser.ts +0 -183
- package/src/quadrant/renderer.ts +0 -322
- package/src/quadrant/types.ts +0 -113
- package/src/render-family-hooks.ts +0 -244
- package/src/renderer.ts +0 -1099
- package/src/route-contracts.ts +0 -2723
- package/src/scene/backend.ts +0 -76
- package/src/scene/fidelity.ts +0 -183
- package/src/scene/hybrid-backend.ts +0 -174
- package/src/scene/ir.ts +0 -181
- package/src/scene/marks.ts +0 -127
- package/src/scene/rough-backend.ts +0 -378
- package/src/scene/seed.ts +0 -46
- package/src/scene/style-registry.ts +0 -556
- package/src/sequence/layout.ts +0 -456
- package/src/sequence/parser.ts +0 -255
- package/src/sequence/renderer.ts +0 -604
- package/src/sequence/types.ts +0 -154
- package/src/shape-clipping.ts +0 -351
- package/src/shared/accessibility-directives.ts +0 -22
- package/src/shared/batched.ts +0 -61
- package/src/shared/color-math.ts +0 -93
- package/src/shared/svg-a11y.ts +0 -29
- package/src/shared/syntax-error.ts +0 -17
- package/src/shared/unicode-ranges.ts +0 -135
- package/src/styles.ts +0 -318
- package/src/svg-paths.ts +0 -28
- package/src/text-metrics.ts +0 -248
- package/src/theme.ts +0 -710
- package/src/timeline/layout.ts +0 -381
- package/src/timeline/parser.ts +0 -215
- package/src/timeline/renderer.ts +0 -538
- package/src/timeline/types.ts +0 -105
- package/src/types.ts +0 -462
- package/src/website-worker-modules.d.ts +0 -18
- package/src/xychart/axis-utils.ts +0 -109
- package/src/xychart/colors.ts +0 -124
- package/src/xychart/config.ts +0 -88
- package/src/xychart/layout.ts +0 -656
- package/src/xychart/parser.ts +0 -504
- package/src/xychart/renderer.ts +0 -640
- package/src/xychart/types.ts +0 -282
package/src/ascii/pathfinder.ts
DELETED
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// ASCII renderer — A* pathfinding for edge routing
|
|
3
|
-
//
|
|
4
|
-
// Ported from AlexanderGrooff/mermaid-ascii cmd/arrow.go.
|
|
5
|
-
// Uses A* search with a corner-penalizing heuristic to find clean
|
|
6
|
-
// paths between nodes on the grid. Prefers straight lines over zigzags.
|
|
7
|
-
// ============================================================================
|
|
8
|
-
|
|
9
|
-
import type { GridCoord, AsciiNode, Direction } from './types.ts'
|
|
10
|
-
import { gridKey, gridCoordEquals } from './types.ts'
|
|
11
|
-
|
|
12
|
-
// ============================================================================
|
|
13
|
-
// Priority queue (min-heap) for A* open set
|
|
14
|
-
// ============================================================================
|
|
15
|
-
|
|
16
|
-
interface PQItem {
|
|
17
|
-
coord: GridCoord
|
|
18
|
-
priority: number
|
|
19
|
-
/**
|
|
20
|
-
* Monotonic insertion sequence. Used as a deterministic FIFO tie-breaker so
|
|
21
|
-
* equal-priority nodes pop in insertion order regardless of heap topology
|
|
22
|
-
* (upstream lukilabs#113). Without this, the order two equal-priority cells
|
|
23
|
-
* are visited depends on incidental heap shape, which let sibling fan-out
|
|
24
|
-
* edges pick inconsistent corners and produce L-shaped detours.
|
|
25
|
-
*/
|
|
26
|
-
seq: number
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Simple min-heap priority queue with deterministic FIFO tie-breaking.
|
|
31
|
-
* For the grid sizes we handle (~100s of cells), this is more than fast enough.
|
|
32
|
-
*/
|
|
33
|
-
class MinHeap {
|
|
34
|
-
private items: PQItem[] = []
|
|
35
|
-
|
|
36
|
-
get length(): number {
|
|
37
|
-
return this.items.length
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
push(item: PQItem): void {
|
|
41
|
-
this.items.push(item)
|
|
42
|
-
this.bubbleUp(this.items.length - 1)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
pop(): PQItem | undefined {
|
|
46
|
-
if (this.items.length === 0) return undefined
|
|
47
|
-
const top = this.items[0]!
|
|
48
|
-
const last = this.items.pop()!
|
|
49
|
-
if (this.items.length > 0) {
|
|
50
|
-
this.items[0] = last
|
|
51
|
-
this.sinkDown(0)
|
|
52
|
-
}
|
|
53
|
-
return top
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Order two heap items: by priority, then by insertion sequence (FIFO).
|
|
58
|
-
* Returns true if `a` should sit above `b` in the min-heap.
|
|
59
|
-
*/
|
|
60
|
-
private before(a: PQItem, b: PQItem): boolean {
|
|
61
|
-
if (a.priority !== b.priority) return a.priority < b.priority
|
|
62
|
-
return a.seq < b.seq
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private bubbleUp(i: number): void {
|
|
66
|
-
while (i > 0) {
|
|
67
|
-
const parent = (i - 1) >> 1
|
|
68
|
-
if (this.before(this.items[i]!, this.items[parent]!)) {
|
|
69
|
-
;[this.items[i], this.items[parent]] = [this.items[parent]!, this.items[i]!]
|
|
70
|
-
i = parent
|
|
71
|
-
} else {
|
|
72
|
-
break
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
private sinkDown(i: number): void {
|
|
78
|
-
const n = this.items.length
|
|
79
|
-
while (true) {
|
|
80
|
-
let smallest = i
|
|
81
|
-
const left = 2 * i + 1
|
|
82
|
-
const right = 2 * i + 2
|
|
83
|
-
// Upstream lukilabs#113 used `<=` here to break ties; we encode the same
|
|
84
|
-
// determinism explicitly via the seq tie-breaker in `before`, which is
|
|
85
|
-
// unambiguous regardless of traversal order.
|
|
86
|
-
if (left < n && this.before(this.items[left]!, this.items[smallest]!)) {
|
|
87
|
-
smallest = left
|
|
88
|
-
}
|
|
89
|
-
if (right < n && this.before(this.items[right]!, this.items[smallest]!)) {
|
|
90
|
-
smallest = right
|
|
91
|
-
}
|
|
92
|
-
if (smallest !== i) {
|
|
93
|
-
;[this.items[i], this.items[smallest]] = [this.items[smallest]!, this.items[i]!]
|
|
94
|
-
i = smallest
|
|
95
|
-
} else {
|
|
96
|
-
break
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// ============================================================================
|
|
103
|
-
// A* heuristic
|
|
104
|
-
// ============================================================================
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Manhattan distance with a +1 penalty when both dx and dy are non-zero.
|
|
108
|
-
* This encourages the pathfinder to prefer straight lines and minimize corners.
|
|
109
|
-
*/
|
|
110
|
-
export function heuristic(a: GridCoord, b: GridCoord): number {
|
|
111
|
-
const absX = Math.abs(a.x - b.x)
|
|
112
|
-
const absY = Math.abs(a.y - b.y)
|
|
113
|
-
if (absX === 0 || absY === 0) {
|
|
114
|
-
return absX + absY
|
|
115
|
-
}
|
|
116
|
-
return absX + absY + 1
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// ============================================================================
|
|
120
|
-
// A* pathfinding
|
|
121
|
-
// ============================================================================
|
|
122
|
-
|
|
123
|
-
/** 4-directional movement (no diagonals in grid pathfinding). */
|
|
124
|
-
const MOVE_DIRS: GridCoord[] = [
|
|
125
|
-
{ x: 1, y: 0 },
|
|
126
|
-
{ x: -1, y: 0 },
|
|
127
|
-
{ x: 0, y: 1 },
|
|
128
|
-
{ x: 0, y: -1 },
|
|
129
|
-
]
|
|
130
|
-
|
|
131
|
-
/** Build a move-direction order that tries the edge's initial cardinal direction first. */
|
|
132
|
-
function buildMoveDirs(preferredDir?: Direction): GridCoord[] {
|
|
133
|
-
if (!preferredDir) return [...MOVE_DIRS]
|
|
134
|
-
|
|
135
|
-
// Direction values are 3x3 node attachment offsets, not signed deltas:
|
|
136
|
-
// Up={1,0}, Down={1,2}, Left={0,1}, Right={2,1}.
|
|
137
|
-
if (preferredDir.x === 2 && preferredDir.y === 1) {
|
|
138
|
-
return [{ x: 1, y: 0 }, { x: -1, y: 0 }, { x: 0, y: 1 }, { x: 0, y: -1 }]
|
|
139
|
-
}
|
|
140
|
-
if (preferredDir.x === 0 && preferredDir.y === 1) {
|
|
141
|
-
return [{ x: -1, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 1 }, { x: 0, y: -1 }]
|
|
142
|
-
}
|
|
143
|
-
if (preferredDir.x === 1 && preferredDir.y === 2) {
|
|
144
|
-
return [{ x: 0, y: 1 }, { x: 0, y: -1 }, { x: 1, y: 0 }, { x: -1, y: 0 }]
|
|
145
|
-
}
|
|
146
|
-
if (preferredDir.x === 1 && preferredDir.y === 0) {
|
|
147
|
-
return [{ x: 0, y: -1 }, { x: 0, y: 1 }, { x: 1, y: 0 }, { x: -1, y: 0 }]
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const dx = preferredDir.x - 1
|
|
151
|
-
const dy = preferredDir.y - 1
|
|
152
|
-
if (Math.abs(dx) >= Math.abs(dy)) {
|
|
153
|
-
return dx >= 0
|
|
154
|
-
? [{ x: 1, y: 0 }, { x: -1, y: 0 }, { x: 0, y: 1 }, { x: 0, y: -1 }]
|
|
155
|
-
: [{ x: -1, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 1 }, { x: 0, y: -1 }]
|
|
156
|
-
}
|
|
157
|
-
return dy >= 0
|
|
158
|
-
? [{ x: 0, y: 1 }, { x: 0, y: -1 }, { x: 1, y: 0 }, { x: -1, y: 0 }]
|
|
159
|
-
: [{ x: 0, y: -1 }, { x: 0, y: 1 }, { x: 1, y: 0 }, { x: -1, y: 0 }]
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/** Check if a grid cell is unoccupied and has non-negative coordinates. */
|
|
163
|
-
function isFreeInGrid(grid: Map<string, AsciiNode>, c: GridCoord): boolean {
|
|
164
|
-
if (c.x < 0 || c.y < 0) return false
|
|
165
|
-
return !grid.has(gridKey(c))
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Find a path from `from` to `to` on the grid using A*.
|
|
171
|
-
* Returns the path as an array of GridCoords, or null if no path exists.
|
|
172
|
-
*
|
|
173
|
-
* @param preferredDir - Optional exit direction tried first during neighbour
|
|
174
|
-
* expansion (see `orderedDirs`). Currently unused by the edge router; kept as
|
|
175
|
-
* a tested capability and exercised by ascii-pathfinder-determinism.test.ts.
|
|
176
|
-
*/
|
|
177
|
-
export function getPath(
|
|
178
|
-
grid: Map<string, AsciiNode>,
|
|
179
|
-
from: GridCoord,
|
|
180
|
-
to: GridCoord,
|
|
181
|
-
preferredDir?: Direction,
|
|
182
|
-
): GridCoord[] | null {
|
|
183
|
-
// #66 (ktrysmt): bound the search. `isFreeInGrid` only guards x/y < 0, so an
|
|
184
|
-
// unreachable target (walled off) would let A* explore +x/+y unboundedly →
|
|
185
|
-
// OOM/hang. Derive an upper bound from the grid extent + the from/to coords,
|
|
186
|
-
// plus a hard iteration cap. On exhaustion we return null and the caller
|
|
187
|
-
// falls back to a direct route, rather than exhausting memory.
|
|
188
|
-
let maxX = Math.max(from.x, to.x)
|
|
189
|
-
let maxY = Math.max(from.y, to.y)
|
|
190
|
-
for (const key of grid.keys()) {
|
|
191
|
-
const comma = key.indexOf(',')
|
|
192
|
-
const kx = Number(key.slice(0, comma))
|
|
193
|
-
const ky = Number(key.slice(comma + 1))
|
|
194
|
-
if (kx > maxX) maxX = kx
|
|
195
|
-
if (ky > maxY) maxY = ky
|
|
196
|
-
}
|
|
197
|
-
// Margin lets routes detour slightly around obstacles past the extent.
|
|
198
|
-
const boundX = maxX + 4
|
|
199
|
-
const boundY = maxY + 4
|
|
200
|
-
// Iteration cap proportional to the bounded grid area (with a floor).
|
|
201
|
-
const maxIterations = Math.max(10_000, (boundX + 1) * (boundY + 1) * 4)
|
|
202
|
-
|
|
203
|
-
const moveDirs = buildMoveDirs(preferredDir)
|
|
204
|
-
|
|
205
|
-
let seq = 0
|
|
206
|
-
const pq = new MinHeap()
|
|
207
|
-
pq.push({ coord: from, priority: 0, seq: seq++ })
|
|
208
|
-
|
|
209
|
-
const costSoFar = new Map<string, number>()
|
|
210
|
-
costSoFar.set(gridKey(from), 0)
|
|
211
|
-
|
|
212
|
-
const cameFrom = new Map<string, GridCoord | null>()
|
|
213
|
-
cameFrom.set(gridKey(from), null)
|
|
214
|
-
|
|
215
|
-
let iterations = 0
|
|
216
|
-
while (pq.length > 0) {
|
|
217
|
-
if (++iterations > maxIterations) return null // #66 guard: bail to caller's fallback
|
|
218
|
-
const current = pq.pop()!.coord
|
|
219
|
-
|
|
220
|
-
if (gridCoordEquals(current, to)) {
|
|
221
|
-
// Reconstruct path by walking backwards through cameFrom
|
|
222
|
-
const path: GridCoord[] = []
|
|
223
|
-
let c: GridCoord | null = current
|
|
224
|
-
while (c !== null) {
|
|
225
|
-
path.unshift(c)
|
|
226
|
-
c = cameFrom.get(gridKey(c)) ?? null
|
|
227
|
-
}
|
|
228
|
-
return path
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const currentCost = costSoFar.get(gridKey(current))!
|
|
232
|
-
|
|
233
|
-
for (const dir of moveDirs) {
|
|
234
|
-
const next: GridCoord = { x: current.x + dir.x, y: current.y + dir.y }
|
|
235
|
-
|
|
236
|
-
// #66 guard: never expand past the bounded extent. Without this, an
|
|
237
|
-
// unreachable target lets the search wander the unbounded +x/+y plane.
|
|
238
|
-
if (next.x > boundX || next.y > boundY) continue
|
|
239
|
-
|
|
240
|
-
// Allow moving to the destination even if it's occupied (it's a node boundary)
|
|
241
|
-
if (!isFreeInGrid(grid, next) && !gridCoordEquals(next, to)) {
|
|
242
|
-
continue
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const newCost = currentCost + 1
|
|
246
|
-
const nextKey = gridKey(next)
|
|
247
|
-
const existingCost = costSoFar.get(nextKey)
|
|
248
|
-
|
|
249
|
-
if (existingCost === undefined || newCost < existingCost) {
|
|
250
|
-
costSoFar.set(nextKey, newCost)
|
|
251
|
-
const priority = newCost + heuristic(next, to)
|
|
252
|
-
pq.push({ coord: next, priority, seq: seq++ })
|
|
253
|
-
cameFrom.set(nextKey, current)
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return null // No path found
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Simplify a path by removing intermediate waypoints on straight segments.
|
|
263
|
-
* E.g., [(0,0), (1,0), (2,0), (2,1)] becomes [(0,0), (2,0), (2,1)].
|
|
264
|
-
* This reduces the number of line-drawing operations.
|
|
265
|
-
*/
|
|
266
|
-
export function mergePath(path: GridCoord[]): GridCoord[] {
|
|
267
|
-
if (path.length <= 2) return path
|
|
268
|
-
|
|
269
|
-
const toRemove = new Set<number>()
|
|
270
|
-
let step0 = path[0]!
|
|
271
|
-
let step1 = path[1]!
|
|
272
|
-
|
|
273
|
-
for (let idx = 2; idx < path.length; idx++) {
|
|
274
|
-
const step2 = path[idx]!
|
|
275
|
-
const prevDx = step1.x - step0.x
|
|
276
|
-
const prevDy = step1.y - step0.y
|
|
277
|
-
const dx = step2.x - step1.x
|
|
278
|
-
const dy = step2.y - step1.y
|
|
279
|
-
|
|
280
|
-
// Same direction — the middle point is redundant
|
|
281
|
-
if (prevDx === dx && prevDy === dy) {
|
|
282
|
-
// In Go: indexToRemove = append(indexToRemove, idx+1) but idx is 0-based from path[2:]
|
|
283
|
-
// which corresponds to index idx in the full path. Go uses idx+1 because idx iterates
|
|
284
|
-
// from 0 in the [2:] slice, mapping to full-array index idx+1.
|
|
285
|
-
// Actually re-checking Go code: the loop is `for idx, step2 := range path[2:]`
|
|
286
|
-
// so idx=0 → path[2], and it removes idx+1 which is index 1 in the full array.
|
|
287
|
-
// Wait, that doesn't look right. Let me re-read:
|
|
288
|
-
// step0 = path[0], step1 = path[1]
|
|
289
|
-
// for idx, step2 := range path[2:] { ... indexToRemove = append(indexToRemove, idx+1) ... }
|
|
290
|
-
// When idx=0, step2=path[2], and it removes index 1 (step1 = path[1]) if directions match
|
|
291
|
-
// So it removes the middle point (step1) which is at index idx+1 in the original array
|
|
292
|
-
// when counting from the 2-ahead loop. Let me just track which middle indices to remove.
|
|
293
|
-
toRemove.add(idx - 1) // Remove the middle point (step1's position)
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
step0 = step1
|
|
297
|
-
step1 = step2
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
return path.filter((_, i) => !toRemove.has(i))
|
|
301
|
-
}
|
package/src/ascii/pie.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// ASCII renderer — Pie chart
|
|
3
|
-
//
|
|
4
|
-
// A pie chart doesn't map cleanly to a character grid, so we render it as a
|
|
5
|
-
// proportional horizontal bar list (the conventional terminal pie rendering):
|
|
6
|
-
//
|
|
7
|
-
// Pets (title, if present)
|
|
8
|
-
// Dogs ████████████████████████ 77.2%
|
|
9
|
-
// Cats █████ 17.0%
|
|
10
|
-
// Rats █ 5.8%
|
|
11
|
-
//
|
|
12
|
-
// Each bar's length is proportional to the slice's share of the total. With
|
|
13
|
-
// `showData` the raw value is shown alongside the percentage. Bars use █
|
|
14
|
-
// (Unicode) or # (ASCII fallback), colored per-slice with the same xychart
|
|
15
|
-
// palette as the SVG renderer for cross-format consistency.
|
|
16
|
-
// ============================================================================
|
|
17
|
-
|
|
18
|
-
import { parsePieChart } from '../pie/parser.ts'
|
|
19
|
-
import { formatPieValue, formatPiePercent } from '../pie/layout.ts'
|
|
20
|
-
import type { AsciiConfig, AsciiTheme, ColorMode } from './types.ts'
|
|
21
|
-
import { colorizeText } from './ansi.ts'
|
|
22
|
-
import { getSeriesColor, CHART_ACCENT_FALLBACK, isValidHex } from '../xychart/colors.ts'
|
|
23
|
-
|
|
24
|
-
/** Maximum bar length in characters (the largest slice fills this). */
|
|
25
|
-
const MAX_BAR = 30
|
|
26
|
-
|
|
27
|
-
function sliceColor(index: number, theme: AsciiTheme): string {
|
|
28
|
-
const accent = theme.accent && isValidHex(theme.accent) ? theme.accent : CHART_ACCENT_FALLBACK
|
|
29
|
-
return getSeriesColor(index, accent, theme.bg)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function renderPieAscii(
|
|
33
|
-
lines: string[],
|
|
34
|
-
config: AsciiConfig,
|
|
35
|
-
colorMode: ColorMode,
|
|
36
|
-
theme: AsciiTheme,
|
|
37
|
-
): string {
|
|
38
|
-
const chart = parsePieChart(lines)
|
|
39
|
-
const barChar = config.useAscii ? '#' : '█'
|
|
40
|
-
|
|
41
|
-
const total = chart.entries.reduce((sum, e) => sum + e.value, 0)
|
|
42
|
-
if (total <= 0) return ''
|
|
43
|
-
|
|
44
|
-
// Column widths.
|
|
45
|
-
const labelWidth = Math.max(...chart.entries.map(e => e.label.length))
|
|
46
|
-
const maxValue = Math.max(...chart.entries.map(e => e.value))
|
|
47
|
-
|
|
48
|
-
const out: string[] = []
|
|
49
|
-
if (chart.title) out.push(chart.title)
|
|
50
|
-
|
|
51
|
-
chart.entries.forEach((entry, index) => {
|
|
52
|
-
const fraction = entry.value / total
|
|
53
|
-
// Bar length proportional to the largest slice, min 1 char for nonzero.
|
|
54
|
-
const barLen = Math.max(1, Math.round((entry.value / maxValue) * MAX_BAR))
|
|
55
|
-
const bar = barChar.repeat(barLen)
|
|
56
|
-
const coloredBar = colorMode === 'none' ? bar : colorizeText(bar, sliceColor(index, theme), colorMode)
|
|
57
|
-
|
|
58
|
-
const label = entry.label.padEnd(labelWidth)
|
|
59
|
-
const barPad = ' '.repeat(MAX_BAR - barLen + 2)
|
|
60
|
-
const pct = formatPiePercent(fraction).padStart(6)
|
|
61
|
-
const valuePart = chart.showData ? ` [${formatPieValue(entry.value)}]` : ''
|
|
62
|
-
|
|
63
|
-
out.push(`${label} ${coloredBar}${barPad}${pct}${valuePart}`)
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
return out.join('\n')
|
|
67
|
-
}
|
package/src/ascii/quadrant.ts
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// ASCII renderer — Quadrant chart
|
|
3
|
-
//
|
|
4
|
-
// Renders a bordered SQUARE grid divided into four quadrants. Quadrant labels
|
|
5
|
-
// sit in their regions, points are plotted at scaled positions marked ●
|
|
6
|
-
// (Unicode) / * (ASCII), and a legend below lists every point's coordinates.
|
|
7
|
-
//
|
|
8
|
-
// Mermaid quadrant numbering: 1=top-right, 2=top-left, 3=bottom-left,
|
|
9
|
-
// 4=bottom-right. Normalized coords (x,y) ∈ [0,1]², y grows UP. Scaling
|
|
10
|
-
// follows the xychart convention: round(t * (extent - 1)).
|
|
11
|
-
// ============================================================================
|
|
12
|
-
|
|
13
|
-
import { parseQuadrantChart } from '../quadrant/parser.ts'
|
|
14
|
-
import type { QuadrantChart } from '../quadrant/types.ts'
|
|
15
|
-
import type { AsciiConfig, AsciiTheme, ColorMode } from './types.ts'
|
|
16
|
-
import { colorizeText } from './ansi.ts'
|
|
17
|
-
|
|
18
|
-
/** Interior plot dimensions in cells (odd so the dividers land cleanly). */
|
|
19
|
-
const GRID_W = 41
|
|
20
|
-
const GRID_H = 21
|
|
21
|
-
|
|
22
|
-
const UNI = {
|
|
23
|
-
tl: '┌', tr: '┐', bl: '└', br: '┘',
|
|
24
|
-
h: '─', v: '│',
|
|
25
|
-
tDown: '┬', tUp: '┴', tLeft: '┤', tRight: '├', cross: '┼',
|
|
26
|
-
point: '●',
|
|
27
|
-
} as const
|
|
28
|
-
|
|
29
|
-
const ASC = {
|
|
30
|
-
tl: '+', tr: '+', bl: '+', br: '+',
|
|
31
|
-
h: '-', v: '|',
|
|
32
|
-
tDown: '+', tUp: '+', tLeft: '+', tRight: '+', cross: '+',
|
|
33
|
-
point: '*',
|
|
34
|
-
} as const
|
|
35
|
-
|
|
36
|
-
export function renderQuadrantAscii(
|
|
37
|
-
lines: string[],
|
|
38
|
-
config: AsciiConfig,
|
|
39
|
-
colorMode: ColorMode,
|
|
40
|
-
theme: AsciiTheme,
|
|
41
|
-
): string {
|
|
42
|
-
const chart = parseQuadrantChart(lines)
|
|
43
|
-
const ch = config.useAscii ? ASC : UNI
|
|
44
|
-
|
|
45
|
-
// Interior grid (rows × cols of single characters). Row 0 = top.
|
|
46
|
-
const grid: string[][] = Array.from({ length: GRID_H }, () => Array.from({ length: GRID_W }, () => ' '))
|
|
47
|
-
const midCol = Math.floor(GRID_W / 2)
|
|
48
|
-
const midRow = Math.floor(GRID_H / 2)
|
|
49
|
-
|
|
50
|
-
// Dividers.
|
|
51
|
-
for (let r = 0; r < GRID_H; r++) grid[r]![midCol] = ch.v
|
|
52
|
-
for (let c = 0; c < GRID_W; c++) grid[midRow]![c] = ch.h
|
|
53
|
-
grid[midRow]![midCol] = ch.cross
|
|
54
|
-
|
|
55
|
-
// Points — scaled into the grid. x grows right, y grows up (invert row).
|
|
56
|
-
// Plotted first so quadrant labels (decorative) never clobber a data point.
|
|
57
|
-
const occupied = new Set<string>()
|
|
58
|
-
chart.points.forEach(p => {
|
|
59
|
-
const col = Math.round(p.x * (GRID_W - 1))
|
|
60
|
-
const row = Math.round((1 - p.y) * (GRID_H - 1))
|
|
61
|
-
grid[row]![col] = ch.point
|
|
62
|
-
occupied.add(`${row},${col}`)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
// Quadrant labels. When points exist, Mermaid renders quadrant text near the
|
|
66
|
-
// TOP of each region (so it doesn't fight the data); with no points it sits
|
|
67
|
-
// in the center. We follow that, and never overwrite a plotted point.
|
|
68
|
-
const top = chart.points.length > 0
|
|
69
|
-
placeLabel(grid, occupied, chart.quadrants[1], 0, 0, midRow, midCol, top) // q2 top-left
|
|
70
|
-
placeLabel(grid, occupied, chart.quadrants[0], 0, midCol + 1, midRow, GRID_W, top) // q1 top-right
|
|
71
|
-
placeLabel(grid, occupied, chart.quadrants[2], midRow + 1, 0, GRID_H, midCol, top) // q3 bottom-left
|
|
72
|
-
placeLabel(grid, occupied, chart.quadrants[3], midRow + 1, midCol + 1, GRID_H, GRID_W, top) // q4 bottom-right
|
|
73
|
-
|
|
74
|
-
// Frame the grid with a border, with axis labels on the edges.
|
|
75
|
-
const out: string[] = []
|
|
76
|
-
if (chart.title) out.push(centerText(chart.title, GRID_W + 2))
|
|
77
|
-
|
|
78
|
-
out.push(ch.tl + ch.h.repeat(GRID_W) + ch.tr)
|
|
79
|
-
for (let r = 0; r < GRID_H; r++) {
|
|
80
|
-
// The mid divider row meets the border as ├ … ┤ junctions.
|
|
81
|
-
const left = r === midRow ? ch.tRight : ch.v
|
|
82
|
-
const right = r === midRow ? ch.tLeft : ch.v
|
|
83
|
-
out.push(left + grid[r]!.join('') + right)
|
|
84
|
-
}
|
|
85
|
-
out.push(ch.bl + ch.h.repeat(GRID_W) + ch.br)
|
|
86
|
-
|
|
87
|
-
// x-axis labels under the grid (left + right).
|
|
88
|
-
if (chart.xAxis) {
|
|
89
|
-
out.push(edgeAxisRow(chart.xAxis.near, chart.xAxis.far, GRID_W + 2))
|
|
90
|
-
}
|
|
91
|
-
// y-axis labels as a separate annotated line.
|
|
92
|
-
if (chart.yAxis) {
|
|
93
|
-
const top = chart.yAxis.far ? `top: ${chart.yAxis.far}` : ''
|
|
94
|
-
const bottom = `bottom: ${chart.yAxis.near}`
|
|
95
|
-
out.push(`y-axis ${bottom}${top ? ` | ${top}` : ''}`)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Legend — every point with its coordinates.
|
|
99
|
-
if (chart.points.length > 0) {
|
|
100
|
-
out.push('')
|
|
101
|
-
const pointGlyph = colorMode === 'none' ? ch.point : colorizeText(ch.point, pointColor(theme), colorMode)
|
|
102
|
-
for (const p of chart.points) {
|
|
103
|
-
out.push(`${pointGlyph} ${p.label}: [${fmt(p.x)}, ${fmt(p.y)}]`)
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return out.join('\n')
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function pointColor(theme: AsciiTheme): string {
|
|
111
|
-
return theme.accent ?? theme.fg
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function fmt(n: number): string {
|
|
115
|
-
return Number.isInteger(n) ? String(n) : String(Math.round(n * 100) / 100)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Place a label horizontally centered within a sub-rectangle of the grid.
|
|
120
|
-
* When `top` is set the label sits one row down from the region top; otherwise
|
|
121
|
-
* it is vertically centered. Never overwrites a plotted point.
|
|
122
|
-
*/
|
|
123
|
-
function placeLabel(
|
|
124
|
-
grid: string[][],
|
|
125
|
-
occupied: Set<string>,
|
|
126
|
-
label: string | undefined,
|
|
127
|
-
rowStart: number,
|
|
128
|
-
colStart: number,
|
|
129
|
-
rowEnd: number,
|
|
130
|
-
colEnd: number,
|
|
131
|
-
top: boolean,
|
|
132
|
-
): void {
|
|
133
|
-
if (!label) return
|
|
134
|
-
const regionW = colEnd - colStart
|
|
135
|
-
const regionH = rowEnd - rowStart
|
|
136
|
-
if (regionW <= 0 || regionH <= 0) return
|
|
137
|
-
const text = label.length > regionW ? label.slice(0, regionW) : label
|
|
138
|
-
const row = top ? rowStart + 1 : rowStart + Math.floor(regionH / 2)
|
|
139
|
-
const col = colStart + Math.max(0, Math.floor((regionW - text.length) / 2))
|
|
140
|
-
for (let i = 0; i < text.length && col + i < colEnd; i++) {
|
|
141
|
-
if (occupied.has(`${row},${col + i}`)) continue
|
|
142
|
-
grid[row]![col + i] = text[i]!
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function centerText(text: string, width: number): string {
|
|
147
|
-
if (text.length >= width) return text
|
|
148
|
-
const pad = Math.floor((width - text.length) / 2)
|
|
149
|
-
return ' '.repeat(pad) + text
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/** Bottom edge axis row: near label left-aligned, far label right-aligned. */
|
|
153
|
-
function edgeAxisRow(near: string, far: string | undefined, width: number): string {
|
|
154
|
-
if (!far) return near
|
|
155
|
-
const gap = width - near.length - far.length
|
|
156
|
-
if (gap < 1) return `${near} ${far}`
|
|
157
|
-
return near + ' '.repeat(gap) + far
|
|
158
|
-
}
|