148 lines
5.1 KiB
PHP
148 lines
5.1 KiB
PHP
<?php
|
|
/**
|
|
* admin2/lib/ascii_chart.php — Server-side ASCII X-bar chart renderer.
|
|
*
|
|
* Produces a monospaced ASCII bar chart (using 'X' characters) from an
|
|
* array of bin counts. Mirrors the layout of the original text_graph.js
|
|
* ASCIIBarChart: Y-axis count labels, X-bar columns, X-axis with
|
|
* elapsed-time labels.
|
|
*
|
|
* The output is a plain text string suitable for both browser <div>
|
|
* injection (white-space: pre) and terminal display via curl.
|
|
*/
|
|
|
|
/**
|
|
* Render an ASCII bar chart from an array of bin counts.
|
|
*
|
|
* @param array $bins Array of integer counts (one per time bin, left=oldest)
|
|
* @param array $options {
|
|
* @var string $title Chart title (centered at top)
|
|
* @var int $max_height Chart height in rows (default 11)
|
|
* @var string $x_axis_label Label below the X-axis (default '')
|
|
* @var int $bin_duration Seconds per bin — for X-axis elapsed labels
|
|
* @var int $label_interval Label every N bins (default 5)
|
|
* }
|
|
* @return string The ASCII chart as a multi-line string.
|
|
*/
|
|
function render_ascii_chart(array $bins, array $options = []): string {
|
|
$title = $options['title'] ?? 'New Events';
|
|
$max_height = $options['max_height'] ?? 11;
|
|
$x_axis_label = $options['x_axis_label'] ?? '';
|
|
$bin_duration = $options['bin_duration'] ?? 10;
|
|
$label_interval = $options['label_interval'] ?? 5;
|
|
|
|
$num_bins = count($bins);
|
|
if ($num_bins === 0) {
|
|
return "No data available.\n";
|
|
}
|
|
|
|
$max_count = max($bins);
|
|
if ($max_count < 1) $max_count = 1; // Avoid division by zero for all-empty bins
|
|
|
|
// Scaling: each X represents scale_factor counts
|
|
$scale_factor = max(1, (int)ceil($max_count / $max_height));
|
|
$scaled_max = (int)ceil($max_count / $scale_factor) * $scale_factor;
|
|
|
|
$output = '';
|
|
|
|
// --- Title (centered) ---
|
|
$chart_width = 4 + $num_bins; // 4 = Y-axis number width (3) + separator (1)
|
|
if ($title !== '') {
|
|
$title_padding = (int)floor(($chart_width - strlen($title)) / 2);
|
|
if ($title_padding < 0) $title_padding = 0;
|
|
$output .= str_repeat(' ', $title_padding) . $title . "\n\n";
|
|
}
|
|
|
|
// --- Bar rows (top to bottom) ---
|
|
for ($row = $max_height; $row > 0; $row--) {
|
|
$row_count = ($row - 1) * $scale_factor + 1;
|
|
$line = str_pad((string)$row_count, 3, ' ', STR_PAD_LEFT) . ' |';
|
|
|
|
for ($i = 0; $i < $num_bins; $i++) {
|
|
$count = $bins[$i];
|
|
$scaled_height = ($count > 0) ? (int)ceil($count / $scale_factor) : 0;
|
|
$line .= ($scaled_height >= $row) ? 'X' : ' ';
|
|
}
|
|
$output .= $line . "\n";
|
|
}
|
|
|
|
// --- X-axis line ---
|
|
$output .= ' +' . str_repeat('-', $num_bins) . "\n";
|
|
|
|
// --- X-axis labels (elapsed time every label_interval bins) ---
|
|
$label_line = ' ';
|
|
$labels = [];
|
|
for ($i = 0; $i < $num_bins; $i++) {
|
|
if ($i % $label_interval === 0) {
|
|
$elapsed_sec = $i * $bin_duration;
|
|
$labels[] = format_elapsed_time($elapsed_sec);
|
|
}
|
|
}
|
|
// Build label line with spacing
|
|
for ($i = 0; $i < count($labels); $i++) {
|
|
$label_line .= $labels[$i];
|
|
if ($i < count($labels) - 1) {
|
|
$spacing = $label_interval - strlen($labels[$i]);
|
|
if ($spacing < 1) $spacing = 1;
|
|
$label_line .= str_repeat(' ', $spacing);
|
|
}
|
|
}
|
|
// Pad to match X-axis dash line length
|
|
$min_label_len = 4 + $num_bins;
|
|
if (strlen($label_line) < $min_label_len) {
|
|
$label_line .= str_repeat(' ', $min_label_len - strlen($label_line));
|
|
}
|
|
$output .= $label_line . "\n";
|
|
|
|
// --- X-axis label (if provided) ---
|
|
if ($x_axis_label !== '') {
|
|
$label_pad = (int)floor(($num_bins - strlen($x_axis_label)) / 2);
|
|
if ($label_pad < 0) $label_pad = 0;
|
|
$output .= "\n" . ' ' . str_repeat(' ', $label_pad) . $x_axis_label . "\n";
|
|
}
|
|
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* Format an elapsed time in seconds as a compact label.
|
|
* < 60s → "Ns"
|
|
* < 1h → "Nm"
|
|
* < 1d → "Nh"
|
|
* else → "Nd"
|
|
*/
|
|
function format_elapsed_time(int $seconds): string {
|
|
if ($seconds < 60) {
|
|
return $seconds . 's';
|
|
} elseif ($seconds < 3600) {
|
|
return (int)floor($seconds / 60) . 'm';
|
|
} elseif ($seconds < 86400) {
|
|
return (int)floor($seconds / 3600) . 'h';
|
|
} else {
|
|
return (int)floor($seconds / 86400) . 'd';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build a fixed-length bin array from SQL query rows.
|
|
*
|
|
* The query returns only non-empty bins. This function creates a
|
|
* zero-filled array of $num_bins length and overlays the counts
|
|
* at the correct positions, so empty time slots show as blank
|
|
* columns — the chart always advances in time.
|
|
*
|
|
* @param array $rows Query rows with 'bin' (int) and 'cnt' (int)
|
|
* @param int $num_bins Total number of bins (fixed length)
|
|
* @return array Zero-filled array of counts
|
|
*/
|
|
function build_bin_array(array $rows, int $num_bins): array {
|
|
$bins = array_fill(0, $num_bins, 0);
|
|
foreach ($rows as $r) {
|
|
$idx = (int)$r['bin'];
|
|
if ($idx >= 0 && $idx < $num_bins) {
|
|
$bins[$idx] = (int)$r['cnt'];
|
|
}
|
|
}
|
|
return $bins;
|
|
}
|