Skip to content

Commit 738deab

Browse files
committed
Add two separate graphs for all/top 5 models and show num other models
1 parent 5670beb commit 738deab

1 file changed

Lines changed: 163 additions & 117 deletions

File tree

src/App.tsx

Lines changed: 163 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -476,35 +476,6 @@ function App() {
476476
.map(([model]) => model);
477477
}, [dailyModelData]);
478478

479-
// Generate bar chart data grouped by date, top 5 models + Other
480-
const barChartData = useCallback(() => {
481-
if (!dailyModelData.length) return [];
482-
483-
const dates = new Set<string>();
484-
dailyModelData.forEach(item => dates.add(item.date));
485-
486-
const groupedByDate: Record<string, any> = {};
487-
dates.forEach(date => {
488-
groupedByDate[date] = { date };
489-
top5Models.forEach(model => { groupedByDate[date][model] = 0; });
490-
groupedByDate[date]['Other'] = 0;
491-
});
492-
493-
dailyModelData.forEach(item => {
494-
if (top5Models.includes(item.model)) {
495-
groupedByDate[item.date][item.model] += item.requests;
496-
} else {
497-
groupedByDate[item.date]['Other'] += item.requests;
498-
}
499-
});
500-
501-
return Object.values(groupedByDate).sort((a: any, b: any) =>
502-
a.date.localeCompare(b.date)
503-
);
504-
}, [dailyModelData, top5Models]);
505-
506-
const modelBarData = useMemo(() => barChartData(), [barChartData]);
507-
508479
const behaviorData = useMemo<BehaviorScatterPoint[]>(() => {
509480
if (!displayData || !displayData.length) return [];
510481
return getUserBehaviorData(displayData, selectedPlan).map((item) => {
@@ -524,61 +495,104 @@ function App() {
524495
}, {} as Record<string, number>);
525496
}, [behaviorData]);
526497

527-
const modelBarYAxis = useMemo(() => {
528-
if (!modelBarData.length) {
529-
return {
530-
domain: [0, 100] as [number, number],
531-
ticks: [0, 25, 50, 75, 100],
532-
};
533-
}
498+
// Get all unique models from the data (not just top 5), sorted by total requests (descending)
499+
const getAllUniqueModels = useCallback(() => {
500+
if (!dailyModelData.length) return [];
501+
const totals: Record<string, number> = {};
502+
dailyModelData.forEach(item => {
503+
totals[item.model] = (totals[item.model] || 0) + item.requests;
504+
});
505+
return Object.entries(totals)
506+
.sort((a, b) => b[1] - a[1])
507+
.map(([model]) => model);
508+
}, [dailyModelData]);
534509

535-
const seriesKeys = [...top5Models, 'Other'];
536-
let maxValue = 0;
510+
// Get count of models in the "Other" category (not in top 5)
511+
const getOtherModelCount = useCallback(() => {
512+
const allModels = getAllUniqueModels();
513+
return Math.max(0, allModels.length - top5Models.length);
514+
}, [getAllUniqueModels, top5Models]);
537515

538-
modelBarData.forEach((row) => {
539-
seriesKeys.forEach((key) => {
540-
const value = Number((row as Record<string, unknown>)[key] ?? 0);
541-
if (value > maxValue) maxValue = value;
542-
});
543-
});
516+
// Generate bar chart data grouped by date, top 5 models + Other
517+
const barChartData = useCallback(() => {
518+
if (!dailyModelData.length) return [];
519+
520+
const dates = new Set<string>();
521+
dailyModelData.forEach(item => dates.add(item.date));
544522

545-
if (maxValue <= 0) {
546-
return {
547-
domain: [0, 100] as [number, number],
548-
ticks: [0, 25, 50, 75, 100],
549-
};
550-
}
523+
const otherCount = getOtherModelCount();
524+
const otherLabel = otherCount > 0 ? `Other (${otherCount})` : 'Other';
551525

552-
// Target ~4 major intervals and round to cleaner, human-friendly 500-sized steps.
553-
const roughStep = maxValue / 4;
554-
const step = Math.max(100, Math.ceil(roughStep / 500) * 500);
555-
const yMax = Math.ceil(maxValue / step) * step;
526+
const groupedByDate: Record<string, any> = {};
527+
dates.forEach(date => {
528+
groupedByDate[date] = { date };
529+
top5Models.forEach(model => { groupedByDate[date][model] = 0; });
530+
groupedByDate[date][otherLabel] = 0;
531+
});
556532

557-
const ticks: number[] = [];
558-
for (let value = 0; value <= yMax; value += step) {
559-
ticks.push(value);
560-
}
533+
dailyModelData.forEach(item => {
534+
if (top5Models.includes(item.model)) {
535+
groupedByDate[item.date][item.model] += item.requests;
536+
} else {
537+
groupedByDate[item.date][otherLabel] += item.requests;
538+
}
539+
});
561540

562-
return {
563-
domain: [0, yMax] as [number, number],
564-
ticks,
565-
};
566-
}, [modelBarData, top5Models]);
541+
return Object.values(groupedByDate).sort((a: any, b: any) =>
542+
a.date.localeCompare(b.date)
543+
);
544+
}, [dailyModelData, top5Models, getOtherModelCount]);
567545

568546
// Get top 5 model names + Other for bar chart
569547
const getUniqueModelsForBarChart = useCallback(() => {
570-
return [...top5Models, 'Other'];
571-
}, [top5Models]);
548+
const otherCount = getOtherModelCount();
549+
return [...top5Models, otherCount > 0 ? `Other (${otherCount})` : 'Other'];
550+
}, [top5Models, getOtherModelCount]);
572551

