Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 48 additions & 32 deletions apps/smartem/src/components/spatial/AcquisitionPathOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface PathPoint {
x: number
y: number
rank: number
gridIndex: number
}

interface AcquisitionPathOverlayProps {
Expand All @@ -24,11 +25,12 @@ interface AcquisitionPathOverlayProps {
vbW: number
}

const START_COLOR = '#2f6feb'
const END_COLOR = '#e59344'
export const START_COLOR = '#2f6feb'
export const END_COLOR = '#e59344'
const LINE_COLOR = '#2f6feb'
const BREAK_COLOR = '#cf222e'

function lerpColor(a: string, b: string, t: number): string {
export function lerpColor(a: string, b: string, t: number): string {
const pa = [
Number.parseInt(a.slice(1, 3), 16),
Number.parseInt(a.slice(3, 5), 16),
Expand Down Expand Up @@ -67,7 +69,9 @@ export function AcquisitionPathOverlay({ points, total, style, vbW }: Acquisitio
const first = points[0]
const last = points[points.length - 1]
const prevLast = points[points.length - 2]
const polyPoints = points.map((p) => `${p.x},${p.y}`).join(' ')

const isBreak = (i: number) =>
i < points.length - 1 && points[i + 1].gridIndex !== points[i].gridIndex + 1

const badge = (p: PathPoint) => (
<g key={`badge-${p.uuid}`}>
Expand Down Expand Up @@ -101,34 +105,46 @@ export function AcquisitionPathOverlay({ points, total, style, vbW }: Acquisitio

return (
<g style={{ pointerEvents: 'none' }}>
{style === 'gradient' ? (
points.slice(0, -1).map((p, i) => {
const next = points[i + 1]
return (
<line
key={`seg-${p.uuid}`}
x1={p.x}
y1={p.y}
x2={next.x}
y2={next.y}
stroke={lerpColor(START_COLOR, END_COLOR, i / (points.length - 1))}
strokeWidth={stroke}
strokeLinecap="round"
opacity={0.9}
/>
)
})
) : (
<polyline
points={polyPoints}
fill="none"
stroke={LINE_COLOR}
strokeWidth={stroke}
strokeLinejoin="round"
strokeLinecap="round"
opacity={0.5}
/>
)}
{style === 'gradient'
? points.slice(0, -1).map((p, i) => {
const next = points[i + 1]
const segColor = lerpColor(START_COLOR, END_COLOR, i / (points.length - 1))
const brk = isBreak(i)
return (
<line
key={`seg-${p.uuid}`}
x1={p.x}
y1={p.y}
x2={next.x}
y2={next.y}
stroke={brk ? BREAK_COLOR : segColor}
strokeWidth={stroke}
strokeLinecap="round"
strokeDasharray={brk ? `${stroke * 3} ${stroke * 2}` : 'none'}
opacity={brk ? 0.7 : 0.9}
/>
)
})
: points.slice(0, -1).map((p, i) => {
const next = points[i + 1]
const brk = isBreak(i)
return (
<line
key={`seg-${p.uuid}`}
x1={p.x}
y1={p.y}
x2={next.x}
y2={next.y}
fill="none"
stroke={brk ? BREAK_COLOR : LINE_COLOR}
strokeWidth={stroke}
strokeLinejoin="round"
strokeLinecap="round"
strokeDasharray={brk ? `${stroke * 3} ${stroke * 2}` : 'none'}
opacity={brk ? 0.5 : 0.5}
/>
)
})}

{/* End-of-path arrowhead for the styles that rely on it for direction. */}
{(style === 'gradient' || style === 'sparse') && (
Expand Down
52 changes: 47 additions & 5 deletions apps/smartem/src/components/spatial/SquareMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ import { useCallback, useMemo, useState } from 'react'
import { PaneFrame } from '~/components/layout/PaneFrame'
import {
AcquisitionPathOverlay,
END_COLOR,
lerpColor,
type PathPoint,
type PathStyle,
START_COLOR,
} from '~/components/spatial/AcquisitionPathOverlay'
import type { MockFoilHole } from '~/data/mock-session-detail'
import { gray, statusColors } from '~/theme'
Expand Down Expand Up @@ -240,11 +243,34 @@ export function SquareMap({
return idx ? [{ fh, idx }] : []
})
.sort((a, b) => a.idx - b.idx)
.map(({ fh }, i) => ({ uuid: fh.uuid, x: fh.xLocation, y: fh.yLocation, rank: i + 1 }))
.map(({ fh, idx }, i) => ({
uuid: fh.uuid,
x: fh.xLocation,
y: fh.yLocation,
rank: i + 1,
gridIndex: idx,
}))
}, [foilholes, orderByFoilhole])
const hasPath = pathPoints.length > 1
const hoveredRank =
showPath && hoveredHole ? pathPoints.find((p) => p.uuid === hoveredHole.uuid)?.rank : undefined

const rankByUuid = useMemo(() => {
const map = new Map<string, number>()
for (const p of pathPoints) map.set(p.uuid, p.rank)
return map
}, [pathPoints])

const hoveredPathPoint =
showPath && hoveredHole ? pathPoints.find((p) => p.uuid === hoveredHole.uuid) : undefined
const hoveredRank = hoveredPathPoint?.rank
const hoveredBreakAfter = (() => {
if (!hoveredPathPoint) return false
const idx = pathPoints.indexOf(hoveredPathPoint)
return (
idx >= 0 &&
idx < pathPoints.length - 1 &&
pathPoints[idx + 1].gridIndex !== hoveredPathPoint.gridIndex + 1
)
})()

// Foilhole coords live in the micrograph's native pixel space; match the viewBox to it (falling
// back to the standard gridsquare size until the image decodes) so the overlay stays on the image.
Expand Down Expand Up @@ -500,8 +526,23 @@ export function SquareMap({

if (isNull && hideNull) return null

const fill = isNull ? 'black' : getFill(fh)
const opacity = isNull ? 0.2 : isHovered || isSelected ? 1.0 : 0.6
const pathRank = showPath ? rankByUuid.get(fh.uuid) : undefined
const fill = isNull
? 'black'
: pathRank != null
? lerpColor(
START_COLOR,
END_COLOR,
(pathRank - 1) / Math.max(pathPoints.length - 1, 1)
)
: getFill(fh)
const opacity = isNull
? 0.2
: isHovered || isSelected
? 1.0
: pathRank != null
? 0.85
: 0.6

return (
<circle
Expand Down Expand Up @@ -599,6 +640,7 @@ export function SquareMap({
: `${Math.round(hoveredHole.quality * 100)}%`}
{hoveredHole.isNearGridBar && ' (near grid bar)'}
{hoveredRank != null && ` · order ${hoveredRank}/${pathPoints.length}`}
{hoveredBreakAfter && ' · break after'}
</Box>
)}
</>
Expand Down