Skip to content

Commit dbd834d

Browse files
committed
Refactor Chart into its own component
1 parent 5e13dfc commit dbd834d

2 files changed

Lines changed: 165 additions & 164 deletions

File tree

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { ChevronLeft, ChevronRight } from 'lucide-react'
2+
import type { UsageData } from '#/routes/jobs.tsx'
3+
4+
export default function Chart({
5+
data,
6+
index,
7+
numComponents,
8+
onPrev,
9+
onNext,
10+
}: {
11+
data: UsageData
12+
index: number
13+
numComponents: number
14+
onPrev: () => void
15+
onNext: () => void
16+
}) {
17+
const max = 100
18+
const width = 600
19+
const height = 180
20+
const padLeft = 40
21+
const padRight = 10
22+
const padTop = 10
23+
const padBottom = 30
24+
const chartW = width - padLeft - padRight
25+
const chartH = height - padTop - padBottom
26+
27+
const points = data.observations.map((val, i) => {
28+
const x = padLeft + (i / (data.observations.length - 1)) * chartW
29+
const y = padTop + chartH - (val / max) * chartH
30+
return `${x},${y}`
31+
})
32+
33+
const areaPoints = [
34+
`${padLeft},${padTop + chartH}`,
35+
...points,
36+
`${padLeft + chartW},${padTop + chartH}`,
37+
].join(' ')
38+
39+
const linePoints = points.join(' ')
40+
41+
const yLabels = [100, 80, 60, 40, 20, 0]
42+
const xLabels = [
43+
'00:00',
44+
'02:00',
45+
'04:00',
46+
'06:00',
47+
'08:00',
48+
'10:00',
49+
'12:00',
50+
'14:00',
51+
'16:00',
52+
'18:00',
53+
'20:00',
54+
'22:00',
55+
'24:00',
56+
]
57+
58+
return (
59+
<div className="mt-4">
60+
<h3 className="text-center font-semibold text-sm mb-1">
61+
{data.component}
62+
</h3>
63+
<div className="flex items-center gap-2">
64+
<button
65+
onClick={onPrev}
66+
className="text-gray-400 hover:text-main p-1"
67+
aria-label="Previous Component"
68+
>
69+
<ChevronLeft size={24} />
70+
</button>
71+
<svg viewBox={`0 0 ${width} ${height}`} className="flex-1">
72+
{/* Y-axis labels and grid */}
73+
{yLabels.map((label) => {
74+
const y = padTop + chartH - (label / max) * chartH
75+
return (
76+
<g key={label}>
77+
<text
78+
x={padLeft - 5}
79+
y={y + 3}
80+
textAnchor="end"
81+
fontSize="9"
82+
fill="#999"
83+
>
84+
{label}%
85+
</text>
86+
<line
87+
x1={padLeft}
88+
y1={y}
89+
x2={padLeft + chartW}
90+
y2={y}
91+
stroke="#e5e7eb"
92+
strokeWidth="0.5"
93+
/>
94+
</g>
95+
)
96+
})}
97+
{/* X-axis labels */}
98+
{xLabels.map((label, i) => {
99+
const x = padLeft + (i / (xLabels.length - 1)) * chartW
100+
return (
101+
<text
102+
key={label}
103+
x={x}
104+
y={height - 5}
105+
textAnchor="middle"
106+
fontSize="8"
107+
fill="#999"
108+
>
109+
{label}
110+
</text>
111+
)
112+
})}
113+
{/* Area fill */}
114+
<polygon points={areaPoints} fill="rgba(234,179,8,0.15)" />
115+
{/* Line */}
116+
<polyline
117+
points={linePoints}
118+
fill="none"
119+
stroke="#eab308"
120+
strokeWidth="2"
121+
/>
122+
</svg>
123+
<button
124+
onClick={onNext}
125+
className="text-gray-400 hover:text-main p-1"
126+
aria-label="Next Component"
127+
>
128+
<ChevronRight size={24} />
129+
</button>
130+
</div>
131+
{/* Pagination dots */}
132+
<div className="flex justify-center gap-1.5 mt-1">
133+
{Array.from({ length: numComponents }).map((_, i) => (
134+
<span
135+
key={i}
136+
className={`w-2 h-2 rounded-full ${i === index ? 'bg-main' : 'bg-gray-300'}`}
137+
/>
138+
))}
139+
</div>
140+
</div>
141+
)
142+
}

