Skip to content

Instantly share code, notes, and snippets.

@tannerlinsley
Created April 27, 2023 03:16
Show Gist options
  • Save tannerlinsley/1125d3957f3a41577f85ff8a8c524c5b to your computer and use it in GitHub Desktop.
Save tannerlinsley/1125d3957f3a41577f85ff8a8c524c5b to your computer and use it in GitHub Desktop.
import * as React from 'react'
import * as Plot from '@observablehq/plot'
import useRect from '../hooks/useRect'
import { twMerge } from 'tailwind-merge'
import { useThemeMode } from '../utils/Theme'
import { data } from '../utils/DataColors'
export function ObservablePlot({
legend,
legendType = 'color',
options,
...props
}: React.HTMLAttributes<HTMLDivElement> & {
legend?: Plot.LegendOptions
legendType?: Plot.LegendType
options: Plot.Options
children?: (props: {
legend: React.ReactNode
plot: React.ReactNode
}) => React.ReactNode
}) {
const [el, setEl] = React.useState<HTMLDivElement>(null!)
const [plotEl, setPlotEl] = React.useState<HTMLDivElement>(null!)
const [legendEl, setLegendEl] = React.useState<HTMLDivElement>(null!)
const { themeMode } = useThemeMode()
const legendRect = useRect(legendEl)
const rect = useRect(el)
const width = rect.width
const height = Math.max(0, rect.height - (legendRect?.height ?? 0))
const plot = React.useMemo(() => {
return Plot.plot({
...options,
color: {
range: data,
...options.color,
},
style: {
background: 'transparent',
color: themeMode === 'dark' ? '#fff' : '#000',
...options.style,
},
width,
height,
})
}, [options, width, height])
const legendPlotEl = React.useMemo(() => {
return plot.legend(legendType, {
...legend,
style: {
background: 'transparent',
color: themeMode === 'dark' ? '#fff' : '#000',
...legend?.style,
},
})
}, [plot])
React.useEffect(() => {
if (!legendEl) {
return
}
legendEl.innerHTML = ''
legendEl.append(legendPlotEl)
}, [legendEl, legendPlotEl])
React.useEffect(() => {
if (!plotEl) {
return
}
plotEl.innerHTML = ''
plotEl.append(plot)
}, [plotEl, plot])
const legendComp = legend ? (
<div
ref={el => {
setLegendEl(el!)
}}
/>
) : null
const plotComp = plot ? (
<div
ref={el => {
setPlotEl(el!)
}}
className="w-full"
/>
) : null
return (
<div
{...props}
ref={el => {
setEl(el!)
}}
className={twMerge('', props.className)}
>
{props.children ? (
props.children({ legend: legendComp, plot: plotComp })
) : (
<>
{plotComp}
{legendComp}
</>
)}
</div>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment