// view.el — SVG renderer for the force-directed graph. // // Takes layout positions + node/edge data and produces a complete SVG string. // Rendering is purely server-side — no DOM, no JavaScript. // // Public API: // graph_render_svg(nodes_json, edges_json, positions_json, width, height) -> String // Returns a complete ... string ready for embedding or serving. // // Visual conventions: // - Background: #0d1117 (dark, matching Studio theme) // - Edges drawn first (below nodes) // - Nodes: filled circle with stroke, radius by salience // - Labels: truncated to 30 chars, below node, 10px IBM Plex Mono // ── SVG helpers ─────────────────────────────────────────────────────────────── fn svg_open(width: Int, height: Int) -> String { "" } fn svg_close() -> String { "" } fn svg_defs() -> String { "" + "" + "" + "" } // ── Edge rendering ──────────────────────────────────────────────────────────── fn svg_edge(x1: Float, y1: Float, x2: Float, y2: Float, weight: Float) -> String { let sw: Float = edge_stroke_width(weight) let sw_str: String = format_float(sw, 1) let x1s: String = format_float(x1, 1) let y1s: String = format_float(y1, 1) let x2s: String = format_float(x2, 1) let y2s: String = format_float(y2, 1) "" } // ── Node rendering ──────────────────────────────────────────────────────────── fn svg_node(x: Float, y: Float, radius: Int, color: String, label: String) -> String { let xs: String = format_float(x, 1) let ys: String = format_float(y, 1) let rs: String = int_to_str(radius) let label_trunc: String = node_label_truncate(label) // Escape XML special chars in label let label_safe: String = str_replace(str_replace(str_replace(label_trunc, "&", "&"), "<", "<"), ">", ">") let label_y: String = format_float(y + int_to_float(radius) + int_to_float(12), 1) "" + "" + label_safe + "" } // ── Position lookup ─────────────────────────────────────────────────────────── // // Build a flat map from node_id -> position JSON in process state. // Key: "pos_" -> "{\"x\":...,\"y\":...}" fn build_position_index(positions_json: String) -> Bool { let count: Int = json_array_len(positions_json) let i: Int = 0 while i < count { let pos: String = json_array_get(positions_json, i) let id: String = json_get_string(pos, "id") if !str_eq(id, "") { state_set("pos_" + id, pos) } let i = i + 1 } true } fn get_pos_x(node_id: String) -> Float { let pos: String = state_get("pos_" + node_id) if str_eq(pos, "") { return int_to_float(0) } json_get_float(pos, "x") } fn get_pos_y(node_id: String) -> Float { let pos: String = state_get("pos_" + node_id) if str_eq(pos, "") { return int_to_float(0) } json_get_float(pos, "y") } // ── Public: graph_render_svg ────────────────────────────────────────────────── fn graph_render_svg(nodes_json: String, edges_json: String, positions_json: String, width: Int, height: Int) -> String { // Index positions by node id build_position_index(positions_json) let out: String = svg_open(width, height) let out = out + svg_defs() // ── Draw edges (behind nodes) ───────────────────────────────────────────── let edge_count: Int = json_array_len(edges_json) let i: Int = 0 while i < edge_count { let e: String = json_array_get(edges_json, i) let src: String = edge_source(e) let tgt: String = edge_target(e) let w: Float = edge_weight(e) // Only draw if both endpoints have positions let src_pos: String = state_get("pos_" + src) let tgt_pos: String = state_get("pos_" + tgt) if !str_eq(src_pos, "") { if !str_eq(tgt_pos, "") { let x1: Float = get_pos_x(src) let y1: Float = get_pos_y(src) let x2: Float = get_pos_x(tgt) let y2: Float = get_pos_y(tgt) let out = out + svg_edge(x1, y1, x2, y2, w) } } let i = i + 1 } // ── Draw nodes (over edges) ─────────────────────────────────────────────── let node_count: Int = json_array_len(nodes_json) let j: Int = 0 while j < node_count { let n: String = json_array_get(nodes_json, j) let id: String = node_id(n) let lbl: String = node_label(n) let ntype: String = node_type_field(n) let sal: Float = node_salience(n) let color: String = node_color(ntype) let radius: Int = node_radius_int(sal) let pos: String = state_get("pos_" + id) if !str_eq(pos, "") { let x: Float = get_pos_x(id) let y: Float = get_pos_y(id) let out = out + svg_node(x, y, radius, color, lbl) } let j = j + 1 } let out = out + svg_close() out }