web-interface/src/routes/jobs.tsx

Lines changed: 23 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
import { createFileRoute } from '@tanstack/react-router'
22
import Card, { CardHeader, CardInfoField } from '#/components/Card.tsx'
3-
import { ChevronLeft, ChevronRight } from 'lucide-react'
43
import { useState } from 'react'
54
import { Button } from '#/components/Buttons.tsx'
5+
import Chart from '#/components/Chart.tsx'
66

77
export const Route = createFileRoute('/jobs')({
88
component: JobsPage,
99
})
1010

11-
// Sample utilization data
12-
type UsageData = {
11+
export type UsageData = {
1312
component: string // Component that we observe. E.g. GPU, CPU, etc.
1413
observations: number[]
1514
}
1615

17-
function generateUsageData(): UsageData {
16+
function generateSampleUsageData(): UsageData {
1817
const components = ['GPU', 'CPU', 'RAM']
1918

2019
return {
@@ -62,11 +61,11 @@ const sampleJobs: Job[] = [
6261
cpuUtilization: '95%',
6362
networkIO: { down: '1.2 GB/s', up: '340 MB/s' },
6463
usageHistory: [
65-
generateUsageData(),
66-
generateUsageData(),
67-
generateUsageData(),
68-
generateUsageData(),
69-
generateUsageData(),
64+
generateSampleUsageData(),
65+
generateSampleUsageData(),
66+
generateSampleUsageData(),
67+
generateSampleUsageData(),
68+
generateSampleUsageData(),
7069
],
7170
},
7271
{
@@ -84,11 +83,11 @@ const sampleJobs: Job[] = [
8483
cpuUtilization: '95%',
8584
networkIO: { down: '1.2 GB/s', up: '340 MB/s' },
8685
usageHistory: [
87-
generateUsageData(),
88-
generateUsageData(),
89-
generateUsageData(),
90-
generateUsageData(),
91-
generateUsageData(),
86+
generateSampleUsageData(),
87+
generateSampleUsageData(),
88+
generateSampleUsageData(),
89+
generateSampleUsageData(),
90+
generateSampleUsageData(),
9291
],
9392
},
9493
{
@@ -106,11 +105,11 @@ const sampleJobs: Job[] = [
106105
cpuUtilization: '95%',
107106
networkIO: { down: '1.2 GB/s', up: '340 MB/s' },
108107
usageHistory: [
109-
generateUsageData(),
110-
generateUsageData(),
111-
generateUsageData(),
112-
generateUsageData(),
113-
generateUsageData(),
108+
generateSampleUsageData(),
109+
generateSampleUsageData(),
110+
generateSampleUsageData(),
111+
generateSampleUsageData(),
112+
generateSampleUsageData(),
114113
],
115114
},
116115
{
@@ -128,11 +127,11 @@ const sampleJobs: Job[] = [
128127
cpuUtilization: '95%',
129128
networkIO: { down: '1.2 GB/s', up: '340 MB/s' },
130129
usageHistory: [
131-
generateUsageData(),
132-
generateUsageData(),
133-
generateUsageData(),
134-
generateUsageData(),
135-
generateUsageData(),
130+
generateSampleUsageData(),
131+
generateSampleUsageData(),
132+
generateSampleUsageData(),
133+
generateSampleUsageData(),
134+
generateSampleUsageData(),
136135
],
137136
},
138137
]
@@ -145,146 +144,6 @@ type JobCardProps = {
145144
onDelete: (id: string) => void
146145
}
147146

148-
function Chart({
149-
data,
150-
index,
151-
numComponents,
152-
onPrev,
153-
onNext,
154-
}: {
155-
data: UsageData
156-
index: number
157-
numComponents: number
158-
onPrev: () => void
159-
onNext: () => void
160-
}) {
161-
const max = 100
162-
const width = 600
163-
const height = 180
164-
const padLeft = 40
165-
const padRight = 10
166-
const padTop = 10
167-
const padBottom = 30
168-
const chartW = width - padLeft - padRight
169-
const chartH = height - padTop - padBottom
170-
171-
const points = data.observations.map((val, i) => {
172-
const x = padLeft + (i / (data.observations.length - 1)) * chartW
173-
const y = padTop + chartH - (val / max) * chartH
174-
return `${x},${y}`
175-
})
176-
177-
const areaPoints = [
178-
`${padLeft},${padTop + chartH}`,
179-
...points,
180-
`${padLeft + chartW},${padTop + chartH}`,
181-
].join(' ')
182-
183-
const linePoints = points.join(' ')
184-
185-
const yLabels = [100, 80, 60, 40, 20, 0]
186-
const xLabels = [
187-
'00:00',
188-
'02:00',
189-
'04:00',
190-
'06:00',
191-
'08:00',
192-
'10:00',
193-
'12:00',
194-
'14:00',
195-
'16:00',
196-
'18:00',
197-
'20:00',
198-
'22:00',
199-
'24:00',
200-
]
201-
202-
return (
203-
<div className="mt-4">
204-
<h3 className="text-center font-semibold text-sm mb-1">
205-
{data.component}
206-
</h3>
207-
<div className="flex items-center gap-2">
208-
<button
209-
onClick={onPrev}
210-
className="text-gray-400 hover:text-main p-1"
211-
aria-label="Previous Component"
212-
>
213-
<ChevronLeft size={24} />
214-
</button>
215-
<svg viewBox={`0 0 ${width} ${height}`} className="flex-1">
216-
{/* Y-axis labels and grid */}
217-
{yLabels.map((label) => {
218-
const y = padTop + chartH - (label / max) * chartH
219-
return (
220-
<g key={label}>
221-
<text
222-
x={padLeft - 5}
223-
y={y + 3}
224-
textAnchor="end"
225-
fontSize="9"
226-
fill="#999"
227-
>
228-
{label}%
229-
</text>
230-
<line
231-
x1={padLeft}
232-
y1={y}
233-
x2={padLeft + chartW}
234-
y2={y}
235-
stroke="#e5e7eb"
236-
strokeWidth="0.5"
237-
/>
238-
</g>
239-
)
240-
})}
241-
{/* X-axis labels */}
242-
{xLabels.map((label, i) => {
243-
const x = padLeft + (i / (xLabels.length - 1)) * chartW
244-
return (
245-
<text
246-
key={label}
247-
x={x}
248-
y={height - 5}
249-
textAnchor="middle"
250-
fontSize="8"
251-
fill="#999"
252-
>
253-
{label}
254-
</text>
255-
)
256-
})}
257-
{/* Area fill */}
258-
<polygon points={areaPoints} fill="rgba(234,179,8,0.15)" />
259-
{/* Line */}
260-
<polyline
261-
points={linePoints}
262-
fill="none"
263-
stroke="#eab308"
264-
strokeWidth="2"
265-
/>
266-
</svg>
267-
<button
268-
onClick={onNext}
269-
className="text-gray-400 hover:text-main p-1"
270-
aria-label="Next Component"
271-
>
272-
<ChevronRight size={24} />
273-
</button>
274-
</div>
275-
{/* Pagination dots */}
276-
<div className="flex justify-center gap-1.5 mt-1">
277-
{Array.from({ length: numComponents }).map((_, i) => (
278-
<span
279-
key={i}
280-
className={`w-2 h-2 rounded-full ${i === index ? 'bg-main' : 'bg-gray-300'}`}
281-
/>
282-
))}
283-
</div>
284-
</div>
285-
)
286-
}
287-
288147
function JobCard({
289148
job,
290149
onStart,

0 commit comments

Comments
 (0)