shadimage
preskok
uiexperimental-chartsource: stale · stale

Experimental Chart

Experimental TanStack chart foundations and utilities

npx shadcn add "https://ui-three-mu.vercel.app/r/experimental-chart.json"
free

This source snapshot is older than our freshness window and couldn't be re-verified just now — it may be outdated.

No selection tags yet — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light
@tanstack/charts@0.12.0d3-shape@3.2.0react-aria-componentscn@^0.2.4

Files2 · source cached 9/28/2026, 5:36:11 PM

registry/preskok/ui/preskok-ui/experimental-chart.tsx
"use client"

import {
  createContext,
  startTransition,
  use,
  useState,
  type CSSProperties,
  type HTMLAttributes,
  type ReactNode,
} from "react"
import type { ChartValue, DomChartDefinition } from "@tanstack/charts"
import {
  Chart as ChartPrimitive,
  type ChartProps as TanStackChartProps,
  type ChartTooltipBodyRenderContext,
} from "@tanstack/charts/react/tooltip"
import { twMerge } from "cn"
import {
  ToggleButton,
  ToggleButtonGroup,
} from "react-aria-components/ToggleButtonGroup"

import {
  getExperimentalChartColors,
  getExperimentalLabel,
  getExperimentalTextLabel,
  getExperimentalTooltipOptions,
  type ExperimentalChartColorPalette,
  type ExperimentalChartConfig,
  type ExperimentalChartLegendProps,
  type ExperimentalChartSizeProps,
  type ExperimentalChartTooltipContentProps,
  type ExperimentalChartTooltipProps,
  type ExperimentalChartTooltipRenderer,
  type ExperimentalTooltipDatum,
} from "./experimental-chart-core"

type ExperimentalChartFrameContextValue = {
  actions: {
    selectSeries: (series: string | null) => void
  }
  meta: {
    colors?: ExperimentalChartColorPalette
    config: ExperimentalChartConfig
  }
  state: {
    selectedSeries: string | null
  }
}

const ExperimentalChartFrameContext =
  createContext<ExperimentalChartFrameContextValue | null>(null)

function useExperimentalChartFrame() {
  const context = use(ExperimentalChartFrameContext)
  if (!context) {
    throw new Error(
      "Chart components must be rendered inside <ExperimentalChartFrame>"
    )
  }
  return context
}

type ExperimentalChartProps<
  TDatum,
  TXValue extends ChartValue = ChartValue,
  TYValue extends ChartValue = ChartValue,
> = Pick<
  TanStackChartProps<TDatum, TXValue, TYValue>,
  | "ariaLabel"
  | "className"
  | "definition"
  | "onSelect"
  | "renderTooltipBody"
  | "style"
> & {
  size?: ExperimentalChartSizeProps
}

function ExperimentalChart<
  TDatum,
  TXValue extends ChartValue = ChartValue,
  TYValue extends ChartValue = ChartValue,
>({
  ariaLabel,
  className,
  definition,
  onSelect,
  renderTooltipBody,
  size,
  style,
}: ExperimentalChartProps<TDatum, TXValue, TYValue>) {
  const [ready, setReady] = useState(false)
  let height = size?.height
  if (height === undefined && size?.aspectRatio === undefined) {
    height = 288
  }

  return (
    <ChartPrimitive
      ariaLabel={ariaLabel}
      aspectRatio={size?.aspectRatio}
      className={twMerge(
        "min-w-0 text-xs text-muted-foreground [&_svg.ts-chart]:outline-none",
        ready ? "opacity-100" : "opacity-0",
        className
      )}
      definition={definition}
      height={height}
      initialWidth={size?.initialWidth ?? 720}
      onRender={(context) => {
        if (ready) {
          return
        }

        const measuredWidth = context.container.getBoundingClientRect().width
        const widthMatches = Math.abs(context.scene.width - measuredWidth) < 1
        if (measuredWidth > 0 && widthMatches) {
          setReady(true)
        }
      }}
      onSelect={onSelect}
      renderTooltipBody={renderTooltipBody}
      style={style}
    />
  )
}

type ExperimentalChartFrameProps = Omit<
  HTMLAttributes<HTMLDivElement>,
  "children"
