diff --git a/README.md b/README.md
index 7ef18c2..d01a553 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,9 @@ A dynamic, real-time ASCII-based vertical bar chart library that renders beautif
- 🏷️ **Customizable Labels**: Add title, X-axis, and vertical Y-axis labels
- 🎯 **Scrolling Data**: Old data automatically scrolls off as new data arrives
- 🎨 **Terminal Theme**: Retro green-on-black aesthetic
+- ⏱️ **Time Bin Mode**: Aggregate data into configurable time bins (e.g., count events per 10-second window)
+- 📈 **Multiple X-Axis Formats**: Display elapsed time, bin numbers, timestamps, or time ranges
+- 👁️ **Active Bin Indicator**: Visual marker shows which bin is currently accumulating data
## Demo
@@ -117,6 +120,9 @@ Creates a new ASCII bar chart instance.
| `xAxisLabel` | string | `''` | X-axis label (displayed centered at bottom) |
| `yAxisLabel` | string | `''` | Y-axis label (displayed vertically on left side) |
| `autoFitWidth` | boolean | `true` | Automatically adjust font size to fit container width |
+| `useBinMode` | boolean | `false` | Enable time bin mode for data aggregation |
+| `binDuration` | number | `4000` | Duration of each time bin in milliseconds (4 seconds default) |
+| `xAxisLabelFormat` | string | `'elapsed'` | X-axis label format: `'elapsed'`, `'bins'`, `'timestamps'`, `'ranges'` |
**Example:**
@@ -249,6 +255,47 @@ cpuChart.addValue(45);
memChart.addValue(2048);
```
+## Time Bin Mode
+
+Time bin mode aggregates data points into time-based bins, showing the count of events within each time window. This is useful for visualizing event frequency over time.
+
+### Basic Time Bin Usage
+
+```javascript
+const chart = new ASCIIBarChart('chart-container', {
+ useBinMode: true,
+ binDuration: 10000, // 10-second bins
+ xAxisLabelFormat: 'elapsed', // Show elapsed time
+ title: 'Event Counter',
+ yAxisLabel: 'Count'
+});
+
+// Each addValue() increments the count in the current active bin
+chart.addValue(1); // Bin 1: count = 1
+chart.addValue(1); // Bin 1: count = 2
+// ... after 10 seconds, automatically creates Bin 2
+```
+
+### X-Axis Label Formats
+
+- **`'elapsed'`** (default): Shows elapsed seconds since chart start ("0s", "10s", "20s")
+- **`'bins'`**: Shows bin numbers ("Bin 1", "Bin 2", "Bin 3")
+- **`'timestamps'`**: Shows actual timestamps ("103000", "103010", "103020")
+- **`'ranges'`**: Shows time ranges ("0-10s", "10-20s", "20-30s")
+
+### Visual Indicators
+
+- **X**: Regular bin data
+- **O**: Active bin currently accumulating data
+- Chart automatically scales when bin counts exceed chart height
+
+### Manual Bin Rotation
+
+```javascript
+// Force rotation to new bin (useful for testing)
+chart.rotateBin();
+```
+
## Styling
The chart uses monospaced fonts and renders as plain text. Style the container element:
diff --git a/text_graph.html b/text_graph.html
index f10005b..bdf39f5 100644
--- a/text_graph.html
+++ b/text_graph.html
@@ -18,7 +18,7 @@
padding: 20px;
border: 2px solid #00ff00;
border-radius: 5px;
- overflow-x: auto;
+ overflow: hidden;
white-space: pre;
font-size: 8px;
line-height: 1.0;
@@ -103,7 +103,25 @@
Chart Height:
+
+
+
+
@@ -115,9 +133,10 @@
-
Legend: Each X represents a count unit
+
Legend:Each X represents a count of events
Values:--
Max value:--, Scale:--
+
Current Bin:--, Time Remaining:--s
@@ -132,7 +151,10 @@
title: 'Real-Time Data Visualization',
xAxisLabel: 'Time (seconds)',
yAxisLabel: 'Count',
- autoFitWidth: true // Automatically adjust font size to fit container width
+ autoFitWidth: true, // Automatically adjust font size to fit container width
+ useBinMode: true, // Start in time bin mode
+ binDuration: 4000, // 4 seconds
+ xAxisLabelFormat: 'elapsed'
});
// Initial render
@@ -163,12 +185,21 @@
// 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) {
@@ -183,46 +214,60 @@
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
- this.nextUpdateTime = Date.now() + this.updateInterval;
- this.intervalId = setInterval(() => {
- chart.addValue(this.generateValue());
- this.nextUpdateTime = Date.now() + this.updateInterval;
- }, this.updateInterval);
-
- // Update countdown every second
+
+ // 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) {
- clearInterval(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() {
@@ -235,18 +280,48 @@
// 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 manually rotate bin
+ function rotateBin() {
+ if (chart && chart.useBinMode) {
+ chart.rotateBin();
+ }
+ }
+
// 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;
+
// Recreate chart with new settings
chart = new ASCIIBarChart('chart-container', {
maxHeight: chartHeight,
@@ -254,20 +329,43 @@
title: 'Real-Time Data Visualization',
xAxisLabel: 'Time (seconds)',
yAxisLabel: 'Count',
- autoFitWidth: true
+ autoFitWidth: true,
+ useBinMode: useBinMode,
+ binDuration: binDuration,
+ xAxisLabelFormat: xAxisLabelFormat
});
-
+
+ // Force font size adjustment for new settings
+ chart.fontSizeAdjusted = false;
chart.render();
chart.updateInfo();
-
+
// 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);