4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
418 lines
14 KiB
JavaScript
418 lines
14 KiB
JavaScript
// Radar Chart for Seqera vs OpenSRE comparison
|
|
// This script initializes the radar chart when the page loads
|
|
|
|
function initRadarChart() {
|
|
const canvas = document.getElementById('seqera-tracer-radar-chart');
|
|
if (!canvas) {
|
|
console.log('Canvas element not found');
|
|
return;
|
|
}
|
|
|
|
if (typeof Chart === 'undefined') {
|
|
console.log('Chart.js not loaded yet, retrying...');
|
|
setTimeout(initRadarChart, 100);
|
|
return;
|
|
}
|
|
|
|
console.log('Initializing radar chart...');
|
|
|
|
const isDark = document.documentElement.classList.contains('dark');
|
|
const gridColor = isDark ? '#374151' : '#e5e7eb';
|
|
const textColor = isDark ? '#ffffff' : '#374151';
|
|
|
|
const data = {
|
|
labels: [
|
|
['Workflow', 'Orchestration'],
|
|
['Task', 'Level', 'Visibility'],
|
|
['Framework', 'Support'],
|
|
['Kernel', 'Level', 'Observability'],
|
|
['Synthetic', 'Logging'],
|
|
['Cost', 'Performance', 'Optimization'],
|
|
['Anomaly', 'Detection'],
|
|
['Scientific', 'Workload', 'Suitability'],
|
|
['Observability', 'Depth']
|
|
],
|
|
datasets: [
|
|
{
|
|
label: 'Seqera-ONLY',
|
|
data: [4, 2, 1, 0, 0, 2, 1, 5, 2],
|
|
borderColor: '#3b82f6',
|
|
backgroundColor: 'rgba(59, 130, 246, 0.15)',
|
|
fill: true,
|
|
borderWidth: 2.5,
|
|
pointBackgroundColor: '#3b82f6',
|
|
pointBorderColor: '#fff',
|
|
pointBorderWidth: 2,
|
|
pointRadius: 4,
|
|
pointHoverRadius: 6,
|
|
pointHoverBackgroundColor: '#fff',
|
|
pointHoverBorderColor: '#3b82f6',
|
|
pointHoverBorderWidth: 3,
|
|
descriptions: [
|
|
'Only Nextflow and Seqera Platform',
|
|
'Basic metrics per task',
|
|
'N/A, only Nextflow',
|
|
'N/A',
|
|
'N/A',
|
|
'Manual via dashboard',
|
|
'Limited detection capabilities',
|
|
'Optimized for Nextflow workflows',
|
|
'Task/runtime level'
|
|
]
|
|
},
|
|
{
|
|
label: 'Seqera + OpenSRE',
|
|
data: [5, 5, 5, 5, 5, 5, 5, 5, 5],
|
|
borderColor: '#27BF9F',
|
|
backgroundColor: 'rgba(39, 191, 159, 0.15)',
|
|
fill: true,
|
|
borderWidth: 2.5,
|
|
pointBackgroundColor: '#27BF9F',
|
|
pointBorderColor: '#fff',
|
|
pointBorderWidth: 2,
|
|
pointRadius: 4,
|
|
pointHoverRadius: 6,
|
|
pointHoverBackgroundColor: '#fff',
|
|
pointHoverBorderColor: '#27BF9F',
|
|
pointHoverBorderWidth: 3,
|
|
descriptions: [
|
|
'Fully compatible',
|
|
'Deep system-level telemetry',
|
|
'Framework-agnostic',
|
|
'Full process and resource tracing',
|
|
'Available for any binary or script',
|
|
'Automated with recommendations',
|
|
'Detects idle time, silent errors, and inefficiencies',
|
|
'Enhanced with observability and optimization',
|
|
'End-to-end coverage: task, process, system, and cost levels'
|
|
]
|
|
}
|
|
]
|
|
};
|
|
|
|
const config = {
|
|
type: 'radar',
|
|
data: data,
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: true,
|
|
aspectRatio: 1.2,
|
|
backgroundColor: 'transparent',
|
|
scales: {
|
|
r: {
|
|
min: 0,
|
|
max: 5,
|
|
ticks: {
|
|
display: false,
|
|
stepSize: 1
|
|
},
|
|
grid: {
|
|
circular: true,
|
|
color: gridColor,
|
|
lineWidth: 1
|
|
},
|
|
angleLines: {
|
|
display: true,
|
|
color: gridColor,
|
|
lineWidth: 1
|
|
},
|
|
pointLabels: {
|
|
font: {
|
|
family: '"Britti Sans Trial", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif',
|
|
size: 16,
|
|
weight: '400'
|
|
},
|
|
color: textColor,
|
|
padding: 35
|
|
}
|
|
}
|
|
},
|
|
plugins: {
|
|
legend: {
|
|
display: false
|
|
},
|
|
tooltip: {
|
|
enabled: false
|
|
}
|
|
},
|
|
interaction: {
|
|
mode: 'point',
|
|
intersect: false
|
|
}
|
|
}
|
|
};
|
|
|
|
// Point-in-polygon algorithm
|
|
function isPointInPolygon(x, y, polygon) {
|
|
let inside = false;
|
|
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
const xi = polygon[i].x, yi = polygon[i].y;
|
|
const xj = polygon[j].x, yj = polygon[j].y;
|
|
if (((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi)) {
|
|
inside = !inside;
|
|
}
|
|
}
|
|
return inside;
|
|
}
|
|
|
|
// Get polygon points for a dataset
|
|
function getDatasetPolygon(chartInstance, datasetIndex) {
|
|
const meta = chartInstance.getDatasetMeta(datasetIndex);
|
|
const points = [];
|
|
meta.data.forEach(point => {
|
|
points.push({ x: point.x, y: point.y });
|
|
});
|
|
return points;
|
|
}
|
|
|
|
// Check which dataset the mouse is hovering over
|
|
function getHoveredDataset(chartInstance, mouseX, mouseY) {
|
|
// Check Seqera-ONLY first (index 0) since it's smaller and should take priority
|
|
const polygon0 = getDatasetPolygon(chartInstance, 0);
|
|
if (isPointInPolygon(mouseX, mouseY, polygon0)) {
|
|
return 0;
|
|
}
|
|
// Then check Seqera + OpenSRE (index 1)
|
|
const polygon1 = getDatasetPolygon(chartInstance, 1);
|
|
if (isPointInPolygon(mouseX, mouseY, polygon1)) {
|
|
return 1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
function highlightDataset(chartInstance, datasetIndex) {
|
|
chartInstance.data.datasets.forEach((dataset, i) => {
|
|
if (i === datasetIndex) {
|
|
dataset.borderColor = i === 0 ? '#3b82f6' : '#27BF9F';
|
|
dataset.backgroundColor = i === 0 ? 'rgba(59, 130, 246, 0.25)' : 'rgba(39, 191, 159, 0.25)';
|
|
dataset.pointBackgroundColor = i === 0 ? '#3b82f6' : '#27BF9F';
|
|
dataset.pointBorderColor = '#fff';
|
|
dataset.borderWidth = 3.5;
|
|
} else {
|
|
dataset.borderColor = i === 0 ? 'rgba(59, 130, 246, 0.2)' : 'rgba(39, 191, 159, 0.2)';
|
|
dataset.backgroundColor = i === 0 ? 'rgba(59, 130, 246, 0.05)' : 'rgba(39, 191, 159, 0.05)';
|
|
dataset.pointBackgroundColor = i === 0 ? 'rgba(59, 130, 246, 0.2)' : 'rgba(39, 191, 159, 0.2)';
|
|
dataset.pointBorderColor = 'rgba(255, 255, 255, 0.2)';
|
|
dataset.borderWidth = 2;
|
|
}
|
|
});
|
|
chartInstance.update('none');
|
|
}
|
|
|
|
function resetHighlight(chartInstance) {
|
|
chartInstance.data.datasets[0].borderColor = '#3b82f6';
|
|
chartInstance.data.datasets[0].backgroundColor = 'rgba(59, 130, 246, 0.15)';
|
|
chartInstance.data.datasets[0].pointBackgroundColor = '#3b82f6';
|
|
chartInstance.data.datasets[0].pointBorderColor = '#fff';
|
|
chartInstance.data.datasets[0].borderWidth = 2.5;
|
|
|
|
chartInstance.data.datasets[1].borderColor = '#27BF9F';
|
|
chartInstance.data.datasets[1].backgroundColor = 'rgba(39, 191, 159, 0.15)';
|
|
chartInstance.data.datasets[1].pointBackgroundColor = '#27BF9F';
|
|
chartInstance.data.datasets[1].pointBorderColor = '#fff';
|
|
chartInstance.data.datasets[1].borderWidth = 2.5;
|
|
|
|
chartInstance.update('none');
|
|
}
|
|
|
|
const chart = new Chart(canvas, config);
|
|
console.log('Radar chart initialized successfully');
|
|
|
|
// Helper to find nearest point
|
|
function getNearestPoint(chartInstance, mouseX, mouseY) {
|
|
let nearestPoint = null;
|
|
let nearestDistance = Infinity;
|
|
const hitRadius = 15; // pixels
|
|
|
|
chartInstance.data.datasets.forEach((dataset, datasetIndex) => {
|
|
const meta = chartInstance.getDatasetMeta(datasetIndex);
|
|
meta.data.forEach((point, index) => {
|
|
const dx = point.x - mouseX;
|
|
const dy = point.y - mouseY;
|
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
if (distance < hitRadius && distance < nearestDistance) {
|
|
nearestDistance = distance;
|
|
nearestPoint = { datasetIndex, index, point };
|
|
}
|
|
});
|
|
});
|
|
return nearestPoint;
|
|
}
|
|
|
|
// Track currently hovered point for visual effect
|
|
let currentHoveredPoint = null;
|
|
|
|
// Store default point radius arrays
|
|
const defaultPointRadius = [4, 4, 4, 4, 4, 4, 4, 4, 4];
|
|
const defaultPointBorderWidth = [2, 2, 2, 2, 2, 2, 2, 2, 2];
|
|
|
|
// Apply hover effect to a point
|
|
function applyPointHover(datasetIndex, pointIndex) {
|
|
// Create arrays for the hovered state
|
|
const hoverRadius = [...defaultPointRadius];
|
|
const hoverBorderWidth = [...defaultPointBorderWidth];
|
|
const hoverBgColor = chart.data.datasets[datasetIndex].data.map(() =>
|
|
datasetIndex === 0 ? '#3b82f6' : '#27BF9F'
|
|
);
|
|
const hoverBorderColor = chart.data.datasets[datasetIndex].data.map(() => '#fff');
|
|
|
|
// Set the hovered point to be larger
|
|
hoverRadius[pointIndex] = 14;
|
|
hoverBorderWidth[pointIndex] = 4;
|
|
hoverBgColor[pointIndex] = '#fff';
|
|
hoverBorderColor[pointIndex] = datasetIndex === 0 ? '#3b82f6' : '#27BF9F';
|
|
|
|
chart.data.datasets[datasetIndex].pointRadius = hoverRadius;
|
|
chart.data.datasets[datasetIndex].pointBorderWidth = hoverBorderWidth;
|
|
chart.data.datasets[datasetIndex].pointBackgroundColor = hoverBgColor;
|
|
chart.data.datasets[datasetIndex].pointBorderColor = hoverBorderColor;
|
|
}
|
|
|
|
// Reset point to default style
|
|
function resetPointHover() {
|
|
chart.data.datasets.forEach((dataset, datasetIndex) => {
|
|
dataset.pointRadius = [...defaultPointRadius];
|
|
dataset.pointBorderWidth = [...defaultPointBorderWidth];
|
|
dataset.pointBackgroundColor = datasetIndex === 0 ? '#3b82f6' : '#27BF9F';
|
|
dataset.pointBorderColor = '#fff';
|
|
});
|
|
}
|
|
|
|
// Custom mousemove handler for area detection and point tooltips
|
|
let currentHighlightedDataset = -1;
|
|
canvas.addEventListener('mousemove', (e) => {
|
|
const rect = canvas.getBoundingClientRect();
|
|
const mouseX = e.clientX - rect.left;
|
|
const mouseY = e.clientY - rect.top;
|
|
|
|
// Check for point hover first (for tooltip and visual effect)
|
|
const nearestPoint = getNearestPoint(chart, mouseX, mouseY);
|
|
let datasetToHighlight = -1;
|
|
|
|
if (nearestPoint) {
|
|
const pointKey = `${nearestPoint.datasetIndex}-${nearestPoint.index}`;
|
|
if (currentHoveredPoint !== pointKey) {
|
|
resetPointHover();
|
|
applyPointHover(nearestPoint.datasetIndex, nearestPoint.index);
|
|
currentHoveredPoint = pointKey;
|
|
chart.update('none');
|
|
}
|
|
|
|
const dataset = data.datasets[nearestPoint.datasetIndex];
|
|
const label = dataset.label;
|
|
const value = dataset.data[nearestPoint.index];
|
|
const description = dataset.descriptions[nearestPoint.index];
|
|
const capability = Array.isArray(data.labels[nearestPoint.index])
|
|
? data.labels[nearestPoint.index].join(' ')
|
|
: data.labels[nearestPoint.index];
|
|
showTooltip(e, label, capability, value, description);
|
|
|
|
// When hovering a point, highlight that point's dataset
|
|
datasetToHighlight = nearestPoint.datasetIndex;
|
|
} else {
|
|
if (currentHoveredPoint !== null) {
|
|
resetPointHover();
|
|
currentHoveredPoint = null;
|
|
chart.update('none');
|
|
}
|
|
hideTooltip();
|
|
|
|
// Only use area detection when not hovering a point
|
|
datasetToHighlight = getHoveredDataset(chart, mouseX, mouseY);
|
|
}
|
|
|
|
// Apply highlighting based on point or area detection
|
|
if (datasetToHighlight !== -1) {
|
|
if (datasetToHighlight !== currentHighlightedDataset) {
|
|
currentHighlightedDataset = datasetToHighlight;
|
|
highlightDataset(chart, datasetToHighlight);
|
|
}
|
|
} else {
|
|
if (currentHighlightedDataset !== -1) {
|
|
currentHighlightedDataset = -1;
|
|
resetHighlight(chart);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Reset highlight when mouse leaves the canvas
|
|
canvas.addEventListener('mouseleave', () => {
|
|
currentHighlightedDataset = -1;
|
|
currentHoveredPoint = null;
|
|
resetPointHover();
|
|
resetHighlight(chart);
|
|
hideTooltip();
|
|
});
|
|
|
|
// Make highlight functions available for legend (if legend is added externally)
|
|
window.radarChartHighlight = (datasetIndex) => highlightDataset(chart, datasetIndex);
|
|
window.radarChartReset = () => resetHighlight(chart);
|
|
|
|
// Handle dark mode changes
|
|
const observer = new MutationObserver(() => {
|
|
const isDark = document.documentElement.classList.contains('dark');
|
|
const gridColor = isDark ? '#374151' : '#e5e7eb';
|
|
const textColor = isDark ? '#ffffff' : '#374151';
|
|
|
|
chart.options.scales.r.grid.color = gridColor;
|
|
chart.options.scales.r.angleLines.color = gridColor;
|
|
chart.options.scales.r.pointLabels.color = textColor;
|
|
chart.update();
|
|
});
|
|
|
|
observer.observe(document.documentElement, {
|
|
attributes: true,
|
|
attributeFilter: ['class']
|
|
});
|
|
}
|
|
|
|
function showTooltip(event, label, capability, value, description) {
|
|
let tooltip = document.getElementById('radar-tooltip');
|
|
if (!tooltip) {
|
|
tooltip = document.createElement('div');
|
|
tooltip.id = 'radar-tooltip';
|
|
tooltip.className = 'radar-tooltip';
|
|
document.body.appendChild(tooltip);
|
|
}
|
|
|
|
tooltip.innerHTML = `
|
|
<div class="tooltip-title">${label}</div>
|
|
<div class="tooltip-content">
|
|
<strong>${capability}</strong><br/>
|
|
Score: ${value}/5<br/>
|
|
${description}
|
|
</div>
|
|
`;
|
|
|
|
tooltip.style.left = event.native.pageX + 10 + 'px';
|
|
tooltip.style.top = event.native.pageY + 10 + 'px';
|
|
tooltip.classList.add('show');
|
|
}
|
|
|
|
function hideTooltip() {
|
|
const tooltip = document.getElementById('radar-tooltip');
|
|
if (tooltip) {
|
|
tooltip.classList.remove('show');
|
|
}
|
|
}
|
|
|
|
// Initialize when DOM is ready
|
|
console.log('Radar chart script loaded');
|
|
|
|
if (document.readyState === 'loading') {
|
|
console.log('Waiting for DOMContentLoaded...');
|
|
document.addEventListener('DOMContentLoaded', initRadarChart);
|
|
} else {
|
|
console.log('DOM already loaded, initializing immediately...');
|
|
initRadarChart();
|
|
}
|
|
|
|
// Also try to initialize when the page is fully loaded
|
|
window.addEventListener('load', function() {
|
|
console.log('Window loaded, checking if chart needs initialization...');
|
|
if (!document.getElementById('seqera-tracer-radar-chart')?.chart) {
|
|
initRadarChart();
|
|
}
|
|
});
|
|
|