Compare commits
4 Commits
0762bfbd1e
...
bf1785f372
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf1785f372 | ||
|
|
fcf94ed2cb | ||
|
|
5f456c0d0b | ||
|
|
f46a1c910b |
@@ -120,6 +120,10 @@
|
||||
<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>
|
||||
@@ -139,6 +143,12 @@
|
||||
<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>
|
||||
|
||||
@@ -154,12 +164,16 @@
|
||||
autoFitWidth: true, // Automatically adjust font size to fit container width
|
||||
useBinMode: true, // Start in time bin mode
|
||||
binDuration: 4000, // 4 seconds
|
||||
xAxisLabelFormat: 'elapsed'
|
||||
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 {
|
||||
@@ -300,6 +314,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@@ -307,6 +328,48 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 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, ' ')
|
||||
.replace(/\t/g, ' ');
|
||||
document.getElementById('code-display').innerHTML = code;
|
||||
}
|
||||
|
||||
// Function to apply settings
|
||||
function applySettings() {
|
||||
const wasRunning = dataGenerator.isRunning;
|
||||
@@ -328,6 +391,9 @@
|
||||
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,
|
||||
@@ -338,7 +404,8 @@
|
||||
autoFitWidth: true,
|
||||
useBinMode: useBinMode,
|
||||
binDuration: binDuration,
|
||||
xAxisLabelFormat: xAxisLabelFormat
|
||||
xAxisLabelFormat: xAxisLabelFormat,
|
||||
debug: debugMode
|
||||
});
|
||||
|
||||
// Force font size adjustment for new settings
|
||||
@@ -346,6 +413,9 @@
|
||||
chart.render();
|
||||
chart.updateInfo();
|
||||
|
||||
// Update code example
|
||||
generateCodeExample();
|
||||
|
||||
// Recreate data generator with new interval
|
||||
dataGenerator = new DataGenerator(updateInterval);
|
||||
|
||||
|
||||
167
text_graph.js
167
text_graph.js
@@ -18,6 +18,7 @@ class ASCIIBarChart {
|
||||
* @param {boolean} [options.useBinMode=false] - Enable time bin mode for data aggregation
|
||||
* @param {number} [options.binDuration=10000] - Duration of each time bin in milliseconds (10 seconds default)
|
||||
* @param {string} [options.xAxisLabelFormat='elapsed'] - X-axis label format: 'elapsed', 'bins', 'timestamps', 'ranges'
|
||||
* @param {boolean} [options.debug=false] - Enable debug logging
|
||||
*/
|
||||
constructor(containerId, options = {}) {
|
||||
this.container = document.getElementById(containerId);
|
||||
@@ -29,6 +30,7 @@ class ASCIIBarChart {
|
||||
this.xAxisLabel = options.xAxisLabel || '';
|
||||
this.yAxisLabel = options.yAxisLabel || '';
|
||||
this.autoFitWidth = options.autoFitWidth !== false; // Default to true
|
||||
this.debug = options.debug || false; // Debug logging option
|
||||
|
||||
// Time bin configuration
|
||||
this.useBinMode = options.useBinMode !== false; // Default to true
|
||||
@@ -55,32 +57,21 @@ class ASCIIBarChart {
|
||||
this.initializeBins();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a new data point to the chart
|
||||
* @param {number} value - The numeric value to add
|
||||
*/
|
||||
addValue(value) {
|
||||
if (this.useBinMode) {
|
||||
// Time bin mode: increment count in current active bin
|
||||
this.checkBinRotation(); // Ensure we have an active bin
|
||||
this.bins[this.currentBinIndex].count++;
|
||||
this.totalDataPoints++;
|
||||
} else {
|
||||
// Legacy mode: add individual values
|
||||
this.data.push(value);
|
||||
this.totalDataPoints++;
|
||||
|
||||
// Keep only the most recent data points
|
||||
if (this.data.length > this.maxDataPoints) {
|
||||
this.data.shift();
|
||||
}
|
||||
}
|
||||
// Time bin mode: add value to current active bin count
|
||||
this.checkBinRotation(); // Ensure we have an active bin
|
||||
this.bins[this.currentBinIndex].count += value; // Changed from ++ to += value
|
||||
this.totalDataPoints++;
|
||||
|
||||
this.render();
|
||||
this.updateInfo();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear all data from the chart
|
||||
*/
|
||||
@@ -98,7 +89,7 @@ class ASCIIBarChart {
|
||||
this.render();
|
||||
this.updateInfo();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the width of the chart in characters
|
||||
* @returns {number} The chart width in characters
|
||||
@@ -119,14 +110,14 @@ class ASCIIBarChart {
|
||||
const totalWidth = yAxisPadding + yAxisNumbers + separator + dataWidth + padding;
|
||||
|
||||
// Only log when width changes
|
||||
if (this.lastChartWidth !== totalWidth) {
|
||||
if (this.debug && this.lastChartWidth !== totalWidth) {
|
||||
console.log('getChartWidth changed:', { dataLength, totalWidth, previous: this.lastChartWidth });
|
||||
this.lastChartWidth = totalWidth;
|
||||
}
|
||||
|
||||
return totalWidth;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adjust font size to fit container width
|
||||
* @private
|
||||
@@ -151,7 +142,7 @@ class ASCIIBarChart {
|
||||
const fontSize = Math.max(4, Math.min(20, optimalFontSize));
|
||||
|
||||
// Only log when font size changes
|
||||
if (this.lastFontSize !== fontSize) {
|
||||
if (this.debug && this.lastFontSize !== fontSize) {
|
||||
console.log('fontSize changed:', { containerWidth, chartWidth, fontSize, previous: this.lastFontSize });
|
||||
this.lastFontSize = fontSize;
|
||||
}
|
||||
@@ -159,7 +150,7 @@ class ASCIIBarChart {
|
||||
this.container.style.fontSize = fontSize + 'px';
|
||||
this.container.style.lineHeight = '1.0';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Render the chart to the container
|
||||
* @private
|
||||
@@ -190,7 +181,9 @@ class ASCIIBarChart {
|
||||
}
|
||||
});
|
||||
|
||||
console.log('render() dataToRender:', dataToRender, 'bins length:', this.bins.length);
|
||||
if (this.debug) {
|
||||
console.log('render() dataToRender:', dataToRender, 'bins length:', this.bins.length);
|
||||
}
|
||||
maxValue = Math.max(...dataToRender);
|
||||
minValue = Math.min(...dataToRender);
|
||||
valueRange = maxValue - minValue;
|
||||
@@ -219,12 +212,12 @@ class ASCIIBarChart {
|
||||
const yAxisPadding = this.yAxisLabel ? ' ' : '';
|
||||
|
||||
// Add title if provided (centered)
|
||||
if (this.title) {
|
||||
// const chartWidth = 4 + this.maxDataPoints * 2; // Y-axis numbers + data columns // TEMP: commented for no-space test
|
||||
const chartWidth = 4 + this.maxDataPoints; // Y-axis numbers + data columns // TEMP: adjusted for no-space columns
|
||||
const titlePadding = Math.floor((chartWidth - this.title.length) / 2);
|
||||
output += yAxisPadding + ' '.repeat(Math.max(0, titlePadding)) + this.title + '\n\n';
|
||||
}
|
||||
if (this.title) {
|
||||
// const chartWidth = 4 + this.maxDataPoints * 2; // Y-axis numbers + data columns // TEMP: commented for no-space test
|
||||
const chartWidth = 4 + this.maxDataPoints; // Y-axis numbers + data columns // TEMP: adjusted for no-space 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--) {
|
||||
@@ -243,8 +236,8 @@ class ASCIIBarChart {
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the actual count value this row represents (0 at bottom, increasing upward)
|
||||
const rowCount = (row - 1) * scaleFactor;
|
||||
// Calculate the actual count value this row represents (1 at bottom, increasing upward)
|
||||
const rowCount = (row - 1) * scaleFactor + 1;
|
||||
|
||||
// Add Y-axis label (show actual count values)
|
||||
line += String(rowCount).padStart(3, ' ') + ' |';
|
||||
@@ -267,75 +260,75 @@ class ASCIIBarChart {
|
||||
}
|
||||
|
||||
// Draw X-axis
|
||||
// output += yAxisPadding + ' +' + '-'.repeat(this.maxDataPoints * 2) + '\n'; // TEMP: commented out for no-space test
|
||||
output += yAxisPadding + ' +' + '-'.repeat(this.maxDataPoints) + '\n'; // TEMP: back to original length
|
||||
// output += yAxisPadding + ' +' + '-'.repeat(this.maxDataPoints * 2) + '\n'; // TEMP: commented out for no-space test
|
||||
output += yAxisPadding + ' +' + '-'.repeat(this.maxDataPoints) + '\n'; // TEMP: back to original length
|
||||
|
||||
// Draw X-axis labels based on mode and format
|
||||
let xAxisLabels = yAxisPadding + ' '; // Initial padding to align with X-axis
|
||||
let xAxisLabels = yAxisPadding + ' '; // Initial padding to align with X-axis
|
||||
|
||||
// Determine label interval (every 5 columns)
|
||||
const labelInterval = 5;
|
||||
// Determine label interval (every 5 columns)
|
||||
const labelInterval = 5;
|
||||
|
||||
// Generate all labels first and store in array
|
||||
let labels = [];
|
||||
for (let i = 0; i < this.maxDataPoints; i++) {
|
||||
if (i % labelInterval === 0) {
|
||||
let label = '';
|
||||
if (this.useBinMode) {
|
||||
// For bin mode, show labels for all possible positions
|
||||
// i=0 is leftmost (most recent), i=maxDataPoints-1 is rightmost (oldest)
|
||||
const elapsedSec = (i * this.binDuration) / 1000;
|
||||
// Format with appropriate precision for sub-second bins
|
||||
if (this.binDuration < 1000) {
|
||||
// Show decimal seconds for sub-second bins
|
||||
label = elapsedSec.toFixed(1) + 's';
|
||||
} else {
|
||||
// Show whole seconds for 1+ second bins
|
||||
label = String(Math.round(elapsedSec)) + 's';
|
||||
}
|
||||
} else {
|
||||
// For legacy mode, show data point numbers
|
||||
const startIndex = Math.max(1, this.totalDataPoints - this.maxDataPoints + 1);
|
||||
label = String(startIndex + i);
|
||||
}
|
||||
labels.push(label);
|
||||
}
|
||||
}
|
||||
// Generate all labels first and store in array
|
||||
let labels = [];
|
||||
for (let i = 0; i < this.maxDataPoints; i++) {
|
||||
if (i % labelInterval === 0) {
|
||||
let label = '';
|
||||
if (this.useBinMode) {
|
||||
// For bin mode, show labels for all possible positions
|
||||
// i=0 is leftmost (most recent), i=maxDataPoints-1 is rightmost (oldest)
|
||||
const elapsedSec = (i * this.binDuration) / 1000;
|
||||
// Format with appropriate precision for sub-second bins
|
||||
if (this.binDuration < 1000) {
|
||||
// Show decimal seconds for sub-second bins
|
||||
label = elapsedSec.toFixed(1) + 's';
|
||||
} else {
|
||||
// Show whole seconds for 1+ second bins
|
||||
label = String(Math.round(elapsedSec)) + 's';
|
||||
}
|
||||
} else {
|
||||
// For legacy mode, show data point numbers
|
||||
const startIndex = Math.max(1, this.totalDataPoints - this.maxDataPoints + 1);
|
||||
label = String(startIndex + i);
|
||||
}
|
||||
labels.push(label);
|
||||
}
|
||||
}
|
||||
|
||||
// Build the label string with calculated spacing
|
||||
for (let i = 0; i < labels.length; i++) {
|
||||
const label = labels[i];
|
||||
xAxisLabels += label;
|
||||
// Build the label string with calculated spacing
|
||||
for (let i = 0; i < labels.length; i++) {
|
||||
const label = labels[i];
|
||||
xAxisLabels += label;
|
||||
|
||||
// Add spacing: labelInterval - label.length (except for last label)
|
||||
if (i < labels.length - 1) {
|
||||
const spacing = labelInterval - label.length;
|
||||
xAxisLabels += ' '.repeat(spacing);
|
||||
}
|
||||
}
|
||||
// Add spacing: labelInterval - label.length (except for last label)
|
||||
if (i < labels.length - 1) {
|
||||
const spacing = labelInterval - label.length;
|
||||
xAxisLabels += ' '.repeat(spacing);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the label line extends to match the X-axis dash line length
|
||||
// The dash line is this.maxDataPoints characters long, starting after " +"
|
||||
const dashLineLength = this.maxDataPoints;
|
||||
const minLabelLineLength = yAxisPadding.length + 4 + dashLineLength; // 4 for " "
|
||||
if (xAxisLabels.length < minLabelLineLength) {
|
||||
xAxisLabels += ' '.repeat(minLabelLineLength - xAxisLabels.length);
|
||||
}
|
||||
// Ensure the label line extends to match the X-axis dash line length
|
||||
// The dash line is this.maxDataPoints characters long, starting after " +"
|
||||
const dashLineLength = this.maxDataPoints;
|
||||
const minLabelLineLength = yAxisPadding.length + 4 + dashLineLength; // 4 for " "
|
||||
if (xAxisLabels.length < minLabelLineLength) {
|
||||
xAxisLabels += ' '.repeat(minLabelLineLength - xAxisLabels.length);
|
||||
}
|
||||
output += xAxisLabels + '\n';
|
||||
|
||||
// Add X-axis label if provided
|
||||
if (this.xAxisLabel) {
|
||||
// const labelPadding = Math.floor((this.maxDataPoints * 2 - this.xAxisLabel.length) / 2); // TEMP: commented for no-space test
|
||||
const labelPadding = Math.floor((this.maxDataPoints - this.xAxisLabel.length) / 2); // TEMP: adjusted for no-space columns
|
||||
output += '\n' + yAxisPadding + ' ' + ' '.repeat(Math.max(0, labelPadding)) + this.xAxisLabel + '\n';
|
||||
}
|
||||
if (this.xAxisLabel) {
|
||||
// const labelPadding = Math.floor((this.maxDataPoints * 2 - this.xAxisLabel.length) / 2); // TEMP: commented for no-space test
|
||||
const labelPadding = Math.floor((this.maxDataPoints - this.xAxisLabel.length) / 2); // TEMP: adjusted for no-space columns
|
||||
output += '\n' + yAxisPadding + ' ' + ' '.repeat(Math.max(0, labelPadding)) + this.xAxisLabel + '\n';
|
||||
}
|
||||
|
||||
this.container.textContent = output;
|
||||
|
||||
// Adjust font size to fit width (only once at initialization)
|
||||
if (this.autoFitWidth) {
|
||||
this.adjustFontSize();
|
||||
}
|
||||
if (this.autoFitWidth) {
|
||||
this.adjustFontSize();
|
||||
}
|
||||
|
||||
// Update the external info display
|
||||
if (this.useBinMode) {
|
||||
@@ -350,7 +343,7 @@ class ASCIIBarChart {
|
||||
document.getElementById('scale').textContent = `Min: ${minValue}, Max: ${maxValue}, Height: ${scale}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the info display
|
||||
* @private
|
||||
|
||||
Reference in New Issue
Block a user