@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
450 lines (403 loc) • 10.6 kB
JavaScript
// Atlas Dashboard Charts
class ChartManager {
constructor() {
this.charts = {};
this.defaultOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
}
},
scales: {
y: {
beginAtZero: true,
grid: {
color: '#e2e8f0'
}
},
x: {
grid: {
color: '#e2e8f0'
}
}
}
};
}
createChart(canvasId, type, data, options = {}) {
const canvas = document.getElementById(canvasId);
if (!canvas) {
console.warn(`Canvas element ${canvasId} not found`);
return null;
}
// Destroy existing chart if it exists
if (this.charts[canvasId]) {
this.charts[canvasId].destroy();
}
const ctx = canvas.getContext('2d');
const mergedOptions = this.mergeOptions(this.defaultOptions, options);
this.charts[canvasId] = new Chart(ctx, {
type,
data,
options: mergedOptions
});
return this.charts[canvasId];
}
updateChart(canvasId, newData) {
const chart = this.charts[canvasId];
if (!chart) {
console.warn(`Chart ${canvasId} not found`);
return;
}
chart.data = newData;
chart.update();
}
destroyChart(canvasId) {
if (this.charts[canvasId]) {
this.charts[canvasId].destroy();
delete this.charts[canvasId];
}
}
mergeOptions(defaults, custom) {
return {
...defaults,
...custom,
plugins: { ...defaults.plugins, ...custom.plugins },
scales: { ...defaults.scales, ...custom.scales }
};
}
}
const chartManager = new ChartManager();
// Performance Charts
function updatePerformanceCharts(performanceData) {
updateToolUsageChart(performanceData);
updateResponseTimeChart(performanceData);
updateSuccessRateChart(performanceData);
updateQualityChart(performanceData);
}
function updateToolUsageChart(data) {
// Use topPerformingTools from actual SystemMetrics
const topTools = data.topPerformingTools || [];
const tools = topTools.map(t => t.tool);
const executions = topTools.map(t => t.calls);
const chartData = {
labels: tools,
datasets: [{
label: 'Tool Calls',
data: executions,
backgroundColor: [
'#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6',
'#ec4899', '#06b6d4', '#84cc16', '#f97316', '#6366f1'
],
borderWidth: 0
}]
};
chartManager.createChart('toolUsageChart', 'bar', chartData, {
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: 'Number of Calls'
}
}
}
});
}
function updateResponseTimeChart(data) {
// Use slowestTools from actual SystemMetrics
const slowestTools = data.slowestTools || [];
const tools = slowestTools.map(t => t.tool);
const responseTimes = slowestTools.map(t => t.avgTime);
const chartData = {
labels: tools,
datasets: [{
label: 'Average Response Time (ms)',
data: responseTimes,
backgroundColor: 'rgba(59, 130, 246, 0.6)',
borderColor: '#3b82f6',
borderWidth: 2,
fill: true
}]
};
chartManager.createChart('responseTimeChart', 'line', chartData, {
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: 'Response Time (ms)'
}
}
}
});
}
function updateSuccessRateChart(data) {
// Use actual success rate from SystemMetrics
const overallSuccessRate = (data.overallSuccessRate || 0) * 100;
// Generate time series data - for now showing current rate across 24 hours
// TODO: Implement historical data tracking in PerformanceMonitor
const now = new Date();
const labels = [];
const successRates = [];
for (let i = 23; i >= 0; i--) {
const hour = new Date(now.getTime() - i * 60 * 60 * 1000);
labels.push(hour.getHours() + ':00');
// Use actual rate with minimal variation for visualization
const variation = (Math.random() - 0.5) * 2; // ±1% variation
successRates.push(Math.max(0, Math.min(100, overallSuccessRate + variation)));
}
const chartData = {
labels,
datasets: [{
label: 'Success Rate (%)',
data: successRates,
backgroundColor: 'rgba(16, 185, 129, 0.1)',
borderColor: '#10b981',
borderWidth: 2,
fill: true,
tension: 0.4
}]
};
chartManager.createChart('successRateChart', 'line', chartData, {
scales: {
y: {
beginAtZero: false,
min: 80,
max: 100,
title: {
display: true,
text: 'Success Rate (%)'
}
}
}
});
}
function updateQualityChart(data) {
// Try to use real quality data if available, otherwise show placeholder
const hasQualityData = data.qualityMetrics && data.qualityMetrics.totalEvaluations > 0;
if (hasQualityData) {
const metrics = data.qualityMetrics;
const chartData = {
labels: ['Quality', 'Appropriateness', 'Completeness', 'Accuracy'],
datasets: [{
label: 'Average Scores',
data: [
metrics.averageQuality,
metrics.averageAppropriateness,
metrics.averageCompleteness,
metrics.averageAccuracy
],
backgroundColor: [
'#3b82f6', '#10b981', '#f59e0b', '#8b5cf6'
],
borderWidth: 0
}]
};
chartManager.createChart('qualityChart', 'doughnut', chartData, {
plugins: {
legend: {
position: 'right'
}
}
});
} else {
// Show placeholder when no quality data is available
const chartData = {
labels: ['No Quality Data'],
datasets: [{
label: 'Evaluations needed',
data: [1],
backgroundColor: ['#e5e7eb'],
borderWidth: 0
}]
};
chartManager.createChart('qualityChart', 'doughnut', chartData, {
plugins: {
legend: {
display: false
}
}
});
}
}
// Agile Charts
function updateBurndownChart(burndownData) {
const { idealBurndown, actualBurndown, sprintDays } = burndownData;
const labels = Array.from({ length: sprintDays + 1 }, (_, i) => `Day ${i}`);
const chartData = {
labels,
datasets: [
{
label: 'Ideal Burndown',
data: idealBurndown.map(point => point.remaining),
borderColor: '#6b7280',
borderDash: [5, 5],
borderWidth: 2,
fill: false,
pointRadius: 0
},
{
label: 'Actual Burndown',
data: actualBurndown.map(point => point.remaining),
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
borderWidth: 3,
fill: true,
tension: 0.2
}
]
};
chartManager.createChart('burndownChart', 'line', chartData, {
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: 'Story Points Remaining'
}
},
x: {
title: {
display: true,
text: 'Sprint Days'
}
}
},
plugins: {
legend: {
position: 'top'
}
}
});
}
function updateVelocityChart(velocityData) {
const { sprints } = velocityData;
const labels = sprints.map(sprint => sprint.name);
const planned = sprints.map(sprint => sprint.planned);
const completed = sprints.map(sprint => sprint.completed);
const chartData = {
labels,
datasets: [
{
label: 'Planned Story Points',
data: planned,
backgroundColor: 'rgba(107, 114, 128, 0.6)',
borderColor: '#6b7280',
borderWidth: 1
},
{
label: 'Completed Story Points',
data: completed,
backgroundColor: 'rgba(59, 130, 246, 0.6)',
borderColor: '#3b82f6',
borderWidth: 1
}
]
};
chartManager.createChart('velocityChart', 'bar', chartData, {
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: 'Story Points'
}
}
},
plugins: {
legend: {
position: 'top'
}
}
});
}
// Error Charts
function updateErrorCharts(errorData) {
updateErrorTrendsChart(errorData);
updateErrorTypesChart(errorData);
}
function updateErrorTrendsChart(data) {
const { dataPoints } = data;
const labels = dataPoints.map(point => {
const date = new Date(point.timestamp);
return date.getHours() + ':' + date.getMinutes().toString().padStart(2, '0');
});
const errorCounts = dataPoints.map(point => point.errorCount);
const chartData = {
labels,
datasets: [{
label: 'Error Count',
data: errorCounts,
backgroundColor: 'rgba(239, 68, 68, 0.1)',
borderColor: '#ef4444',
borderWidth: 2,
fill: true,
tension: 0.4
}]
};
chartManager.createChart('errorTrendsChart', 'line', chartData, {
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: 'Number of Errors'
}
}
}
});
}
function updateErrorTypesChart(data) {
// Mock error type distribution
const errorTypes = ['validation', 'network', 'permission', 'resource', 'logic'];
const errorCounts = [12, 8, 5, 3, 7]; // Mock data
const chartData = {
labels: errorTypes.map(type => type.charAt(0).toUpperCase() + type.slice(1)),
datasets: [{
data: errorCounts,
backgroundColor: [
'#ef4444', '#f59e0b', '#eab308', '#06b6d4', '#8b5cf6'
],
borderWidth: 0
}]
};
chartManager.createChart('errorTypesChart', 'pie', chartData, {
plugins: {
legend: {
position: 'bottom'
}
}
});
}
// Chart theme helpers
function getChartColors() {
return {
primary: '#3b82f6',
success: '#10b981',
warning: '#f59e0b',
error: '#ef4444',
info: '#06b6d4',
purple: '#8b5cf6',
pink: '#ec4899',
gray: '#6b7280'
};
}
function createGradient(ctx, color) {
const gradient = ctx.createLinearGradient(0, 0, 0, 300);
gradient.addColorStop(0, color + '40'); // 25% opacity
gradient.addColorStop(1, color + '00'); // 0% opacity
return gradient;
}
// Export functions for global access
window.updatePerformanceCharts = updatePerformanceCharts;
window.updateBurndownChart = updateBurndownChart;
window.updateVelocityChart = updateVelocityChart;
window.updateErrorCharts = updateErrorCharts;
window.chartManager = chartManager;