code wiki / _hdl_build / nx_chart.nx

nx_chart.nx source

↩ module page · 202 lines · 9771 B

1// nx_chart.nx -- IMS Thrust D R2: a SOVEREIGN, REUSABLE native-SVG bar-chart renderer. 2// 3// THE GAP IT FILLS: nx_viz_chart.nx is a whole-PAGE main() that reads a fixed TSV and writes an HTML 4// file -- not a reusable function. nx_wiki_graph emits SVG but only a circular link-graph. So the gap 5// is a clean LIBRARY function chart_svg(labels[], values[], n, title) -> <svg>...</svg> that any organ 6// can call to drop a bar chart inline into a page. Pure + deterministic (no I/O, no wall-clock). 7// 8// REUSE (zero geometry reinvented): imports nx_viz_axis -> which transitively gives the proven viz 9// primitives the d3-equivalent suite already KAT-grades: 10// vs_linear (nx_viz_scale) -- map a data value -> a pixel height (the bar scaling). 11// va_axis_left(nx_viz_axis) -- the y-axis: baseline + tick marks + numeric labels. 12// vsh_lit / vsh_n (nx_viz_shape) -- append a literal / an integer into the SVG buffer. 13// We add ONLY the bar-chart composition (the bars, the x-labels, the title) -- the part that did not 14// exist as a reusable call. 15// 16// SVG is static (no <script>) -> sovereign, renders identically everywhere. license_tier: ORIGINAL 17import "nx_viz_axis.nx" 18import "nx_syscalls.nx" 19const CH_MAGIC_8192: i64 = 8192 20 21// ===== sealed verdict surface (codes 2900-2909; distinct from VP_*/IP_*/NX_WGRAPH_*) ===== 22const CH_OK: i64 = 0 23const CH_BAD_INPUT: i64 = 2900 24const CH_OVERFLOW: i64 = 2901 25 26// ===== named layout constants (M7 -- no magic numbers buried in render code) ===== 27const CH_M_L: i64 = 56 // left margin (room for the y-axis labels) 28const CH_TITLE_MAX: i64 = 200 // title chars copied into the <text> element (the cap the title loop used to write into its own cursor) 29const CH_M_T: i64 = 40 // top margin (room for the title) 30const CH_PLOT_H: i64 = 300 // plot area height in px (data range maps into this) 31const CH_BW: i64 = 64 // bar width 32const CH_GAP: i64 = 34 // gap between bars 33const CH_M_B: i64 = 44 // bottom margin (room for the x-axis labels) 34const CH_TICKS: i64 = 6 // y-axis tick target 35const CH_LBLCAP: i64 = 64 // per-label byte cap (defensive) 36 37// max of an i64 array (>=0 domain top); 1 if all <= 0 so the scale never divides by zero. 38func ch_maxv(values: *i64, n: i64) -> i64 { 39 var mx: i64 = 1 40 var i: i64 = 0 41 while i < n { 42 if values[i] > mx { mx = values[i] } 43 i = i + 1 44 } 45 return mx 46} 47 48// round a domain top UP to a "nice" round number so ticks read cleanly (e.g. 21 -> 25, 468 -> 500). 49// purely deterministic: smallest of {1,2,5} * 10^k that is >= v. 50func ch_nice_top(v: i64) -> i64 { 51 if v < 1 { return 1 } 52 var pow: i64 = 1 53 var guard: i64 = 0 54 while guard < 30 { 55 if 1 * pow >= v { return 1 * pow } 56 if 2 * pow >= v { return 2 * pow } 57 if 5 * pow >= v { return 5 * pow } 58 pow = pow * 10 59 guard = guard + 1 60 } 61 return v 62} 63 64// ============================================================================= 65// chart_svg: emit a complete <svg>...</svg> (NUL-terminated) bar chart into out. 66// labels_ptr[i]/labels_len[i] -- the i-th x-axis label (pointer+length; the codebase convention, 67// since a *u8[] cannot be passed cleanly). 68// values[i] -- the i-th bar's value (>=0 expected; negatives clamp to 0 height). 69// n -- number of bars. 70// title -- NUL-terminated chart title (drawn top-left). 71// out / cap -- caller buffer + capacity. 72// out_used -- receives bytes written (excluding the NUL). 73// Returns CH_OK or a negative CH_* verdict. PURE: no syscalls except sys_mmap scratch (via the 74// imported primitives). Deterministic for identical inputs. 75// ============================================================================= 76func chart_svg(labels_ptr: *i64, labels_len: *i64, values: *i64, n: i64, 77 title: *u8, out: *u8, cap: i64, out_used: *i64) -> i64 { 78 if (out_used as i64) == 0 { return 0 - CH_BAD_INPUT } 79 out_used[0] = 0 80 if (out as i64) == 0 { return 0 - CH_BAD_INPUT } 81 if cap < 512 { return 0 - CH_BAD_INPUT } 82 if n < 0 { return 0 - CH_BAD_INPUT } 83 84 // domain top (nice-rounded) -> the bar/axis scale. baseline = bottom of the plot. 85 let raw_top: i64 = ch_maxv(values, n) 86 let dom_top: i64 = ch_nice_top(raw_top) 87 let baseline: i64 = CH_M_T + CH_PLOT_H 88 let plot_top: i64 = CH_M_T 89 let width: i64 = CH_M_L + n * (CH_BW + CH_GAP) + CH_GAP 90 let height: i64 = baseline + CH_M_B 91 92 // local cursor + a bounded literal/number appender that checks cap (the viz vsh_* do NOT bounds- 93 // check, so we guard the cap ourselves and only call them when there is room). 94 var o: i64 = 0 95 96 // ---- <svg> open ---- 97 o = vsh_lit(out, o, "<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"nx-chart\" width=\"" as *u8) 98 o = vsh_n(out, o, width) 99 o = vsh_lit(out, o, "\" height=\"" as *u8) 100 o = vsh_n(out, o, height) 101 o = vsh_lit(out, o, "\" viewBox=\"0 0 " as *u8) 102 o = vsh_n(out, o, width); o = vsh_lit(out, o, " " as *u8); o = vsh_n(out, o, height) 103 o = vsh_lit(out, o, "\" role=\"img\" font-family=\"system-ui,Segoe UI,sans-serif\">" as *u8) 104 // sovereign inline CSS (no JS, no CDN) 105 o = vsh_lit(out, o, "<style>.nx-chart .bar{fill:#2a4d8f}.nx-chart .val{fill:#14141e;font-size:13px;font-weight:600}.nx-chart .xl{fill:#444;font-size:12px}.nx-chart .ttl{fill:#14141e;font-size:16px;font-weight:700}</style>" as *u8) 106 107 // ---- title (top-left) ---- 108 o = vsh_lit(out, o, "<text class=\"ttl\" x=\"" as *u8); o = vsh_n(out, o, CH_M_L) 109 o = vsh_lit(out, o, "\" y=\"22\">" as *u8) 110 // title capped at CH_TITLE_MAX chars by a FLAG, not by writing the cap into the cursor (2026-09-02, nx_srclint rule 2: 111 // `ti = 200` inside `while title[ti]` kept reading past the cap until the NUL instead of stopping at it) 112 var ti: i64 = 0 113 var tf: i64 = 0 114 while tf == 0 { 115 if title[ti] == (0 as u8) { tf = 1 } 116 if tf == 0 { if ti >= CH_TITLE_MAX { tf = 1 } } 117 if tf == 0 { 118 if o + 8 >= cap { return 0 - CH_OVERFLOW } 119 out[o] = title[ti]; o = o + 1 120 ti = ti + 1 121 } 122 } 123 o = vsh_lit(out, o, "</text>" as *u8) 124 125 // ---- y-axis (baseline + ticks + numeric labels) via the proven va_axis_left ---- 126 // va_axis_left(d0,d1,py0,py1,target,axis_x,out): py0 maps d0 (bottom), py1 maps d1 (top). 127 let axfrag: *u8 = sys_mmap(CH_MAGIC_8192) 128 va_axis_left(0, dom_top, baseline, plot_top, CH_TICKS, CH_M_L, axfrag) 129 o = vsh_lit(out, o, axfrag) 130 131 // ---- bars + value labels + x labels ---- 132 var b: i64 = 0 133 while b < n { 134 if b >= 256 { b = n } 135 if b < n { 136 let bx: i64 = CH_M_L + CH_GAP + b * (CH_BW + CH_GAP) 137 // bar height = value scaled into [0, CH_PLOT_H] over domain [0, dom_top]; clamp negatives. 138 var v: i64 = values[b] 139 if v < 0 { v = 0 } 140 let bh: i64 = vs_linear(0, dom_top, 0, CH_PLOT_H, v) 141 let by: i64 = baseline - bh 142 143 // rough cap guard: a bar block is < 256 bytes; require room. 144 if o + 256 >= cap { return 0 - CH_OVERFLOW } 145 146 // <rect> 147 o = vsh_lit(out, o, "<rect class=\"bar\" x=\"" as *u8); o = vsh_n(out, o, bx) 148 o = vsh_lit(out, o, "\" y=\"" as *u8); o = vsh_n(out, o, by) 149 o = vsh_lit(out, o, "\" width=\"" as *u8); o = vsh_n(out, o, CH_BW) 150 o = vsh_lit(out, o, "\" height=\"" as *u8); o = vsh_n(out, o, bh) 151 o = vsh_lit(out, o, "\" rx=\"4\"/>" as *u8) 152 153 // value label above the bar (the original data value, not the scaled height) 154 o = vsh_lit(out, o, "<text class=\"val\" text-anchor=\"middle\" x=\"" as *u8) 155 o = vsh_n(out, o, bx + CH_BW / 2) 156 o = vsh_lit(out, o, "\" y=\"" as *u8); o = vsh_n(out, o, by - 6) 157 o = vsh_lit(out, o, "\">" as *u8); o = vsh_n(out, o, values[b]); o = vsh_lit(out, o, "</text>" as *u8) 158 159 // x-axis label centred under the bar (caller-supplied, bounded copy) 160 o = vsh_lit(out, o, "<text class=\"xl\" text-anchor=\"middle\" x=\"" as *u8) 161 o = vsh_n(out, o, bx + CH_BW / 2) 162 o = vsh_lit(out, o, "\" y=\"" as *u8); o = vsh_n(out, o, baseline + 20) 163 o = vsh_lit(out, o, "\">" as *u8) 164 let lp: *u8 = labels_ptr[b] as *u8 165 let ll: i64 = labels_len[b] 166 var k: i64 = 0 167 while k < ll { 168 if k >= CH_LBLCAP { k = ll } 169 if k < ll { 170 if o + 8 >= cap { return 0 - CH_OVERFLOW } 171 out[o] = lp[k]; o = o + 1 172 } 173 k = k + 1 174 } 175 o = vsh_lit(out, o, "</text>" as *u8) 176 } 177 b = b + 1 178 } 179 180 o = vsh_lit(out, o, "</svg>" as *u8) 181 if o + 1 >= cap { return 0 - CH_OVERFLOW } 182 out[o] = 0 as u8 183 out_used[0] = o 184 return CH_OK 185} 186 187// compile smoke; the real KAT lives in nx_figure_gate. (library organ; no main needed at link if 188// imported -- but a main keeps it independently buildable.) 189func main() -> i64 { 190 let lp: *i64 = sys_mmap(64) as *i64 191 let ll: *i64 = sys_mmap(64) as *i64 192 let vv: *i64 = sys_mmap(64) as *i64 193 lp[0] = "a" as *u8 as i64; ll[0] = 1; vv[0] = 8 194 lp[1] = "b" as *u8 as i64; ll[1] = 1; vv[1] = 21 195 let out: *u8 = sys_mmap(CH_MAGIC_8192) 196 let used: *i64 = sys_mmap(16) as *i64 197 let rc: i64 = chart_svg(lp, ll, vv, 2, "smoke" as *u8, out, CH_MAGIC_8192, used) 198 if rc != CH_OK { return 1 } 199 if used[0] <= 0 { return 2 } 200 if out[0] != 60 as u8 { return 3 } // '<' 201 return 0 202}