nx_render_svg.nx source
↩ module page · 156 lines · 5453 B
1// nx_render_svg.nx -- native SVG (browser-visual) emitter.
2//
3// Per user 2026-05-15: "build the visual in cli, browser, etc it
4// should be native".
5//
6// Emits SVG markup as text -- standard XML. No JS dependency, no
7// canvas runtime. Output can be:
8// 1. piped to a .svg file and opened in any browser
9// 2. served by the sovereign nishi_web HTTP server (memory: Sovereign
10// nishi-web project) for live in-browser viewing
11//
12// Pure stdout writes. Caller redirects to a file via shell.
13
14// nx_safety_envelope:
15// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
16// sil_target: SIL1
17// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
18// verdict: NOT_YET_EVALUATED
19
20import "nx_kernel_v2.nx"
21
22const NX_SVG_W: nx_int = 600
23const NX_SVG_H: nx_int = 400
24
25func nx_svg_header(width: nx_int, height: nx_int) -> nx_int {
26 print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" as *u8); println("" as *u8)
27 print("<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"" as *u8)
28 print_i64(width)
29 print("\" height=\"" as *u8)
30 print_i64(height)
31 print("\" viewBox=\"0 0 " as *u8)
32 print_i64(width)
33 print(" " as *u8)
34 print_i64(height)
35 print("\">" as *u8); println("" as *u8)
36 return 0
37}
38
39func nx_svg_footer() -> nx_int {
40 println("</svg>" as *u8)
41 return 0
42}
43
44func nx_svg_rect(x: nx_int, y: nx_int, w: nx_int, h: nx_int, fill: *u8) -> nx_int {
45 print("<rect x=\"" as *u8); print_i64(x)
46 print("\" y=\"" as *u8); print_i64(y)
47 print("\" width=\"" as *u8); print_i64(w)
48 print("\" height=\"" as *u8); print_i64(h)
49 print("\" fill=\"" as *u8); print(fill)
50 print("\"/>" as *u8); println("" as *u8)
51 return 0
52}
53
54func nx_svg_line(x1: nx_int, y1: nx_int, x2: nx_int, y2: nx_int, stroke: *u8) -> nx_int {
55 print("<line x1=\"" as *u8); print_i64(x1)
56 print("\" y1=\"" as *u8); print_i64(y1)
57 print("\" x2=\"" as *u8); print_i64(x2)
58 print("\" y2=\"" as *u8); print_i64(y2)
59 print("\" stroke=\"" as *u8); print(stroke)
60 print("\" stroke-width=\"2\"/>" as *u8); println("" as *u8)
61 return 0
62}
63
64func nx_svg_circle(cx: nx_int, cy: nx_int, r: nx_int, fill: *u8) -> nx_int {
65 print("<circle cx=\"" as *u8); print_i64(cx)
66 print("\" cy=\"" as *u8); print_i64(cy)
67 print("\" r=\"" as *u8); print_i64(r)
68 print("\" fill=\"" as *u8); print(fill)
69 print("\"/>" as *u8); println("" as *u8)
70 return 0
71}
72
73func nx_svg_text(x: nx_int, y: nx_int, s: *u8) -> nx_int {
74 print("<text x=\"" as *u8); print_i64(x)
75 print("\" y=\"" as *u8); print_i64(y)
76 print("\" font-family=\"monospace\" font-size=\"14\">" as *u8)
77 print(s)
78 print("</text>" as *u8); println("" as *u8)
79 return 0
80}
81
82// ===== Bar chart -- emits a complete SVG document ===================
83func nx_svg_bar_chart(values: *nx_int, n: nx_int, title: *u8) -> nx_int {
84 let _h: nx_int = nx_svg_header(NX_SVG_W, NX_SVG_H)
85 let _t: nx_int = nx_svg_text(20, 24, title)
86 // Find max
87 var max_v: nx_int = 0
88 var i: nx_int = 0
89 while i < n {
90 let p: *nx_int = ((values as nx_int) + (i * 8)) as *nx_int
91 if p[0] > max_v { max_v = p[0] }
92 i = i + 1
93 }
94 if max_v <= 0 { max_v = 1 }
95 let bar_w: nx_int = (NX_SVG_W - 60) / n
96 let chart_h: nx_int = NX_SVG_H - 80
97 var k: nx_int = 0
98 while k < n {
99 let pv: *nx_int = ((values as nx_int) + (k * 8)) as *nx_int
100 let h: nx_int = (pv[0] * chart_h) / max_v
101 let x: nx_int = 30 + k * bar_w
102 let y: nx_int = NX_SVG_H - 40 - h
103 let _r: nx_int = nx_svg_rect(x, y, bar_w - 4, h, "steelblue" as *u8)
104 i = i + 1
105 k = k + 1
106 }
107 // Axis line
108 let _ax: nx_int = nx_svg_line(20, NX_SVG_H - 40, NX_SVG_W - 20, NX_SVG_H - 40, "black" as *u8)
109 let _f: nx_int = nx_svg_footer()
110 return n
111}
112
113// ===== Line plot -- complete SVG ====================================
114func nx_svg_line_plot(values: *nx_int, n: nx_int, title: *u8) -> nx_int {
115 if n < 2 { return 0 }
116 let _h: nx_int = nx_svg_header(NX_SVG_W, NX_SVG_H)
117 let _t: nx_int = nx_svg_text(20, 24, title)
118 // Find min/max
119 var p0: *nx_int = values
120 var min_v: nx_int = p0[0]
121 var max_v: nx_int = p0[0]
122 var i: nx_int = 1
123 while i < n {
124 let p: *nx_int = ((values as nx_int) + (i * 8)) as *nx_int
125 if p[0] < min_v { min_v = p[0] }
126 if p[0] > max_v { max_v = p[0] }
127 i = i + 1
128 }
129 var range: nx_int = max_v - min_v
130 if range <= 0 { range = 1 }
131 let chart_w: nx_int = NX_SVG_W - 60
132 let chart_h: nx_int = NX_SVG_H - 80
133
134 // Axes
135 let _xax: nx_int = nx_svg_line(40, NX_SVG_H - 40, NX_SVG_W - 20, NX_SVG_H - 40, "black" as *u8)
136 let _yax: nx_int = nx_svg_line(40, 30, 40, NX_SVG_H - 40, "black" as *u8)
137
138 // Polyline as a series of line segments + circles.
139 var k: nx_int = 0
140 var prev_x: nx_int = 0
141 var prev_y: nx_int = 0
142 while k < n {
143 let pv: *nx_int = ((values as nx_int) + (k * 8)) as *nx_int
144 let x: nx_int = 40 + (k * chart_w) / (n - 1)
145 let y: nx_int = NX_SVG_H - 40 - ((pv[0] - min_v) * chart_h) / range
146 if k > 0 {
147 let _ln: nx_int = nx_svg_line(prev_x, prev_y, x, y, "crimson" as *u8)
148 }
149 let _ci: nx_int = nx_svg_circle(x, y, 3, "crimson" as *u8)
150 prev_x = x
151 prev_y = y
152 k = k + 1
153 }
154 let _f: nx_int = nx_svg_footer()
155 return n
156}