spb/vquant Public MIT
VibeQuant — AI-powered institutional-grade financial intelligence platform.
TypeScript 84.3%
Python 11.7%
JavaScript 1.6%
CSS 1.5%
HTML 0.7%
1/*2 * =============================================================================3 * VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4 * -----------------------------------------------------------------------------5 * File: client/src/components/ui/chart.tsx6 *7 * Author: Simon-Pierre Boucher8 * Contact: contact@spboucher.ai9 * Website: https://www.spboucher.ai10 * Demo: https://www.vquant.ai11 * License: MIT (see LICENSE)12 *13 * Copyright © 2026 Simon-Pierre Boucher. All rights reserved.14 * =============================================================================15 */1617"use client"1819import * as React from "react"20import * as RechartsPrimitive from "recharts"2122import { cn } from "@/lib/utils"2324// Format: { THEME_NAME: CSS_SELECTOR }25const THEMES = { light: "", dark: ".dark" } as const2627export type ChartConfig = {28 [k in string]: {29 label?: React.ReactNode30 icon?: React.ComponentType31 } & (32 | { color?: string; theme?: never }33 | { color?: never; theme: Record<keyof typeof THEMES, string> }34 )35}3637type ChartContextProps = {38 config: ChartConfig39}4041const ChartContext = React.createContext<ChartContextProps | null>(null)4243function useChart() {44 const context = React.useContext(ChartContext)4546 if (!context) {47 throw new Error("useChart must be used within a <ChartContainer />")48 }4950 return context51}5253const ChartContainer = React.forwardRef<54 HTMLDivElement,55 React.ComponentProps<"div"> & {56 config: ChartConfig57 children: React.ComponentProps<58 typeof RechartsPrimitive.ResponsiveContainer59 >["children"]60 }61>(({ id, className, children, config, ...props }, ref) => {62 const uniqueId = React.useId()63 const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`6465 return (66 <ChartContext.Provider value={{ config }}>67 <div68 data-chart={chartId}69 ref={ref}70 className={cn(71 "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",72 className73 )}74 {...props}75 >76 <ChartStyle id={chartId} config={config} />77 <RechartsPrimitive.ResponsiveContainer>78 {children}79 </RechartsPrimitive.ResponsiveContainer>80 </div>81 </ChartContext.Provider>82 )83})84ChartContainer.displayName = "Chart"8586const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {87 const colorConfig = Object.entries(config).filter(88 ([, config]) => config.theme || config.color89 )9091 if (!colorConfig.length) {92 return null93 }9495 return (96 <style97 dangerouslySetInnerHTML={{98 __html: Object.entries(THEMES)99 .map(100 ([theme, prefix]) => `101${prefix} [data-chart=${id}] {102${colorConfig103 .map(([key, itemConfig]) => {104 const color =105 itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||106 itemConfig.color107 return color ? ` --color-${key}: ${color};` : null108 })109 .join("\n")}110}111`112 )113 .join("\n"),114 }}115 />116 )117}118119const ChartTooltip = RechartsPrimitive.Tooltip120121const ChartTooltipContent = React.forwardRef<122 HTMLDivElement,123 React.ComponentProps<typeof RechartsPrimitive.Tooltip> &124 React.ComponentProps<"div"> & {125 hideLabel?: boolean126 hideIndicator?: boolean127 indicator?: "line" | "dot" | "dashed"128 nameKey?: string129 labelKey?: string130 }131>(132 (133 {134 active,135 payload,136 className,137 indicator = "dot",138 hideLabel = false,139 hideIndicator = false,140 label,141 labelFormatter,142 labelClassName,143 formatter,144 color,145 nameKey,146 labelKey,147 },148 ref149 ) => {150 const { config } = useChart()151152 const tooltipLabel = React.useMemo(() => {153 if (hideLabel || !payload?.length) {154 return null155 }156157 const [item] = payload158 const key = `${labelKey || item?.dataKey || item?.name || "value"}`159 const itemConfig = getPayloadConfigFromPayload(config, item, key)160 const value =161 !labelKey && typeof label === "string"162 ? config[label as keyof typeof config]?.label || label163 : itemConfig?.label164165 if (labelFormatter) {166 return (167 <div className={cn("font-medium", labelClassName)}>168 {labelFormatter(value, payload)}169 </div>170 )171 }172173 if (!value) {174 return null175 }176177 return <div className={cn("font-medium", labelClassName)}>{value}</div>178 }, [179 label,180 labelFormatter,181 payload,182 hideLabel,183 labelClassName,184 config,185 labelKey,186 ])187188 if (!active || !payload?.length) {189 return null190 }191192 const nestLabel = payload.length === 1 && indicator !== "dot"193194 return (195 <div196 ref={ref}197 className={cn(198 "grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",199 className200 )}201 >202 {!nestLabel ? tooltipLabel : null}203 <div className="grid gap-1.5">204 {payload.map((item, index) => {205 const key = `${nameKey || item.name || item.dataKey || "value"}`206 const itemConfig = getPayloadConfigFromPayload(config, item, key)207 const indicatorColor = color || item.payload.fill || item.color208209 return (210 <div211 key={item.dataKey}212 className={cn(213 "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",214 indicator === "dot" && "items-center"215 )}216 >217 {formatter && item?.value !== undefined && item.name ? (218 formatter(item.value, item.name, item, index, item.payload)219 ) : (220 <>221 {itemConfig?.icon ? (222 <itemConfig.icon />223 ) : (224 !hideIndicator && (225 <div226 className={cn(227 "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",228 {229 "h-2.5 w-2.5": indicator === "dot",230 "w-1": indicator === "line",231 "w-0 border-[1.5px] border-dashed bg-transparent":232 indicator === "dashed",233 "my-0.5": nestLabel && indicator === "dashed",234 }235 )}236 style={237 {238 "--color-bg": indicatorColor,239 "--color-border": indicatorColor,240 } as React.CSSProperties241 }242 />243 )244 )}245 <div246 className={cn(247 "flex flex-1 justify-between leading-none",248 nestLabel ? "items-end" : "items-center"249 )}250 >251 <div className="grid gap-1.5">252 {nestLabel ? tooltipLabel : null}253 <span className="text-muted-foreground">254 {itemConfig?.label || item.name}255 </span>256 </div>257 {item.value && (258 <span className="font-mono font-medium tabular-nums text-foreground">259 {item.value.toLocaleString()}260 </span>261 )}262 </div>263 </>264 )}265 </div>266 )267 })}268 </div>269 </div>270 )271 }272)273ChartTooltipContent.displayName = "ChartTooltip"274275const ChartLegend = RechartsPrimitive.Legend276277const ChartLegendContent = React.forwardRef<278 HTMLDivElement,279 React.ComponentProps<"div"> &280 Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {281 hideIcon?: boolean282 nameKey?: string283 }284>(285 (286 { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },287 ref288 ) => {289 const { config } = useChart()290291 if (!payload?.length) {292 return null293 }294295 return (296 <div297 ref={ref}298 className={cn(299 "flex items-center justify-center gap-4",300 verticalAlign === "top" ? "pb-3" : "pt-3",301 className302 )}303 >304 {payload.map((item) => {305 const key = `${nameKey || item.dataKey || "value"}`306 const itemConfig = getPayloadConfigFromPayload(config, item, key)307308 return (309 <div310 key={item.value}311 className={cn(312 "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"313 )}314 >315 {itemConfig?.icon && !hideIcon ? (316 <itemConfig.icon />317 ) : (318 <div319 className="h-2 w-2 shrink-0 rounded-[2px]"320 style={{321 backgroundColor: item.color,322 }}323 />324 )}325 {itemConfig?.label}326 </div>327 )328 })}329 </div>330 )331 }332)333ChartLegendContent.displayName = "ChartLegend"334335// Helper to extract item config from a payload.336function getPayloadConfigFromPayload(337 config: ChartConfig,338 payload: unknown,339 key: string340) {341 if (typeof payload !== "object" || payload === null) {342 return undefined343 }344345 const payloadPayload =346 "payload" in payload &&347 typeof payload.payload === "object" &&348 payload.payload !== null349 ? payload.payload350 : undefined351352 let configLabelKey: string = key353354 if (355 key in payload &&356 typeof payload[key as keyof typeof payload] === "string"357 ) {358 configLabelKey = payload[key as keyof typeof payload] as string359 } else if (360 payloadPayload &&361 key in payloadPayload &&362 typeof payloadPayload[key as keyof typeof payloadPayload] === "string"363 ) {364 configLabelKey = payloadPayload[365 key as keyof typeof payloadPayload366 ] as string367 }368369 return configLabelKey in config370 ? config[configLabelKey]371 : config[key as keyof typeof config]372}373374export {375 ChartContainer,376 ChartTooltip,377 ChartTooltipContent,378 ChartLegend,379 ChartLegendContent,380 ChartStyle,381}382