From e3ed07a999b9c1f69cd8afed221e83dd6895f506 Mon Sep 17 00:00:00 2001 From: Henry Dollman Date: Sun, 4 Aug 2024 16:35:12 -0400 Subject: [PATCH] adapt y axis width in recharts --- .../src/components/charts/bandwidth-chart.tsx | 141 ++++++++------- .../components/charts/container-cpu-chart.tsx | 114 ++++++------ .../components/charts/container-mem-chart.tsx | 122 +++++++------ .../components/charts/container-net-chart.tsx | 171 +++++++++--------- hub/site/src/components/charts/cpu-chart.tsx | 97 +++++----- hub/site/src/components/charts/disk-chart.tsx | 116 ++++++------ .../src/components/charts/disk-io-chart.tsx | 141 ++++++++------- hub/site/src/components/charts/mem-chart.tsx | 139 +++++++------- hub/site/src/lib/utils.ts | 18 ++ 9 files changed, 556 insertions(+), 503 deletions(-) diff --git a/hub/site/src/components/charts/bandwidth-chart.tsx b/hub/site/src/components/charts/bandwidth-chart.tsx index 112a4e1..bcf430f 100644 --- a/hub/site/src/components/charts/bandwidth-chart.tsx +++ b/hub/site/src/components/charts/bandwidth-chart.tsx @@ -1,11 +1,12 @@ import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts' import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart' -import { chartTimeData, formatShortDate } from '@/lib/utils' +import { chartTimeData, formatShortDate, useYaxisWidth } from '@/lib/utils' import Spinner from '../spinner' import { useStore } from '@nanostores/react' import { $chartTime } from '@/lib/stores' import { SystemStatsRecord } from '@/types' +import { useRef } from 'react' export default function BandwidthChart({ ticks, @@ -14,6 +15,8 @@ export default function BandwidthChart({ ticks: number[] systemData: SystemStatsRecord[] }) { + const chartRef = useRef(null) + const yAxisWidth = useYaxisWidth(chartRef) const chartTime = useStore($chartTime) if (!systemData.length || !ticks.length) { @@ -21,73 +24,75 @@ export default function BandwidthChart({ } return ( - - - - (max <= 0.4 ? 0.4 : Math.ceil(max))]} - tickFormatter={(value) => { - if (value >= 100) { - return value.toFixed(0) - } - return value.toFixed((value * 100) % 1 === 0 ? 1 : 2) +
+ + - - formatShortDate(data[0].payload.created)} - indicator="line" - /> - } - /> - - - - + > + + (max <= 0.4 ? 0.4 : Math.ceil(max))]} + tickFormatter={(value) => { + if (value >= 100) { + return value.toFixed(0) + } + return value.toFixed((value * 100) % 1 === 0 ? 1 : 2) + }} + tickLine={false} + axisLine={false} + unit={' MB/s'} + /> + + formatShortDate(data[0].payload.created)} + indicator="line" + /> + } + /> + + + + +
) } diff --git a/hub/site/src/components/charts/container-cpu-chart.tsx b/hub/site/src/components/charts/container-cpu-chart.tsx index 0e7d128..b162145 100644 --- a/hub/site/src/components/charts/container-cpu-chart.tsx +++ b/hub/site/src/components/charts/container-cpu-chart.tsx @@ -5,8 +5,8 @@ import { ChartTooltip, ChartTooltipContent, } from '@/components/ui/chart' -import { useMemo } from 'react' -import { chartTimeData, formatShortDate } from '@/lib/utils' +import { useMemo, useRef } from 'react' +import { chartTimeData, formatShortDate, useYaxisWidth } from '@/lib/utils' import Spinner from '../spinner' import { useStore } from '@nanostores/react' import { $chartTime } from '@/lib/stores' @@ -18,6 +18,8 @@ export default function ContainerCpuChart({ chartData: Record[] ticks: number[] }) { + const chartRef = useRef(null) + const yAxisWidth = useYaxisWidth(chartRef) const chartTime = useStore($chartTime) const chartConfig = useMemo(() => { @@ -60,59 +62,61 @@ export default function ContainerCpuChart({ } return ( - - - - Math.max(Math.ceil(max), 0.4)]} - width={47} - tickLine={false} - axisLine={false} - unit={'%'} - tickFormatter={(x) => (x % 1 === 0 ? x : x.toFixed(1))} - /> - - formatShortDate(data[0].payload.time)} - // @ts-ignore - itemSorter={(a, b) => b.value - a.value} - content={} - /> - {Object.keys(chartConfig).map((key) => ( - + + + + Math.max(Math.ceil(max), 0.4)]} + width={yAxisWidth} + tickLine={false} + axisLine={false} + unit={'%'} + tickFormatter={(x) => (x % 1 === 0 ? x : x.toFixed(1))} /> - ))} - - + + formatShortDate(data[0].payload.time)} + // @ts-ignore + itemSorter={(a, b) => b.value - a.value} + content={} + /> + {Object.keys(chartConfig).map((key) => ( + + ))} + + + ) } diff --git a/hub/site/src/components/charts/container-mem-chart.tsx b/hub/site/src/components/charts/container-mem-chart.tsx index 96b1ea5..e266d8a 100644 --- a/hub/site/src/components/charts/container-mem-chart.tsx +++ b/hub/site/src/components/charts/container-mem-chart.tsx @@ -5,8 +5,8 @@ import { ChartTooltip, ChartTooltipContent, } from '@/components/ui/chart' -import { useMemo } from 'react' -import { chartTimeData, formatShortDate } from '@/lib/utils' +import { useMemo, useRef } from 'react' +import { chartTimeData, formatShortDate, useYaxisWidth } from '@/lib/utils' import Spinner from '../spinner' import { useStore } from '@nanostores/react' import { $chartTime } from '@/lib/stores' @@ -18,6 +18,8 @@ export default function ContainerMemChart({ chartData: Record[] ticks: number[] }) { + const chartRef = useRef(null) + const yAxisWidth = useYaxisWidth(chartRef) const chartTime = useStore($chartTime) const chartConfig = useMemo(() => { @@ -60,64 +62,66 @@ export default function ContainerMemChart({ } return ( - - - - Math.ceil(max)]} - tickLine={false} - axisLine={false} - unit={' GB'} - width={70} - tickFormatter={(value) => { - value = value / 1024 - return value.toFixed((value * 100) % 1 === 0 ? 1 : 2) +
+ + - - formatShortDate(data[0].payload.time)} - // @ts-ignore - itemSorter={(a, b) => b.value - a.value} - content={} - /> - {Object.keys(chartConfig).map((key) => ( - + + Math.ceil(max)]} + tickLine={false} + axisLine={false} + unit={' GB'} + width={yAxisWidth} + tickFormatter={(value) => { + value = value / 1024 + return value.toFixed((value * 100) % 1 === 0 ? 1 : 2) + }} /> - ))} - - + + formatShortDate(data[0].payload.time)} + // @ts-ignore + itemSorter={(a, b) => b.value - a.value} + content={} + /> + {Object.keys(chartConfig).map((key) => ( + + ))} + + +
) } diff --git a/hub/site/src/components/charts/container-net-chart.tsx b/hub/site/src/components/charts/container-net-chart.tsx index 93b8d7f..b5eea0b 100644 --- a/hub/site/src/components/charts/container-net-chart.tsx +++ b/hub/site/src/components/charts/container-net-chart.tsx @@ -5,8 +5,8 @@ import { ChartTooltip, ChartTooltipContent, } from '@/components/ui/chart' -import { useMemo } from 'react' -import { chartTimeData, formatShortDate } from '@/lib/utils' +import { useMemo, useRef } from 'react' +import { chartTimeData, formatShortDate, useYaxisWidth } from '@/lib/utils' import Spinner from '../spinner' import { useStore } from '@nanostores/react' import { $chartTime } from '@/lib/stores' @@ -19,6 +19,8 @@ export default function ContainerCpuChart({ chartData: Record[] ticks: number[] }) { + const chartRef = useRef(null) + const yAxisWidth = useYaxisWidth(chartRef) const chartTime = useStore($chartTime) const chartConfig = useMemo(() => { @@ -60,88 +62,91 @@ export default function ContainerCpuChart({ } return ( - - - - Math.max(Math.ceil(max), 0.4)]} - width={75} - tickLine={false} - axisLine={false} - unit={' MB'} - tickFormatter={(x) => (x % 1 === 0 ? x : x.toFixed(1))} - /> - - { - return ( - - {formatShortDate(data[0].payload.time)} -
- Total MB received / transmitted -
- ) +
+ + b.value - a.value} - content={ - { - try { - const sent = item?.payload?.[key][0] ?? 0 - const received = item?.payload?.[key][1] ?? 0 - return ( - - {received.toLocaleString()} MB rx - - {sent.toLocaleString()} MB tx - - ) - } catch (e) { - return null - } - }} - /> - } - /> - {Object.keys(chartConfig).map((key) => ( - data?.[key]?.[2] ?? 0} - type="monotoneX" - fill={chartConfig[key].color} - fillOpacity={0.4} - stroke={chartConfig[key].color} - stackId="a" + reverseStackOrder={true} + > + + Math.max(Math.ceil(max), 0.4)]} + width={yAxisWidth} + tickLine={false} + axisLine={false} + unit={' MB'} + tickFormatter={(x) => (x % 1 === 0 ? x : x.toFixed(1))} /> - ))} - - + + { + return ( + + {formatShortDate(data[0].payload.time)} +
+ Total MB received / transmitted +
+ ) + }} + // @ts-ignore + + itemSorter={(a, b) => b.value - a.value} + content={ + { + try { + const sent = item?.payload?.[key][0] ?? 0 + const received = item?.payload?.[key][1] ?? 0 + return ( + + {received.toLocaleString()} MB + rx + + {sent.toLocaleString()} MB tx + + ) + } catch (e) { + return null + } + }} + /> + } + /> + {Object.keys(chartConfig).map((key) => ( + data?.[key]?.[2] ?? 0} + type="monotoneX" + fill={chartConfig[key].color} + fillOpacity={0.4} + stroke={chartConfig[key].color} + stackId="a" + /> + ))} + + +
) } diff --git a/hub/site/src/components/charts/cpu-chart.tsx b/hub/site/src/components/charts/cpu-chart.tsx index 59e4c8e..0d1f1bb 100644 --- a/hub/site/src/components/charts/cpu-chart.tsx +++ b/hub/site/src/components/charts/cpu-chart.tsx @@ -1,11 +1,12 @@ import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts' import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart' -import { chartTimeData, formatShortDate } from '@/lib/utils' +import { chartTimeData, formatShortDate, useYaxisWidth } from '@/lib/utils' import Spinner from '../spinner' import { useStore } from '@nanostores/react' import { $chartTime } from '@/lib/stores' import { SystemStatsRecord } from '@/types' +import { useRef } from 'react' export default function CpuChart({ ticks, @@ -14,6 +15,8 @@ export default function CpuChart({ ticks: number[] systemData: SystemStatsRecord[] }) { + const chartRef = useRef(null) + const yAxisWidth = useYaxisWidth(chartRef) const chartTime = useStore($chartTime) if (!systemData.length || !ticks.length) { @@ -21,50 +24,52 @@ export default function CpuChart({ } return ( - - - - Math.ceil(max)]} - width={48} - tickLine={false} - axisLine={false} - unit={'%'} - /> - - formatShortDate(data[0].payload.created)} - indicator="line" - /> - } - /> - - - +
+ + + + Math.ceil(max)]} + width={yAxisWidth} + tickLine={false} + axisLine={false} + unit={'%'} + /> + + formatShortDate(data[0].payload.created)} + indicator="line" + /> + } + /> + + + +
) } diff --git a/hub/site/src/components/charts/disk-chart.tsx b/hub/site/src/components/charts/disk-chart.tsx index 6d97a64..b488a02 100644 --- a/hub/site/src/components/charts/disk-chart.tsx +++ b/hub/site/src/components/charts/disk-chart.tsx @@ -1,8 +1,8 @@ import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts' import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart' -import { chartTimeData, formatShortDate } from '@/lib/utils' -import { useMemo } from 'react' +import { chartTimeData, formatShortDate, useYaxisWidth } from '@/lib/utils' +import { useMemo, useRef } from 'react' import Spinner from '../spinner' import { useStore } from '@nanostores/react' import { $chartTime } from '@/lib/stores' @@ -15,6 +15,8 @@ export default function DiskChart({ ticks: number[] systemData: SystemStatsRecord[] }) { + const chartRef = useRef(null) + const yAxisWidth = useYaxisWidth(chartRef) const chartTime = useStore($chartTime) const diskSize = useMemo(() => { @@ -35,59 +37,61 @@ export default function DiskChart({ } return ( - - - - = 1000 ? 75 : 65} - domain={[0, diskSize]} - tickCount={9} - tickLine={false} - axisLine={false} - unit={' GB'} - /> - - formatShortDate(data[0].payload.created)} - indicator="line" - /> - } - /> - - - +
+ + + + + + formatShortDate(data[0].payload.created)} + indicator="line" + /> + } + /> + + + +
) } diff --git a/hub/site/src/components/charts/disk-io-chart.tsx b/hub/site/src/components/charts/disk-io-chart.tsx index 16a936a..813f0f6 100644 --- a/hub/site/src/components/charts/disk-io-chart.tsx +++ b/hub/site/src/components/charts/disk-io-chart.tsx @@ -1,11 +1,12 @@ import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts' import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart' -import { chartTimeData, formatShortDate } from '@/lib/utils' +import { chartTimeData, formatShortDate, useYaxisWidth } from '@/lib/utils' import Spinner from '../spinner' import { useStore } from '@nanostores/react' import { $chartTime } from '@/lib/stores' import { SystemStatsRecord } from '@/types' +import { useRef } from 'react' export default function DiskIoChart({ ticks, @@ -14,6 +15,8 @@ export default function DiskIoChart({ ticks: number[] systemData: SystemStatsRecord[] }) { + const chartRef = useRef(null) + const yAxisWidth = useYaxisWidth(chartRef) const chartTime = useStore($chartTime) if (!systemData.length || !ticks.length) { @@ -21,73 +24,75 @@ export default function DiskIoChart({ } return ( - - - - (max <= 0.4 ? 0.4 : Math.ceil(max))]} - tickFormatter={(value) => { - if (value >= 100) { - return value.toFixed(0) - } - return value.toFixed((value * 100) % 1 === 0 ? 1 : 2) +
+ + - - formatShortDate(data[0].payload.created)} - indicator="line" - /> - } - /> - - - - + > + + (max <= 0.4 ? 0.4 : Math.ceil(max))]} + tickFormatter={(value) => { + if (value >= 100) { + return value.toFixed(0) + } + return value.toFixed((value * 100) % 1 === 0 ? 1 : 2) + }} + tickLine={false} + axisLine={false} + unit={' MB/s'} + /> + + formatShortDate(data[0].payload.created)} + indicator="line" + /> + } + /> + + + + +
) } diff --git a/hub/site/src/components/charts/mem-chart.tsx b/hub/site/src/components/charts/mem-chart.tsx index 1951920..0dbc477 100644 --- a/hub/site/src/components/charts/mem-chart.tsx +++ b/hub/site/src/components/charts/mem-chart.tsx @@ -1,8 +1,8 @@ import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts' import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart' -import { chartTimeData, formatShortDate } from '@/lib/utils' -import { useMemo } from 'react' +import { chartTimeData, formatShortDate, useYaxisWidth } from '@/lib/utils' +import { useMemo, useRef } from 'react' import Spinner from '../spinner' import { useStore } from '@nanostores/react' import { $chartTime } from '@/lib/stores' @@ -16,6 +16,8 @@ export default function MemChart({ systemData: SystemStatsRecord[] }) { const chartTime = useStore($chartTime) + const chartRef = useRef(null) + const yAxisWidth = useYaxisWidth(chartRef) const totalMem = useMemo(() => { const maxMem = Math.ceil(systemData[0]?.stats.m) @@ -27,71 +29,72 @@ export default function MemChart({ } return ( - - - - = 100 ? 65 : 58} - // allowDecimals={false} - axisLine={false} - unit={' GB'} - /> - - a.name.localeCompare(b.name)} - labelFormatter={(_, data) => formatShortDate(data[0].payload.created)} - indicator="line" - /> - } - /> - - - - +
+ + + + + + a.name.localeCompare(b.name)} + labelFormatter={(_, data) => formatShortDate(data[0].payload.created)} + indicator="line" + /> + } + /> + + + + +
) } diff --git a/hub/site/src/lib/utils.ts b/hub/site/src/lib/utils.ts index 2fdfd41..50e728e 100644 --- a/hub/site/src/lib/utils.ts +++ b/hub/site/src/lib/utils.ts @@ -6,6 +6,7 @@ import { AlertRecord, ChartTimeData, ChartTimes, SystemRecord } from '@/types' import { RecordModel, RecordSubscription } from 'pocketbase' import { WritableAtom } from 'nanostores' import { timeDay, timeHour } from 'd3-time' +import { useEffect, useState } from 'react' export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) @@ -179,3 +180,20 @@ export const chartTimeData: ChartTimeData = { getOffset: (endTime: Date) => timeDay.offset(endTime, -30), }, } + +/** Hacky solution to set the correct width of the yAxis in recharts */ +export function useYaxisWidth(chartRef: React.RefObject) { + const [yAxisWidth, setYAxisWidth] = useState(90) + useEffect(() => { + let interval = setInterval(() => { + // console.log('chartRef', chartRef.current) + const yAxisElement = chartRef?.current?.querySelector('.yAxis') + if (yAxisElement) { + console.log('yAxisElement', yAxisElement) + setYAxisWidth(yAxisElement.getBoundingClientRect().width + 22) + clearInterval(interval) + } + }, 16) + }, []) + return yAxisWidth +}