573552
// Generate colors for top 5 models + Other in bar chart
574553
const getModelColors = useCallback(() => {
575554
const result: Record<string, string> = {};
576555
top5Models.forEach((model, i) => {
577556
result[model] = MODEL_COLORS[i % MODEL_COLORS.length];
578557
});
579-
result['Other'] = OTHER_COLOR;
558+
const otherCount = getOtherModelCount();
559+
const otherLabel = otherCount > 0 ? `Other (${otherCount})` : 'Other';
560+
result[otherLabel] = OTHER_COLOR;
561+
return result;
562+
}, [top5Models, getOtherModelCount]);
563+
564+
// Generate colors for all models (sorted by request amount)
565+
const getAllModelColors = useCallback(() => {
566+
const allModels = getAllUniqueModels();
567+
const result: Record<string, string> = {};
568+
allModels.forEach((model, i) => {
569+
result[model] = MODEL_COLORS[i % MODEL_COLORS.length];
570+
});
580571
return result;
581-
}, [top5Models]);
572+
}, [getAllUniqueModels]);
573+
574+
// Generate bar chart data grouped by date with all models
575+
const allModelsChartData = useCallback(() => {
576+
if (!dailyModelData.length) return [];
577+
578+
const allModels = getAllUniqueModels();
579+
const dates = new Set<string>();
580+
dailyModelData.forEach(item => dates.add(item.date));
581+
582+
const groupedByDate: Record<string, any> = {};
583+
dates.forEach(date => {
584+
groupedByDate[date] = { date };
585+
allModels.forEach(model => { groupedByDate[date][model] = 0; });
586+
});
587+
588+
dailyModelData.forEach(item => {
589+
groupedByDate[item.date][item.model] += item.requests;
590+
});
591+
592+
return Object.values(groupedByDate).sort((a: any, b: any) =>
593+
a.date.localeCompare(b.date)
594+
);
595+
}, [dailyModelData, getAllUniqueModels]);
582596

