Skip to content

Commit f8f740e

Browse files
committed
feat: added bard chart as availability trend
1 parent ecc2d6d commit f8f740e

File tree

2 files changed

+57
-120
lines changed

2 files changed

+57
-120
lines changed

thingconnect.pulse.client/src/components/AvailabilityChart.tsx

Lines changed: 45 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function AvailabilityChart({
2727
if (isLoading) {
2828
return (
2929
<Box
30-
height={height}
30+
height='100%'
3131
display='flex'
3232
alignItems='center'
3333
justifyContent='center'
@@ -37,8 +37,7 @@ export function AvailabilityChart({
3737
_dark={{ bg: 'gray.800' }}
3838
>
3939
<VStack w='full' px={6} gap={4}>
40-
<Skeleton height='24px' width='40%' />
41-
<Skeleton height={`${height - 100}px`} width='100%' />
40+
<Skeleton height='100%' width='100%' />
4241
</VStack>
4342
</Box>
4443
);
@@ -55,11 +54,7 @@ export function AvailabilityChart({
5554
minute: '2-digit',
5655
}),
5756
uptime: check.status === 'up' ? 100 : 0,
58-
responseTime: check.rttMs || null,
59-
status: check.status,
60-
error: check.error,
6157
}));
62-
6358
case '15m':
6459
return data.rollup15m.map(bucket => ({
6560
timestamp: new Date(bucket.bucketTs).getTime(),
@@ -68,10 +63,7 @@ export function AvailabilityChart({
6863
minute: '2-digit',
6964
}),
7065
uptime: bucket.upPct,
71-
responseTime: bucket.avgRttMs || null,
72-
downEvents: bucket.downEvents,
7366
}));
74-
7567
case 'daily':
7668
return data.rollupDaily.map(bucket => ({
7769
timestamp: new Date(bucket.bucketDate).getTime(),
@@ -80,10 +72,7 @@ export function AvailabilityChart({
8072
day: 'numeric',
8173
}),
8274
uptime: bucket.upPct,
83-
responseTime: bucket.avgRttMs || null,
84-
downEvents: bucket.downEvents,
8575
}));
86-
8776
default:
8877
return [];
8978
}
@@ -92,7 +81,7 @@ export function AvailabilityChart({
9281
if (chartData?.length === 0) {
9382
return (
9483
<Box
95-
height={height}
84+
height='100%'
9685
display='flex'
9786
alignItems='center'
9887
justifyContent='center'
@@ -112,139 +101,82 @@ export function AvailabilityChart({
112101
);
113102
}
114103

115-
// Simple SVG chart implementation
116-
const margin = { top: 20, right: 30, bottom: 40, left: 50 };
117-
const chartWidth = 800 - margin.left - margin.right;
118-
const chartHeight = height - margin.top - margin.bottom;
119-
120-
// Calculate scales
121-
const minTime = Math.min(...(chartData?.map(d => d.timestamp) ?? []));
122-
const maxTime = Math.max(...(chartData?.map(d => d.timestamp) ?? []));
123-
const timeRange = maxTime - minTime || 1;
124-
125-
const maxUptime = Math.max(...(chartData?.map(d => d.uptime) ?? []));
126-
const minUptime = Math.min(...(chartData?.map(d => d.uptime) ?? []));
127-
const uptimeRange = maxUptime - minUptime || 1;
128-
129-
// Create path for area chart
130-
const createPath = () => {
131-
let path = '';
132-
chartData?.forEach((point, index) => {
133-
const x = ((point.timestamp - minTime) / timeRange) * chartWidth;
134-
const y = chartHeight - ((point.uptime - minUptime) / uptimeRange) * chartHeight;
135-
136-
if (index === 0) {
137-
path += `M ${x} ${y}`;
138-
} else {
139-
path += ` L ${x} ${y}`;
140-
}
141-
});
142-
return path;
143-
};
144-
145-
// Create area path (includes bottom line)
146-
const createAreaPath = () => {
147-
let path = createPath();
148-
if ((chartData?.length ?? 0) > 0) {
149-
const lastX =
150-
(((chartData?.[chartData.length - 1]?.timestamp ?? minTime) - minTime) / timeRange) *
151-
chartWidth;
152-
const firstX = (((chartData?.[0]?.timestamp ?? minTime) - minTime) / timeRange) * chartWidth;
153-
path += ` L ${lastX} ${chartHeight} L ${firstX} ${chartHeight} Z`;
154-
}
155-
return path;
156-
};
104+
const margin = { top: 20, right: 30, bottom: 40, left: 70 };
157105

158106
return (
159-
<Box height={'100%'} w='100%' position='relative'>
160-
<svg width='100%' height='100%'>
107+
<Box w='full' h='full' position='relative'>
108+
<svg width='100%' height='100%' viewBox={`0 0 1000 ${height}`} preserveAspectRatio='none'>
161109
<g transform={`translate(${margin.left}, ${margin.top})`}>
162-
{/* Grid lines */}
163-
<defs>
164-
<pattern id='grid' width='40' height='40' patternUnits='userSpaceOnUse'>
165-
<path
166-
d='M 40 0 L 0 0 0 40'
167-
fill='none'
168-
stroke='#e2e8f0'
169-
strokeWidth='1'
170-
opacity='0.3'
171-
/>
172-
</pattern>
173-
</defs>
174-
<rect width={chartWidth} height={chartHeight} fill='url(#grid)' />
175-
176-
{/* Y-axis labels */}
177-
{[0, 25, 50, 75, 100].map(value => (
110+
{/* Grid + Y-axis labels */}
111+
{[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100].map(value => (
178112
<g key={value}>
179113
<line
180114
x1={0}
181-
y1={chartHeight - (value / 100) * chartHeight}
182-
x2={chartWidth}
183-
y2={chartHeight - (value / 100) * chartHeight}
115+
y1={
116+
height -
117+
margin.top -
118+
margin.bottom -
119+
(value / 100) * (height - margin.top - margin.bottom)
120+
}
121+
x2={1000 - margin.left - margin.right}
122+
y2={
123+
height -
124+
margin.top -
125+
margin.bottom -
126+
(value / 100) * (height - margin.top - margin.bottom)
127+
}
184128
stroke='#e2e8f0'
185129
strokeWidth='1'
186130
opacity='0.5'
187131
/>
188132
<text
189133
x={-10}
190-
y={chartHeight - (value / 100) * chartHeight + 4}
191-
fontSize='12'
134+
y={
135+
height -
136+
margin.top -
137+
margin.bottom -
138+
(value / 100) * (height - margin.top - margin.bottom) +
139+
4
140+
}
192141
fill='#718096'
193142
textAnchor='end'
143+
style={{ fontSize: '10px' }}
194144
>
195145
{value}%
196146
</text>
197147
</g>
198148
))}
199149

200-
{/* X-axis */}
201-
<line
202-
x1={0}
203-
y1={chartHeight}
204-
x2={chartWidth}
205-
y2={chartHeight}
206-
stroke='#718096'
207-
strokeWidth='2'
208-
/>
209-
210-
{/* Y-axis */}
211-
<line x1={0} y1={0} x2={0} y2={chartHeight} stroke='#718096' strokeWidth='2' />
212-
213-
{/* Area chart */}
214-
<path
215-
d={createAreaPath()}
216-
fill='#3182ce'
217-
fillOpacity='0.3'
218-
stroke='#3182ce'
219-
strokeWidth='2'
220-
/>
221-
222-
{/* Data points */}
150+
{/* Bars */}
223151
{chartData?.map((point, index) => {
224-
const x = ((point.timestamp - minTime) / timeRange) * chartWidth;
225-
const y = chartHeight - ((point.uptime - minUptime) / uptimeRange) * chartHeight;
152+
const chartWidth = 1000 - margin.left - margin.right;
153+
const chartHeight = height - margin.top - margin.bottom;
154+
const slotWidth = chartWidth / chartData.length;
155+
const barWidth = slotWidth * 0.6;
156+
const x = index * slotWidth + (slotWidth - barWidth) / 2;
157+
const barHeight = (point.uptime / 100) * chartHeight;
158+
const y = chartHeight - barHeight;
226159

227160
return (
228-
<circle
161+
<rect
229162
key={`${point.timestamp}-${index}`}
230-
cx={x}
231-
cy={y}
232-
r='3'
163+
x={x}
164+
y={y}
165+
width={barWidth}
166+
height={barHeight}
233167
fill='#3182ce'
234-
stroke='white'
235-
strokeWidth='2'
236168
/>
237169
);
238170
})}
239171

240172
{/* Y-axis label */}
241173
<text
242174
x={-35}
243-
y={chartHeight / 2}
244-
fontSize='12'
175+
y={(height - margin.top - margin.bottom) / 2.3}
176+
style={{ fontSize: '12' }}
245177
fill='#718096'
246178
textAnchor='middle'
247-
transform={`rotate(-90, -35, ${chartHeight / 2})`}
179+
transform={`rotate(-90, -35, ${(height - margin.top - margin.bottom) / 2})`}
248180
>
249181
Uptime %
250182
</text>

thingconnect.pulse.client/src/pages/History.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -283,16 +283,16 @@ export default function History() {
283283
<Tabs.Trigger value='history'>Historical Data</Tabs.Trigger>
284284
</Tabs.List>
285285
<Tabs.Content value='chart' flex={1} display='flex' minH={0}>
286-
<Card.Root flex={1} display='flex' flexDirection='column'>
287-
<Card.Header>
286+
<Card.Root flex={1} display='flex' flexDirection='column' size={'sm'}>
287+
<Card.Header px={3} pt={3}>
288288
<HStack gap={2}>
289289
<TrendingUp size={20} />
290290
<Text fontWeight='medium' fontSize='sm'>
291291
Availability Trend
292292
</Text>
293293
</HStack>
294294
</Card.Header>
295-
<Card.Body flex={1} minH={0}>
295+
<Card.Body flex={1} minH={0} p={3}>
296296
<AvailabilityChart
297297
data={historyData}
298298
bucket={bucket}
@@ -301,10 +301,15 @@ export default function History() {
301301
</Card.Body>
302302
</Card.Root>
303303
</Tabs.Content>
304-
305304
<Tabs.Content value='history' flex={1} display='flex' minH={0}>
306-
<Card.Root flex={1} display='flex' flexDirection='column' overflow='hidden'>
307-
<Card.Header>
305+
<Card.Root
306+
flex={1}
307+
display='flex'
308+
flexDirection='column'
309+
overflow='hidden'
310+
size={'sm'}
311+
>
312+
<Card.Header px={3} pt={3}>
308313
<HStack gap={2}>
309314
<AlertCircle size={20} />
310315
<Text fontWeight='medium' fontSize='sm'>
@@ -315,7 +320,7 @@ export default function History() {
315320
</Text>
316321
</HStack>
317322
</Card.Header>
318-
<Card.Body flex={1} display='flex' flexDirection='column' minH={0}>
323+
<Card.Body flex={1} display='flex' flexDirection='column' minH={0} p={3}>
319324
<HistoryTable
320325
data={historyData}
321326
bucket={bucket}

0 commit comments

Comments
 (0)