diff --git a/frontend/src/lib/components/RunChart.svelte b/frontend/src/lib/components/RunChart.svelte index deaa0773a2..47453689ab 100644 --- a/frontend/src/lib/components/RunChart.svelte +++ b/frontend/src/lib/components/RunChart.svelte @@ -22,11 +22,18 @@ export let maxIsNow: boolean = false export let minTimeSet: string | undefined = undefined export let maxTimeSet: string | undefined = undefined + export let selectedIds: string[] = [] const dispatch = createEventDispatcher() + const SUCCESS_COLOR = '#4ade80' + // const SUCCESS_COLOR_TRANSPARENT = '#c9b638' + const SUCCESS_COLOR_TRANSPARENT = mergeColors(SUCCESS_COLOR, getBackgorundColor(), 0.8) + const FAIL_COLOR = '#f87171' + const FAIL_COLOR_TRANSPARENT = mergeColors(FAIL_COLOR, getBackgorundColor(), 0.8) $: success = jobs?.filter((x) => x.success) $: failed = jobs?.filter((x) => !x.success) + ChartJS.register( Title, Tooltip, @@ -41,11 +48,11 @@ ) $: data = { - labels: ['Duration'], datasets: [ { - // borderColor: 'rgba(99,0,125, .2)', - backgroundColor: '#f87171', + borderColor: 'rgba(99,0,125, 0)', + backgroundColor: FAIL_COLOR as string | string[], + radius: 3, label: 'Failed', data: failed?.map((job) => ({ @@ -56,8 +63,9 @@ })) ?? [] }, { - // borderColor: 'rgba(99,0,125, .2)', - backgroundColor: '#4ade80', + borderColor: 'rgba(99,0,125, 0)', + backgroundColor: SUCCESS_COLOR as string | string[], + radius: 3, label: 'Successful', data: success?.map((job) => ({ @@ -95,6 +103,58 @@ } } + function getBackgorundColor(): string { + const isDark = document.documentElement.classList.contains('dark') + + return isDark ? '#2e3440' : '#ffffff' + } + function hexToRgb(hexColor: string): number[] { + hexColor = hexColor.replace(/^#/, '') + + const r = parseInt(hexColor.substring(0, 2), 16) + const g = parseInt(hexColor.substring(2, 4), 16) + const b = parseInt(hexColor.substring(4, 6), 16) + + return [r, g, b] + } + + function rgbToHex(rgbColor: number[]): string { + // Convert RGB components to hexadecimal string + return ( + '#' + + rgbColor + .map((c) => { + const hex = c.toString(16) + return hex.length === 1 ? '0' + hex : hex + }) + .join('') + ) + } + + function mergeColors(color1: string, color2: string, slider: number): string { + const rgb1 = hexToRgb(color1) + const rgb2 = hexToRgb(color2) + + // Blend colors based on percentage + const blendedRgb = rgb1.map((c1, i) => Math.round(c1 * (1 - slider) + rgb2[i] * slider)) + + return rgbToHex(blendedRgb) + } + + function highlightSelectedPoints(ids: string[]) { + if (ids.length === 0) { + data.datasets[0].backgroundColor = FAIL_COLOR + data.datasets[1].backgroundColor = SUCCESS_COLOR + } else { + data.datasets[0].backgroundColor = data.datasets[0].data.map((p) => + ids.includes(p.id) ? FAIL_COLOR : FAIL_COLOR_TRANSPARENT + ) + data.datasets[1].backgroundColor = data.datasets[1].data.map((p) => + ids.includes(p.id) ? SUCCESS_COLOR : SUCCESS_COLOR_TRANSPARENT + ) + } + } + function getPath(x: any): string { return x.path } @@ -176,6 +236,10 @@ } } }, + onClick: (e, u) => { + const ids = u.map((j) => data.datasets[j.datasetIndex].data[j.index].id) + selectedIds = ids + }, scales: { x: { @@ -199,6 +263,9 @@ }, animation: false } as any + + $: data && scatterOptions && highlightSelectedPoints(selectedIds) +