Skip to content

Commit c6bc0b4

Browse files
committed
Fix tulip chart collapsing daily data points to monthly positions
The XAxis was using a formatted time string as the data key, so all daily points within a month shared the same "Oct 25" label and collapsed to one position. Now the raw ISO date string is the data key (giving each point a unique X position), with formatting only applied to tick labels and tooltips. Tooltip shows full date for clarity when hovering individual days. Moves formatTime to types.ts so both App and TulipCard can use it.
1 parent fa20502 commit c6bc0b4

3 files changed

Lines changed: 50 additions & 33 deletions

File tree

packages/oaf/src/server/apps/tulips/types.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,35 @@ import type { Duration } from "date-fns";
22

33
export type Range = "1D" | "1W" | "1M" | "YTD" | "1Y" | "10Y";
44

5+
export function formatTime(dateStr: string, range: Range): string {
6+
const date = new Date(dateStr);
7+
switch (range) {
8+
case "1D":
9+
return date.toLocaleTimeString(undefined, {
10+
hour: "numeric",
11+
minute: "2-digit",
12+
});
13+
case "1W":
14+
return date.toLocaleDateString(undefined, {
15+
weekday: "short",
16+
hour: "numeric",
17+
});
18+
case "1M":
19+
case "YTD":
20+
return date.toLocaleDateString(undefined, {
21+
month: "short",
22+
day: "numeric",
23+
hour: "numeric",
24+
});
25+
case "1Y":
26+
case "10Y":
27+
return date.toLocaleDateString(undefined, {
28+
month: "short",
29+
year: "2-digit",
30+
});
31+
}
32+
}
33+
534
export const RANGES: Record<
635
Range,
736
{ duration: Duration | "YTD"; bucket: "hour" | "day" | null }

packages/oaf/src/server/apps/tulips/web/App.tsx

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useCallback, useEffect, useState } from "react";
22

3-
import { RANGES, type Range } from "../types.js";
3+
import { formatTime, RANGES, type Range } from "../types.js";
44
import TulipCard from "./components/TulipCard.js";
55

66
type OHLC = { open: number; high: number; low: number; close: number };
@@ -26,35 +26,6 @@ function toClose(v: ColorValue | null): number | null {
2626

2727
const RANGE_KEYS = Object.keys(RANGES) as Range[];
2828

29-
function formatTime(dateStr: string, range: Range): string {
30-
const date = new Date(dateStr);
31-
switch (range) {
32-
case "1D":
33-
return date.toLocaleTimeString(undefined, {
34-
hour: "numeric",
35-
minute: "2-digit",
36-
});
37-
case "1W":
38-
return date.toLocaleDateString(undefined, {
39-
weekday: "short",
40-
hour: "numeric",
41-
});
42-
case "1M":
43-
case "YTD":
44-
return date.toLocaleDateString(undefined, {
45-
month: "short",
46-
day: "numeric",
47-
hour: "numeric",
48-
});
49-
case "1Y":
50-
case "10Y":
51-
return date.toLocaleDateString(undefined, {
52-
month: "short",
53-
year: "2-digit",
54-
});
55-
}
56-
}
57-
5829
const tulips = [
5930
{
6031
name: "Red Tulips",
@@ -142,13 +113,14 @@ export default function App() {
142113
dataKey={t.key}
143114
current={toClose(current?.[t.key] ?? 0)}
144115
first={toClose(first?.[t.key] ?? null)}
116+
range={range}
145117
history={prices.map((p) => {
146118
const v = p[t.key];
147119
if (!isOHLC(v)) {
148-
return { time: formatTime(p.createdAt, range), value: v };
120+
return { time: p.createdAt, value: v };
149121
}
150122
return {
151-
time: formatTime(p.createdAt, range),
123+
time: p.createdAt,
152124
value: v.close,
153125
open: v.open,
154126
high: v.high,

packages/oaf/src/server/apps/tulips/web/components/TulipCard.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
type TooltipContentProps,
99
} from "recharts";
1010

11+
import { formatTime, type Range } from "../../types.js";
12+
1113
const numberFormat = new Intl.NumberFormat();
1214

1315
type RawEntry = { time: string; value: number };
@@ -31,6 +33,7 @@ export default function TulipCard({
3133
first,
3234
history,
3335
dataKey,
36+
range,
3437
}: {
3538
name: string;
3639
color: string;
@@ -39,6 +42,7 @@ export default function TulipCard({
3942
first: number | null;
4043
history: HistoryEntry[];
4144
dataKey: string;
45+
range: Range;
4246
}) {
4347
const bucketed = history.some(isOHLCEntry);
4448
const [high, low] = history.reduce<[number | null, number | null]>(
@@ -113,6 +117,7 @@ export default function TulipCard({
113117
axisLine={{ stroke: "#1a1a3e" }}
114118
interval="preserveStartEnd"
115119
minTickGap={40}
120+
tickFormatter={(v: string) => formatTime(v, range)}
116121
/>
117122
<YAxis
118123
domain={[
@@ -137,7 +142,18 @@ export default function TulipCard({
137142
const entry: HistoryEntry = payload[0].payload;
138143
return (
139144
<div className="tulip-tooltip">
140-
<p className="tulip-tooltip-label">{entry.time}</p>
145+
<p className="tulip-tooltip-label">
146+
{new Date(entry.time).toLocaleDateString(undefined, {
147+
month: "short",
148+
day: "numeric",
149+
year: "numeric",
150+
...(range === "1D" || range === "1W"
151+
? { hour: "numeric", minute: "2-digit" }
152+
: range === "1M" || range === "YTD"
153+
? { hour: "numeric" }
154+
: {}),
155+
})}
156+
</p>
141157
{isOHLCEntry(entry) ? (
142158
<p className="tulip-tooltip-value" style={{ color }}>
143159
O: {numberFormat.format(entry.open)} H:{" "}

0 commit comments

Comments
 (0)