commit c64cf49b245d1761c36db2283f4896b5aa882045 Author: Your Name Date: Thu Oct 16 07:26:56 2025 -0400 first diff --git a/README.md b/README.md new file mode 100644 index 0000000..ac3c27b --- /dev/null +++ b/README.md @@ -0,0 +1,301 @@ +# ASCII Bar Chart + +A dynamic, real-time ASCII-based vertical bar chart library that renders beautiful terminal-style visualizations using monospaced characters. Perfect for dashboards, monitoring tools, or any application that needs lightweight, text-based data visualization. + +## Features + +- 📊 **Real-time Updates**: Add data points dynamically and watch the chart animate +- 📏 **Auto-scaling**: Y-axis automatically adjusts to show min/max values +- 🔤 **Monospaced Rendering**: Uses 'X' characters in Courier font for clean visualization +- 📱 **Responsive**: Automatically adjusts font size to fit container width +- 🏷️ **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 + +## Demo + +``` + Real-Time Data Visualization + +C 20 | X X X X X X X X X X X X X X X X X X X X +o 19 | X X X X X X X X X X X X X X X X X X X X +u 18 | X X X X X X X X X X X X X X X X X X X X +n 17 | X X X X X X X X X X X X X X X X X X X X +t 16 | X X X X X X X X X X X X X X X X X X X X + 15 | X X X X X X X X X X X X X X X X X X X X + 14 | X X X X X X X X X X X X X X X X X X X X + 13 | X X X X X X X X X X X X X X X X X X X X + 12 | X X X X X X X X X X X X X X X X X X X X + 11 | X X X X X X X X X X X X X X X X X X X X + 10 | X X X X X X X X X X X X X X X X X X X X + 9 | X X X X X X X X X X X X X X X X X X X X + 8 | X X X X X X X X X X X X X X X X X X X X + 7 | X X X X X X X X X X X X X X X X X X X X + 6 | X X X X X X X X X X X X X X X X X X X X + 5 | X X X X X X X X X X X X X X X X X X X X + 4 | X X X X X X X X X X X X X X X X X X X X + 3 | X X X X X X X X X X X X X X X X X X X X + 2 | X X X X X X X X X X X X X X X X X X X X + 1 | X X X X X X X X X X X X X X X X X X X X + +---------------------------------------- + 1 6 11 16 21 26 31 36 41 + + Time (seconds) +``` + +## Installation + +Simply include the JavaScript file in your HTML: + +```html + +``` + +## Quick Start + +```html + + + + + + +
+ + + + + +``` + +## API Reference + +### Constructor + +```javascript +new ASCIIBarChart(containerId, options) +``` + +Creates a new ASCII bar chart instance. + +**Parameters:** + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `containerId` | string | Yes | ID of the HTML element to render the chart in | +| `options` | object | No | Configuration options (see below) | + +**Options:** + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `maxHeight` | number | `20` | Maximum height of the chart in rows | +| `maxDataPoints` | number | `30` | Maximum number of columns before old data scrolls off | +| `title` | string | `''` | Chart title (displayed centered at top) | +| `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 | + +**Example:** + +```javascript +const chart = new ASCIIBarChart('my-chart', { + maxHeight: 25, + maxDataPoints: 50, + title: 'Server Response Times', + xAxisLabel: 'Request Number', + yAxisLabel: 'Milliseconds', + autoFitWidth: true +}); +``` + +### Methods + +#### `addValue(value)` + +Adds a new data point to the chart. + +**Parameters:** +- `value` (number): The numeric value to add to the chart + +**Example:** + +```javascript +chart.addValue(42); +chart.addValue(38); +chart.addValue(45); +``` + +#### `clear()` + +Clears all data from the chart and resets it to initial state. + +**Example:** + +```javascript +chart.clear(); +``` + +## Features in Detail + +### Auto-Scaling Y-Axis + +The Y-axis automatically scales to show the full range of your data: +- Minimum value always displays as one 'X' at the bottom +- Maximum value uses the full chart height +- Y-axis labels show actual data values, not just row numbers +- Dynamically adjusts as new data arrives + +### Scrolling Data Window + +When the number of data points exceeds `maxDataPoints`: +- Old data automatically scrolls off the left side +- X-axis labels update to show actual data point numbers +- Maintains a sliding window of the most recent data + +### Responsive Font Sizing + +When `autoFitWidth` is enabled: +- Font size automatically adjusts to fill container width +- Responds to window resizing in real-time +- Maintains readability with min/max font size bounds (4px-20px) +- Uses ResizeObserver for efficient updates + +### Vertical Y-Axis Label + +The Y-axis label is rendered vertically, one character per row: +``` +V +a +l +u +e +``` + +## Advanced Usage + +### Real-Time Data Streaming + +```javascript +const chart = new ASCIIBarChart('chart', { + maxHeight: 20, + maxDataPoints: 60, + title: 'Live Metrics' +}); + +// Simulate real-time data +setInterval(() => { + const value = Math.random() * 100; + chart.addValue(value); +}, 1000); +``` + +### Integration with Data Sources + +```javascript +// WebSocket example +const ws = new WebSocket('ws://your-server.com/data'); +ws.onmessage = (event) => { + const data = JSON.parse(event.data); + chart.addValue(data.value); +}; + +// API polling example +async function fetchAndUpdate() { + const response = await fetch('/api/metrics'); + const data = await response.json(); + chart.addValue(data.currentValue); +} +setInterval(fetchAndUpdate, 5000); +``` + +### Multiple Charts + +```javascript +const cpuChart = new ASCIIBarChart('cpu-chart', { + title: 'CPU Usage %', + yAxisLabel: 'Percent' +}); + +const memChart = new ASCIIBarChart('mem-chart', { + title: 'Memory Usage MB', + yAxisLabel: 'Megabytes' +}); + +// Update both charts +cpuChart.addValue(45); +memChart.addValue(2048); +``` + +## Styling + +The chart uses monospaced fonts and renders as plain text. Style the container element: + +```css +#chart-container { + font-family: 'Courier New', Courier, monospace; + background-color: #000; + color: #0f0; + padding: 20px; + border: 2px solid #0f0; + border-radius: 5px; + white-space: pre; + overflow-x: auto; + font-size: 12px; + line-height: 1.0; +} +``` + +## Browser Support + +- Modern browsers with ES6 support +- ResizeObserver API (for auto-fit width feature) +- All major browsers: Chrome, Firefox, Safari, Edge + +## License + +MIT License - feel free to use in your projects! + +## Examples + +See [`ascii-bar-chart.html`](ascii-bar-chart.html) for a complete working example with: +- Interactive controls (Start/Stop/Reset) +- Configurable settings (update interval, max columns, chart height) +- Test data generator with increasing trend +- Full styling and layout + +## Contributing + +Contributions welcome! This is a simple, focused library for ASCII-based charting. + +## Changelog + +### v1.0.0 +- Initial release +- Dynamic vertical bar charts +- Auto-scaling Y-axis +- Scrolling data window +- Responsive font sizing +- Customizable labels and titles \ No newline at end of file diff --git a/ascii-bar-chart.html b/ascii-bar-chart.html new file mode 100644 index 0000000..81b9b26 --- /dev/null +++ b/ascii-bar-chart.html @@ -0,0 +1,273 @@ + + + + + + ASCII Bar Chart + + + +

