@@ -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