code wiki / _hdl_build / nx_chart.nx

nx_chart.nx source

↩ module page · 197 lines · 9325 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_M_T: i64 = 40 // top margin (room for the title) 29const CH_PLOT_H: i64 = 300 // plot area height in px (data range maps into this) 30const CH_BW: i64 = 64 // bar width 31const CH_GAP: i64 = 34 // gap between bars 32const CH_M_B: i64 = 44 // bottom margin (room for the x-axis labels) 33const CH_TICKS: i64 = 6 // y-axis tick target 34const CH_LBLCAP: i64 = 64 // per-label byte cap (defensive) 35 36// max of an i64 array (>=0 domain top); 1 if all <= 0 so the scale never divides by zero. 37func ch_maxv(values: *i64, n: i64) -> i64 { 38 var mx: i64 = 1 39 var i: i64 = 0 40 while i < n { 41 if values[i] > mx { mx = values[i] } 42 i = i + 1 43 } 44 return mx 45} 46 47// round a domain top UP to a "nice" round number so ticks read cleanly (e.g. 21 -> 25, 468 -> 500). 48// purely deterministic: smallest of {1,2,5} * 10^k that is >= v. 49func ch_nice_top(v: i64) -> i64 { 50 if v < 1 { return 1 } 51 var pow: i64 = 1 52 var guard: i64 = 0 53 while guard < 30 { 54 if 1 * pow >= v { return 1 * pow } 55 if 2 * pow >= v { return 2 * pow } 56 if 5 * pow >= v { return 5 * pow } 57 pow = pow * 10 58 guard = guard + 1 59 } 60 return v 61} 62 63// ============================================================================= 64// chart_svg: emit a complete <svg>...</svg> (NUL-terminated) bar chart into out. 65// labels_ptr[i]/labels_len[i] -- the i-th x-axis label (pointer+length; the codebase convention, 66// since a *u8[] cannot be passed cleanly). 67// values[i] -- the i-th bar's value (>=0 expected; negatives clamp to 0 height). 68// n -- number of bars. 69// title -- NUL-terminated chart title (drawn top-left). 70// out / cap -- caller buffer + capacity. 71// out_used -- receives bytes written (excluding the NUL). 72// Returns CH_OK or a negative CH_* verdict. PURE: no syscalls except sys_mmap scratch (via the 73// imported primitives). Deterministic for identical inputs. 74// ============================================================================= 75func chart_svg(labels_ptr: *i64, labels_len: *i64, values: *i64, n: i64, 76 title: *u8, out: *u8, cap: i64, out_used: *i64) -> i64 { 77 if (out_used as i64) == 0 { return 0 - CH_BAD_INPUT } 78 out_used[0] = 0 79 if (out as i64) == 0 { return 0 - CH_BAD_INPUT } 80 if cap < 512 { return 0 - CH_BAD_INPUT } 81 if n < 0 { return 0 - CH_BAD_INPUT } 82 83 // domain top (nice-rounded) -> the bar/axis scale. baseline = bottom of the plot. 84 let raw_top: i64 = ch_maxv(values, n) 85 let dom_top: i64 = ch_nice_top(raw_top) 86 let baseline: i64 = CH_M_T + CH_PLOT_H 87 let plot_top: i64 = CH_M_T 88 let width: i64 = CH_M_L + n * (CH_BW + CH_GAP) + CH_GAP 89 let height: i64 = baseline + CH_M_B 90 91 // local cursor + a bounded literal/number appender that checks cap (the viz vsh_* do NOT bounds- 92 // check, so we guard the cap ourselves and only call them when there is room). 93 var o: i64 = 0 94 95 // ---- <svg> open ---- 96 o = vsh_lit(out, o, "<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"nx-chart\" width=\"" as *u8) 97 o = vsh_n(out, o, width) 98 o = vsh_lit(out, o, "\" height=\"" as *u8) 99 o = vsh_n(out, o, height) 100 o = vsh_lit(out, o, "\" viewBox=\"0 0 " as *u8) 101 o = vsh_n(out, o, width); o = vsh_lit(out, o, " " as *u8); o = vsh_n(out, o, height) 102 o = vsh_lit(out, o, "\" role=\"img\" font-family=\"system-ui,Segoe UI,sans-serif\">" as *u8) 103 // sovereign inline CSS (no JS, no CDN) 104 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) 105 106 // ---- title (top-left) ---- 107 o = vsh_lit(out, o, "<text class=\"ttl\" x=\"" as *u8); o = vsh_n(out, o, CH_M_L) 108 o = vsh_lit(out, o, "\" y=\"22\">" as *u8) 109 var ti: i64 = 0 110 while title[ti] != (0 as u8) { 111 if ti >= 200 { ti = 200 } 112 if ti < 200 { 113 if o + 8 >= cap { return 0 - CH_OVERFLOW } 114 out[o] = title[ti]; o = o + 1 115 } 116 ti = ti + 1 117 } 118 o = vsh_lit(out, o, "</text>" as *u8) 119 120 // ---- y-axis (baseline + ticks + numeric labels) via the proven va_axis_left ---- 121 // va_axis_left(d0,d1,py0,py1,target,axis_x,out): py0 maps d0 (bottom), py1 maps d1 (top). 122 let axfrag: *u8 = sys_mmap(CH_MAGIC_8192) 123 va_axis_left(0, dom_top, baseline, plot_top, CH_TICKS, CH_M_L, axfrag) 124 o = vsh_lit(out, o, axfrag) 125 126 // ---- bars + value labels + x labels ---- 127 var b: i64 = 0 128 while b < n { 129 if b >= 256 { b = n } 130 if b < n { 131 let bx: i64 = CH_M_L + CH_GAP + b * (CH_BW + CH_GAP) 132 // bar height = value scaled into [0, CH_PLOT_H] over domain [0, dom_top]; clamp negatives. 133 var v: i64 = values[b] 134 if v < 0 { v = 0 } 135 let bh: i64 = vs_linear(0, dom_top, 0, CH_PLOT_H, v) 136 let by: i64 = baseline - bh 137 138 // rough cap guard: a bar block is < 256 bytes; require room. 139 if o + 256 >= cap { return 0 - CH_OVERFLOW } 140 141 // <rect> 142 o = vsh_lit(out, o, "<rect class=\"bar\" x=\"" as *u8); o = vsh_n(out, o, bx) 143 o = vsh_lit(out, o, "\" y=\"" as *u8); o = vsh_n(out, o, by) 144 o = vsh_lit(out, o, "\" width=\"" as *u8); o = vsh_n(out, o, CH_BW) 145 o = vsh_lit(out, o, "\" height=\"" as *u8); o = vsh_n(out, o, bh) 146 o = vsh_lit(out, o, "\" rx=\"4\"/>" as *u8) 147 148 // value label above the bar (the original data value, not the scaled height) 149 o = vsh_lit(out, o, "<text class=\"val\" text-anchor=\"middle\" x=\"" as *u8) 150 o = vsh_n(out, o, bx + CH_BW / 2) 151 o = vsh_lit(out, o, "\" y=\"" as *u8); o = vsh_n(out, o, by - 6) 152 o = vsh_lit(out, o, "\">" as *u8); o = vsh_n(out, o, values[b]); o = vsh_lit(out, o, "</text>" as *u8) 153 154 // x-axis label centred under the bar (caller-supplied, bounded copy) 155 o = vsh_lit(out, o, "<text class=\"xl\" text-anchor=\"middle\" x=\"" as *u8) 156 o = vsh_n(out, o, bx + CH_BW / 2) 157 o = vsh_lit(out, o, "\" y=\"" as *u8); o = vsh_n(out, o, baseline + 20) 158 o = vsh_lit(out, o, "\">" as *u8) 159 let lp: *u8 = labels_ptr[b] as *u8 160 let ll: i64 = labels_len[b] 161 var k: i64 = 0 162 while k < ll { 163 if k >= CH_LBLCAP { k = ll } 164 if k < ll { 165 if o + 8 >= cap { return 0 - CH_OVERFLOW } 166 out[o] = lp[k]; o = o + 1 167 } 168 k = k + 1 169 } 170 o = vsh_lit(out, o, "</text>" as *u8) 171 } 172 b = b + 1 173 } 174 175 o = vsh_lit(out, o, "</svg>" as *u8) 176 if o + 1 >= cap { return 0 - CH_OVERFLOW } 177 out[o] = 0 as u8 178 out_used[0] = o 179 return CH_OK 180} 181 182// compile smoke; the real KAT lives in nx_figure_gate. (library organ; no main needed at link if 183// imported -- but a main keeps it independently buildable.) 184func main() -> i64 { 185 let lp: *i64 = sys_mmap(64) as *i64 186 let ll: *i64 = sys_mmap(64) as *i64 187 let vv: *i64 = sys_mmap(64) as *i64 188 lp[0] = "a" as *u8 as i64; ll[0] = 1; vv[0] = 8 189 lp[1] = "b" as *u8 as i64; ll[1] = 1; vv[1] = 21 190 let out: *u8 = sys_mmap(CH_MAGIC_8192) 191 let used: *i64 = sys_mmap(16) as *i64 192 let rc: i64 = chart_svg(lp, ll, vv, 2, "smoke" as *u8, out, CH_MAGIC_8192, used) 193 if rc != CH_OK { return 1 } 194 if used[0] <= 0 { return 2 } 195 if out[0] != 60 as u8 { return 3 } // '<' 196 return 0 197}