/**
* Context Pressure Visualization
* Tractatus Framework - Phase 3: Data Visualization
*
* Visual representation of Context Pressure Monitor metrics
* Uses amber color scheme matching the ContextPressureMonitor service
*/
class PressureChart {
constructor(containerId, gaugeContainerId = 'pressure-gauge') {
this.container = document.getElementById(containerId);
this.gaugeContainer = document.getElementById(gaugeContainerId);
if (!this.container) {
console.error(`[PressureChart] Container #${containerId} not found`);
return;
}
this.currentLevel = 0; // 0-100
this.targetLevel = 0;
this.animating = false;
this.colors = {
low: '#10b981', // Green - NORMAL
moderate: '#f59e0b', // Amber - ELEVATED
high: '#ef4444', // Red - HIGH
critical: '#991b1b' // Dark Red - CRITICAL
};
this.init();
}
init() {
this.render();
this.attachEventListeners();
console.log('[PressureChart] Initialized');
}
render() {
console.log('[PressureChart] render() called, container:', this.container);
this.container.innerHTML = `
Context Pressure Monitor
NORMAL
Interactive Demo: Click "Simulate Pressure" to watch how context pressure builds. As token usage increases, tasks become more complex, and error rates rise. The framework monitors this relationship to detect when AI performance may degrade.
The timeline on the right shows how six governance components coordinate to validate each request and maintain safe operation.
0
Tokens Used
Low
Complexity
0
Error Rate
`;
// Clear gauge container if it exists (no longer needed)
if (this.gaugeContainer) {
this.gaugeContainer.innerHTML = '';
}
// Store references
this.elements = {
gaugeFill: document.getElementById('gauge-fill'),
gaugeValue: document.getElementById('gauge-value'),
status: document.getElementById('pressure-status'),
tokens: document.getElementById('metric-tokens'),
complexity: document.getElementById('metric-complexity'),
errors: document.getElementById('metric-errors'),
simulateBtn: document.getElementById('pressure-simulate-btn'),
resetBtn: document.getElementById('pressure-reset-btn')
};
// Verify innerHTML was set
console.log('[PressureChart] innerHTML length:', this.container.innerHTML.length);
console.log('[PressureChart] First 100 chars:', this.container.innerHTML.substring(0, 100));
// Verify elements were found
console.log('[PressureChart] Elements found:', {
gaugeFill: !!this.elements.gaugeFill,
gaugeValue: !!this.elements.gaugeValue,
status: !!this.elements.status,
simulateBtn: !!this.elements.simulateBtn,
resetBtn: !!this.elements.resetBtn
});
}
attachEventListeners() {
if (!this.elements.simulateBtn || !this.elements.resetBtn) {
console.error('[PressureChart] Cannot attach event listeners - buttons not found');
return;
}
console.log('[PressureChart] Attaching event listeners to buttons');
this.elements.simulateBtn.addEventListener('click', () => this.simulate());
this.elements.resetBtn.addEventListener('click', () => this.reset());
console.log('[PressureChart] Event listeners attached successfully');
}
setLevel(level) {
this.targetLevel = Math.max(0, Math.min(100, level));
this.animateToTarget();
}
animateToTarget() {
if (this.animating) return;
this.animating = true;
const animate = () => {
const diff = this.targetLevel - this.currentLevel;
if (Math.abs(diff) < 0.5) {
this.currentLevel = this.targetLevel;
this.animating = false;
this.updateGauge();
return;
}
this.currentLevel += diff * 0.1;
this.updateGauge();
requestAnimationFrame(animate);
};
animate();
}
updateGauge() {
const level = this.currentLevel;
const angle = (level / 100) * 180; // 0-180 degrees
const radians = (angle * Math.PI) / 180;
// Calculate arc endpoint (20% smaller gauge: radius 96 instead of 120)
const centerX = 150;
const centerY = 120;
const radius = 96;
const startX = 54;
const startY = 120;
const endX = centerX + radius * Math.cos(Math.PI - radians);
const endY = centerY - radius * Math.sin(Math.PI - radians);
const largeArcFlag = angle > 180 ? 1 : 0;
const path = `M ${startX} ${startY} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${endX} ${endY}`;
this.elements.gaugeFill.setAttribute('d', path);
this.elements.gaugeValue.textContent = `${Math.round(level)}%`;
// Update color based on level
let color, status;
if (level < 25) {
color = this.colors.low;
status = 'NORMAL';
} else if (level < 50) {
color = this.colors.moderate;
status = 'ELEVATED';
} else if (level < 75) {
color = this.colors.high;
status = 'HIGH';
} else {
color = this.colors.critical;
status = 'CRITICAL';
}
this.elements.gaugeFill.setAttribute('stroke', color);
// Update status badge with animation
const previousStatus = this.elements.status.textContent;
this.elements.status.textContent = status;
// Badge styling based on level
const baseClasses = 'px-4 py-2 rounded-full text-sm font-bold uppercase transition-all duration-500';
let bgClass, textClass;
if (level < 25) {
bgClass = 'bg-green-100';
textClass = 'text-green-700';
} else if (level < 50) {
bgClass = 'bg-amber-100';
textClass = 'text-amber-700';
} else if (level < 75) {
bgClass = 'bg-red-100';
textClass = 'text-red-700';
} else {
bgClass = 'bg-red-200';
textClass = 'text-red-900';
}
// Add pulse animation when status changes
const pulseClass = previousStatus !== status ? 'animate-pulse' : '';
this.elements.status.className = `${baseClasses} ${bgClass} ${textClass} ${pulseClass}`;
// Remove pulse after animation
if (pulseClass) {
setTimeout(() => {
this.elements.status.className = `${baseClasses} ${bgClass} ${textClass}`;
}, 1000);
}
// Update metrics based on pressure level
const tokens = Math.round(level * 2000); // 0-200k tokens
const complexityLevels = ['Low', 'Moderate', 'High', 'Extreme'];
const complexityIndex = Math.min(3, Math.floor(level / 25));
const errorRate = Math.round(level / 5); // 0-20%
this.elements.tokens.textContent = tokens.toLocaleString();
this.elements.complexity.textContent = complexityLevels[complexityIndex];
this.elements.errors.textContent = `${errorRate}%`;
}
simulate() {
console.log('[PressureChart] Simulate button clicked - starting pressure simulation');
// Trigger timeline simulation if available
if (window.activityTimeline) {
console.log('[PressureChart] Triggering governance flow timeline');
window.activityTimeline.simulateFlow();
}
// Simulate pressure increasing from current to 85%
const targetLevels = [30, 50, 70, 85];
let index = 0;
const step = () => {
if (index >= targetLevels.length) return;
console.log('[PressureChart] Setting pressure level to', targetLevels[index]);
this.setLevel(targetLevels[index]);
index++;
setTimeout(step, 1500);
};
step();
}
reset() {
console.log('[PressureChart] Reset button clicked');
// Reset timeline if available
if (window.activityTimeline) {
console.log('[PressureChart] Resetting governance flow timeline');
window.activityTimeline.reset();
}
this.setLevel(0);
}
}
// Auto-initialize if container exists
if (typeof window !== 'undefined') {
function initPressureChart() {
console.log('[PressureChart] Attempting to initialize, readyState:', document.readyState);
const container = document.getElementById('pressure-chart');
if (container) {
console.log('[PressureChart] Container found, creating instance');
window.pressureChart = new PressureChart('pressure-chart');
} else {
console.error('[PressureChart] Container #pressure-chart not found in DOM');
}
}
// Initialize immediately if DOM is already loaded, otherwise wait for DOMContentLoaded
console.log('[PressureChart] Script loaded, readyState:', document.readyState);
if (document.readyState === 'loading') {
console.log('[PressureChart] Waiting for DOMContentLoaded');
document.addEventListener('DOMContentLoaded', initPressureChart);
} else {
console.log('[PressureChart] DOM already loaded, initializing immediately');
initPressureChart();
}
}
// Export for module systems
if (typeof module !== 'undefined' && module.exports) {
module.exports = PressureChart;
}