import type { HeatmapConfig } from '../types' /** Color for the built-in sample types when `sampleColors` has no entry. */ export const DEFAULT_SAMPLE_TYPE_COLORS: Record = { sample: '#10b981', control: '#3b82f6', blank: '#f97316', qc: '#8b5cf6', iqc: '#ec4899', } export const HEATMAP_COLORS: Record = { viridis: ['#440154', '#482878', '#3e4989', '#31688e', '#26828e', '#1f9e89', '#35b779', '#6ece58', '#b5de2b', '#fde725'], plasma: ['#0d0887', '#46039f', '#7201a8', '#9c179e', '#bd3786', '#d8576b', '#ed7953', '#fb9f3a', '#fdca26', '#f0f921'], turbo: ['#30123b', '#4145ab', '#4675ed', '#39a2fc', '#1bcfd4', '#24e79e', '#71f05f', '#c1f034', '#f1c83c', '#f99538', '#e45a31', '#ba2512', '#7a0403'], } export function conditionGradientStyle( color: string, concentration: number, concentrations: number[], ): Record { const min = Math.min(...concentrations) const max = Math.max(...concentrations) const t = max <= min ? 1 : (concentration - min) / (max - min) const opacity = 0.25 + t * 0.50 const r = parseInt(color.slice(1, 3), 16) const g = parseInt(color.slice(3, 5), 16) const b = parseInt(color.slice(5, 7), 16) const er = 255 * (1 - opacity) + r * opacity const eg = 255 * (1 - opacity) + g * opacity const eb = 255 * (1 - opacity) + b * opacity const luminance = (0.299 * er + 0.587 * eg + 0.114 * eb) / 255 return { backgroundColor: `rgba(${r}, ${g}, ${b}, ${opacity})`, color: luminance > 0.55 ? '#1e293b' : '#ffffff', } } export function formatConcentration(value: number): string { if (value >= 1000) return `${value / 1000}k` if (value < 0.01) return value.toExponential(0) return String(value) } export function getHeatmapColor(config: HeatmapConfig | undefined, value: number | undefined): string | null { if (!config?.enabled || value === undefined) return null const min = config.min ?? 0 const max = config.max ?? 1 const normalized = Math.max(0, Math.min(1, (value - min) / (max - min))) const colors = config.colorScale === 'custom' && config.customColors?.length ? config.customColors : HEATMAP_COLORS[config.colorScale || 'viridis'] const index = Math.min(Math.floor(normalized * (colors.length - 1)), colors.length - 1) return colors[index] }