583597
// Compute sorted list of week start dates (ISO string for Monday) present in the data
584598
const availableWeeks = useMemo(() => {
@@ -662,46 +676,6 @@ function App() {
662676
return `${fmt(startDate)}${fmt(endDate)}`;
663677
}, [selectedWeekDates]);
664678

665-
const weeklyBarYAxis = useMemo(() => {
666-
if (!selectedWeekDailyData.length) {
667-
return {
668-
domain: [0, 100] as [number, number],
669-
ticks: [0, 25, 50, 75, 100],
670-
};
671-
}
672-
673-
const seriesKeys = [...top5Models, 'Other'];
674-
let maxValue = 0;
675-
676-
selectedWeekDailyData.forEach((row) => {
677-
seriesKeys.forEach((key) => {
678-
const value = Number((row as Record<string, unknown>)[key] ?? 0);
679-
if (value > maxValue) maxValue = value;
680-
});
681-
});
682-
683-
if (maxValue <= 0) {
684-
return {
685-
domain: [0, 100] as [number, number],
686-
ticks: [0, 25, 50, 75, 100],
687-
};
688-
}
689-
690-
const roughStep = maxValue / 4;
691-
const step = Math.max(100, Math.ceil(roughStep / 500) * 500);
692-
const yMax = Math.ceil(maxValue / step) * step;
693-
694-
const ticks: number[] = [];
695-
for (let value = 0; value <= yMax; value += step) {
696-
ticks.push(value);
697-
}
698-
699-
return {
700-
domain: [0, yMax] as [number, number],
701-
ticks,
702-
};
703-
}, [selectedWeekDailyData, top5Models]);
704-
705679
// Helper function to get plan limit based on selected plan
706680
const getPlanLimit = useCallback((item: ModelUsageSummary) => {
707681
let limit: number;
@@ -1598,11 +1572,89 @@ function App() {
15981572
</LineChart>
15991573
</ChartContainer>
16001574
</div>
1575+
1576+
{/* Bar Chart - Requests per Model per Day (All Models) */}
1577+
<div className="flex justify-between items-center mb-2 mt-8">
1578+
<h2 className="text-2xl font-semibold">
1579+
Requests per Model per Day (All Models)
1580+
{selectedSearchUser && (
1581+
<span className="ml-2 text-lg font-medium text-blue-600">
1582+
- {selectedSearchUser}
1583+
</span>
1584+
)}
1585+
</h2>
1586+
{lastDateAvailable && (
1587+
<div className="text-sm text-muted-foreground">
1588+
Data available through: <span className="font-medium">{lastDateAvailable}</span>
1589+
</div>
1590+
)}
1591+
</div>
1592+
<Separator className="mb-6" />
1593+
<div className="bg-card p-4 rounded-lg border">
1594+
<ChartContainer
1595+
config={Object.entries(getAllModelColors()).reduce((acc, [model, color]) => {
1596+
acc[model] = { color };
1597+
return acc;
1598+
}, {} as Record<string, { color: string }>)}
1599+
className="h-[500px] w-full"
1600+
>
1601+
<BarChart data={allModelsChartData()}>
1602+
<CartesianGrid strokeDasharray="3 3" opacity={0.2} />
1603+
<XAxis
1604+
dataKey="date"
1605+
tick={{ fill: 'var(--foreground)' }}
1606+
tickLine={{ stroke: 'var(--border)' }}
1607+
domain={['dataMin', lastDateAvailable || 'dataMax']}
1608+
/>
1609+
<YAxis
1610+
tick={{ fill: 'var(--foreground)' }}
1611+
tickLine={{ stroke: 'var(--border)' }}
1612+
/>
1613+
<Tooltip
1614+
content={({ active, payload, label }) => {
1615+
if (active && payload && payload.length) {
1616+
return (
1617+
<div className="border rounded-lg bg-background shadow-lg p-3">
1618+
<div className="font-medium mb-2">{label}</div>
1619+
<div className="space-y-2">
1620+
{payload.map((entry, index) => (
1621+
<div key={`item-${index}`} className="flex justify-between items-center gap-4">
1622+
<div className="flex items-center gap-1.5">
1623+
<div
1624+
className="w-2 h-2 rounded-full"
1625+
style={{ backgroundColor: entry.color }}
1626+
/>
1627+
<span>{entry.name}:</span>
1628+
</div>
1629+
<div className="font-medium">{Number(entry.value).toLocaleString(undefined, {maximumFractionDigits: 2, minimumFractionDigits: 0})}</div>
1630+
</div>
1631+
))}
1632+
</div>
1633+
</div>
1634+
);
1635+
}
1636+
return null;
1637+
}}
1638+
/>
1639+
<Legend />
1640+
1641+
{/* Generate a bar for each model */}
1642+
{getAllUniqueModels().map((model) => (
1643+
<Bar
1644+
key={model}
1645+
dataKey={model}
1646+
name={model}
1647+
fill={getAllModelColors()[model]}
1648+
/>
1649+
))}
1650+
</BarChart>
1651+
</ChartContainer>
1652+
</div>
16011653

1602-
{/* Bar Chart - Requests per Model per Day */}
1654+
{/* Bar Chart - Requests per Model per Day (Top 5 Models) */}
16031655
<div className="flex justify-between items-center mb-2">
16041656
<h2 className="text-2xl font-semibold">
1605-
Requests per Model per Day
1657+
Requests per Model per Day (Top 5 Models)
16061658
{selectedSearchUser && (
16071659
<span className="ml-2 text-lg font-medium text-blue-600">
16081660
- {selectedSearchUser}
@@ -1624,7 +1676,7 @@ function App() {
16241676
}, {} as Record<string, { color: string }>)}
16251677
className="h-[500px] w-full"
16261678
>
1627-
<BarChart data={modelBarData}>
1679+
<BarChart data={barChartData()}>
16281680
<CartesianGrid strokeDasharray="3 3" opacity={0.2} />
16291681
<XAxis
16301682
dataKey="date"
@@ -1633,9 +1685,6 @@ function App() {
16331685
domain={['dataMin', lastDateAvailable || 'dataMax']}
16341686
/>
16351687
<YAxis
1636-
domain={modelBarYAxis.domain}
1637-
ticks={modelBarYAxis.ticks}
1638-
allowDecimals={false}
16391688
tick={{ fill: 'var(--foreground)' }}
16401689
tickLine={{ stroke: 'var(--border)' }}
16411690
/>
@@ -1729,9 +1778,6 @@ function App() {
17291778
interval={0}
17301779
/>
17311780
<YAxis
1732-
domain={weeklyBarYAxis.domain}
1733-
ticks={weeklyBarYAxis.ticks}
1734-
allowDecimals={false}
17351781
tick={{ fill: 'var(--foreground)' }}
17361782
tickLine={{ stroke: 'var(--border)' }}
17371783
/>
@@ -1770,7 +1816,7 @@ function App() {
17701816
}}
17711817
/>
17721818
<Legend />
1773-
{[...top5Models, 'Other'].map((model) => (
1819+
{[...top5Models, getOtherModelCount() > 0 ? `Other (${getOtherModelCount()})` : 'Other'].map((model) => (
17741820
<Bar
17751821
key={model}
17761822
dataKey={model}

0 commit comments

Comments
 (0)