> & {
  children: ReactNode
  colors?: ExperimentalChartColorPalette
  config: ExperimentalChartConfig
  legend?: ReactNode | false
}

function ExperimentalChartFrame({
  children,
  className,
  colors,
  config,
  legend,
  ...props
}: ExperimentalChartFrameProps) {
  const [selectedSeries, setSelectedSeries] = useState<string | null>(null)

  const selectSeries = (series: string | null) => {
    startTransition(() => {
      setSelectedSeries(series)
    })
  }

  let legendContent = legend
  if (legend === undefined) {
    legendContent = <ExperimentalChartLegend />
  }

  const context = {
    actions: { selectSeries },
    meta: { colors, config },
    state: { selectedSeries },
  }

  return (
    <ExperimentalChartFrameContext value={context}>
      <div
        {...props}
        className={twMerge("z-20 flex w-full min-w-0 flex-col", className)}
      >
        {children}
        {legendContent}
      </div>
    </ExperimentalChartFrameContext>
  )
}

function ExperimentalChartLegendContent({
  align = "center",
  className,
  hideIcon = false,
  verticalAlign = "bottom",
  ...props
}: ExperimentalChartLegendProps) {
  const {
    actions: { selectSeries },
    meta: { colors, config },
    state: { selectedSeries },
  } = useExperimentalChartFrame()
  const chartColors = getExperimentalChartColors(config, colors)
  let justifyClass = "justify-center"
  if (align === "left") {
    justifyClass = "justify-start"
  } else if (align === "right") {
    justifyClass = "justify-end"
  }

  return (
    <ToggleButtonGroup
      aria-label="Chart series"
      className={twMerge(
        "flex flex-wrap items-center gap-1",
        verticalAlign === "top" ? "order-first pb-2" : "order-last pt-2",
        justifyClass,
        className
      )}
      onSelectionChange={(keys) => {
        const key = [...keys][0]?.toString() ?? null
        selectSeries(key)
      }}
      selectedKeys={selectedSeries ? [selectedSeries] : []}
      selectionMode="single"
      {...props}
    >
      {Object.entries(config).map(([series, item]) => {
        const Icon = item.icon
        return (
          <ToggleButton
            aria-label={`${getExperimentalTextLabel(config, series)} series`}
            className={twMerge(
              "relative flex min-h-10 items-center gap-2 rounded-full px-3 py-1.5 text-xs font-medium text-muted-foreground outline-none",
              "transition-[background-color,color,opacity,scale,box-shadow] duration-150 ease-out",
              "hover:bg-muted/70 hover:text-foreground selected:bg-muted selected:text-foreground selected:shadow-[inset_0_0_0_1px_color-mix(in_srgb,var(--border)_80%,transparent)]",
              "focus-visible:ring-2 focus-visible:ring-ring/40 active:scale-[0.96]"
            )}
            id={series}
            key={series}
          >
            {Icon && !hideIcon ? (
              <Icon data-slot="icon" />
            ) : (
              <span
                aria-hidden
                className="size-2 shrink-0 rounded-full shadow-[0_0_0_1px_color-mix(in_srgb,currentColor_10%,transparent)]"
                style={{ backgroundColor: chartColors[series] }}
              />
            )}
            {item.label ?? series}
          </ToggleButton>
        )
      })}
    </ToggleButtonGroup>
  )
}

function ExperimentalChartLegend(props: ExperimentalChartLegendProps) {
  return <ExperimentalChartLegendContent {...props} />
}

function ExperimentalChartTooltipContent<
  TDatum extends ExperimentalTooltipDatum,
  TXValue extends ChartValue = ChartValue,
  TYValue extends ChartValue = ChartValue,
