From 48c7aa5dfc4e542f79c42ea84c4580c9417d2cd9 Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Thu, 5 Oct 2023 11:36:26 +0100 Subject: [PATCH 1/5] Use data for y scale, clamping at capacity --- app/components/TimeSeriesChart.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/app/components/TimeSeriesChart.tsx b/app/components/TimeSeriesChart.tsx index 5843adbf84..c0ef670e03 100644 --- a/app/components/TimeSeriesChart.tsx +++ b/app/components/TimeSeriesChart.tsx @@ -125,13 +125,22 @@ export default function TimeSeriesChart({ maxValue, unit, }: Props) { + // We use the largest data point +20% for the graph scale + // clamping at `maxValue` (if set) which is usually overall capacity + const calculatedMaxValue = useMemo(() => { + if (!maxValue) return null + if (!rawData) return maxValue + const dataMax = Math.max(...rawData.map((datum) => datum.value)) + return Math.min(maxValue, dataMax * 1.2) + }, [rawData, maxValue]) + // If max value is set we normalize the graph so that // is the maximum, we also use our own function as recharts // doesn't fill the whole domain (just upto the data max) - const yTicks = maxValue + const yTicks = calculatedMaxValue ? { - domain: [0, maxValue], - ticks: getVerticalTicks(6, maxValue), + domain: [0, calculatedMaxValue], + ticks: getVerticalTicks(6, calculatedMaxValue), } : { tickSize: 6, From baa7e023e7cb0612fed0c13a262607939c912e81 Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Thu, 5 Oct 2023 11:41:25 +0100 Subject: [PATCH 2/5] Normalize the mock data to sit within overall capacity --- libs/api-mocks/msw/util.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/libs/api-mocks/msw/util.ts b/libs/api-mocks/msw/util.ts index 77d61271bf..bf4c0fb3ff 100644 --- a/libs/api-mocks/msw/util.ts +++ b/libs/api-mocks/msw/util.ts @@ -197,7 +197,7 @@ export function generateUtilization( const valueInterval = Math.floor(dataCount / timeInterval) // Pick a reasonable start value - const startVal = cap / 2 + const startVal = 500 const values = new Array(dataCount) values[0] = startVal @@ -213,15 +213,10 @@ export function generateUtilization( const threshold = i < 250 || (i > 500 && i < 750) ? 1 : 0.375 if (random < threshold) { - const amount = - metricName === 'cpus_provisioned' - ? 3 - : metricName === 'virtual_disk_space_provisioned' - ? TiB - : TiB / 20 + const amount = 50 offset = Math.floor(random * amount) - if (random < threshold / 3) { + if (random < threshold / 2.5) { offset = offset * -1 } } @@ -237,7 +232,13 @@ export function generateUtilization( } } - return values + // Find the current maximum value in the generated data + const currentMax = Math.max(...values) + + // Normalize the data to sit within the range of 0 to overall capacity (with some headroom) + const normalizedValues = values.map((value) => (value / currentMax) * cap * 0.8) + + return normalizedValues } type MetricParams = { From 78487d2b8af324eaf9e2b6cacfb694e56795575a Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Thu, 5 Oct 2023 11:45:07 +0100 Subject: [PATCH 3/5] Adds a bit more variety in total utilization --- libs/api-mocks/msw/util.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/api-mocks/msw/util.ts b/libs/api-mocks/msw/util.ts index bf4c0fb3ff..9278931c28 100644 --- a/libs/api-mocks/msw/util.ts +++ b/libs/api-mocks/msw/util.ts @@ -235,8 +235,9 @@ export function generateUtilization( // Find the current maximum value in the generated data const currentMax = Math.max(...values) - // Normalize the data to sit within the range of 0 to overall capacity (with some headroom) - const normalizedValues = values.map((value) => (value / currentMax) * cap * 0.8) + // Normalize the data to sit within the range of 0 to overall capacity + const randomFactor = Math.random() * (1 - 0.33) + 0.33 + const normalizedValues = values.map((value) => (value / currentMax) * cap * randomFactor) return normalizedValues } From 310e9586add4967dcad896e7e6074d936763bfc3 Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Thu, 5 Oct 2023 11:52:09 +0100 Subject: [PATCH 4/5] Round `calculatedMaxValue` for better ticks --- app/components/TimeSeriesChart.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/components/TimeSeriesChart.tsx b/app/components/TimeSeriesChart.tsx index c0ef670e03..d4f67969f8 100644 --- a/app/components/TimeSeriesChart.tsx +++ b/app/components/TimeSeriesChart.tsx @@ -131,7 +131,11 @@ export default function TimeSeriesChart({ if (!maxValue) return null if (!rawData) return maxValue const dataMax = Math.max(...rawData.map((datum) => datum.value)) - return Math.min(maxValue, dataMax * 1.2) + const unroundedValue = Math.min(maxValue, dataMax * 1.2) + + // Round up to the nearest number divisible by 6 + // This avoids uneven ticks + return Math.ceil(unroundedValue / 6) * 6 }, [rawData, maxValue]) // If max value is set we normalize the graph so that From 82515cd78dd316a017811893a0e6fc9515273500 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Thu, 12 Oct 2023 15:04:46 -0500 Subject: [PATCH 5/5] get rid of maxValue prop, simplify chart max calc --- app/components/SystemMetric.tsx | 1 - app/components/TimeSeriesChart.tsx | 44 ++++++++++++++---------------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/app/components/SystemMetric.tsx b/app/components/SystemMetric.tsx index bd138b0c81..1c4ab7ac66 100644 --- a/app/components/SystemMetric.tsx +++ b/app/components/SystemMetric.tsx @@ -188,7 +188,6 @@ export function SystemMetric({ interpolation="stepAfter" startTime={startTime} endTime={endTime} - maxValue={capacity} unit={unit !== 'count' ? unit : undefined} /> diff --git a/app/components/TimeSeriesChart.tsx b/app/components/TimeSeriesChart.tsx index d4f67969f8..026267cfc0 100644 --- a/app/components/TimeSeriesChart.tsx +++ b/app/components/TimeSeriesChart.tsx @@ -100,7 +100,7 @@ function renderTooltip(props: TooltipProps, unit?: string) { ) } -type Props = { +type TimeSeriesChartProps = { className?: string data: ChartDatum[] | undefined title: string @@ -109,10 +109,16 @@ type Props = { interpolation?: 'linear' | 'stepAfter' startTime: Date endTime: Date - maxValue?: number unit?: string } +const TICK_COUNT = 6 + +/** Round `value` up to nearest number divisible by `divisor` */ +function roundUpToDivBy(value: number, divisor: number) { + return Math.ceil(value / divisor) * divisor +} + export default function TimeSeriesChart({ className, data: rawData, @@ -122,33 +128,23 @@ export default function TimeSeriesChart({ interpolation = 'linear', startTime, endTime, - maxValue, unit, -}: Props) { - // We use the largest data point +20% for the graph scale - // clamping at `maxValue` (if set) which is usually overall capacity - const calculatedMaxValue = useMemo(() => { - if (!maxValue) return null - if (!rawData) return maxValue +}: TimeSeriesChartProps) { + // We use the largest data point +20% for the graph scale. !rawData doesn't + // mean it's empty (it will never be empty because we fill in artificial 0s at + // beginning and end), it means the metrics requests haven't come back yet + const maxY = useMemo(() => { + if (!rawData) return null const dataMax = Math.max(...rawData.map((datum) => datum.value)) - const unroundedValue = Math.min(maxValue, dataMax * 1.2) - - // Round up to the nearest number divisible by 6 - // This avoids uneven ticks - return Math.ceil(unroundedValue / 6) * 6 - }, [rawData, maxValue]) + return roundUpToDivBy(dataMax * 1.2, TICK_COUNT) // avoid uneven ticks + }, [rawData]) // If max value is set we normalize the graph so that // is the maximum, we also use our own function as recharts - // doesn't fill the whole domain (just upto the data max) - const yTicks = calculatedMaxValue - ? { - domain: [0, calculatedMaxValue], - ticks: getVerticalTicks(6, calculatedMaxValue), - } - : { - tickSize: 6, - } + // doesn't fill the whole domain (just up to the data max) + const yTicks = maxY + ? { domain: [0, maxY], ticks: getVerticalTicks(TICK_COUNT, maxY) } + : undefined // falling back here instead of in the parent lets us avoid causing a // re-render on every render of the parent when the data is undefined