Skip to content
8 changes: 8 additions & 0 deletions .changelog/unreleased/9997-referral-chart-hourly-labels.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
type: fixed
area: referrals
pr: 9997
breaking: false
---

Referral charts now display distinct hourly labels for the 24-hour view without duplicate chart keys or repeated dates.
Original file line number Diff line number Diff line change
@@ -1,9 +1,39 @@
import { afterEach, describe, expect, it } from "vitest"
import { afterEach, describe, expect, it, vi } from "vitest"
import { cleanup, render, screen } from "@testing-library/react"
import { StatChartCard } from "./stat-chart-card"

describe("StatChartCard", () => {
afterEach(cleanup)
afterEach(() => {
cleanup()
vi.useRealTimers()
})

it.each(["24h", "7d", "30d", "90d", "total"] as const)(
"renders five distinct labels for the %s period",
(period) => {
vi.useFakeTimers()
vi.setSystemTime(new Date("2026-10-15T12:30:00"))

const { container } = render(
<StatChartCard title="Volume" tooltip="test" value={0} period={period} />,
)
const labels = Array.from(container.querySelectorAll('text[y="100"]')).map(
(element) => element.textContent,
)

expect(labels).toHaveLength(5)
expect(new Set(labels).size).toBe(labels.length)
if (period === "24h") {
expect(labels).toEqual(
expect.arrayContaining([expect.stringMatching(/^\d{2}:\d{2}$/)]),
)
expect(labels.every((label) => /^\d{2}:\d{2}$/.test(label))).toBe(true)
} else {
expect(labels.every((label) => /^\d{2}\/\d{2}$/.test(label))).toBe(true)
}
},
)

it("renders the title", () => {
render(
<StatChartCard title="Trading Volume" tooltip="Total volume" value={0} period="total" />,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import { formatUsd } from "@/shared/lib/format"

function xAxisLabels(period: TimePeriod): Array<string> {
const now = new Date()
const pad = (value: number) => String(value).padStart(2, "0")
const fmt = (d: Date) =>
`${String(d.getMonth() + 1).padStart(2, "0")}/${String(d.getDate()).padStart(2, "0")}`
period === "24h"
? `${pad(d.getHours())}:${pad(d.getMinutes())}`
: `${pad(d.getMonth() + 1)}/${pad(d.getDate())}`
const msAgo = (ms: number) => new Date(now.getTime() - ms)
const day = 86_400_000

Expand All @@ -18,7 +21,9 @@ function xAxisLabels(period: TimePeriod): Array<string> {
total: 365 * day,
}

const total = ranges[period]
// Exact 24-hour endpoints share a clock time. Exclude the first minute
// so five HH:mm labels are meaningful and distinct without adding a date.
const total = period === "24h" ? ranges[period] - 60_000 : ranges[period]
return Array.from({ length: 5 }, (_, i) => fmt(msAgo(total - (i / 4) * total)))
}

Expand Down Expand Up @@ -118,7 +123,7 @@ export function StatChartCard({ title, tooltip, value, period, accent = "blue" }
const x = 28 + (i / (labels.length - 1)) * 368
return (
<text
key={label}
key={i}
x={x}
y={100}
textAnchor="middle"
Expand Down