Appearance
How to render a diagram
Run the canonical GraphIR 2.0 workflow from Coral text to laid-out SVG and standalone HTML.
Before you begin
Install the language and render packages with the exact external validator:
sh
npm install @coral-viz/language@0.2.5 @coral-viz/render@0.2.5 \
@graph-ir/core@0.2.1Published @graph-ir/core@0.2.1 is pinned exactly by all four direct Coral dependencies. Release verification uses the registry package and one deduplicated installed resolution; local paths, sibling checkouts, and archive substitutions are not release evidence.
Render the graph
- Parse Coral text with
@coral-viz/languageand stop on parser errors. - Validate the returned GraphIR 2.0 graph with
validateIRfrom@graph-ir/core@0.2.1. - Map the graph to a
LayoutRequestand callcomputeElkLayoutfrom@coral-viz/render/layout. - Apply the returned absolute positions, dimensions, and edge sections.
- Call
renderSvgandrenderHtmlfrom@coral-viz/render.
The executable source is included directly from render-diagram.mjs:
mjs
import assert from 'node:assert/strict';
import { validateIR } from '@graph-ir/core';
import { parse } from '@coral-viz/language';
import { renderHtml, renderSvg } from '@coral-viz/render';
import { computeElkLayout } from '@coral-viz/render/layout';
const source = `
service "API" as api
database "Orders" as orders
api -> orders as writes
`;
const parsed = parse(source);
assert.equal(parsed.success, true, parsed.errors.map((error) => error.message).join('; '));
assert.equal(parsed.graph.version, '2.0.0');
const validation = validateIR(parsed.graph);
assert.equal(validation.valid, true, validation.errors.map((error) => error.message).join('; '));
// LayoutRequest is deliberately explicit. Applications choose their own size,
// hierarchy, port, pinning, and fallback policies.
const layout = await computeElkLayout({
id: parsed.graph.id,
nodes: parsed.graph.nodes.map((node) => ({
id: node.id,
width: node.dimensions?.width ?? 160,
height: node.dimensions?.height ?? 80,
})),
edges: parsed.graph.edges.map((edge) => ({
id: edge.id,
source: edge.source,
target: edge.target,
})),
defaults: {
algorithm: 'layered',
direction: 'RIGHT',
edgeRouting: 'ORTHOGONAL',
},
});
const rectangles = new Map(layout.nodes.map((node) => [node.id, node]));
const graph = {
...parsed.graph,
nodes: parsed.graph.nodes.map((node) => {
const rectangle = rectangles.get(node.id);
assert.ok(rectangle, `layout omitted ${node.id}`);
return {
...node,
position: rectangle.absolutePosition,
dimensions: { width: rectangle.width, height: rectangle.height },
};
}),
};
const edgeRoutes = new Map(layout.edges.map((edge) => [edge.id, edge.sections]));
const svg = renderSvg(graph, { edgeRoutes, title: 'Order service' });
const html = renderHtml(graph, { edgeRoutes, title: 'Order service' });
assert.match(svg, /^<svg/);
assert.match(svg, /API/);
assert.match(html, /^<!doctype html>/);
assert.match(html, /<svg/);
// Root rendering consumes geometry. It does not call the layout engine.
const unlaid = renderSvg(parsed.graph, { padding: 0 });
assert.notEqual(svg, unlaid);
assert.equal(
[...unlaid.matchAll(/<g transform="translate\(0,0\)/g)].length,
parsed.graph.nodes.length,
);
console.log(`Rendered ${graph.nodes.length} nodes to SVG and standalone HTML.`);This example is intentionally flat. A host application decides how nested nodes, ports, pinned positions, and missing dimensions map into a layout request.
Check the boundary
renderSvg and renderHtml serialize supplied geometry. They don't call a layout engine. Without positions, nodes draw at the origin. Call the layout subpath explicitly as shown above.
The MCP coral_render tool is a separate convenience boundary: it lays out input whose geometry is incomplete, while preserving complete supplied geometry.