OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
 
master @ 105 LINES
 
[ HISTORY ]  [ UP ]
 

// Chain layout for generated diagrams: a linear pipeline renders as
// horizontal boxes with labels over the wires, or stacks on a straight
// vertical spine when it doesn't fit. Branching layout is delegated to
// mermaid-ascii (see mermaid.ts).
import { G, center, len } from "./ascii";

export interface FlowEdge {
  from: string;
  to: string;
  label: string;
}

export interface FlowGraph {
  labels: Map<string, string>;
  edges: FlowEdge[];
}

// Orders the nodes if the edges form a single path, else null.
function chainOf(g: FlowGraph): string[] | null {
  const nodes = new Set<string>(g.edges.flatMap((e) => [e.from, e.to]));
  const out = new Map<string, string>();
  const hasIn = new Set<string>();
  for (const e of g.edges) {
    if (out.has(e.from) || hasIn.has(e.to)) return null;
    out.set(e.from, e.to);
    hasIn.add(e.to);
  }
  const heads = [...nodes].filter((n) => !hasIn.has(n));
  if (heads.length !== 1) return null;
  const chain = [heads[0]];
  while (out.has(chain[chain.length - 1])) chain.push(out.get(chain[chain.length -
 1])!);
  return chain.length === nodes.size ? chain : null;
}

function horizontal(chain: string[], g: FlowGraph, width: number): string | null {
  const disp = chain.map((id) => g.labels.get(id) ?? id);
  const bw = disp.map((d) => len(d) + 4);
  const ew = g.edges.map((e) => Math.max(len(e.label) + 2, 4));
  const total = bw.reduce((a, b) => a + b, 0) + ew.reduce((a, b) => a + b, 0);
  if (total > width) return null;

  let labelRow = "";
  let top = "";
  let mid = "";
  let bot = "";
  chain.forEach((_, i) => {
    labelRow += " ".repeat(bw[i]);
    top += G.TL + G.H.repeat(bw[i] - 2) + G.TR;
    mid += G.V + center(disp[i], bw[i] - 2) + G.V;
    bot += G.BL + G.H.repeat(bw[i] - 2) + G.BR;
    if (i < g.edges.length) {
      labelRow += center(g.edges[i].label, ew[i]);
      top += " ".repeat(ew[i]);
      mid += G.H.repeat(ew[i] - 1) + ">";
      bot += " ".repeat(ew[i]);
    }
  });

  const rows = [top, mid, bot].map((r) => r.replace(/\s+$/, ""));
  if (g.edges.some((e) => e.label)) rows.unshift(labelRow.replace(/\s+$/, ""));
  return rows.join("\n");
}

function vertical(chain: string[], g: FlowGraph): string {
  const disp = chain.map((id) => g.labels.get(id) ?? id);
  const bw = disp.map((d) => len(d) + 4);
  const spine = Math.floor(Math.max(...bw) / 2);

  const border = (l: string, r: string, w: number, left: number, junction: string 
| null) => {
    if (junction === null) return " ".repeat(left) + l + G.H.repeat(w - 2) + r;
    const before = spine - left - 1;
    return " ".repeat(left) + l + G.H.repeat(before) + junction + G.H.repeat(w - 2
 - before - 1) + r;
  };

  const rows: string[] = [];
  chain.forEach((_, i) => {
    const w = bw[i];
    const left = spine - Math.floor(w / 2);
    rows.push(border(G.TL, G.TR, w, left, i > 0 ? G.BJ : null));
    rows.push(" ".repeat(left) + G.V + center(disp[i], w - 2) + G.V);
    rows.push(border(G.BL, G.BR, w, left, i < chain.length - 1 ? G.TJ : null));
    if (i < g.edges.length) {
      const label = g.edges[i].label;
      rows.push(" ".repeat(spine) + G.V + (label ? ` ${label}` : ""));
      rows.push(" ".repeat(spine) + "↓");
    }
  });
  return rows.join("\n");
}

/** Renders g if its edges form one linear chain; null otherwise. */
export function renderChain(g: FlowGraph, width: number, preferHorizontal = true):
 string | null {
  const chain = chainOf(g);
  if (!chain) return null;

  // Edges may be authored in any order; realign them with the chain.
  const byFrom = new Map(g.edges.map((e) => [e.from, e]));
  const ordered: FlowGraph = {
    labels: g.labels,
    edges: chain.slice(0, -1).map((id) => byFrom.get(id)!),
  };
  if (!preferHorizontal) return vertical(chain, ordered);
  return horizontal(chain, ordered, width) ?? vertical(chain, ordered);
}

