SPB Git

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%
10.8 KB · 382 lines tsx
Raw Blame History
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