>({
  config,
  points,
  tooltipProps,
  valueFormatter,
}: ExperimentalChartTooltipContentProps<TDatum, TXValue, TYValue>) {
  const {
    className,
    hideIndicator = false,
    hideLabel = false,
    indicator = "dot",
    labelFormatter,
    labelSeparator = true,
  } = tooltipProps ?? {}
  const firstPoint = points[0]

  if (!firstPoint) {
    return null
  }

  const rawLabel = String(firstPoint.datum.category)
  const label = labelFormatter ? labelFormatter(rawLabel) : rawLabel
  const labelContent = hideLabel ? null : (
    <span className="font-semibold text-foreground">{label}</span>
  )
  const separator =
    hideLabel || !labelSeparator ? null : (
      <span
        aria-hidden
        className="mt-2 mb-2.5 block h-px w-full bg-border/70"
      />
    )

  return (
    <div
      className={twMerge(
        "grid min-w-36 items-start text-xs text-current",
        className
      )}
    >
      {labelContent}
      {separator}
      <div className="grid gap-2.5">
        {points.map((point) => {
          const { series, value } = point.datum
          if (value === null) {
            return null
          }

          let indicatorStyle = { backgroundColor: point.color }
          if (indicator === "dashed") {
            indicatorStyle = { backgroundColor: "transparent" }
          }

          return (
            <div className="flex items-center gap-2.5" key={point.key}>
              {!hideIndicator ? (
                <span
                  aria-hidden
                  className={twMerge(
                    "shrink-0 border-current",
                    indicator === "dot" && "size-2.5 rounded-full",
                    indicator === "line" && "h-4 w-1 rounded-full",
                    indicator === "dashed" &&
                      "h-4 w-0 border-l-2 border-dashed bg-transparent"
                  )}
                  style={{
                    ...indicatorStyle,
                    borderColor: point.color,
                  }}
                />
              ) : null}
              <span className="flex-1 text-muted-foreground">
                {getExperimentalLabel(config, series)}
              </span>
              <span className="font-mono font-medium text-foreground tabular-nums">
                {valueFormatter(value)}
              </span>
            </div>
          )
        })}
      </div>
    </div>
  )
}

function createExperimentalTooltipRenderer<
  TDatum extends ExperimentalTooltipDatum,
  TXValue extends ChartValue = ChartValue,
  TYValue extends ChartValue = ChartValue,
>({
  config,
  tooltip,
  tooltipProps,
  valueFormatter,
}: {
  config: ExperimentalChartConfig
  tooltip?: ExperimentalChartTooltipRenderer<TDatum, TXValue, TYValue> | false
  tooltipProps?: ExperimentalChartTooltipProps
  valueFormatter: (value: number) => string
}) {
  return (context: ChartTooltipBodyRenderContext<TDatum, TXValue, TYValue>) => {
    const contentProps = {
      config,
      points: context.points,
      tooltipProps,
      valueFormatter,
    }

    if (typeof tooltip === "function") {
      return tooltip(contentProps)
    }

    return <ExperimentalChartTooltipContent {...contentProps} />
  }
}

function getExperimentalChartTooltip<
  TDatum extends ExperimentalTooltipDatum,
  TXValue extends ChartValue = ChartValue,
  TYValue extends ChartValue = ChartValue,
>({
  config,
  definition,
  tooltip,
  tooltipProps,
  valueFormatter,
}: {
  config: ExperimentalChartConfig
  definition: DomChartDefinition<TDatum, TXValue, TYValue>
  tooltip?: ExperimentalChartTooltipRenderer<TDatum, TXValue, TYValue> | false
  tooltipProps?: ExperimentalChartTooltipProps
  valueFormatter: (value: number) => string
}) {
  if (tooltip === false) {
    return { definition, renderTooltipBody: undefined }
  }

  return {
    definition: {
      ...definition,
      ...getExperimentalTooltipOptions(tooltipProps),
    },
    renderTooltipBody: createExperimentalTooltipRenderer({
      config,
      tooltip,
      tooltipProps,
      valueFormatter,
    }),
  }
}

const experimentalChartTooltipStyle = {
  "--ts-chart-tooltip-background": "transparent",
  "--ts-chart-tooltip-border": "0",
  "--ts-chart-tooltip-padding": "0",
  "--ts-chart-tooltip-shadow": "none",
} as CSSProperties

export type {
  ExperimentalChartFrameContextValue,
  ExperimentalChartFrameProps,
  ExperimentalChartProps,
}

export {
  ExperimentalChart,
  ExperimentalChartFrame,
  ExperimentalChartLegend,
  ExperimentalChartLegendContent,
  ExperimentalChartTooltipContent,
  experimentalChartTooltipStyle,
  createExperimentalTooltipRenderer,
  getExperimentalChartTooltip,
  useExperimentalChartFrame,
}

export * from "./experimental-chart-core"
registry/preskok/ui/preskok-ui/experimental-chart-core.ts
import type { ComponentType, HTMLAttributes, ReactNode } from "react"
import type {
  ChartAxisTickLabelOptions,
  ChartAxisTickOptions,
  ChartCurve,
  ChartTooltipOptions,
  ChartValue,
} from "@tanstack/charts"
import { d3Curve } from "@tanstack/charts/d3/shape"
import type { ChartProps as TanStackChartProps } from "@tanstack/charts/react/tooltip"
import { scaleLinear } from "@tanstack/charts/scales/linear"
import { tooltip as tooltipExtension } from "@tanstack/charts/tooltip"
import {
  curveBasis,
  curveBumpX,
  curveLinear,
  curveMonotoneX,
  curveNatural,
  curveStep,
  curveStepAfter,
  curveStepBefore,
} from "d3-shape"
import type { ToggleButtonGroupProps } from "react-aria-components/ToggleButtonGroup"

type ExperimentalChartType = "default" | "stacked" | "percent"
type ExperimentalChartColor = (typeof EXPERIMENTAL_CHART_COLORS)[number]
type ExperimentalChartColorPalette = readonly [
  ExperimentalChartColor,
  ...ExperimentalChartColor[],
]
type ExperimentalChartDatum = Record<string, unknown>
type ExperimentalChartCurveType =
  | "basis"
  | "bump"
  | "linear"
  | "monotone"
  | "monotoneX"
  | "natural"
  | "step"
  | "stepAfter"
  | "stepBefore"
  | ChartCurve

type ExperimentalChartConfig = Record<
  string,
  {
    color?: ExperimentalChartColor
    icon?: ComponentType<{ "data-slot"?: string }>
    label?: ReactNode
  }
>

type ExperimentalChartAxisProps<TValue extends ChartValue = ChartValue> = {
  label?: string
  minTickGap?: number
  tickFormatter?: (value: TValue) => string
  tickMargin?: number
  ticks?: readonly TValue[]
  tickStrategy?: "all" | "auto" | "edges"
}

type ExperimentalChartNumericAxisProps = ExperimentalChartAxisProps<number> & {
  domain?: readonly [number, number]
}

type ExperimentalChartTooltipProps = Pick<
  ChartTooltipOptions,
  "anchor" | "offset" | "placement"
> & {
  className?: string
  hideIndicator?: boolean
  hideLabel?: boolean
  indicator?: "line" | "dot" | "dashed"
  labelFormatter?: (label: ReactNode) => ReactNode
  labelSeparator?: boolean
}

type ExperimentalChartLegendProps = Omit<
  ToggleButtonGroupProps,
  | "children"
  | "className"
  | "onSelectionChange"
  | "selectedKeys"
  | "selectionMode"
> & {
  align?: "left" | "center" | "right"
  className?: string
  hideIcon?: boolean
  verticalAlign?: "top" | "bottom"
}

type ExperimentalChartSizeProps = Pick<
  TanStackChartProps,
  "aspectRatio" | "height" | "initialWidth"
>

type ExperimentalTooltipDatum = {
  category: ChartValue
  series: string
  source: unknown
  value: number | null
}

type ExperimentalChartTooltipContentProps<
  TDatum extends ExperimentalTooltipDatum = ExperimentalTooltipDatum,
  TXValue extends ChartValue = ChartValue,
  TYValue extends ChartValue = ChartValue,
> = {
  config: ExperimentalChartConfig
  points: readonly import("@tanstack/charts").ChartPoint<
    TDatum,
    TXValue,
    TYValue
  >[]
  tooltipProps?: ExperimentalChartTooltipProps
  valueFormatter: (value: number) => string
}

type ExperimentalChartTooltipRenderer<
  TDatum extends ExperimentalTooltipDatum = ExperimentalTooltipDatum,
  TXValue extends ChartValue = ChartValue,
  TYValue extends ChartValue = ChartValue,