// Chain layout for generated diagrams: a li
near pipeline renders as
// horizontal boxes with labels over the wir
es, or stacks on a straight
// vertical spine when it doesn't fit. Branc
hing layout is delegated to
// mermaid-ascii (see mermaid.ts).
import { G, center, len } from "./ascii";

export interface FlowEdge {
  from: string;
  to: string;
  label: string;
}

export interface FlowGraph {
  labels: Map<string, string>;
  edges: FlowEdge[];
}

// Orders the nodes if the edges form a sing
le path, else null.
function chainOf(g: FlowGraph): string[] | n
ull {
  const nodes = new Set<string>(g.edges.flat
Map((e) => [e.from, e.to]));
  const out = new Map<string, string>();
  const hasIn = new Set<string>();
  for (const e of g.edges) {
    if (out.has(e.from) || hasIn.has(e.to)) 
return null;
    out.set(e.from, e.to);
    hasIn.add(e.to);
  }
  const heads = [...nodes].filter((n) => !ha
sIn.has(n));
  if (heads.length !== 1) return null;
  const chain = [heads[0]];
  while (out.has(chain[chain.length - 1])) c
hain.push(out.get(chain[chain.length - 1])!)
;
  return chain.length === nodes.size ? chain
 : null;
}

function horizontal(chain: string[], g: Flow
Graph, width: number): string | null {
  const disp = chain.map((id) => g.labels.ge
t(id) ?? id);
  const bw = disp.map((d) => len(d) + 4);
  const ew = g.edges.map((e) => Math.max(len
(e.label) + 2, 4));
  const total = bw.reduce((a, b) => a + b, 0
) + ew.reduce((a, b) => a + b, 0);
  if (total > width) return null;

  let labelRow = "";
  let top = "";
  let mid = "";
  let bot = "";
  chain.forEach((_, i) => {
    labelRow += " ".repeat(bw[i]);
    top += G.TL + G.H.repeat(bw[i] - 2) + G.
TR;
    mid += G.V + center(disp[i], bw[i] - 2) 
+ G.V;
    bot += G.BL + G.H.repeat(bw[i] - 2) + G.
BR;
    if (i < g.edges.length) {
      labelRow += center(g.edges[i].label, e
w[i]);
      top += " ".repeat(ew[i]);
      mid += G.H.repeat(ew[i] - 1) + ">";
      bot += " ".repeat(ew[i]);
    }
  });

  const rows = [top, mid, bot].map((r) => r.
replace(/\s+$/, ""));
  if (g.edges.some((e) => e.label)) rows.uns
hift(labelRow.replace(/\s+$/, ""));
  return rows.join("\n");
}

function vertical(chain: string[], g: FlowGr
aph): string {
  const disp = chain.map((id) => g.labels.ge
t(id) ?? id);
  const bw = disp.map((d) => len(d) + 4);
  const spine = Math.floor(Math.max(...bw) /
 2);

  const border = (l: string, r: string, w: n
umber, left: number, junction: string | null
) => {
    if (junction === null) return " ".repeat
(left) + l + G.H.repeat(w - 2) + r;
    const before = spine - left - 1;
    return " ".repeat(left) + l + G.H.repeat
(before) + junction + G.H.repeat(w - 2 - bef
ore - 1) + r;
  };

  const rows: string[] = [];
  chain.forEach((_, i) => {
    const w = bw[i];
    const left = spine - Math.floor(w / 2);
    rows.push(border(G.TL, G.TR, w, left, i 
> 0 ? G.BJ : null));
    rows.push(" ".repeat(left) + G.V + cente
r(disp[i], w - 2) + G.V);
    rows.push(border(G.BL, G.BR, w, left, i 
< chain.length - 1 ? G.TJ : null));
    if (i < g.edges.length) {
      const label = g.edges[i].label;
      rows.push(" ".repeat(spine) + G.V + (l
abel ? ` ${label}` : ""));
      rows.push(" ".repeat(spine) + "↓");
    }
  });
  return rows.join("\n");
}

/** Renders g if its edges form one linear c
hain; null otherwise. */
export function renderChain(g: FlowGraph, wi
dth: number, preferHorizontal = true): strin
g | null {
  const chain = chainOf(g);
  if (!chain) return null;

  // Edges may be authored in any order; rea
lign them with the chain.
  const byFrom = new Map(g.edges.map((e) => 
[e.from, e]));
  const ordered: FlowGraph = {
    labels: g.labels,
    edges: chain.slice(0, -1).map((id) => by
From.get(id)!),
  };
  if (!preferHorizontal) return vertical(cha
in, ordered);
  return horizontal(chain, ordered, width) ?
? vertical(chain, ordered);
}
 
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET