Alien-libhisto

 view release on metacpan or  search on metacpan

bundled/tools/src/cmd_top.c  view on Meta::CPAN

/*
 * CLI subcommand histo top: real-time interactive terminal monitoring TUI.
 */

#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif
#ifndef _DEFAULT_SOURCE
#define _DEFAULT_SOURCE 1
#endif
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 700
#endif

#include "cli_common.h"
#include "cli_palette.h"
#include "tui_term.h"
#include "tui_engine.h"
#include "histo/fit.h"
#include "histo/kde.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if !defined(_WIN32)
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#endif
#include <stdbool.h>
#include <ctype.h>
#include <math.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;

            if (st->monochrome) {
                static const char density[] = " .:-=+*#%@";
                int idx = (int)(frac * 9.0);
                if (idx < 0) idx = 0;
                if (idx > 9) idx = 9;
                pos += snprintf(row_buf + pos, sizeof(row_buf) - pos, "%c ", density[idx]);
            } else {
                char bg[32];
                histo_palette_sample_ansi_bg(st->palette, frac, bg, sizeof(bg));
                pos += snprintf(row_buf + pos, sizeof(row_buf) - pos, "%s  \033[0m", bg);
            }
        }

        tui_render_row(f, row_buf, width, true);
        rows_drawn++;
    }

    /* X-axis border row */
    if (rows_drawn < max_rows) {
        char axis_buf[4096];
        int pos = snprintf(axis_buf, sizeof(axis_buf), "        â””");
        uint32_t x_cells = (nx + x_step - 1) / x_step;
        for (uint32_t ix = 0; ix < x_cells && pos + 8 < (int)sizeof(axis_buf); ++ix) {
            pos += snprintf(axis_buf + pos, sizeof(axis_buf) - pos, "──");
        }
        pos += snprintf(axis_buf + pos, sizeof(axis_buf) - pos, "─┘");
        tui_render_row(f, axis_buf, width, true);
        rows_drawn++;
    }

    /* 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);
    }

bundled/tools/src/cmd_top.c  view on Meta::CPAN

                                tui_engine_pan_2d(&eng, -0.10, 0.0, st.paused ? &st.frozen_snapshot_2d : NULL);
                            } else {
                                tui_engine_pan_1d(&eng, -0.10, st.paused ? &st.frozen_snapshot : NULL);
                            }
                            snprintf(st.status_msg, sizeof(st.status_msg), "Pan Left [-10%%]");
                            st.status_msg_time = cli_get_time_sec();
                            break;
                        case ']':
                        case '>':
                            if (eng.is_2d) {
                                tui_engine_pan_2d(&eng, 0.10, 0.0, st.paused ? &st.frozen_snapshot_2d : NULL);
                            } else {
                                tui_engine_pan_1d(&eng, 0.10, st.paused ? &st.frozen_snapshot : NULL);
                            }
                            snprintf(st.status_msg, sizeof(st.status_msg), "Pan Right [+10%%]");
                            st.status_msg_time = cli_get_time_sec();
                            break;
                        case '0':
                            tui_engine_set_autorange(&eng, true, eng.auto_range_threshold);
                            snprintf(st.status_msg, sizeof(st.status_msg), "Auto-range: ON (Reset View)");
                            st.status_msg_time = cli_get_time_sec();
                            break;
                        case ':':
                            st.cmd_active = true;
                            st.cmd_len = 0;
                            st.cmd_buf[0] = '\0';
                            break;
                        case '?':
                            st.modal = MODAL_HELP;
                            break;
                        case 'p':
                        case 'P':
                            st.palette = (histo_palette_t)((st.palette + 1) % HISTO_PALETTE_COUNT);
                            snprintf(st.status_msg, sizeof(st.status_msg), "Colormap: %s", histo_palette_name(st.palette));
                            st.status_msg_time = cli_get_time_sec();
                            break;
                        case 'l':
                            if (eng.is_2d) {
                                st.log_z = !st.log_z;
                            } else {
                                st.scale_mode = (st.scale_mode == SCALE_LINEAR) ? SCALE_LOG_Y :
                                                (st.scale_mode == SCALE_LOG_Y) ? SCALE_LOG_X :
                                                (st.scale_mode == SCALE_LOG_X) ? SCALE_LOG_LOG : SCALE_LINEAR;
                                if (st.scale_mode == SCALE_LOG_X || st.scale_mode == SCALE_LOG_LOG) {
                                    tui_engine_rebuild_1d_log(&eng, 50, NULL);
                                } else {
                                    tui_engine_rebuild_1d(&eng, 50, 0, 0, NULL);
                                }
                                if (st.paused && st.frozen_snapshot) {
                                    histo_destroy(st.frozen_snapshot);
                                    if (st.scale_mode == SCALE_LOG_X || st.scale_mode == SCALE_LOG_LOG) {
                                        tui_engine_rebuild_1d_log(&eng, 50, &st.frozen_snapshot);
                                    } else {
                                        tui_engine_rebuild_1d(&eng, 50, 0, 0, &st.frozen_snapshot);
                                    }
                                }
                            }
                            break;
                        case 'g':
                        case 'G':
                            st.show_legend = !st.show_legend;
                            break;
                        case 'y':
                        case 'Y':
                            st.show_y_axis = !st.show_y_axis;
                            break;
                        case 'k':
                            if (!eng.is_2d) st.show_kde = !st.show_kde;
                            break;
                        case 'f':
                            if (!eng.is_2d) st.show_fit = !st.show_fit;
                            break;
                        case 'a':
                        case 'A':
                            if (!eng.is_2d) {
                                tui_engine_set_autorange(&eng, !eng.auto_range, eng.auto_range_threshold);
                                snprintf(st.status_msg, sizeof(st.status_msg), "Auto-range: %s", eng.auto_range ? "ON" : "OFF");
                                st.status_msg_time = cli_get_time_sec();
                            }
                            break;
                        case 'e':
                            if (!eng.is_2d) st.show_errors = !st.show_errors;
                            break;
                        case 'C':
                            st.monochrome = !st.monochrome;
                            break;
                        case 'c':
                            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

bundled/tools/src/cmd_top.c  view on Meta::CPAN

                    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);
        }



( run in 0.815 second using v1.01-cache-2.11-cpan-364913b4093 )