Files
text_graph/text_graph.html
2025-10-19 12:35:43 -04:00

447 lines
17 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ASCII Bar Chart</title>
<style>
body {
background-color: #1e1e1e;
color: #00ff00;
font-family: 'Courier New', Courier, monospace;
padding: 20px;
margin: 0;
}
#chart-container {
background-color: #000;
padding: 20px;
border: 2px solid #00ff00;
border-radius: 5px;
overflow: hidden;
white-space: pre;
font-size: 8px;
line-height: 1.0;
width: 100%;
box-sizing: border-box;
}
#controls {
margin-bottom: 20px;
}
button {
background-color: #00ff00;
color: #000;
border: none;
padding: 10px 20px;
font-family: 'Courier New', Courier, monospace;
font-size: 14px;
cursor: pointer;
margin-right: 10px;
border-radius: 3px;
}
button:hover {
background-color: #00cc00;
}
#info {
margin-bottom: 10px;
font-size: 12px;
}
#chart-info {
margin-top: 15px;
font-size: 12px;
line-height: 1.6;
}
#chart-info div {
margin-bottom: 5px;
}
#settings {
background-color: #2a2a2a;
padding: 10px;
border-radius: 3px;
border: 1px solid #00ff00;
}
#settings input {
background-color: #1e1e1e;
color: #00ff00;
border: 1px solid #00ff00;
padding: 3px 5px;
font-family: 'Courier New', Courier, monospace;
}
#settings label {
display: inline-block;
}
</style>
</head>
<body>
<h1>ASCII Vertical Bar Chart</h1>
<div id="controls">
<button onclick="dataGenerator.start()">Start</button>
<button onclick="dataGenerator.stop()">Stop</button>
<button onclick="dataGenerator.reset()">Reset</button>
</div>
<div id="settings" style="margin-bottom: 15px; font-size: 12px;">
<label>
Update Interval (ms):
<input type="number" id="update-interval" value="100" min="100" max="10000" step="100" style="width: 80px;">
</label>
<label style="margin-left: 15px;">
Max Columns:
<input type="number" id="max-columns" value="30" min="10" max="100" step="5" style="width: 60px;">
</label>
<label style="margin-left: 15px;">
Chart Height:
<input type="number" id="chart-height" value="20" min="10" max="50" step="5" style="width: 60px;">
</label>
<label style="margin-left: 15px;">
<input type="checkbox" id="use-bin-mode" checked onchange="toggleBinMode()">
Time Bin Mode
</label>
<label style="margin-left: 15px;">
Bin Duration (s):
<input type="number" id="bin-duration" value="4" min="0.1" max="300" step="0.1" style="width: 60px;">
</label>
<label style="margin-left: 15px;">
X-Axis Format:
<select id="x-axis-format" style="width: 100px;">
<option value="elapsed">Elapsed</option>
<option value="bins">Bin Numbers</option>
<option value="timestamps">Timestamps</option>
<option value="ranges">Time Ranges</option>
</select>
</label>
<label style="margin-left: 15px;">
<input type="checkbox" id="debug-mode" onchange="toggleDebugMode()">
Debug Mode
</label>
<button onclick="applySettings()" style="margin-left: 15px; padding: 5px 15px;">Apply Settings</button>
<button onclick="rotateBin()" id="rotate-bin-btn" style="margin-left: 10px; padding: 5px 15px; display: none;">Rotate Bin</button>
</div>
<div id="info">
<span>Data points: <span id="count">0</span></span> |
<span>Status: <span id="status">Stopped</span></span> |
<span>Next update in: <span id="countdown">--</span>s</span>
</div>
<div id="chart-container"></div>
<div id="chart-info">
<div><strong>Legend:</strong> <span id="legend-text">Each X represents a count of events</span></div>
<div><strong>Values:</strong> <span id="values">--</span></div>
<div><strong>Max value:</strong> <span id="max-value">--</span>, <strong>Scale:</strong> <span id="scale">--</span></div>
<div id="bin-info" style="display: none;"><strong>Current Bin:</strong> <span id="current-bin">--</span>, <strong>Time Remaining:</strong> <span id="time-remaining">--</span>s</div>
</div>
<div id="code-example" style="margin-top: 20px; background-color: #2a2a2a; padding: 15px; border-radius: 5px; border: 1px solid #00ff00;">
<h3 style="margin-top: 0; color: #00ff00;">Implementation Code Example</h3>
<p style="margin-bottom: 10px; font-size: 12px;">Copy this code to implement the chart in your project:</p>
<div id="code-display" style="background-color: #1e1e1e; padding: 10px; border-radius: 3px; font-size: 11px; line-height: 1.4; overflow-x: auto; color: #00ff00; border: 1px solid #444; font-family: 'Courier New', Courier, monospace;"></div>
</div>
<!-- Include the ASCII Bar Chart library -->
<script src="text_graph.js"></script>
<script>
// Initialize the chart
let chart = new ASCIIBarChart('chart-container', {
maxHeight: 20,
maxDataPoints: 30,
title: 'Real-Time Data Visualization',
xAxisLabel: 'Time (seconds)',
yAxisLabel: 'Count',
autoFitWidth: true, // Automatically adjust font size to fit container width
useBinMode: true, // Start in time bin mode
binDuration: 4000, // 4 seconds
xAxisLabelFormat: 'elapsed',
debug: false // Debug mode disabled by default
});
// Initial render
chart.render();
chart.updateInfo();
// Generate initial code example
generateCodeExample();
// Test data generator (separate from chart API)
class DataGenerator {
constructor(updateInterval = 100) {
this.baseValue = 10;
this.intervalId = null;
this.countdownId = null;
this.nextUpdateTime = null;
this.isRunning = false;
this.updateInterval = updateInterval;
}
setUpdateInterval(interval) {
this.updateInterval = interval;
// If running, restart with new interval
if (this.isRunning) {
this.stop();
this.start();
}
}
generateValue() {
// Add some randomness around the base value (±3)
const variation = Math.floor(Math.random() * 7) - 3;
const value = this.baseValue + variation;
// Increase the base value slightly for next time (0.5 to 1.5 increase)
this.baseValue += 0.5 + Math.random();
return Math.max(1, Math.round(value));
}
generateRandomInterval() {
// Generate random interval based on update interval setting
// Random between 10% and 200% of the base update interval
const baseInterval = parseInt(document.getElementById('update-interval').value);
const minInterval = Math.max(50, baseInterval * 0.1); // Minimum 50ms
const maxInterval = baseInterval * 2; // Maximum 2x the base interval
return Math.floor(Math.random() * (maxInterval - minInterval)) + minInterval;
}
updateCountdown() {
if (!this.isRunning || !this.nextUpdateTime) {
document.getElementById('countdown').textContent = '--';
return;
}
const now = Date.now();
const remaining = Math.max(0, Math.ceil((this.nextUpdateTime - now) / 1000));
document.getElementById('countdown').textContent = remaining;
}
start() {
if (this.isRunning) return;
this.isRunning = true;
document.getElementById('status').textContent = 'Running';
// Add first data point immediately
chart.addValue(this.generateValue());
// Set up interval for subsequent updates with random timing
this.scheduleNextUpdate();
this.countdownId = setInterval(() => {
this.updateCountdown();
}, 1000);
this.updateCountdown();
}
scheduleNextUpdate() {
if (!this.isRunning) return;
const randomInterval = this.generateRandomInterval();
this.nextUpdateTime = Date.now() + randomInterval;
this.intervalId = setTimeout(() => {
if (this.isRunning) {
chart.addValue(this.generateValue());
this.scheduleNextUpdate();
}
}, randomInterval);
}
stop() {
if (!this.isRunning) return;
this.isRunning = false;
document.getElementById('status').textContent = 'Stopped';
if (this.intervalId) {
clearTimeout(this.intervalId);
this.intervalId = null;
}
if (this.countdownId) {
clearInterval(this.countdownId);
this.countdownId = null;
}
this.nextUpdateTime = null;
this.updateCountdown();
// Stop bin rotation timer when stopping data generation
if (chart.binCheckInterval) {
clearInterval(chart.binCheckInterval);
chart.binCheckInterval = null;
}
}
reset() {
this.stop();
this.baseValue = 10;
chart.clear();
}
}
// Create data generator for testing
let dataGenerator = new DataGenerator(1000);
// Function to toggle bin mode
function toggleBinMode() {
const useBinMode = document.getElementById('use-bin-mode').checked;
const binDurationInput = document.getElementById('bin-duration');
const xAxisFormatSelect = document.getElementById('x-axis-format');
const rotateBinBtn = document.getElementById('rotate-bin-btn');
const binInfo = document.getElementById('bin-info');
const legendText = document.getElementById('legend-text');
if (useBinMode) {
rotateBinBtn.style.display = 'inline-block';
binInfo.style.display = 'block';
legendText.textContent = 'Each X represents a count of events. O marks the active bin.';
} else {
rotateBinBtn.style.display = 'none';
binInfo.style.display = 'none';
legendText.textContent = 'Each X represents a count of events';
}
}
// Function to toggle debug mode
function toggleDebugMode() {
const debugMode = document.getElementById('debug-mode').checked;
// Note: Debug mode requires recreating the chart to take effect
// This is a limitation since debug mode is set at construction time
}
// Function to manually rotate bin
function rotateBin() {
if (chart && chart.useBinMode) {
chart.rotateBin();
}
}
// Function to generate code example
function generateCodeExample() {
const maxColumns = parseInt(document.getElementById('max-columns').value);
const chartHeight = parseInt(document.getElementById('chart-height').value);
const useBinMode = document.getElementById('use-bin-mode').checked;
const binDuration = parseFloat(document.getElementById('bin-duration').value);
const xAxisLabelFormat = document.getElementById('x-axis-format').value;
const debugMode = document.getElementById('debug-mode').checked;
let code = '// Include the ASCII Bar Chart library\n';
code += '<script src="text_graph.js"><' + '/script>\n\n';
code += '// Create the chart\n';
code += 'const chart = new ASCIIBarChart(\'chart-container\', {\n';
code += ' maxHeight: ' + chartHeight + ',\n';
code += ' maxDataPoints: ' + maxColumns + ',\n';
code += ' title: \'Real-Time Data Visualization\',\n';
code += ' xAxisLabel: \'Time (seconds)\',\n';
code += ' yAxisLabel: \'Count\',\n';
code += ' autoFitWidth: true,\n';
if (useBinMode) {
code += ' useBinMode: true,\n';
code += ' binDuration: ' + (binDuration * 1000) + ', // ' + binDuration + ' seconds in milliseconds\n';
code += ' xAxisLabelFormat: \'' + xAxisLabelFormat + '\',\n';
} else {
code += ' useBinMode: false,\n';
}
code += ' debug: ' + debugMode + '\n';
code += '});\n\n';
code += '// Add data points\n';
code += 'chart.addValue(5);\n';
code += 'chart.addValue(12);\n';
code += 'chart.addValue(8);\n\n';
code += '// Clear the chart\n';
code += 'chart.clear();';
// Format the code for better display
code = code.replace(/\n/g, '<br>')
.replace(/ /g, '&nbsp;')
.replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;');
document.getElementById('code-display').innerHTML = code;
}
// Function to apply settings
function applySettings() {
const wasRunning = dataGenerator.isRunning;
// Stop current generator
dataGenerator.stop();
// Get new settings
const updateInterval = parseInt(document.getElementById('update-interval').value);
const maxColumns = parseInt(document.getElementById('max-columns').value);
const chartHeight = parseInt(document.getElementById('chart-height').value);
const useBinMode = document.getElementById('use-bin-mode').checked;
const binDuration = parseFloat(document.getElementById('bin-duration').value) * 1000; // Convert to milliseconds
const xAxisLabelFormat = document.getElementById('x-axis-format').value;
// Stop any existing bin rotation timer before recreating chart
if (chart && chart.binCheckInterval) {
clearInterval(chart.binCheckInterval);
chart.binCheckInterval = null;
}
// Get debug mode setting
const debugMode = document.getElementById('debug-mode').checked;
// Recreate chart with new settings
chart = new ASCIIBarChart('chart-container', {
maxHeight: chartHeight,
maxDataPoints: maxColumns,
title: 'Real-Time Data Visualization',
xAxisLabel: 'Time (seconds)',
yAxisLabel: 'Count',
autoFitWidth: true,
useBinMode: useBinMode,
binDuration: binDuration,
xAxisLabelFormat: xAxisLabelFormat,
debug: debugMode
});
// Force font size adjustment for new settings
chart.fontSizeAdjusted = false;
chart.render();
chart.updateInfo();
// Update code example
generateCodeExample();
// Recreate data generator with new interval
dataGenerator = new DataGenerator(updateInterval);
// Restart if it was running
if (wasRunning) {
dataGenerator.start();
} else {
// If not restarting, make sure bin timer is also stopped
if (chart.binCheckInterval) {
clearInterval(chart.binCheckInterval);
chart.binCheckInterval = null;
}
}
}
// Update bin info display
setInterval(() => {
if (chart && chart.useBinMode && chart.bins.length > 0) {
const currentBin = chart.currentBinIndex + 1;
const timeRemaining = chart.binStartTime ?
Math.max(0, Math.ceil((chart.binStartTime + chart.binDuration - Date.now()) / 1000)) : 0;
document.getElementById('current-bin').textContent = currentBin;
document.getElementById('time-remaining').textContent = timeRemaining;
}
}, 1000);
</script>
</body>
</html>