import { getBpDisplayStr, toLocale } from '@jbrowse/core/util'
import { max } from 'd3-array'
import type { NodeWithIds } from './types'
import type { HierarchyNode } from 'd3-hierarchy'
export interface HoveredInfo {
sampleId: string
sampleLabel: string
pos: number
base: string
chr: string
isInsertion?: boolean
isLargeInsertion?: boolean
[key: string]: unknown
}
export interface GenomicPosition {
refName: string
coord: number
}
/**
* Generates tooltip HTML content for MAF alignments
* Truncates long sequences to 50 characters, with ellipses added if display exceeds 20 characters
* @param hoveredInfo - Information about the hovered base/position
* @param p1 - Start position (for range selections)
* @param p2 - End position (current mouse position)
* @returns HTML string for tooltip content
*/
export function generateTooltipContent(
hoveredInfo: HoveredInfo | undefined,
p1: GenomicPosition | undefined,
p2: GenomicPosition,
): string {
const contentLines: string[] = []
if (p1) {
// Range selection mode
contentLines.push(
`Start: ${p1.refName}:${toLocale(p1.coord)}`,
`End: ${p2.refName}:${toLocale(p2.coord)}`,
`Length: ${getBpDisplayStr(Math.abs(p1.coord - p2.coord))}`,
)
} else {
// Single position mode
contentLines.push(`Ref: ${p2.refName}:${toLocale(p2.coord)}`)
if (hoveredInfo) {
const { base, sampleLabel, pos, chr, isInsertion } = hoveredInfo
const thresh = 20
const len = base.length
const lengthSuffix = len > 1 ? ` ${len}bp` : ''
const baseDisplay =
base.length > thresh ? base.slice(0, thresh) + '...' : base
const insertionLabel = isInsertion ? ' Insertion' : ''
contentLines.push(
`Alt ${sampleLabel}: ${chr}:${pos.toLocaleString('en-US')} (${baseDisplay}${lengthSuffix}${insertionLabel})`,
)
}
}
return contentLines.filter(line => !!line).join('
')
}
// basically same as maxLength from https://observablehq.com/@d3/tree-of-life
export function maxLength(d: HierarchyNode): number {
return (
(d.data.length || 0) + (d.children ? max(d.children, maxLength) || 0 : 0)
)
}
// basically same as setRadius from https://observablehq.com/@d3/tree-of-life
export function setBrLength(
d: HierarchyNode,
y0: number,
k: number,
) {
const newY0 = y0 + Math.max(d.data.length || 0, 0)
// @ts-expect-error
d.len = newY0 * k
if (d.children) {
for (const child of d.children) {
setBrLength(child, newY0, k)
}
}
}
export function computeNodeDescendantNames(
root: HierarchyNode,
): Map, string[]> {
const map = new Map, string[]>()
function visit(node: HierarchyNode): string[] {
if (!node.children || node.children.length === 0) {
const names = [node.data.name]
map.set(node, names)
return names
}
const names: string[] = []
for (const child of node.children) {
for (const name of visit(child)) {
names.push(name)
}
}
map.set(node, names)
return names
}
visit(root)
return map
}