> = (
  props: ExperimentalChartTooltipContentProps<TDatum, TXValue, TYValue>
) => ReactNode

interface ExperimentalBaseChartProps extends Omit<
  HTMLAttributes<HTMLDivElement>,
  "children"
> {
  ariaLabel?: string
  children?: never
  colors?: ExperimentalChartColorPalette
  config: ExperimentalChartConfig
  data: ExperimentalChartDatum[]
  dataKey: string
  legend?: ReactNode | false
  size?: ExperimentalChartSizeProps
  tooltip?: ExperimentalChartTooltipRenderer | false
  tooltipProps?: ExperimentalChartTooltipProps
  valueFormatter?: (value: number) => string
}

type ExperimentalChartPlotProps<TProps extends ExperimentalBaseChartProps> =
  Omit<TProps, keyof HTMLAttributes<HTMLDivElement> | "legend">

type ExperimentalCartesianChartProps = ExperimentalBaseChartProps & {
  grid?: "hidden" | "visible"
  xAxis?: ExperimentalChartAxisProps | false
  yAxis?: ExperimentalChartNumericAxisProps | false
}

type ExperimentalSeriesDatum = ExperimentalTooltipDatum & {
  index: number
  source: ExperimentalChartDatum
}

type ExperimentalNamedSeriesDatum = Omit<ExperimentalTooltipDatum, "value"> & {
  color: string
  index: number
  source: ExperimentalChartDatum
  value: number
}

const EXPERIMENTAL_CHART_COLORS = [
  "var(--chart-1)",
  "var(--chart-2)",
  "var(--chart-3)",
  "var(--chart-4)",
  "var(--chart-5)",
] as const

function experimentalValueToPercent(value: number) {
  return `${(value * 100).toFixed(0)}%`
}

function experimentalDefaultValueFormatter(value: number) {
  return String(value)
}

function getExperimentalChartSize(
  size: ExperimentalChartSizeProps | undefined,
  defaultHeight: number
): ExperimentalChartSizeProps {
  if (size?.height !== undefined || size?.aspectRatio !== undefined) {
    return size
  }

  return { ...size, height: defaultHeight }
}

function getExperimentalSelectedSeriesColor({
  color,
  opacity,
  selectedSeries,
  series,
}: {
  color: string
  opacity: number
  selectedSeries: string | null
  series: string
}) {
  if (selectedSeries === null || selectedSeries === series) {
    return color
  }

  return `color-mix(in srgb, ${color} ${opacity}%, transparent)`
}

function getExperimentalPositiveMaximum(
  values: readonly number[],
  maximum?: number
) {
  const resolvedMaximum = maximum ?? Math.max(...values, 1)
  return Number.isFinite(resolvedMaximum) && resolvedMaximum > 0
    ? resolvedMaximum
    : 1
}

function constructExperimentalCategoryColors(
  categories: string[],
  colors: ExperimentalChartColorPalette
) {
  return new Map(
    categories.map((category, index) => [
      category,
      colors[index % colors.length] ?? colors[0],
    ])
  )
}

function getExperimentalChartColors(
  config: ExperimentalChartConfig,
  colors: ExperimentalChartColorPalette = EXPERIMENTAL_CHART_COLORS
) {
  const categoryColors = constructExperimentalCategoryColors(
    Object.keys(config),
    colors
  )

  return Object.fromEntries(
    Object.entries(config).map(([series, item]) => {
      const color = item.color ?? categoryColors.get(series) ?? colors[0]
      return [series, color]
    })
  )
}

function getExperimentalSeriesChartOptions(
  config: ExperimentalChartConfig,
  colors?: ExperimentalChartColorPalette
) {
  const chartColors = getExperimentalChartColors(config, colors)
  const seriesNames = Object.keys(config)

  return {
    chartColors,
    options: {
      color: {
        domain: seriesNames,
        range: seriesNames.map((series) => chartColors[series] ?? ""),
      },
      svgAnimation: true,
      theme: getExperimentalChartTheme(
        seriesNames.map((series) => chartColors[series] ?? "")
      ),
    },
    seriesNames,
  }
}

