Skip to content

Commit 0006490

Browse files
authored
Adds Report Settings, Chart Header and Tooltip Sync features (supabase#37866)
* add charthover state hook * update area and barchart * update chart handler * update chart header * add report settings * update stacked barchart * update composedchart * rm old components * add tests
1 parent b5a79f9 commit 0006490

File tree

12 files changed

+625
-359
lines changed

12 files changed

+625
-359
lines changed

apps/studio/components/ui/Charts/AreaChart.tsx

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import dayjs from 'dayjs'
22
import { useState } from 'react'
33
import { Area, AreaChart as RechartAreaChart, Tooltip, XAxis } from 'recharts'
4-
import { useChartSync } from './useChartSync'
4+
import { useChartHoverState } from './useChartHoverState'
55

66
import { CHART_COLORS, DateTimeFormats } from 'components/ui/Charts/Charts.constants'
77
import ChartHeader from './ChartHeader'
@@ -35,11 +35,9 @@ const AreaChart = ({
3535
syncId,
3636
}: AreaChartProps) => {
3737
const { Container } = useChartSize(size)
38-
const {
39-
state: syncState,
40-
updateState: updateSyncState,
41-
clearState: clearSyncState,
42-
} = useChartSync(syncId)
38+
const { hoveredIndex, syncTooltip, setHover, clearHover } = useChartHoverState(
39+
syncId || 'default'
40+
)
4341
const [focusDataIndex, setFocusDataIndex] = useState<number | null>(null)
4442

4543
const day = (value: number | string) => (displayDateInUtc ? dayjs(value).utc() : dayjs(value))
@@ -102,21 +100,12 @@ const AreaChart = ({
102100
setFocusDataIndex(e.activeTooltipIndex)
103101
}
104102

105-
if (syncId) {
106-
updateSyncState({
107-
activeIndex: e.activeTooltipIndex,
108-
activePayload: e.activePayload,
109-
activeLabel: e.activeLabel,
110-
isHovering: true,
111-
})
112-
}
103+
setHover(e.activeTooltipIndex)
113104
}}
114105
onMouseLeave={() => {
115106
setFocusDataIndex(null)
116107

117-
if (syncId) {
118-
clearSyncState()
119-
}
108+
clearHover()
120109
}}
121110
>
122111
<defs>
@@ -137,16 +126,13 @@ const AreaChart = ({
137126
/>
138127
<Tooltip
139128
content={(props) =>
140-
syncId && syncState.isHovering && syncState.activeIndex !== null ? (
129+
syncId && syncTooltip && hoveredIndex !== null ? (
141130
<div className="bg-black/90 text-white p-2 rounded text-xs">
142131
<div className="font-medium">
143-
{dayjs(data[syncState.activeIndex]?.[xAxisKey]).format(customDateFormat)}
132+
{dayjs(data[hoveredIndex]?.[xAxisKey]).format(customDateFormat)}
144133
</div>
145134
<div>
146-
{numberFormatter(
147-
Number(data[syncState.activeIndex]?.[yAxisKey]) || 0,
148-
valuePrecision
149-
)}
135+
{numberFormatter(Number(data[hoveredIndex]?.[yAxisKey]) || 0, valuePrecision)}
150136
{format}
151137
</div>
152138
</div>

apps/studio/components/ui/Charts/BarChart.tsx

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import dayjs from 'dayjs'
22
import { ComponentProps, useState, useMemo } from 'react'
3-
import { useChartSync } from './useChartSync'
3+
import { useChartHoverState } from './useChartHoverState'
44
import {
55
Bar,
66
CartesianGrid,
@@ -58,11 +58,9 @@ const BarChart = ({
5858
syncId,
5959
}: BarChartProps) => {
6060
const { Container } = useChartSize(size)
61-
const {
62-
state: syncState,
63-
updateState: updateSyncState,
64-
clearState: clearSyncState,
65-
} = useChartSync(syncId)
61+
const { hoveredIndex, syncTooltip, setHover, clearHover } = useChartHoverState(
62+
syncId || 'default'
63+
)
6664
const [focusDataIndex, setFocusDataIndex] = useState<number | null>(null)
6765

6866
// Transform data to ensure yAxisKey values are numbers
@@ -151,21 +149,12 @@ const BarChart = ({
151149
setFocusDataIndex(e.activeTooltipIndex)
152150
}
153151

154-
if (syncId) {
155-
updateSyncState({
156-
activeIndex: e.activeTooltipIndex,
157-
activePayload: e.activePayload,
158-
activeLabel: e.activeLabel,
159-
isHovering: true,
160-
})
161-
}
152+
setHover(e.activeTooltipIndex)
162153
}}
163154
onMouseLeave={() => {
164155
setFocusDataIndex(null)
165156

166-
if (syncId) {
167-
clearSyncState()
168-
}
157+
clearHover()
169158
}}
170159
onClick={(tooltipData) => {
171160
const datum = tooltipData?.activePayload?.[0]?.payload
@@ -188,16 +177,13 @@ const BarChart = ({
188177
/>
189178
<Tooltip
190179
content={(props) =>
191-
syncId && syncState.isHovering && syncState.activeIndex !== null ? (
180+
syncId && syncTooltip && hoveredIndex !== null ? (
192181
<div className="bg-black/90 text-white p-2 rounded text-xs">
193182
<div className="font-medium">
194-
{dayjs(data[syncState.activeIndex]?.[xAxisKey]).format(customDateFormat)}
183+
{dayjs(data[hoveredIndex]?.[xAxisKey]).format(customDateFormat)}
195184
</div>
196185
<div>
197-
{numberFormatter(
198-
Number(data[syncState.activeIndex]?.[yAxisKey]) || 0,
199-
valuePrecision
200-
)}
186+
{numberFormatter(Number(data[hoveredIndex]?.[yAxisKey]) || 0, valuePrecision)}
201187
{typeof format === 'string' ? format : ''}
202188
</div>
203189
</div>

apps/studio/components/ui/Charts/ChartHandler.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ interface ChartHandlerProps {
3333
isLoading?: boolean
3434
format?: string
3535
highlightedValue?: string | number
36+
syncId?: string
3637
}
3738

3839
/**
@@ -59,6 +60,7 @@ const ChartHandler = ({
5960
isLoading,
6061
format,
6162
highlightedValue,
63+
syncId,
6264
...otherProps
6365
}: PropsWithChildren<ChartHandlerProps>) => {
6466
const router = useRouter()
@@ -176,6 +178,7 @@ const ChartHandler = ({
176178
highlightedValue={_highlightedValue}
177179
title={label}
178180
customDateFormat={customDateFormat}
181+
syncId={syncId}
179182
{...otherProps}
180183
/>
181184
) : (
@@ -187,6 +190,7 @@ const ChartHandler = ({
187190
highlightedValue={_highlightedValue}
188191
title={label}
189192
customDateFormat={customDateFormat}
193+
syncId={syncId}
190194
{...otherProps}
191195
/>
192196
)}

apps/studio/components/ui/Charts/ChartHeader.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Link from 'next/link'
44
import { useEffect, useState } from 'react'
55
import dayjs from 'dayjs'
66
import { formatBytes } from 'lib/helpers'
7-
import { useChartSync } from './useChartSync'
7+
import { useChartHoverState } from './useChartHoverState'
88
import { numberFormatter } from './Charts.utils'
99

1010
export interface ChartHeaderProps {
@@ -58,7 +58,7 @@ const ChartHeader = ({
5858
isNetworkChart = false,
5959
attributes,
6060
}: ChartHeaderProps) => {
61-
const { state: syncState } = useChartSync(syncId)
61+
const { hoveredIndex, syncHover } = useChartHoverState(syncId || 'default')
6262
const [localHighlightedValue, setLocalHighlightedValue] = useState(highlightedValue)
6363
const [localHighlightedLabel, setLocalHighlightedLabel] = useState(highlightedLabel)
6464

@@ -76,8 +76,8 @@ const ChartHeader = ({
7676
}
7777

7878
useEffect(() => {
79-
if (syncId && syncState.activeIndex !== null && data && xAxisKey && yAxisKey) {
80-
const activeDataPoint = data[syncState.activeIndex]
79+
if (syncId && hoveredIndex !== null && syncHover && data && xAxisKey && yAxisKey) {
80+
const activeDataPoint = data[hoveredIndex]
8181
if (activeDataPoint) {
8282
// For stacked charts, we need to calculate the total of all attributes
8383
// that should be included in the total (excluding reference lines, max values, etc.)
@@ -127,7 +127,8 @@ const ChartHeader = ({
127127
setLocalHighlightedLabel(highlightedLabel)
128128
}
129129
}, [
130-
syncState,
130+
hoveredIndex,
131+
syncHover,
131132
syncId,
132133
data,
133134
xAxisKey,

apps/studio/components/ui/Charts/ComposedChart.tsx

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import dayjs from 'dayjs'
44
import { formatBytes } from 'lib/helpers'
55
import { useTheme } from 'next-themes'
66
import { ComponentProps, useEffect, useState } from 'react'
7-
import { useChartSync } from './useChartSync'
7+
import { useChartHoverState } from './useChartHoverState'
88
import {
99
Area,
1010
Bar,
@@ -102,11 +102,9 @@ export default function ComposedChart({
102102
docsUrl,
103103
}: ComposedChartProps) {
104104
const { resolvedTheme } = useTheme()
105-
const {
106-
state: syncState,
107-
updateState: updateSyncState,
108-
clearState: clearSyncState,
109-
} = useChartSync(syncId)
105+
const { hoveredIndex, syncTooltip, setHover, clearHover } = useChartHoverState(
106+
syncId || 'default'
107+
)
110108
const [_activePayload, setActivePayload] = useState<any>(null)
111109
const [_showMaxValue, setShowMaxValue] = useState(showMaxValue)
112110
const [focusDataIndex, setFocusDataIndex] = useState<number | null>(null)
@@ -339,14 +337,7 @@ export default function ComposedChart({
339337
setActivePayload(e.activePayload)
340338
}
341339

342-
if (syncId) {
343-
updateSyncState({
344-
activeIndex: e.activeTooltipIndex,
345-
activePayload: e.activePayload,
346-
activeLabel: e.activeLabel,
347-
isHovering: true,
348-
})
349-
}
340+
setHover(e.activeTooltipIndex)
350341

351342
const activeTimestamp = data[e.activeTooltipIndex]?.timestamp
352343
chartHighlight?.handleMouseMove({
@@ -367,9 +358,7 @@ export default function ComposedChart({
367358
setFocusDataIndex(null)
368359
setActivePayload(null)
369360

370-
if (syncId) {
371-
clearSyncState()
372-
}
361+
clearHover()
373362
}}
374363
onClick={(tooltipData) => {
375364
const datum = tooltipData?.activePayload?.[0]?.payload
@@ -404,7 +393,9 @@ export default function ComposedChart({
404393
attributes={attributes}
405394
valuePrecision={valuePrecision}
406395
showTotal={showTotal}
407-
isActiveHoveredChart={isActiveHoveredChart || (!!syncId && syncState.isHovering)}
396+
isActiveHoveredChart={
397+
isActiveHoveredChart || (!!syncId && syncTooltip && hoveredIndex !== null)
398+
}
408399
/>
409400
) : null
410401
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Settings } from 'lucide-react'
2+
import { useState } from 'react'
3+
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, Label_Shadcn_, Switch } from 'ui'
4+
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
5+
import { useChartHoverState } from './useChartHoverState'
6+
import { cn } from 'ui'
7+
8+
interface ReportSettingsProps {
9+
chartId: string
10+
}
11+
12+
export const ReportSettings = ({ chartId }: ReportSettingsProps) => {
13+
const [isOpen, setIsOpen] = useState(false)
14+
const { syncHover, syncTooltip, setSyncHover, setSyncTooltip } = useChartHoverState(chartId)
15+
16+
return (
17+
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
18+
<DropdownMenuTrigger asChild>
19+
<ButtonTooltip
20+
type="default"
21+
icon={<Settings />}
22+
className="w-7"
23+
tooltip={{ content: { side: 'bottom', text: 'Report settings' } }}
24+
/>
25+
</DropdownMenuTrigger>
26+
<DropdownMenuContent side="bottom" className="w-64 p-3">
27+
<div className="space-y-4">
28+
<Label_Shadcn_ htmlFor="sync-hover" className="text-sm font-normal">
29+
<div className="flex items-center justify-between space-x-2">
30+
Sync chart headers
31+
<Switch id="sync-hover" checked={syncHover} onCheckedChange={setSyncHover} />
32+
</div>
33+
<p className="text-xs text-foreground-light mt-1">
34+
When enabled, hovering over any chart will update headers across all charts
35+
</p>
36+
</Label_Shadcn_>
37+
38+
<Label_Shadcn_ htmlFor="sync-tooltips" className="text-sm font-normal flex flex-col">
39+
<div className="flex items-center justify-between space-x-2">
40+
Sync tooltips
41+
<Switch
42+
id="sync-tooltips"
43+
checked={syncTooltip}
44+
disabled={!syncHover}
45+
onCheckedChange={setSyncTooltip}
46+
/>
47+
</div>
48+
<p className="text-xs text-foreground-light mt-1">
49+
When enabled, also shows tooltips on all charts.{' '}
50+
<span className={cn(syncHover ? 'text-foreground-light' : 'text-foreground')}>
51+
Requires header sync.
52+
</span>
53+
</p>
54+
</Label_Shadcn_>
55+
</div>
56+
</DropdownMenuContent>
57+
</DropdownMenu>
58+
)
59+
}

apps/studio/components/ui/Charts/StackedBarChart.tsx

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useState } from 'react'
22
import { Bar, BarChart, Cell, Legend, Tooltip, XAxis } from 'recharts'
33
import dayjs from 'dayjs'
44
import utc from 'dayjs/plugin/utc'
5-
import { useChartSync } from './useChartSync'
5+
import { useChartHoverState } from './useChartHoverState'
66
import ChartHeader from './ChartHeader'
77
import {
88
CHART_COLORS,
@@ -58,11 +58,9 @@ const StackedBarChart: React.FC<Props> = ({
5858
syncId,
5959
}) => {
6060
const { Container } = useChartSize(size)
61-
const {
62-
updateState: updateSyncState,
63-
clearState: clearSyncState,
64-
state: syncState,
65-
} = useChartSync(syncId)
61+
const { hoveredIndex, syncTooltip, setHover, clearHover } = useChartHoverState(
62+
syncId || 'default'
63+
)
6664
const { dataKeys, stackedData, percentagesStackedData } = useStacked({
6765
data,
6866
xAxisKey,
@@ -125,21 +123,12 @@ const StackedBarChart: React.FC<Props> = ({
125123
setFocusDataIndex(e.activeTooltipIndex)
126124
}
127125

128-
if (syncId) {
129-
updateSyncState({
130-
activeIndex: e.activeTooltipIndex,
131-
activePayload: e.activePayload,
132-
activeLabel: e.activeLabel,
133-
isHovering: true,
134-
})
135-
}
126+
setHover(e.activeTooltipIndex)
136127
}}
137128
onMouseLeave={() => {
138129
setFocusDataIndex(null)
139130

140-
if (syncId) {
141-
clearSyncState()
142-
}
131+
clearHover()
143132
}}
144133
>
145134
{!hideLegend && (
@@ -205,7 +194,7 @@ const StackedBarChart: React.FC<Props> = ({
205194
fontSize: '12px',
206195
}}
207196
wrapperClassName="bg-gray-600 rounded min-w-md"
208-
active={!!syncId && syncState.isHovering}
197+
active={!!syncId && syncTooltip && hoveredIndex !== null}
209198
/>
210199
</BarChart>
211200
</Container>

0 commit comments

Comments
 (0)