ASCII Vertical Bar Chart

+ +
+ + + +
+ +
+ + + + +
+ +
+ Data points: 0 | + Status: Stopped | + Next update in: --s +
+ +
+ +
+
Legend: Each X represents a count unit
+
Values: --
+
Max value: --, Scale: --
+
+ + + + + + + \ No newline at end of file diff --git a/ascii-bar-chart.js b/ascii-bar-chart.js new file mode 100644 index 0000000..31586cd --- /dev/null +++ b/ascii-bar-chart.js @@ -0,0 +1,221 @@ +/** + * ASCIIBarChart - A dynamic ASCII-based vertical bar chart renderer + * + * Creates real-time animated bar charts using monospaced characters (X) + * with automatic scaling, labels, and responsive font sizing. + */ +class ASCIIBarChart { + /** + * Create a new ASCII bar chart + * @param {string} containerId - The ID of the HTML element to render the chart in + * @param {Object} options - Configuration options + * @param {number} [options.maxHeight=20] - Maximum height of the chart in rows + * @param {number} [options.maxDataPoints=30] - Maximum number of data columns before scrolling + * @param {string} [options.title=''] - Chart title (displayed centered at top) + * @param {string} [options.xAxisLabel=''] - X-axis label (displayed centered at bottom) + * @param {string} [options.yAxisLabel=''] - Y-axis label (displayed vertically on left) + * @param {boolean} [options.autoFitWidth=true] - Automatically adjust font size to fit container width + */ + constructor(containerId, options = {}) { + this.container = document.getElementById(containerId); + this.data = []; + this.maxHeight = options.maxHeight || 20; + this.maxDataPoints = options.maxDataPoints || 30; + this.totalDataPoints = 0; // Track total number of data points added + this.title = options.title || ''; + this.xAxisLabel = options.xAxisLabel || ''; + this.yAxisLabel = options.yAxisLabel || ''; + this.autoFitWidth = options.autoFitWidth !== false; // Default to true + + // Set up resize observer if auto-fit is enabled + if (this.autoFitWidth) { + this.resizeObserver = new ResizeObserver(() => { + this.adjustFontSize(); + }); + this.resizeObserver.observe(this.container); + } + } + + /** + * Add a new data point to the chart + * @param {number} value - The numeric value to add + */ + addValue(value) { + this.data.push(value); + this.totalDataPoints++; + + // Keep only the most recent data points + if (this.data.length > this.maxDataPoints) { + this.data.shift(); + } + + this.render(); + this.updateInfo(); + } + + /** + * Clear all data from the chart + */ + clear() { + this.data = []; + this.totalDataPoints = 0; + this.render(); + this.updateInfo(); + } + + /** + * Calculate the width of the chart in characters + * @returns {number} The chart width in characters + * @private + */ + getChartWidth() { + if (this.data.length === 0) return 50; // Default width for empty chart + + const yAxisPadding = this.yAxisLabel ? 2 : 0; + const yAxisNumbers = 3; // Width of Y-axis numbers + const separator = 1; // The '|' character + const dataWidth = this.data.length * 2; // Each column is 2 characters wide + const padding = 1; // Extra padding + + return yAxisPadding + yAxisNumbers + separator + dataWidth + padding; + } + + /** + * Adjust font size to fit container width + * @private + */ + adjustFontSize() { + if (!this.autoFitWidth) return; + + const containerWidth = this.container.clientWidth; + const chartWidth = this.getChartWidth(); + + if (chartWidth === 0) return; + + // Calculate optimal font size + // For monospace fonts, character width is approximately 0.6 * font size + const charWidthRatio = 0.6; + const padding = 40; // Account for container padding + const availableWidth = containerWidth - padding; + const optimalFontSize = Math.floor((availableWidth / chartWidth) / charWidthRatio); + + // Set reasonable bounds (min 4px, max 20px) + const fontSize = Math.max(4, Math.min(20, optimalFontSize)); + + this.container.style.fontSize = fontSize + 'px'; + this.container.style.lineHeight = '1.0'; + } + + /** + * Render the chart to the container + * @private + */ + render() { + if (this.data.length === 0) { + this.container.textContent = 'No data yet. Click Start to begin.'; + return; + } + + let output = ''; + const maxValue = Math.max(...this.data); + const minValue = Math.min(...this.data); + const valueRange = maxValue - minValue; + const scale = this.maxHeight; + + // Calculate Y-axis label width (for vertical text) + const yLabelWidth = this.yAxisLabel ? 2 : 0; + const yAxisPadding = this.yAxisLabel ? ' ' : ''; + + // Add title if provided (centered) + if (this.title) { + const chartWidth = 4 + this.data.length * 2; // Y-axis numbers + data columns + const titlePadding = Math.floor((chartWidth - this.title.length) / 2); + output += yAxisPadding + ' '.repeat(Math.max(0, titlePadding)) + this.title + '\n\n'; + } + + // Draw from top to bottom + for (let row = scale; row > 0; row--) { + let line = ''; + + // Add vertical Y-axis label character + if (this.yAxisLabel) { + const labelIndex = Math.floor((scale - row) / scale * this.yAxisLabel.length); + if (labelIndex < this.yAxisLabel.length) { + line += this.yAxisLabel[labelIndex] + ' '; + } else { + line += ' '; + } + } + + // Calculate the actual value this row represents + const rowValue = minValue + (valueRange * (row - 1) / (scale - 1)); + + // Add Y-axis label (show actual values) + line += String(Math.round(rowValue)).padStart(3, ' ') + ' |'; + + // Draw each column + for (let i = 0; i < this.data.length; i++) { + const value = this.data[i]; + + // Scale the value to fit between 1 and scale + let scaledValue; + if (valueRange === 0) { + // All values are the same + scaledValue = 1; + } else { + scaledValue = 1 + Math.round(((value - minValue) / valueRange) * (scale - 1)); + } + + if (scaledValue >= row) { + line += ' X'; + } else { + line += ' '; + } + } + + output += line + '\n'; + } + + // Draw X-axis + output += yAxisPadding + ' +' + '-'.repeat(this.data.length * 2) + '\n'; + + // Draw X-axis labels (column numbers that move with the data) + let xAxisLabels = yAxisPadding + ' '; + const startIndex = this.totalDataPoints - this.data.length + 1; + for (let i = 0; i < this.data.length; i++) { + const dataPointNumber = startIndex + i; + if (i % 5 === 0) { + xAxisLabels += String(dataPointNumber).padStart(2, ' '); + } else { + xAxisLabels += ' '; + } + } + output += xAxisLabels + '\n'; + + // Add X-axis label if provided + if (this.xAxisLabel) { + const labelPadding = Math.floor((this.data.length * 2 - this.xAxisLabel.length) / 2); + output += '\n' + yAxisPadding + ' ' + ' '.repeat(Math.max(0, labelPadding)) + this.xAxisLabel + '\n'; + } + + this.container.textContent = output; + + // Adjust font size to fit width + if (this.autoFitWidth) { + this.adjustFontSize(); + } + + // Update the external info display + document.getElementById('values').textContent = `[${this.data.join(', ')}]`; + document.getElementById('max-value').textContent = maxValue; + document.getElementById('scale').textContent = `Min: ${minValue}, Max: ${maxValue}, Height: ${scale}`; + } + + /** + * Update the info display + * @private + */ + updateInfo() { + document.getElementById('count').textContent = this.data.length; + } +} \ No newline at end of file