function isChartValue(value: unknown): value is ChartValue {
  return (
    typeof value === "string" ||
    typeof value === "number" ||
    value instanceof Date
  )
}

function toExperimentalSeriesData({
  config,
  connectNulls = false,
  data,
  dataKey,
}: {
  config: ExperimentalChartConfig
  connectNulls?: boolean
  data: ExperimentalChartDatum[]
  dataKey: string
}) {
  const seriesNames = Object.keys(config)

  return data.flatMap((source, index) => {
    const category = source[dataKey]
    if (!isChartValue(category)) {
      return []
    }

    return seriesNames.flatMap((series) => {
      const rawValue = source[series]
      const value =
        typeof rawValue === "number" && Number.isFinite(rawValue)
          ? rawValue
          : null

      if (connectNulls && value === null) {
        return []
      }

      return [
        {
          category,
          index,
          series,
          source,
          value,
        } satisfies ExperimentalSeriesDatum,
      ]
    })
  })
}

function toExperimentalNamedSeriesData({
  colors,
  config,
  data,
  nameKey,
  selectedSeries,
  selectedOpacity,
  valueKey,
}: {
  colors?: ExperimentalChartColorPalette
  config: ExperimentalChartConfig
  data: ExperimentalChartDatum[]
  nameKey: string
  selectedSeries: string | null
  selectedOpacity: number
  valueKey: string
}) {
  const chartColors = getExperimentalChartColors(config, colors)
  const fallbackColors = colors ?? EXPERIMENTAL_CHART_COLORS

  return data.flatMap((source, index) => {
    const rawName = source[nameKey]
    const rawValue = source[valueKey]
    if (
      (typeof rawName !== "string" && typeof rawName !== "number") ||
      typeof rawValue !== "number" ||
      !Number.isFinite(rawValue)
    ) {
      return []
    }

    const series = String(rawName)
    const fallback =
      fallbackColors[index % fallbackColors.length] ?? fallbackColors[0]
    const color = chartColors[series] ?? fallback

    return [
      {
        category: series,
        color: getExperimentalSelectedSeriesColor({
          color,
          opacity: selectedOpacity,
          selectedSeries,
          series,
        }),
        index,
        series,
        source,
        value: rawValue,
      } satisfies ExperimentalNamedSeriesDatum,
    ]
  })
}

function getExperimentalLabel(config: ExperimentalChartConfig, series: string) {
  return config[series]?.label ?? series
}

function getExperimentalTextLabel(
  config: ExperimentalChartConfig,
  series: string
) {
  const label = getExperimentalLabel(config, series)
  return typeof label === "string" || typeof label === "number"
    ? String(label)
    : series
}

function getExperimentalChartTheme(colors: readonly string[]) {
  return {
    background: "transparent",
    foreground: "var(--muted-foreground)",
    grid: "color-mix(in srgb, var(--muted-foreground) 14%, transparent)",
    muted: "color-mix(in srgb, var(--muted-foreground) 78%, transparent)",
    palette: colors,
  }
}

function getExperimentalChartCurve(
  lineType: ExperimentalChartCurveType = "linear"
) {
  if (typeof lineType !== "string") {
    return lineType
  }

  switch (lineType) {
    case "basis":
      return d3Curve(curveBasis)
    case "bump":
      return d3Curve(curveBumpX)
    case "monotone":
    case "monotoneX":
      return d3Curve(curveMonotoneX)
    case "natural":
      return d3Curve(curveNatural)
    case "step":
      return d3Curve(curveStep)
    case "stepAfter":
      return d3Curve(curveStepAfter)
    case "stepBefore":
      return d3Curve(curveStepBefore)
    default:
      return d3Curve(curveLinear)
  }
}

function getExperimentalAxisTickOptions<TValue extends ChartValue>({
  edgeValues,
  props,
}: {
  edgeValues?: readonly TValue[]
  props?: ExperimentalChartAxisProps<TValue>
}): ChartAxisTickOptions<TValue> {
  let values = props?.ticks
  if (props?.tickStrategy === "edges") {
    values = edgeValues
  }

  return {
    format: props?.tickFormatter,
    padding: props?.tickMargin ?? 9,
    size: 0,
    values,
  }
}

