feat(sprint-5/etapa-3): heatmap gradiente 4 stops + modo percentil + localStorage + fix zoom
- colors.ts: gradiente continuo verde→amarillo→naranja→rojo (interpolación N-stops genérica) - Renombra modos: relative→percentile (rank-based), absolute→minmax - Hook useHeatmapMode(): persiste selección en localStorage con clave inq-roi:heatmap-mode - HeatmapLegend: barra de 4 stops, labels P0/P50/P100 en modo percentil - HeatmapModeToggle: etiquetas "Percentil" / "Min-max" - pdf-sections.ts: leyenda PDF con 4 stops interpolados - BpmnCanvas: controles de zoom movidos a esquina inferior izquierda (evita logo bpmn.io) - Tests: 528 unitarios pasan; E2E recalibrados con vecino más cercano (distancia euclidiana) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,7 @@ export const PDF = {
|
||||
slate50: [248, 250, 252] as [number, number, number],
|
||||
heatLow: [16, 185, 129] as [number, number, number],
|
||||
heatMid: [245, 158, 11] as [number, number, number],
|
||||
heatHighMid: [249, 115, 22] as [number, number, number],
|
||||
heatHigh: [239, 68, 68] as [number, number, number],
|
||||
}
|
||||
|
||||
@@ -155,22 +156,31 @@ export function drawHeatmapLegend(doc: DocLike, startY: number): number {
|
||||
doc.setTextColor(...PDF.heatLow)
|
||||
doc.text('Bajo costo', x, labelY)
|
||||
|
||||
// Barra de gradiente: dibujada como N segmentos de colores
|
||||
// Barra de gradiente: dibujada como N segmentos de colores, 4 stops
|
||||
// (mismo gradiente que src/lib/colors.ts — verde → amarillo → naranja → rojo)
|
||||
const heatStops: Array<[number, [number, number, number]]> = [
|
||||
[0.00, PDF.heatLow],
|
||||
[0.33, PDF.heatMid],
|
||||
[0.67, PDF.heatHighMid],
|
||||
[1.00, PDF.heatHigh],
|
||||
]
|
||||
const steps = 40
|
||||
const segW = barW / steps
|
||||
for (let i = 0; i < steps; i++) {
|
||||
const t = i / (steps - 1)
|
||||
let r: number, g: number, b: number
|
||||
if (t <= 0.5) {
|
||||
const lt = t * 2
|
||||
r = Math.round(PDF.heatLow[0] + (PDF.heatMid[0] - PDF.heatLow[0]) * lt)
|
||||
g = Math.round(PDF.heatLow[1] + (PDF.heatMid[1] - PDF.heatLow[1]) * lt)
|
||||
b = Math.round(PDF.heatLow[2] + (PDF.heatMid[2] - PDF.heatLow[2]) * lt)
|
||||
} else {
|
||||
const lt = (t - 0.5) * 2
|
||||
r = Math.round(PDF.heatMid[0] + (PDF.heatHigh[0] - PDF.heatMid[0]) * lt)
|
||||
g = Math.round(PDF.heatMid[1] + (PDF.heatHigh[1] - PDF.heatMid[1]) * lt)
|
||||
b = Math.round(PDF.heatMid[2] + (PDF.heatHigh[2] - PDF.heatMid[2]) * lt)
|
||||
let r = heatStops[heatStops.length - 1][1][0]
|
||||
let g = heatStops[heatStops.length - 1][1][1]
|
||||
let b = heatStops[heatStops.length - 1][1][2]
|
||||
for (let s = 0; s < heatStops.length - 1; s++) {
|
||||
const [t0, c0] = heatStops[s]
|
||||
const [t1, c1] = heatStops[s + 1]
|
||||
if (t <= t1) {
|
||||
const lt = t1 === t0 ? 0 : (t - t0) / (t1 - t0)
|
||||
r = Math.round(c0[0] + (c1[0] - c0[0]) * lt)
|
||||
g = Math.round(c0[1] + (c1[1] - c0[1]) * lt)
|
||||
b = Math.round(c0[2] + (c1[2] - c0[2]) * lt)
|
||||
break
|
||||
}
|
||||
}
|
||||
doc.setFillColor(r, g, b)
|
||||
doc.rect(x + i * segW, startY, segW + 0.1, barH, 'F')
|
||||
|
||||
Reference in New Issue
Block a user