diff --git a/text_graph.js b/text_graph.js index c8cb06e..d5ac2bc 100644 --- a/text_graph.js +++ b/text_graph.js @@ -20,22 +20,22 @@ class ASCIIBarChart { * @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); - 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 - this.debug = options.debug || false; // Debug logging option + 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 + this.debug = options.debug || false; // Debug logging option - // Time bin configuration - this.useBinMode = options.useBinMode !== false; // Default to true - this.binDuration = options.binDuration || 4000; // 4 seconds default - this.xAxisLabelFormat = options.xAxisLabelFormat || 'elapsed'; + // Time bin configuration + this.useBinMode = options.useBinMode !== false; // Default to true + this.binDuration = options.binDuration || 4000; // 4 seconds default + this.xAxisLabelFormat = options.xAxisLabelFormat || 'elapsed'; // Time bin data structures this.bins = []; @@ -57,32 +57,22 @@ 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: increment count in current active bin + this.checkBinRotation(); // Ensure we have an active bin + this.bins[this.currentBinIndex].count++; + this.totalDataPoints++; this.render(); this.updateInfo(); } - + /** * Clear all data from the chart */ @@ -100,7 +90,7 @@ class ASCIIBarChart { this.render(); this.updateInfo(); } - + /** * Calculate the width of the chart in characters * @returns {number} The chart width in characters @@ -121,14 +111,14 @@ class ASCIIBarChart { const totalWidth = yAxisPadding + yAxisNumbers + separator + dataWidth + padding; // Only log when width changes - if (this.debug && this.lastChartWidth !== totalWidth) { - console.log('getChartWidth changed:', { dataLength, totalWidth, previous: this.lastChartWidth }); - 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 @@ -153,15 +143,15 @@ class ASCIIBarChart { const fontSize = Math.max(4, Math.min(20, optimalFontSize)); // Only log when font size changes - if (this.debug && this.lastFontSize !== fontSize) { - console.log('fontSize changed:', { containerWidth, chartWidth, fontSize, previous: this.lastFontSize }); - this.lastFontSize = fontSize; - } + if (this.debug && this.lastFontSize !== fontSize) { + console.log('fontSize changed:', { containerWidth, chartWidth, fontSize, previous: this.lastFontSize }); + this.lastFontSize = fontSize; + } this.container.style.fontSize = fontSize + 'px'; this.container.style.lineHeight = '1.0'; } - + /** * Render the chart to the container * @private @@ -223,12 +213,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--) { @@ -271,75 +261,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) { @@ -354,7 +344,7 @@ class ASCIIBarChart { document.getElementById('scale').textContent = `Min: ${minValue}, Max: ${maxValue}, Height: ${scale}`; } } - + /** * Update the info display * @private