function getExperimentalAxisTickLabelOptions<TValue extends ChartValue>(
  props?: ExperimentalChartAxisProps<TValue>
): ChartAxisTickLabelOptions<TValue> {
  if (props?.tickStrategy === "all") {
    return { fontSize: 11, fontWeight: 450, opacity: 0.78, thin: false }
  }

  return {
    fontSize: 11,
    fontWeight: 450,
    opacity: 0.78,
    thin: {
      minGap: props?.minTickGap ?? 8,
      priority: "ends",
    },
  }
}

function getExperimentalCategoryAxis({
  data,
  dataKey,
  props,
}: {
  data: ExperimentalChartDatum[]
  dataKey: string
  props?: ExperimentalChartAxisProps | false
}) {
  if (props === false) {
    return false
  }

  return {
    line: false,
    label: props?.label,
    tickLabels: getExperimentalAxisTickLabelOptions(props),
    ticks: getExperimentalAxisTickOptions({
      edgeValues: getExperimentalEdgeValues(data, dataKey),
      props,
    }),
  }
}

function getExperimentalNumericAxis({
  props,
  valueFormatter,
}: {
  props?: ExperimentalChartNumericAxisProps | false
  valueFormatter: (value: number) => string
}) {
  if (props === false) {
    return false
  }

  return {
    line: false,
    label: props?.label,
    tickLabels: getExperimentalAxisTickLabelOptions(props),
    ticks: {
      ...getExperimentalAxisTickOptions({ props }),
      format: props?.tickFormatter ?? valueFormatter,
    },
  }
}

function getExperimentalNumericScale(
  props?: ExperimentalChartNumericAxisProps | false
) {
  if (!props || !props.domain) {
    return scaleLinear
  }

  const domain = props.domain
  return () => scaleLinear().domain(domain)
}

function getExperimentalTooltipOptions(
  tooltipProps?: ExperimentalChartTooltipProps
) {
  return {
    tooltip: {
      anchor: tooltipProps?.anchor,
      offset: tooltipProps?.offset,
      placement: tooltipProps?.placement,
      use: tooltipExtension,
    },
  } as const
}

function getExperimentalEdgeValues(
  data: ExperimentalChartDatum[],
  dataKey: string
) {
  const first = data.at(0)?.[dataKey]
  const last = data.at(-1)?.[dataKey]

  return isChartValue(first) && isChartValue(last) ? [first, last] : undefined
}

export type {
  ExperimentalBaseChartProps,
  ExperimentalCartesianChartProps,
  ExperimentalChartAxisProps,
  ExperimentalChartColor,
  ExperimentalChartColorPalette,
  ExperimentalChartConfig,
  ExperimentalChartCurveType,
  ExperimentalChartDatum,
  ExperimentalChartLegendProps,
  ExperimentalChartNumericAxisProps,
  ExperimentalChartPlotProps,
  ExperimentalChartSizeProps,
  ExperimentalChartTooltipContentProps,
  ExperimentalChartTooltipProps,
  ExperimentalChartTooltipRenderer,
  ExperimentalChartType,
  ExperimentalNamedSeriesDatum,
  ExperimentalSeriesDatum,
  ExperimentalTooltipDatum,
}

export {
  EXPERIMENTAL_CHART_COLORS,
  constructExperimentalCategoryColors,
  getExperimentalAxisTickLabelOptions,
  getExperimentalAxisTickOptions,
  getExperimentalCategoryAxis,
  getExperimentalChartColors,
  getExperimentalChartSize,
  getExperimentalChartCurve,
  getExperimentalChartTheme,
  getExperimentalEdgeValues,
  getExperimentalLabel,
  getExperimentalNumericAxis,
  getExperimentalNumericScale,
  getExperimentalPositiveMaximum,
  getExperimentalSelectedSeriesColor,
  getExperimentalSeriesChartOptions,
  getExperimentalTextLabel,
  getExperimentalTooltipOptions,
  toExperimentalNamedSeriesData,
  toExperimentalSeriesData,
  experimentalDefaultValueFormatter,
  experimentalValueToPercent,
}

More from preskok

Browse all components in this library →