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
23 changes: 0 additions & 23 deletions js/components/control-buttons.ts

This file was deleted.

56 changes: 56 additions & 0 deletions js/components/control-buttons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Cosmograph, CosmographButtonFitView, CosmographButtonPlayPause, CosmographButtonPolygonalSelection, CosmographButtonZoomInOut } from '@cosmograph/cosmograph'
import { useEffect, useRef } from 'preact/hooks'

type CleanupTarget = { remove?: () => void; destroy?: () => void }

function cleanupButton(target: CleanupTarget | undefined): void {
if (!target) return
if (typeof target.remove === 'function') {
target.remove()
return
}
if (typeof target.destroy === 'function') {
target.destroy()
}
}

export interface ControlButtonsProps {
cosmograph: Cosmograph | null
}

export function ControlButtons({ cosmograph }: ControlButtonsProps) {
const fitViewRef = useRef<HTMLDivElement | null>(null)
const zoomRef = useRef<HTMLDivElement | null>(null)
const playRef = useRef<HTMLDivElement | null>(null)
const selectRef = useRef<HTMLDivElement | null>(null)

useEffect(() => {
if (!cosmograph) return
if (!fitViewRef.current || !zoomRef.current || !playRef.current || !selectRef.current) return

const fitViewButton = new CosmographButtonFitView(cosmograph, fitViewRef.current)
const zoomButton = new CosmographButtonZoomInOut(cosmograph, zoomRef.current)
const playButton = new CosmographButtonPlayPause(cosmograph, playRef.current)
const selectButton = new CosmographButtonPolygonalSelection(cosmograph, selectRef.current, {})

return () => {
cleanupButton(fitViewButton as CleanupTarget)
cleanupButton(zoomButton as CleanupTarget)
cleanupButton(playButton as CleanupTarget)
cleanupButton(selectButton as CleanupTarget)
}
}, [cosmograph])

return (
<div class="controls">
<div class="leftControls">
<div class="playButton" ref={playRef} />
<div class="selectAreaButton" ref={selectRef} />
</div>
<div class="rightControls">
<div class="fitViewButton" ref={fitViewRef} />
<div class="zoomInOutButton" ref={zoomRef} />
</div>
</div>
)
}
144 changes: 0 additions & 144 deletions js/components/point-info.ts

This file was deleted.

107 changes: 107 additions & 0 deletions js/components/point-info.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { Cosmograph } from '@cosmograph/cosmograph'
import { useEffect, useState } from 'preact/hooks'

interface PointInfoField {
key: string
value: string
muted?: boolean
}

interface PointInfoContent {
index: number
fields: PointInfoField[]
}

export interface PointInfoPanelProps {
cosmograph: Cosmograph | null
selectedIndex: number | null
onClose: () => void
}

function formatValue(value: unknown): { text: string; muted?: boolean } {
if (typeof value === 'number') {
return { text: Number.isInteger(value) ? value.toString() : value.toFixed(3) }
}
if (value === null || value === undefined) {
return { text: 'null', muted: true }
}
return { text: String(value) }
}

export function PointInfoPanel({ cosmograph, selectedIndex, onClose }: PointInfoPanelProps) {
const [info, setInfo] = useState<PointInfoContent | null>(null)

useEffect(() => {
let cancelled = false
if (!cosmograph || selectedIndex === null || selectedIndex === undefined) {
setInfo(null)
return
}

void (async () => {
try {
const table: any = await cosmograph.getPointsByIndices([selectedIndex])
if (cancelled) return
if (!table || table.numRows === 0) {
setInfo(null)
return
}

const result: Record<string, unknown> = {}
const schemaFields = table.schema?.fields ?? []
schemaFields.forEach((field: any) => {
const column = table.getChild(field.name)
if (column && column.length > 0) {
result[field.name] = column.get(0)
}
})

const fields: PointInfoField[] = Object.entries(result)
.filter(([key, value]) => {
if (key.startsWith('_') || key.startsWith('__')) return false
if (typeof value === 'object' && value !== null) return false
return true
})
.slice(0, 8)
.map(([key, value]) => {
const formatted = formatValue(value)
return { key, value: formatted.text, muted: formatted.muted }
})

setInfo({ index: selectedIndex, fields })
} catch (error) {
console.error('Error fetching point data:', error)
if (!cancelled) setInfo(null)
}
})()

return () => {
cancelled = true
}
}, [cosmograph, selectedIndex])

if (!info) return null

return (
<div class="point-info-panel">
<button class="point-info-close" onClick={onClose}>×</button>
<div class="point-info-content">
<div class="point-info-header">{`Point #${info.index}`}</div>
</div>
<div class="point-info-items">
{info.fields.length === 0 ? (
<div class="point-info-item">No displayable data</div>
) : (
info.fields.map(field => (
<div class="point-info-item" key={field.key}>
<div class="point-info-label">{field.key}</div>
<div class="point-info-value" style={field.muted ? { opacity: '0.5' } : undefined}>
{field.value}
</div>
</div>
))
)}
</div>
</div>
)
}
Loading