Alien-libhisto
view release on metacpan or search on metacpan
bundled/tools/src/cmd_top.c view on Meta::CPAN
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <stdbool.h>
#include <ctype.h>
#include <math.h>
#include <unistd.h>
#include <fcntl.h>
typedef enum {
VIEW_1D_BARS = 0,
VIEW_SPARKLINE,
VIEW_CDF,
VIEW_2D_HEATMAP
} tui_view_mode_t;
typedef enum {
SCALE_LINEAR = 0,
SCALE_LOG_Y,
SCALE_LOG_X,
SCALE_LOG_LOG
} tui_scale_mode_t;
typedef enum {
MODAL_NONE = 0,
MODAL_HELP,
MODAL_SCALE_PICKER
} tui_modal_type_t;
typedef struct {
tui_view_mode_t view_mode;
tui_scale_mode_t scale_mode;
tui_modal_type_t modal;
histo_palette_t palette;
bool show_kde;
bool show_fit;
bool show_errors;
bool show_legend; /* 2D: color range reference bar */
bool show_y_axis; /* 1D: count axis ruler tick line */
bool monochrome;
bool paused;
bool cmd_active;
bool log_z; /* 2D: logarithmic intensity scaling */
bool compact_header;
bool auto_bins;
int inspect_row;
char cmd_buf[256];
size_t cmd_len;
char status_msg[128];
double status_msg_time;
histo_t *frozen_snapshot;
histo2d_t *frozen_snapshot_2d;
} tui_state_t;
static const char *const BLOCKS_UTF8[9] = { " ", "â", "â", "â", "â", "â", "â", "â", "â" };
static void render_top_border(tui_frame_t *f, const char *title, const char *badge, int width) {
tui_frame_printf(f, "\033[1mââ %s ", title);
int title_cols = 3 + (int)strlen(title) + 1; // "ââ " (3) + title + " " (1)
int badge_cols = (badge && badge[0]) ? (tui_visual_width(badge) + 4) : 2; // " " + badge + " ââ"
int dashes = width - title_cols - badge_cols;
if (dashes < 0) dashes = 0;
for (int i = 0; i < dashes; ++i) tui_frame_puts(f, "â");
if (badge && badge[0]) {
tui_frame_printf(f, " %s ââ\033[0m\r\n", badge);
} else {
tui_frame_puts(f, "ââ\033[0m\r\n");
}
}
static void render_divider(tui_frame_t *f, int width) {
tui_frame_puts(f, "â");
for (int i = 0; i < width - 2; ++i) tui_frame_puts(f, "â");
tui_frame_puts(f, "â¤\r\n");
}
static void render_bottom_border(tui_frame_t *f, int width, bool newline) {
tui_frame_puts(f, "â");
for (int i = 0; i < width - 2; ++i) tui_frame_puts(f, "â");
tui_frame_puts(f, "â");
if (newline) tui_frame_puts(f, "\r\n");
}
static void render_2d_heatmap_viewport(tui_frame_t *f, const tui_state_t *st, const histo2d_t *h, int width, int max_rows) {
if (!h || max_rows <= 0) {
for (int r = 0; r < max_rows; ++r) tui_render_row(f, "", width, true);
return;
}
uint32_t nx = histo2d_nbins_x(h);
uint32_t ny = histo2d_nbins_y(h);
if (nx == 0 || ny == 0) {
for (int r = 0; r < max_rows; ++r) tui_render_row(f, "", width, true);
return;
}
histo2d_axis_t ax, ay;
histo2d_axis_x(h, &ax);
histo2d_axis_y(h, &ay);
double max_content = 0.0;
for (uint32_t ix = 0; ix < nx; ++ix) {
for (uint32_t iy = 0; iy < ny; ++iy) {
double c = 0.0;
histo2d_bin_content(h, ix, iy, &c);
if (c > max_content) max_content = c;
}
}
double max_scaled = (st->log_z && max_content > 0.0) ? log10(max_content + 1.0) : max_content;
if (max_scaled <= 0.0) max_scaled = 1.0;
/* Y-axis label width: " yval â " = ~10 chars */
int label_cols = 10;
/* Available width for heatmap cells: 2 chars per X bin */
int avail_cols = width - label_cols - 4;
uint32_t x_step = 1;
if ((int)(nx * 2) > avail_cols && avail_cols > 0) {
x_step = (nx * 2 + (uint32_t)avail_cols - 1) / (uint32_t)avail_cols;
}
/* Y rows: map ny bins to max_rows, stepping if needed */
/* Reserve 2 rows for X-axis line and labels, plus 1 if legend active */
int leg_rows = st->show_legend ? 1 : 0;
int heatmap_rows = max_rows - 2 - leg_rows;
if (heatmap_rows < 1) heatmap_rows = 1;
uint32_t y_step = (ny > (uint32_t)heatmap_rows) ? (ny + (uint32_t)heatmap_rows - 1) / (uint32_t)heatmap_rows : 1;
int rows_drawn = 0;
/* Render Y rows from top (ny-1) to bottom (0) */
for (int iy = (int)ny - 1; iy >= 0 && rows_drawn < heatmap_rows; iy -= (int)y_step) {
char row_buf[4096];
double ymin_b, ymax_b, dummy;
histo2d_bin_bounds(h, 0, (uint32_t)iy, &dummy, &dummy, &ymin_b, &ymax_b);
double y_center = 0.5 * (ymin_b + ymax_b);
int pos = snprintf(row_buf, sizeof(row_buf), "%7.1f â ", y_center);
for (uint32_t ix = 0; ix < nx && pos + 32 < (int)sizeof(row_buf); ix += x_step) {
double c = 0.0;
histo2d_bin_content(h, ix, (uint32_t)iy, &c);
double val_scaled = (st->log_z && c > 0.0) ? log10(c + 1.0) : c;
double frac = (val_scaled > 0.0) ? (val_scaled / max_scaled) : 0.0;
bundled/tools/src/cmd_top.c view on Meta::CPAN
/* X-axis labels row */
if (rows_drawn < max_rows) {
char label_buf[512];
char min_str[32], max_str[32];
snprintf(min_str, sizeof(min_str), "%.1f", ax.min);
snprintf(max_str, sizeof(max_str), "%.1f", ax.max);
uint32_t x_cells = (nx + x_step - 1) / x_step;
int bar_w = (int)(x_cells * 2 + 1);
if (bar_w < 10) bar_w = 10;
char l_bar[256];
if (bar_w > (int)sizeof(l_bar) - 1) bar_w = (int)sizeof(l_bar) - 1;
memset(l_bar, ' ', bar_w);
l_bar[bar_w] = '\0';
memcpy(l_bar, min_str, strlen(min_str));
int max_st = bar_w - (int)strlen(max_str);
if (max_st > (int)strlen(min_str)) {
memcpy(l_bar + max_st, max_str, strlen(max_str));
}
snprintf(label_buf, sizeof(label_buf), " %s", l_bar);
tui_render_row(f, label_buf, width, true);
rows_drawn++;
}
/* Optional Color Range Legend reference bar */
if (st->show_legend && rows_drawn < max_rows) {
char leg_buf[4096];
if (st->monochrome) {
snprintf(leg_buf, sizeof(leg_buf), " Scale: [ .:-=+*#%%@ ] (0.00 -> %.2e) [%s]", max_content, st->log_z ? "LOG-Z" : "LIN");
} else {
int pos = snprintf(leg_buf, sizeof(leg_buf), " Color Scale: 0.00 ");
for (int step = 0; step <= 16 && pos + 32 < (int)sizeof(leg_buf); ++step) {
double frac = (double)step / 16.0;
char bg[32];
histo_palette_sample_ansi_bg(st->palette, frac, bg, sizeof(bg));
pos += snprintf(leg_buf + pos, sizeof(leg_buf) - pos, "%s \033[0m", bg);
}
pos += snprintf(leg_buf + pos, sizeof(leg_buf) - pos, " %.2e [%s, %s]", max_content, histo_palette_name(st->palette), st->log_z ? "LOG-Z" : "LIN");
}
tui_render_row(f, leg_buf, width, true);
rows_drawn++;
}
while (rows_drawn < max_rows) {
tui_render_row(f, "", width, true);
rows_drawn++;
}
}
static void format_top_coord(char *buf, size_t sz, double val, bool use_sci) {
if (use_sci) {
snprintf(buf, sz, "%8.2e", val);
} else if (fabs(val) >= 10000.0) {
snprintf(buf, sz, "%8.1f", val);
} else if (fabs(val) >= 100.0) {
snprintf(buf, sz, "%7.2f", val);
} else {
snprintf(buf, sz, "%6.2f", val);
}
}
static void render_1d_bars_viewport(tui_frame_t *f, const tui_state_t *st, const histo_t *h, int width, int max_rows) {
if (!h || max_rows <= 0) {
for (int r = 0; r < max_rows; ++r) tui_render_row(f, "", width, true);
return;
}
uint32_t nbins = histo_nbins(h);
if (nbins == 0) {
for (int r = 0; r < max_rows; ++r) tui_render_row(f, "", width, true);
return;
}
double total_w = histo_total_weight(h);
uint64_t num_entries = histo_num_entries(h);
histo_kde_t *kde = NULL;
if (st->show_kde && num_entries >= 5) {
kde = histo_kde_create_from_histo(h, NULL);
}
histo_fit_result_t *fit_res = NULL;
if (st->show_fit && num_entries >= 5) {
histo_fit_model(h, HISTO_FIT_MODEL_GAUSSIAN, NULL, NULL, &fit_res);
}
double max_content = 0.0;
for (uint32_t i = 0; i < nbins; ++i) {
double c = 0.0;
histo_bin_content(h, i, &c);
if (c > max_content) max_content = c;
}
double max_scaled = (st->scale_mode == SCALE_LOG_Y || st->scale_mode == SCALE_LOG_LOG) ?
log10(max_content + 1.0) : max_content;
if (max_scaled <= 0.0) max_scaled = 1.0;
uint32_t step = (nbins > (uint32_t)max_rows) ? (nbins + (uint32_t)max_rows - 1) / (uint32_t)max_rows : 1;
double r_min = 0.0, r_max = 0.0;
histo_range(h, &r_min, &r_max);
bool use_sci = (fabs(r_max) >= 1e5 || fabs(r_min) >= 1e5 || (r_max - r_min > 0.0 && r_max - r_min < 0.001));
int max_bounds_len = 0;
int max_count_len = 0;
for (uint32_t i = 0; i < nbins; i += step) {
double lower = 0.0, upper = 0.0, content = 0.0;
histo_bin_bounds(h, i, &lower, &upper);
histo_bin_content(h, i, &content);
char b1[32], b2[32], b_tmp[128], c_tmp[32];
format_top_coord(b1, sizeof(b1), lower, use_sci);
format_top_coord(b2, sizeof(b2), upper, use_sci);
int blen = snprintf(b_tmp, sizeof(b_tmp), "[%s, %s)", b1, b2);
if (blen > max_bounds_len) max_bounds_len = blen;
int clen;
if (content == floor(content) && content >= 0.0 && content < 1e9) {
clen = snprintf(c_tmp, sizeof(c_tmp), "%7.0f", content);
} else if (fabs(content) < 1e6) {
clen = snprintf(c_tmp, sizeof(c_tmp), "%7.2f", content);
bundled/tools/src/cmd_top.c view on Meta::CPAN
tui_term_get_color(frac, true, color_ansi, sizeof(color_ansi));
} else {
histo_palette_sample_ansi_fg(st->palette, frac, color_ansi, sizeof(color_ansi));
}
int pos = snprintf(row_buf, sizeof(row_buf), "%-*s â %*s â ", max_bounds_len, bounds_str, max_count_len, count_str);
int max_col = full_chars + (rem_eighths > 0 ? 1 : 0);
if (kde_col >= max_col) max_col = kde_col + 1;
if (fit_col >= max_col) max_col = fit_col + 1;
if (max_col > bar_max) max_col = bar_max;
bool in_bar_color = false;
for (int k = 0; k < max_col && pos + 32 < (int)sizeof(row_buf); ++k) {
if (k == kde_col && k == fit_col) {
if (in_bar_color) { pos += snprintf(row_buf + pos, sizeof(row_buf) - pos, "\033[0m"); in_bar_color = false; }
pos += snprintf(row_buf + pos, sizeof(row_buf) - pos, "\033[1;33mâ¦\033[0m");
} else if (k == kde_col) {
if (in_bar_color) { pos += snprintf(row_buf + pos, sizeof(row_buf) - pos, "\033[0m"); in_bar_color = false; }
pos += snprintf(row_buf + pos, sizeof(row_buf) - pos, "\033[1;36mâ\033[0m");
} else if (k == fit_col) {
if (in_bar_color) { pos += snprintf(row_buf + pos, sizeof(row_buf) - pos, "\033[0m"); in_bar_color = false; }
pos += snprintf(row_buf + pos, sizeof(row_buf) - pos, "\033[1;35mâ\033[0m");
} else if (k < full_chars) {
if (!in_bar_color) { pos += snprintf(row_buf + pos, sizeof(row_buf) - pos, "%s", color_ansi); in_bar_color = true; }
pos += snprintf(row_buf + pos, sizeof(row_buf) - pos, "â");
} else if (k == full_chars && rem_eighths > 0) {
if (!in_bar_color) { pos += snprintf(row_buf + pos, sizeof(row_buf) - pos, "%s", color_ansi); in_bar_color = true; }
pos += snprintf(row_buf + pos, sizeof(row_buf) - pos, "%s", BLOCKS_UTF8[rem_eighths]);
} else {
if (in_bar_color) { pos += snprintf(row_buf + pos, sizeof(row_buf) - pos, "\033[0m"); in_bar_color = false; }
pos += snprintf(row_buf + pos, sizeof(row_buf) - pos, " ");
}
}
if (in_bar_color) {
pos += snprintf(row_buf + pos, sizeof(row_buf) - pos, "\033[0m");
}
/* Error whiskers if enabled */
if (st->show_errors && total_w > 0.0) {
double err = 0.0;
histo_bin_error(h, i, &err);
if (err > 0.0 && pos + 32 < (int)sizeof(row_buf)) {
pos += snprintf(row_buf + pos, sizeof(row_buf) - pos, "\033[90m â±%.2gâ\033[0m", err);
}
}
tui_render_row(f, row_buf, width, true);
rows_drawn++;
}
if (kde) histo_kde_destroy(kde);
if (fit_res) histo_fit_result_destroy(fit_res);
while (rows_drawn < max_rows) {
tui_render_row(f, "", width, true);
rows_drawn++;
}
}
static void render_help_viewport(tui_frame_t *f, const tui_state_t *st, int width, int max_rows) {
if (st && st->view_mode == VIEW_2D_HEATMAP) {
const char *help_lines_2d[] = {
"ââ Interactive Help Cheatsheet (2D Heatmap Mode) âââââââââââââââââââââââââââ",
"",
"DISPLAY & INTENSITY SCALING",
" l Toggle Log-Z intensity color scale (LIN <-> LOG-Z)",
" p / P Cycle color palettes (viridis, plasma, inferno, magma, turbo, cividis, gray, rainbow)",
" C Toggle TrueColor / Monochrome ASCII density",
" g Toggle Color Range Legend reference bar",
" [Space] Freeze / Unfreeze live streaming display",
" c Clear all accumulators and reset live count",
"",
"COMMANDS & PROMPT (:)",
" :palette <NAME> Set color palette (or shorthand :p <NAME>)",
" :clear Clear accumulators",
" :quit / :q Quit application cleanly",
" :help Open this help modal",
"",
"Press [?], [Esc], or [Space] to dismiss this help window."
};
size_t n_lines = sizeof(help_lines_2d) / sizeof(help_lines_2d[0]);
int rows_drawn = 0;
for (size_t i = 0; i < n_lines && rows_drawn < max_rows; ++i) {
tui_render_row(f, help_lines_2d[i], width, true);
rows_drawn++;
}
while (rows_drawn < max_rows) {
tui_render_row(f, "", width, true);
rows_drawn++;
}
return;
}
const char *help_lines[] = {
"ââ Interactive Help Cheatsheet âââââââââââââââââââââââââââââââââââââââââââ",
"",
"NAVIGATION & VIEWPORT DISPLAY & SCALING",
" + / - , Wheel Zoom in / out l Cycle Log scales (Y, X, Log-Log)",
" â/â, h/l Pan viewport left/right p Cycle color palettes",
" 0 Reset to full range a Toggle Dynamic Auto-Range",
" r / R Rebin coarser / finer C Toggle TrueColor / Monochrome",
" k Toggle KDE curve f Toggle Curve Fit overlay",
" Tab / 1..9 Switch active column s Save snapshot to disk",
" w Cycle rolling window H Toggle Compact header mode",
" B Auto-fit bins to rows Click Inspect bin details",
"COMMANDS & PROMPT (:)",
" :bins <N|auto> Set bin count / auto-fit :save [path] Save binary / JSON snapshot",
" :col <N> Select metric column (1..16) :window <N> Set rolling window size",
" :decay <lambda> Set exponential decay rate :compact [on] Toggle compact header",
" :range A B Set range (e.g. :r 0 100) [Space] Freeze / Unfreeze display",
" :autorange Toggle dynamic autorange c Clear all accumulators",
" :help Open full help modal q Quit cleanly",
"",
"Press [?], [Esc], or [Space] to dismiss this help window."
};
size_t n_lines = sizeof(help_lines) / sizeof(help_lines[0]);
int rows_drawn = 0;
bundled/tools/src/cmd_top.c view on Meta::CPAN
palette = histo_palette_from_name(arg + 11);
} else if (strcmp(arg, "-M") == 0 || strcmp(arg, "--mono") == 0) {
monochrome = true;
} else if (arg[0] != '-') {
file_arg = arg;
}
}
FILE *in_fp = stdin;
if (file_arg && strcmp(file_arg, "-") != 0) {
in_fp = fopen(file_arg, "r");
if (!in_fp) {
fprintf(stderr, "Error: Cannot open input file '%s'\n", file_arg);
return 1;
}
}
tui_engine_t eng;
if (is_2d) {
if (!tui_engine_init(&eng, in_fp, true, xbins, xmin, xmax, flags, has_weights)) {
fprintf(stderr, "Error: Failed to initialize 2D TUI engine.\n");
if (in_fp != stdin) fclose(in_fp);
return 1;
}
if (eng.live_2d && (xbins != ybins || xmin != ymin || xmax != ymax)) {
histo2d_destroy(eng.live_2d);
eng.live_2d = histo2d_create_uniform(xbins, xmin, xmax, ybins, ymin, ymax, flags);
}
} else {
if (!tui_engine_init(&eng, in_fp, false, nbins, rmin, rmax, flags, has_weights)) {
fprintf(stderr, "Error: Failed to initialize TUI engine.\n");
if (in_fp != stdin) fclose(in_fp);
return 1;
}
}
eng.auto_range = auto_range;
eng.delim = delim;
eng.val_col = val_col;
eng.x_col = x_col;
eng.y_col = y_col;
eng.w_col = (has_weights && w_col == 2 && is_2d) ? 3 : w_col;
if (!tui_term_init() || !tui_term_raw_enter()) {
fprintf(stderr, "Error: Failed to initialize terminal raw mode.\n");
tui_engine_free(&eng);
if (in_fp != stdin) fclose(in_fp);
return 1;
}
tui_state_t st;
memset(&st, 0, sizeof(st));
st.monochrome = monochrome;
st.palette = palette;
st.view_mode = is_2d ? VIEW_2D_HEATMAP : VIEW_1D_BARS;
tui_engine_start(&eng);
int tty_fd = tui_term_get_tty_fd();
tui_frame_t frame;
tui_frame_init(&frame, 65536);
while (eng.running) {
tui_key_event_t ev = tui_term_read_key(tty_fd, 33);
if (ev.type != TUI_KEY_NONE) {
if (ev.type == TUI_KEY_CTRL_C || (ev.type == TUI_KEY_CHAR && ev.ch == 3)) {
eng.running = false;
break;
}
if (st.cmd_active) {
if (ev.type == TUI_KEY_ESC) {
st.cmd_active = false;
st.cmd_len = 0;
st.cmd_buf[0] = '\0';
} else if (ev.type == TUI_KEY_ENTER) {
st.cmd_active = false;
handle_command(&st, &eng, st.cmd_buf);
st.cmd_len = 0;
st.cmd_buf[0] = '\0';
} else if (ev.type == TUI_KEY_BACKSPACE) {
if (st.cmd_len > 0) {
st.cmd_buf[--st.cmd_len] = '\0';
}
} else if (ev.type == TUI_KEY_CHAR && ev.ch >= 32 && ev.ch < 127) {
if (st.cmd_len + 1 < sizeof(st.cmd_buf)) {
st.cmd_buf[st.cmd_len++] = (char)ev.ch;
st.cmd_buf[st.cmd_len] = '\0';
}
}
} else if (st.modal != MODAL_NONE) {
if (ev.type == TUI_KEY_ESC || (ev.type == TUI_KEY_CHAR && (ev.ch == '?' || ev.ch == ' ' || ev.ch == 'q'))) {
st.modal = MODAL_NONE;
}
} else {
if (ev.type == TUI_KEY_TAB) {
int cur_c = eng.val_col;
if (ev.ch == -1) { /* Shift-Tab */
cur_c = (cur_c > 1) ? cur_c - 1 : (eng.reservoir.num_cols > 0 ? (int)eng.reservoir.num_cols : 9);
} else { /* Tab */
cur_c = (cur_c < (int)eng.reservoir.num_cols && cur_c < 16) ? cur_c + 1 : 1;
}
tui_engine_set_column(&eng, cur_c, 0, 0, st.paused ? &st.frozen_snapshot : NULL, NULL);
snprintf(st.status_msg, sizeof(st.status_msg), "Switched to Column %d", cur_c);
st.status_msg_time = cli_get_time_sec();
} else if (ev.type == TUI_KEY_MOUSE_SCROLL_UP) {
if (eng.is_2d) {
tui_engine_zoom_2d(&eng, 0.80, st.paused ? &st.frozen_snapshot_2d : NULL);
} else {
tui_engine_zoom_1d(&eng, 0.80, st.paused ? &st.frozen_snapshot : NULL);
}
snprintf(st.status_msg, sizeof(st.status_msg), "Zoom In [1.25x]");
st.status_msg_time = cli_get_time_sec();
} else if (ev.type == TUI_KEY_MOUSE_SCROLL_DOWN) {
if (eng.is_2d) {
tui_engine_zoom_2d(&eng, 1.25, st.paused ? &st.frozen_snapshot_2d : NULL);
} else {
tui_engine_zoom_1d(&eng, 1.25, st.paused ? &st.frozen_snapshot : NULL);
}
snprintf(st.status_msg, sizeof(st.status_msg), "Zoom Out [0.8x]");
st.status_msg_time = cli_get_time_sec();
} else if (ev.type == TUI_KEY_MOUSE_CLICK) {
bundled/tools/src/cmd_top.c view on Meta::CPAN
tui_engine_clear(&eng);
if (st.frozen_snapshot) {
histo_destroy(st.frozen_snapshot);
st.frozen_snapshot = NULL;
}
if (st.frozen_snapshot_2d) {
histo2d_destroy(st.frozen_snapshot_2d);
st.frozen_snapshot_2d = NULL;
}
snprintf(st.status_msg, sizeof(st.status_msg), "Cleared data");
st.status_msg_time = cli_get_time_sec();
break;
case 'r':
if (!eng.is_2d) tui_engine_rebuild_1d(&eng, (nbins > 10) ? nbins / 2 : 5, 0, 0, NULL);
break;
case 'R':
if (!eng.is_2d) tui_engine_rebuild_1d(&eng, nbins * 2, 0, 0, NULL);
break;
default:
break;
}
}
}
}
int term_cols = 80, term_rows = 24;
tui_term_get_size(&term_cols, &term_rows);
if (term_cols < 40) term_cols = 40;
if (term_rows < 10) term_rows = 10;
int cols = term_cols - 1;
int rows = term_rows;
// Viewport rows budget
int viewport_rows = st.compact_header ? (rows - 4) : (rows - 7);
if (viewport_rows < 3) viewport_rows = 3;
if (st.auto_bins && !eng.is_2d) {
uint32_t target_nb = (viewport_rows > 3) ? (uint32_t)viewport_rows : 10;
histo_t *active = st.paused ? st.frozen_snapshot : eng.live_1d;
if (active && histo_nbins(active) != target_nb) {
tui_engine_rebuild_1d(&eng, target_nb, 0, 0, st.paused ? &st.frozen_snapshot : NULL);
}
}
histo_t *snap = NULL;
histo2d_t *snap_2d = NULL;
uint64_t n_entries = 0;
double tot_w = 0.0;
if (eng.is_2d) {
snap_2d = tui_engine_get_snapshot_2d(&eng);
n_entries = snap_2d ? histo2d_num_entries(snap_2d) : 0;
tot_w = snap_2d ? histo2d_total_weight(snap_2d) : 0.0;
} else {
snap = st.paused ? st.frozen_snapshot : tui_engine_get_snapshot_1d(&eng);
n_entries = snap ? histo_num_entries(snap) : 0;
tot_w = snap ? histo_total_weight(snap) : 0.0;
}
tui_frame_clear(&frame);
tui_frame_puts(&frame, "\033[H"); // Cursor home
// Row 1: Top border with badge
char badge[96] = "";
if (st.paused) {
if (eng.has_weights) {
snprintf(badge, sizeof(badge), "[PAUSED | N=%lu | W=%.2f]", (unsigned long)n_entries, tot_w);
} else {
snprintf(badge, sizeof(badge), "[PAUSED | N=%lu]", (unsigned long)n_entries);
}
} else if (tui_engine_is_finished(&eng)) {
if (eng.has_weights) {
snprintf(badge, sizeof(badge), "[EOF | N=%lu | W=%.2f]", (unsigned long)n_entries, tot_w);
} else {
snprintf(badge, sizeof(badge), "[EOF | N=%lu]", (unsigned long)n_entries);
}
} else {
double rate = eng.current_rate_ops;
char rate_str[32];
if (rate >= 1e6) snprintf(rate_str, sizeof(rate_str), "%.1fM/s", rate / 1e6);
else if (rate >= 1e3) snprintf(rate_str, sizeof(rate_str), "%.1fk/s", rate / 1e3);
else snprintf(rate_str, sizeof(rate_str), "%.0f/s", rate);
if (eng.has_weights) {
snprintf(badge, sizeof(badge), "[LIVE: %s | N=%lu | W=%.2f]", rate_str, (unsigned long)n_entries, tot_w);
} else {
snprintf(badge, sizeof(badge), "[LIVE: %s | N=%lu]", rate_str, (unsigned long)n_entries);
}
}
render_top_border(&frame, eng.is_2d ? "libhisto top (2D)" : "libhisto top", badge, cols);
if (st.compact_header) {
char stats_buf[256];
if (eng.is_2d) {
if (snap_2d && n_entries > 0) {
histo2d_axis_t ax, ay;
histo2d_axis_x(snap_2d, &ax);
histo2d_axis_y(snap_2d, &ay);
snprintf(stats_buf, sizeof(stats_buf), "[2D] X:[%.1f,%.1f] Y:[%.1f,%.1f] â N=%lu â %s â Pal:%s",
ax.min, ax.max, ay.min, ay.max, (unsigned long)n_entries,
st.log_z ? "LOG-Z" : "LIN", histo_palette_name(st.palette));
} else {
snprintf(stats_buf, sizeof(stats_buf), "Waiting for 2D stream...");
}
} else {
if (snap && n_entries > 0) {
double mean = 0, sdev = 0, med = 0, p95 = 0;
histo_mean(snap, &mean);
histo_std_dev(snap, &sdev);
histo_median(snap, &med);
histo_quantile(snap, 0.95, &p95);
snprintf(stats_buf, sizeof(stats_buf), "[Col %d] Mean: %.2f ± %.2f â Med: %.2f â P95: %.2f â N=%lu â Pal:%s",
eng.val_col, mean, sdev, med, p95, (unsigned long)n_entries, histo_palette_name(st.palette));
} else {
snprintf(stats_buf, sizeof(stats_buf), "Waiting for stream...");
}
}
tui_render_row(&frame, stats_buf, cols, true);
render_divider(&frame, cols);
} else {
// Row 2: Stats summary
char stats_buf[256];
if (eng.is_2d) {
if (snap_2d && n_entries > 0) {
histo2d_axis_t ax, ay;
histo2d_axis_x(snap_2d, &ax);
histo2d_axis_y(snap_2d, &ay);
snprintf(stats_buf, sizeof(stats_buf),
"X: [%.2f, %.2f] %ux â Y: [%.2f, %.2f] %uy â Entries: %lu â Weight: %.2f",
ax.min, ax.max, histo2d_nbins_x(snap_2d),
ay.min, ay.max, histo2d_nbins_y(snap_2d),
(unsigned long)n_entries, tot_w);
} else {
snprintf(stats_buf, sizeof(stats_buf), "Waiting for streaming 'x y' samples...");
}
} else {
if (snap && n_entries > 0) {
double mean = 0, sdev = 0, med = 0, iqr = 0, p95 = 0, p99 = 0;
histo_mean(snap, &mean);
histo_std_dev(snap, &sdev);
histo_median(snap, &med);
histo_iqr(snap, &iqr);
histo_quantile(snap, 0.95, &p95);
histo_quantile(snap, 0.99, &p99);
snprintf(stats_buf, sizeof(stats_buf), "Mean: %.2f ± %.2f â Med: %.2f â IQR: %.2f â P95: %.2f â P99: %.2f",
mean, sdev, med, iqr, p95, p99);
} else {
snprintf(stats_buf, sizeof(stats_buf), "Waiting for streaming samples...");
}
}
tui_render_row(&frame, stats_buf, cols, true);
// Row 3: Divider
render_divider(&frame, cols);
// Row 4: Subheader
char subhdr[384];
if (eng.is_2d) {
if (snap_2d) {
histo2d_axis_t ax, ay;
histo2d_axis_x(snap_2d, &ax);
histo2d_axis_y(snap_2d, &ay);
snprintf(subhdr, sizeof(subhdr), "2D Heatmap â X: %u bins [%.1f, %.1f] â Y: %u bins [%.1f, %.1f] â Pal: %s â %s %s",
histo2d_nbins_x(snap_2d), ax.min, ax.max,
histo2d_nbins_y(snap_2d), ay.min, ay.max,
histo_palette_name(st.palette),
st.log_z ? "LOG-Z" : "LIN",
st.show_legend ? "â Legend: ON" : "");
} else {
snprintf(subhdr, sizeof(subhdr), "Initializing 2D...");
}
} else if (snap) {
double r_min = 0, r_max = 0;
histo_range(snap, &r_min, &r_max);
uint32_t nb = histo_nbins(snap);
const char *sc = (st.scale_mode == SCALE_LOG_Y) ? "LOG-Y" :
(st.scale_mode == SCALE_LOG_X) ? "LOG-X" :
(st.scale_mode == SCALE_LOG_LOG) ? "LOG-LOG" : "LIN";
char kde_tag[64] = "";
char fit_tag[64] = "";
if (st.show_kde && n_entries >= 5) {
histo_kde_t *sub_kde = histo_kde_create_from_histo(snap, NULL);
if (sub_kde) {
double bw = histo_kde_get_bandwidth(sub_kde);
snprintf(kde_tag, sizeof(kde_tag), "â KDE: Gauss(h=%.2g) ", bw);
histo_kde_destroy(sub_kde);
} else {
snprintf(kde_tag, sizeof(kde_tag), "â KDE ");
}
}
if (st.show_fit && n_entries >= 5) {
histo_fit_result_t *sub_fit = NULL;
histo_fit_model(snap, HISTO_FIT_MODEL_GAUSSIAN, NULL, NULL, &sub_fit);
if (sub_fit && sub_fit->converged) {
snprintf(fit_tag, sizeof(fit_tag), "â Fit: μ=%.2f Ï=%.2f ", sub_fit->params[1], sub_fit->params[2]);
histo_fit_result_destroy(sub_fit);
} else {
if (sub_fit) histo_fit_result_destroy(sub_fit);
snprintf(fit_tag, sizeof(fit_tag), "â Fit: Gauss ");
}
}
char err_tag[32] = "";
if (st.show_errors) snprintf(err_tag, sizeof(err_tag), "â Err: ON ");
char yaxis_tag[32] = "";
if (st.show_y_axis) snprintf(yaxis_tag, sizeof(yaxis_tag), "â Axis: ON ");
char col_tag[32] = "";
if (eng.val_col > 1) snprintf(col_tag, sizeof(col_tag), "Col:%d â ", eng.val_col);
char win_tag[32] = "";
if (eng.window_size > 0) snprintf(win_tag, sizeof(win_tag), "Win:%zu â ", eng.window_size);
snprintf(subhdr, sizeof(subhdr), "%s%sRange: [%.2f, %.2f] â Bins: %u â Scale: %s â Pal: %s â Auto: %s %s%s%s%s",
col_tag, win_tag, r_min, r_max, nb, sc,
histo_palette_name(st.palette),
eng.auto_range ? "ON" : "OFF",
kde_tag, fit_tag, err_tag, yaxis_tag);
} else {
snprintf(subhdr, sizeof(subhdr), "Initializing...");
}
tui_render_row(&frame, subhdr, cols, true);
}
if (st.modal == MODAL_HELP) {
render_help_viewport(&frame, &st, cols, viewport_rows);
} else if (eng.is_2d) {
render_2d_heatmap_viewport(&frame, &st, snap_2d, cols, viewport_rows);
} else {
render_1d_bars_viewport(&frame, &st, snap, cols, viewport_rows);
}
// Row rows - 2: Divider
render_divider(&frame, cols);
// Row rows - 1: Footer / Command
if (st.cmd_active) {
char cmd_line[300];
snprintf(cmd_line, sizeof(cmd_line), ":%s\033[7m \033[0m", st.cmd_buf);
tui_render_row(&frame, cmd_line, cols, true);
} else if (st.status_msg[0] != '\0' && (cli_get_time_sec() - st.status_msg_time < 3.0)) {
char status_row[256];
snprintf(status_row, sizeof(status_row), ">> %s", st.status_msg);
tui_render_row(&frame, status_row, cols, true);
} else {
const char *hints = eng.is_2d ?
"[Space] Freeze [+/-] Zoom [ââââ] Pan [l] Log-Z [p] Pal [g] Legend [:] Cmd [?] Help [q] Quit" :
"[Space] Freeze [+/-] Zoom [Tab] Col [w] Win [s] Save [H] Compact [B] Auto-Bins [:] Cmd [?] Help";
tui_render_row(&frame, hints, cols, true);
}
// Row rows: Bottom border (no trailing newline to avoid scrolling on final cell)
render_bottom_border(&frame, cols, false);
tui_frame_puts(&frame, "\033[J"); // Erase below in case window grew or shrank
tui_frame_flush(&frame, stdout);
if (!st.paused && snap) {
histo_destroy(snap);
}
if (!st.paused && snap_2d) {
histo2d_destroy(snap_2d);
}
}
if (st.frozen_snapshot) {
histo_destroy(st.frozen_snapshot);
}
if (st.frozen_snapshot_2d) {
histo2d_destroy(st.frozen_snapshot_2d);
}
tui_frame_free(&frame);
tui_term_restore();
tui_engine_free(&eng);
if (in_fp != stdin) fclose(in_fp);
return 0;
}
( run in 0.421 second using v1.01-cache-2.11-cpan-2e0ccfb7a10 )