chart tooltip sorting

This commit is contained in:
Henry Dollman
2024-07-12 17:37:49 -04:00
parent e7ff1172d5
commit 998fa6e03a
7 changed files with 50 additions and 69 deletions

View File

@@ -11,13 +11,7 @@ import { useMemo } from 'react'
import { formatShortDate, formatShortTime } from '@/lib/utils' import { formatShortDate, formatShortTime } from '@/lib/utils'
import Spinner from '../spinner' import Spinner from '../spinner'
export default function ({ export default function ({ chartData }: { chartData: Record<string, number | string>[] }) {
chartData,
max,
}: {
chartData: Record<string, number | string>[]
max: number
}) {
const chartConfig = useMemo(() => { const chartConfig = useMemo(() => {
let config = {} as Record< let config = {} as Record<
string, string,
@@ -65,8 +59,7 @@ export default function ({
margin={{ margin={{
top: 10, top: 10,
}} }}
reverseStackOrder={true}
// reverseStackOrder={true}
> >
<CartesianGrid vertical={false} /> <CartesianGrid vertical={false} />
<YAxis <YAxis
@@ -88,10 +81,8 @@ export default function ({
<ChartTooltip <ChartTooltip
cursor={false} cursor={false}
labelFormatter={formatShortDate} labelFormatter={formatShortDate}
// itemSorter={(item) => { // @ts-ignore
// console.log('itemSorter', item) itemSorter={(a, b) => b.value - a.value}
// return -item.value
// }}
content={<ChartTooltipContent unit="%" indicator="line" />} content={<ChartTooltipContent unit="%" indicator="line" />}
/> />
{Object.keys(chartConfig).map((key) => ( {Object.keys(chartConfig).map((key) => (
@@ -100,7 +91,7 @@ export default function ({
// isAnimationActive={false} // isAnimationActive={false}
animateNewValues={false} animateNewValues={false}
dataKey={key} dataKey={key}
type="bump" type="monotone"
fill={chartConfig[key].color} fill={chartConfig[key].color}
fillOpacity={0.4} fillOpacity={0.4}
stroke={chartConfig[key].color} stroke={chartConfig[key].color}

View File

@@ -11,14 +11,7 @@ import { useMemo } from 'react'
import { formatShortDate, formatShortTime } from '@/lib/utils' import { formatShortDate, formatShortTime } from '@/lib/utils'
import Spinner from '../spinner' import Spinner from '../spinner'
export default function ({ export default function ({ chartData }: { chartData: Record<string, number | string>[] }) {
chartData,
max,
}: {
chartData: Record<string, number | string>[]
max: number
}) {
console.log('max', max)
const chartConfig = useMemo(() => { const chartConfig = useMemo(() => {
let config = {} as Record< let config = {} as Record<
string, string,
@@ -63,6 +56,7 @@ export default function ({
<AreaChart <AreaChart
accessibilityLayer accessibilityLayer
data={chartData} data={chartData}
reverseStackOrder={true}
margin={{ margin={{
top: 10, top: 10,
}} }}
@@ -71,12 +65,16 @@ export default function ({
> >
<CartesianGrid vertical={false} /> <CartesianGrid vertical={false} />
<YAxis <YAxis
domain={[0, max]} domain={[0, (max: number) => Math.ceil(max)]}
tickCount={9} // tickCount={9}
allowDecimals={false} allowDecimals={false}
tickLine={false} tickLine={false}
axisLine={false} axisLine={false}
tickFormatter={(v) => `${Math.ceil(v / 1024)} GiB`} unit={' GiB'}
tickFormatter={(x) => {
x = x / 1024
return x % 1 === 0 ? x : x.toFixed(1)
}}
/> />
<XAxis <XAxis
dataKey="time" dataKey="time"
@@ -89,10 +87,8 @@ export default function ({
<ChartTooltip <ChartTooltip
cursor={false} cursor={false}
labelFormatter={formatShortDate} labelFormatter={formatShortDate}
// itemSorter={(item) => { // @ts-ignore
// console.log('itemSorter', item) itemSorter={(a, b) => b.value - a.value}
// return -item.value
// }}
content={<ChartTooltipContent unit=" MiB" indicator="line" />} content={<ChartTooltipContent unit=" MiB" indicator="line" />}
/> />
{Object.keys(chartConfig).map((key) => ( {Object.keys(chartConfig).map((key) => (
@@ -101,7 +97,7 @@ export default function ({
// isAnimationActive={false} // isAnimationActive={false}
animateNewValues={false} animateNewValues={false}
dataKey={key} dataKey={key}
type="natural" type="monotone"
fill={chartConfig[key].color} fill={chartConfig[key].color}
fillOpacity={0.4} fillOpacity={0.4}
stroke={chartConfig[key].color} stroke={chartConfig[key].color}

View File

@@ -19,13 +19,7 @@ const chartConfig = {
}, },
} satisfies ChartConfig } satisfies ChartConfig
export default function ({ export default function ({ chartData }: { chartData: { time: string; cpu: number }[] }) {
chartData,
max,
}: {
chartData: { time: string; cpu: number }[]
max: number
}) {
if (!chartData?.length) { if (!chartData?.length) {
return <Spinner /> return <Spinner />
} }
@@ -35,7 +29,7 @@ export default function ({
<AreaChart accessibilityLayer data={chartData} margin={{ top: 10 }}> <AreaChart accessibilityLayer data={chartData} margin={{ top: 10 }}>
<CartesianGrid vertical={false} /> <CartesianGrid vertical={false} />
<YAxis <YAxis
domain={[0, max]} domain={[0, (max: number) => Math.ceil(max)]}
width={47} width={47}
// tickCount={5} // tickCount={5}
tickLine={false} tickLine={false}

View File

@@ -82,7 +82,7 @@ export default function ({
/> />
<Area <Area
dataKey="diskUsed" dataKey="diskUsed"
type="bump" type="monotone"
fill="var(--color-diskUsed)" fill="var(--color-diskUsed)"
fillOpacity={0.4} fillOpacity={0.4}
stroke="var(--color-diskUsed)" stroke="var(--color-diskUsed)"

View File

@@ -61,12 +61,12 @@ export default function ({
<ChartTooltip <ChartTooltip
cursor={false} cursor={false}
content={ content={
<ChartTooltipContent unit=" GiB" labelFormatter={formatShortDate} indicator="line" /> <ChartTooltipContent unit="GiB" labelFormatter={formatShortDate} indicator="line" />
} }
/> />
<Area <Area
dataKey="memUsed" dataKey="memUsed"
type="bump" type="monotone"
fill="var(--color-memUsed)" fill="var(--color-memUsed)"
fillOpacity={0.4} fillOpacity={0.4}
stroke="var(--color-memUsed)" stroke="var(--color-memUsed)"

View File

@@ -31,14 +31,12 @@ export default function ServerDetail({ name }: { name: string }) {
const [containers, setContainers] = useState([] as ContainerStatsRecord[]) const [containers, setContainers] = useState([] as ContainerStatsRecord[])
const [serverStats, setServerStats] = useState([] as SystemStatsRecord[]) const [serverStats, setServerStats] = useState([] as SystemStatsRecord[])
const [cpuChartData, setCpuChartData] = useState( const [cpuChartData, setCpuChartData] = useState([] as { time: string; cpu: number }[])
{} as { max: number; data: { time: string; cpu: number }[] }
)
const [memChartData, setMemChartData] = useState( const [memChartData, setMemChartData] = useState(
{} as { time: string; mem: number; memUsed: number }[] [] as { time: string; mem: number; memUsed: number }[]
) )
const [diskChartData, setDiskChartData] = useState( const [diskChartData, setDiskChartData] = useState(
{} as { time: string; disk: number; diskUsed: number }[] [] as { time: string; disk: number; diskUsed: number }[]
) )
const [containerCpuChartData, setContainerCpuChartData] = useState( const [containerCpuChartData, setContainerCpuChartData] = useState(
[] as Record<string, number | string>[] [] as Record<string, number | string>[]
@@ -51,7 +49,7 @@ export default function ServerDetail({ name }: { name: string }) {
document.title = name document.title = name
return () => { return () => {
setContainerCpuChartData([]) setContainerCpuChartData([])
setCpuChartData({} as { max: number; data: { time: string; cpu: number }[] }) setCpuChartData([])
setMemChartData([]) setMemChartData([])
setDiskChartData([]) setDiskChartData([])
} }
@@ -70,7 +68,7 @@ export default function ServerDetail({ name }: { name: string }) {
sort: '-created', sort: '-created',
}) })
.then((records) => { .then((records) => {
console.log('stats', records) // console.log('sctats', records)
setServerStats(records.items) setServerStats(records.items)
}) })
}, [server]) }, [server])
@@ -80,35 +78,32 @@ export default function ServerDetail({ name }: { name: string }) {
if (!serverStats.length) { if (!serverStats.length) {
return return
} }
let maxCpu = 0 // let maxCpu = 0
const cpuData = [] as { time: string; cpu: number }[] const cpuData = [] as { time: string; cpu: number }[]
const memData = [] as { time: string; mem: number; memUsed: number }[] const memData = [] as { time: string; mem: number; memUsed: number }[]
const diskData = [] as { time: string; disk: number; diskUsed: number }[] const diskData = [] as { time: string; disk: number; diskUsed: number }[]
for (let { created, stats } of serverStats) { for (let { created, stats } of serverStats) {
cpuData.push({ time: created, cpu: stats.c }) cpuData.push({ time: created, cpu: stats.c })
maxCpu = Math.max(maxCpu, stats.c) // maxCpu = Math.max(maxCpu, stats.c)
memData.push({ time: created, mem: stats.m, memUsed: stats.mu }) memData.push({ time: created, mem: stats.m, memUsed: stats.mu })
diskData.push({ time: created, disk: stats.d, diskUsed: stats.du }) diskData.push({ time: created, disk: stats.d, diskUsed: stats.du })
} }
setCpuChartData({ setCpuChartData(cpuData.reverse())
max: Math.ceil(maxCpu),
data: cpuData.reverse(),
})
setMemChartData(memData.reverse()) setMemChartData(memData.reverse())
setDiskChartData(diskData.reverse()) setDiskChartData(diskData.reverse())
}, [serverStats]) }, [serverStats])
useEffect(() => { useEffect(() => {
if ($servers.get().length === 0) { if ($servers.get().length === 0) {
console.log('skipping') // console.log('skipping')
return return
} }
console.log('running') // console.log('running')
const matchingServer = servers.find((s) => s.name === name) as SystemRecord const matchingServer = servers.find((s) => s.name === name) as SystemRecord
setServer(matchingServer) setServer(matchingServer)
console.log('matchingServer', matchingServer) console.log('found server', matchingServer)
// pb.collection<SystemRecord>('systems') // pb.collection<SystemRecord>('systems')
// .getOne(serverId) // .getOne(serverId)
// .then((record) => { // .then((record) => {
@@ -129,7 +124,7 @@ export default function ServerDetail({ name }: { name: string }) {
// container stats for charts // container stats for charts
useEffect(() => { useEffect(() => {
console.log('containers', containers) // console.log('containers', containers)
const containerCpuData = [] as Record<string, number | string>[] const containerCpuData = [] as Record<string, number | string>[]
const containerMemData = [] as Record<string, number | string>[] const containerMemData = [] as Record<string, number | string>[]
@@ -153,7 +148,7 @@ export default function ServerDetail({ name }: { name: string }) {
<Card className="pb-2"> <Card className="pb-2">
<CardHeader> <CardHeader>
<CardTitle className="flex gap-2 justify-between"> <CardTitle className="flex gap-2 justify-between">
<span>CPU Usage</span> <span>Total CPU Usage</span>
<CpuIcon className="opacity-70" /> <CpuIcon className="opacity-70" />
</CardTitle> </CardTitle>
<CardDescription> <CardDescription>
@@ -162,7 +157,7 @@ export default function ServerDetail({ name }: { name: string }) {
</CardHeader> </CardHeader>
<CardContent className={'pl-1 w-[calc(100%-2em)] h-52 relative'}> <CardContent className={'pl-1 w-[calc(100%-2em)] h-52 relative'}>
<Suspense fallback={<Spinner />}> <Suspense fallback={<Spinner />}>
<CpuChart chartData={cpuChartData.data} max={cpuChartData.max} /> <CpuChart chartData={cpuChartData} />
</Suspense> </Suspense>
</CardContent> </CardContent>
</Card> </Card>
@@ -177,15 +172,15 @@ export default function ServerDetail({ name }: { name: string }) {
</CardHeader> </CardHeader>
<CardContent className={'pl-1 w-[calc(100%-2em)] h-52 relative'}> <CardContent className={'pl-1 w-[calc(100%-2em)] h-52 relative'}>
<Suspense fallback={<Spinner />}> <Suspense fallback={<Spinner />}>
<ContainerCpuChart chartData={containerCpuChartData} max={cpuChartData.max} /> <ContainerCpuChart chartData={containerCpuChartData} />
</Suspense> </Suspense>
</CardContent> </CardContent>
</Card> </Card>
)} )}
<Card className="pb-2"> <Card className="pb-2">
<CardHeader> <CardHeader>
<CardTitle>Memory Usage</CardTitle> <CardTitle>Total Memory Usage</CardTitle>
<CardDescription>Precise usage at the recorded time</CardDescription> <CardDescription>Precise utilization at the recorded time</CardDescription>
</CardHeader> </CardHeader>
<CardContent className={'pl-1 w-[calc(100%-2em)] h-52 relative'}> <CardContent className={'pl-1 w-[calc(100%-2em)] h-52 relative'}>
<Suspense fallback={<Spinner />}> <Suspense fallback={<Spinner />}>
@@ -204,12 +199,7 @@ export default function ServerDetail({ name }: { name: string }) {
</CardHeader> </CardHeader>
<CardContent className={'pl-1 w-[calc(100%-2em)] h-52 relative'}> <CardContent className={'pl-1 w-[calc(100%-2em)] h-52 relative'}>
<Suspense fallback={<Spinner />}> <Suspense fallback={<Spinner />}>
{server?.stats?.m && ( {server?.stats?.m && <ContainerMemChart chartData={containerMemChartData} />}
<ContainerMemChart
chartData={containerMemChartData}
max={server.stats.m * 1024}
/>
)}
</Suspense> </Suspense>
</CardContent> </CardContent>
</Card> </Card>

View File

@@ -118,11 +118,21 @@ const ChartTooltipContent = React.forwardRef<
nameKey, nameKey,
labelKey, labelKey,
unit, unit,
itemSorter,
}, },
ref ref
) => { ) => {
const { config } = useChart() const { config } = useChart()
payload = React.useMemo(() => {
if (itemSorter) {
return payload.sort(itemSorter)
}
return payload
}, [itemSorter, payload])
// console.log('iiiiii', itemSorter)
const tooltipLabel = React.useMemo(() => { const tooltipLabel = React.useMemo(() => {
if (hideLabel || !payload?.length) { if (hideLabel || !payload?.length) {
return null return null