Skip to content

Commit 0ba6b35

Browse files
committed
[llvm-advisor] add web-based dashboard and user interface
-implement responsive HTML5 dashboard - add interactive javascript modules for data visualization - implement code explorer - add performance analysis views
1 parent 26474d4 commit 0ba6b35

File tree

12 files changed

+7268
-0
lines changed

12 files changed

+7268
-0
lines changed

llvm/tools/llvm-advisor/tools/webserver/frontend/static/css/custom.css

Lines changed: 805 additions & 0 deletions
Large diffs are not rendered by default.

llvm/tools/llvm-advisor/tools/webserver/frontend/static/js/api-client.js

Lines changed: 436 additions & 0 deletions
Large diffs are not rendered by default.

llvm/tools/llvm-advisor/tools/webserver/frontend/static/js/app.js

Lines changed: 412 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,372 @@
1+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
2+
// See https://llvm.org/LICENSE.txt for license information.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
5+
/**
6+
* Chart Components
7+
* Wrapper components for Chart.js with LLVM Advisor styling and functionality
8+
*/
9+
10+
export class ChartComponents {
11+
constructor() {
12+
this.defaultColors = {
13+
primary : '#3b82f6',
14+
secondary : '#64748b',
15+
success : '#10b981',
16+
warning : '#f59e0b',
17+
error : '#ef4444',
18+
info : '#06b6d4'
19+
};
20+
21+
this.colorPalettes = {
22+
blue : [
23+
'#dbeafe', '#bfdbfe', '#93c5fd', '#60a5fa', '#3b82f6', '#2563eb',
24+
'#1d4ed8', '#1e40af'
25+
],
26+
green : [
27+
'#d1fae5', '#a7f3d0', '#6ee7b7', '#34d399', '#10b981', '#059669',
28+
'#047857', '#065f46'
29+
],
30+
purple : [
31+
'#e9d5ff', '#d8b4fe', '#c084fc', '#a855f7', '#9333ea', '#7c3aed',
32+
'#6d28d9', '#5b21b6'
33+
],
34+
orange : [
35+
'#fed7aa', '#fdba74', '#fb923c', '#f97316', '#ea580c', '#dc2626',
36+
'#b91c1c', '#991b1b'
37+
],
38+
mixed : [
39+
'#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#06b6d4',
40+
'#84cc16', '#f97316'
41+
]
42+
};
43+
}
44+
45+
/**
46+
* Initialize Chart.js with global configurations
47+
*/
48+
init() {
49+
if (typeof Chart === 'undefined') {
50+
console.warn('Chart.js not loaded. Charts will not be available.');
51+
return;
52+
}
53+
54+
// Set global Chart.js defaults
55+
Chart.defaults.font.family = 'Inter, system-ui, -apple-system, sans-serif';
56+
Chart.defaults.font.size = 12;
57+
Chart.defaults.color = '#6b7280';
58+
Chart.defaults.borderColor = '#e5e7eb';
59+
Chart.defaults.backgroundColor = '#f9fafb';
60+
61+
// Configure default responsive options
62+
Chart.defaults.responsive = true;
63+
Chart.defaults.maintainAspectRatio = false;
64+
65+
// Configure default animation
66+
Chart.defaults.animation.duration = 400;
67+
Chart.defaults.animation.easing = 'easeInOutQuart';
68+
69+
console.log('Chart components initialized');
70+
}
71+
72+
/**
73+
* Generate color palette for charts
74+
*/
75+
generateColors(count, palette = 'mixed', opacity = 1) {
76+
const colors = this.colorPalettes[palette] || this.colorPalettes.mixed;
77+
const result = [];
78+
79+
for (let i = 0; i < count; i++) {
80+
const colorIndex = i % colors.length;
81+
const color = colors[colorIndex];
82+
83+
if (opacity < 1) {
84+
// Convert hex to rgba
85+
const r = parseInt(color.slice(1, 3), 16);
86+
const g = parseInt(color.slice(3, 5), 16);
87+
const b = parseInt(color.slice(5, 7), 16);
88+
result.push(`rgba(${r}, ${g}, ${b}, ${opacity})`);
89+
} else {
90+
result.push(color);
91+
}
92+
}
93+
94+
return result;
95+
}
96+
97+
/**
98+
* Generate gradient colors for charts
99+
*/
100+
generateGradient(ctx, color1, color2) {
101+
const gradient = ctx.createLinearGradient(0, 0, 0, 400);
102+
gradient.addColorStop(0, color1);
103+
gradient.addColorStop(1, color2);
104+
return gradient;
105+
}
106+
107+
/**
108+
* Get default chart options with LLVM Advisor styling
109+
*/
110+
getDefaultOptions(type = 'default') {
111+
const baseOptions = {
112+
responsive : true,
113+
maintainAspectRatio : false,
114+
plugins : {
115+
legend : {
116+
display : true,
117+
position : 'top',
118+
align : 'start',
119+
labels : {
120+
padding : 20,
121+
usePointStyle : true,
122+
font : {size : 11, weight : '500'}
123+
}
124+
},
125+
tooltip : {
126+
backgroundColor : 'rgba(17, 24, 39, 0.95)',
127+
titleColor : '#f9fafb',
128+
bodyColor : '#f3f4f6',
129+
borderColor : '#374151',
130+
borderWidth : 1,
131+
cornerRadius : 8,
132+
displayColors : true,
133+
padding : 12,
134+
titleFont : {size : 12, weight : '600'},
135+
bodyFont : {size : 11}
136+
}
137+
},
138+
interaction : {intersect : false, mode : 'index'}
139+
};
140+
141+
// Type-specific options
142+
switch (type) {
143+
case 'bar':
144+
return {
145+
...baseOptions,
146+
scales : {
147+
x : {grid : {display : false}, border : {display : false}},
148+
y : {
149+
beginAtZero : true,
150+
grid : {color : '#f3f4f6', drawBorder : false},
151+
border : {display : false}
152+
}
153+
}
154+
};
155+
156+
case 'line':
157+
return {
158+
...baseOptions,
159+
elements : {
160+
point : {radius : 4, hoverRadius : 6, borderWidth : 2},
161+
line : {tension : 0.4, borderWidth : 2}
162+
},
163+
scales : {
164+
x : {grid : {display : false}, border : {display : false}},
165+
y : {
166+
beginAtZero : true,
167+
grid : {color : '#f3f4f6', drawBorder : false},
168+
border : {display : false}
169+
}
170+
}
171+
};
172+
173+
case 'doughnut':
174+
case 'pie':
175+
return {
176+
...baseOptions,
177+
cutout : type === 'doughnut' ? '60%' : 0,
178+
plugins : {
179+
...baseOptions.plugins,
180+
legend : {...baseOptions.plugins.legend, position : 'bottom'}
181+
}
182+
};
183+
184+
default:
185+
return baseOptions;
186+
}
187+
}
188+
189+
/**
190+
* Create a loading placeholder for charts
191+
*/
192+
createLoadingPlaceholder(canvasId) {
193+
const canvas = document.getElementById(canvasId);
194+
if (!canvas)
195+
return;
196+
197+
const ctx = canvas.getContext('2d');
198+
const width = canvas.width;
199+
const height = canvas.height;
200+
201+
// Clear canvas
202+
ctx.clearRect(0, 0, width, height);
203+
204+
// Draw loading spinner
205+
const centerX = width / 2;
206+
const centerY = height / 2;
207+
const radius = 20;
208+
209+
ctx.strokeStyle = '#3b82f6';
210+
ctx.lineWidth = 3;
211+
ctx.lineCap = 'round';
212+
213+
// Create animated spinner effect
214+
const drawSpinner = (rotation) => {
215+
ctx.clearRect(0, 0, width, height);
216+
217+
ctx.beginPath();
218+
ctx.arc(centerX, centerY, radius, rotation, rotation + Math.PI * 1.5);
219+
ctx.stroke();
220+
221+
// Add loading text
222+
ctx.fillStyle = '#6b7280';
223+
ctx.font = '12px Inter, sans-serif';
224+
ctx.textAlign = 'center';
225+
ctx.fillText('Loading chart...', centerX, centerY + radius + 20);
226+
};
227+
228+
let rotation = 0;
229+
const interval = setInterval(() => {
230+
rotation += 0.1;
231+
drawSpinner(rotation);
232+
}, 50);
233+
234+
// Store interval reference for cleanup
235+
canvas.dataset.loadingInterval = interval;
236+
}
237+
238+
/**
239+
* Clear loading placeholder
240+
*/
241+
clearLoadingPlaceholder(canvasId) {
242+
const canvas = document.getElementById(canvasId);
243+
if (!canvas)
244+
return;
245+
246+
const interval = canvas.dataset.loadingInterval;
247+
if (interval) {
248+
clearInterval(interval);
249+
delete canvas.dataset.loadingInterval;
250+
}
251+
252+
const ctx = canvas.getContext('2d');
253+
ctx.clearRect(0, 0, canvas.width, canvas.height);
254+
}
255+
256+
/**
257+
* Create a chart with error state
258+
*/
259+
showChartError(canvasId, message = 'Failed to load chart data') {
260+
const canvas = document.getElementById(canvasId);
261+
if (!canvas)
262+
return;
263+
264+
const ctx = canvas.getContext('2d');
265+
const width = canvas.width;
266+
const height = canvas.height;
267+
268+
// Clear canvas
269+
ctx.clearRect(0, 0, width, height);
270+
271+
// Draw error icon and message
272+
const centerX = width / 2;
273+
const centerY = height / 2;
274+
275+
// Error icon (triangle with exclamation)
276+
ctx.fillStyle = '#ef4444';
277+
ctx.beginPath();
278+
ctx.moveTo(centerX, centerY - 15);
279+
ctx.lineTo(centerX - 12, centerY + 10);
280+
ctx.lineTo(centerX + 12, centerY + 10);
281+
ctx.closePath();
282+
ctx.fill();
283+
284+
// Exclamation mark
285+
ctx.fillStyle = '#ffffff';
286+
ctx.font = 'bold 16px Inter, sans-serif';
287+
ctx.textAlign = 'center';
288+
ctx.fillText('!', centerX, centerY + 5);
289+
290+
// Error message
291+
ctx.fillStyle = '#6b7280';
292+
ctx.font = '12px Inter, sans-serif';
293+
ctx.fillText(message, centerX, centerY + 35);
294+
}
295+
296+
/**
297+
* Animate chart on data update
298+
*/
299+
animateChart(chart, newData) {
300+
if (!chart || !newData)
301+
return;
302+
303+
// Update data with animation
304+
chart.data = newData;
305+
chart.update('active');
306+
}
307+
308+
/**
309+
* Export chart as image
310+
*/
311+
exportChart(canvasId, filename = 'chart.png') {
312+
const canvas = document.getElementById(canvasId);
313+
if (!canvas)
314+
return;
315+
316+
const link = document.createElement('a');
317+
link.download = filename;
318+
link.href = canvas.toDataURL();
319+
link.click();
320+
}
321+
322+
/**
323+
* Get responsive font size based on container
324+
*/
325+
getResponsiveFontSize(container) {
326+
const width = container.offsetWidth;
327+
if (width < 400)
328+
return 10;
329+
if (width < 600)
330+
return 11;
331+
return 12;
332+
}
333+
334+
/**
335+
* Format numbers for chart labels
336+
*/
337+
formatChartNumber(value, type = 'default') {
338+
switch (type) {
339+
case 'bytes':
340+
return this.formatBytes(value);
341+
case 'time':
342+
return this.formatTime(value);
343+
case 'percentage':
344+
return `${value.toFixed(1)}%`;
345+
case 'compact':
346+
if (value >= 1000000)
347+
return `${(value / 1000000).toFixed(1)}M`;
348+
if (value >= 1000)
349+
return `${(value / 1000).toFixed(1)}K`;
350+
return value.toString();
351+
default:
352+
return value.toLocaleString();
353+
}
354+
}
355+
356+
formatBytes(bytes) {
357+
if (bytes === 0)
358+
return '0 B';
359+
const k = 1024;
360+
const sizes = [ 'B', 'KB', 'MB', 'GB' ];
361+
const i = Math.floor(Math.log(bytes) / Math.log(k));
362+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
363+
}
364+
365+
formatTime(ms) {
366+
if (ms < 1000)
367+
return `${ms}ms`;
368+
if (ms < 60000)
369+
return `${(ms / 1000).toFixed(1)}s`;
370+
return `${(ms / 60000).toFixed(1)}m`;
371+
}
372+
}

0 commit comments

Comments
 (0)