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