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 )