import { useMemo } from 'react' import { VChart } from '@visactor/react-vchart' import { PieChart } from 'lucide-react' import { useTranslation } from 'react-i18next' import { useChartTheme } from '@/lib/use-chart-theme' import { VCHART_OPTION } from '@/lib/vchart' import { formatShare, formatTokens } from '../lib/format' import type { RankingPeriod, VendorRanking, VendorShareSeries } from '../types' import { VendorLink } from './entity-links' const PERIOD_DESCRIPTIONS: Record = { today: 'Token share by model author across the last 24 hours', week: 'Token share by model author across the past few weeks', month: 'Token share by model author across the past month', year: 'Token share by model author across the past year', all: 'Token share by model author since launch', } /** Stable colour palette for vendors, used in both the area chart and the * legend dots. Falls back to a neutral palette for unknown vendors so that * future additions still render. */ const VENDOR_COLOURS: Record = { OpenAI: '#10a37f', Anthropic: '#d97757', Google: '#4285f4', DeepSeek: '#7c5cff', Alibaba: '#ff9900', xAI: '#1f2937', Meta: '#1877f2', Moonshot: '#ec4899', Zhipu: '#06b6d4', Mistral: '#ff7000', ByteDance: '#3b82f6', Tencent: '#22c55e', MiniMax: '#a855f7', Cohere: '#fb923c', Baidu: '#ef4444', Others: '#94a3b8', } const FALLBACK_PALETTE = [ '#0ea5e9', '#22c55e', '#a855f7', '#f97316', '#14b8a6', '#eab308', '#ec4899', '#84cc16', '#6366f1', '#10b981', '#f43f5e', '#0891b2', '#94a3b8', ] function buildVendorColourMap(names: string[]): Record { const result: Record = {} let fallbackIdx = 0 for (const name of names) { if (VENDOR_COLOURS[name]) { result[name] = VENDOR_COLOURS[name] } else { result[name] = FALLBACK_PALETTE[fallbackIdx % FALLBACK_PALETTE.length] fallbackIdx += 1 } } return result } const MAX_VENDORS_IN_LIST = 12 type MarketShareSectionProps = { history: VendorShareSeries rows: VendorRanking[] period: RankingPeriod } /** * Combined "Market Share" card: a 100%-stacked area chart showing each * vendor's slice of total token volume, paired below with a two-column * vendor list. */ export function MarketShareSection(props: MarketShareSectionProps) { const { t } = useTranslation() const { resolvedTheme, themeReady } = useChartTheme() const colourMap = useMemo( () => buildVendorColourMap(props.history.vendors.map((v) => v.name)), [props.history] ) const orderedPoints = useMemo(() => { const order = new Map( props.history.vendors.map((v, idx) => [v.name, idx] as const) ) return [...props.history.points].sort((a, b) => { const tsCmp = a.ts.localeCompare(b.ts) if (tsCmp !== 0) return tsCmp return (order.get(a.vendor) ?? 999) - (order.get(b.vendor) ?? 999) }) }, [props.history]) const spec = useMemo(() => { if (orderedPoints.length === 0) return null return { type: 'area' as const, data: [{ id: 'vendor-share', values: orderedPoints }], xField: 'label', yField: 'share', seriesField: 'vendor', stack: true, legends: { visible: false }, area: { style: { fillOpacity: 0.85, curveType: 'monotone' }, }, line: { style: { lineWidth: 0, curveType: 'monotone' } }, point: { visible: false }, color: { specified: colourMap }, axes: [ { orient: 'bottom', label: { style: { fill: 'currentColor', fontSize: 10 }, autoHide: true, autoLimit: true, }, tick: { visible: false }, }, { orient: 'left', min: 0, max: 1, label: { formatMethod: (val: number | string) => `${Math.round(Number(val) * 100)}%`, style: { fill: 'currentColor', fontSize: 10 }, }, grid: { visible: true, style: { lineDash: [3, 3] } }, }, ], tooltip: { mark: { content: [ { key: (datum: Record) => String(datum?.vendor ?? ''), value: (datum: Record) => `${(Number(datum?.share) * 100).toFixed(1)}% ยท ${formatTokens(Number(datum?.tokens) || 0)}`, }, ], }, dimension: { title: { value: (datum: Record) => String(datum?.label ?? ''), }, content: [ { key: (datum: Record) => String(datum?.vendor ?? ''), value: (datum: Record) => Number(datum?.share) || 0, }, ], updateContent: ( array: Array<{ key: string; value: string | number }> ) => { return array .filter((item) => Number(item.value) > 0.001) .sort((a, b) => Number(b.value) - Number(a.value)) .map((item) => ({ key: item.key, value: `${(Number(item.value) * 100).toFixed(1)}%`, })) }, }, }, animationAppear: { duration: 500 }, } }, [colourMap, orderedPoints]) const visible = props.rows.slice(0, MAX_VENDORS_IN_LIST) const half = Math.ceil(visible.length / 2) const left = visible.slice(0, half) const right = visible.slice(half) return (
{/* Chart block ----------------------------------------------------- */}

{t('Market Share')}

{t(PERIOD_DESCRIPTIONS[props.period])}

{themeReady && spec ? ( ) : (
{t('No history data available')}
)}
{/* Vendor list block ----------------------------------------------- */}

{t('By model author')}

{t('Vendors ranked by aggregated token volume')}

{visible.length === 0 ? (
{t('No vendor data available')}
) : (
{right.length > 0 && ( )}
)}
) } function VendorList(props: { rows: VendorRanking[] colourMap: Record }) { return (
    {props.rows.map((vendor) => (
  • {vendor.rank}. {vendor.vendor}
    {formatTokens(vendor.total_tokens)}
    {formatShare(vendor.share)}
  • ))}
) }