Charts

Line Chart

A config-driven line chart on the quebi Chart wrapper, rendering one teal-led series per config key with styled axes, grid, tooltip, and an interactive legend. Requires the recharts package.

chartlinerechartsdatagraphvisualization

Default

A config-driven line chart with two teal-led series, an interactive legend, and a tooltip.

Single series

One brand-teal line driven entirely by the config.

No grid lines

Hide the cartesian grid for a cleaner surface.

Edge labels only

Show only the first and last X-axis labels for dense series.

Custom series via children

Pass children to take full manual control of the rendered lines while keeping the styled wrapper.

Source

Copy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.

"use client"

import { useMemo } from "react"
import { Line, LineChart as LineChartPrimitive, type LineProps } from "recharts"
import type { NameType, ValueType } from "recharts/types/component/DefaultTooltipContent"
import {
  type BaseChartProps,
  CartesianGrid,
  Chart,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  constructCategoryColors,
  DEFAULT_COLORS,
  getColorValue,
  valueToPercent,
  XAxis,
  YAxis,
} from "@/components/chart"

/**
 * LineChart — quebi design system
 *
 * A config-driven line chart built on the quebi `Chart` wrapper. It renders one
 * teal-led `<Line>` per `config` key with a styled grid, axes, tooltip, and an
 * interactive legend. Series colors come from the quebi palette and can be
 * overridden per key via the `config` prop. Pass `children` to take full manual
 * control of the rendered series.
 *
 * Requires the `recharts` npm package as a peer dependency.
 */

export interface LineChartProps<TValue extends ValueType, TName extends NameType>
  extends BaseChartProps<TValue, TName> {
  connectNulls?: boolean
  lineProps?: LineProps
  chartProps?: Omit<React.ComponentProps<typeof LineChartPrimitive>, "data" | "stackOffset">
}

export function LineChart<TValue extends ValueType, TName extends NameType>({
  data = [],
  dataKey,
  colors = DEFAULT_COLORS,
  connectNulls = false,
  type = "default",
  config,
  children,

  // Components
  tooltip = true,
  tooltipProps,

  legend = true,
  legendProps,

  intervalType = "equidistantPreserveStart",

  valueFormatter = (value: number) => value.toString(),

  // XAxis
  displayEdgeLabelsOnly = false,
  xAxisProps,
  hideXAxis = false,

  // YAxis
  yAxisProps,
  hideYAxis = false,

  hideGridLines = false,
  chartProps,
  lineProps,
  ...props
}: LineChartProps<TValue, TName>) {
  const configKeys = useMemo(() => Object.keys(config), [config])
  const categoryColors = useMemo(
    () => constructCategoryColors(configKeys, colors),
    [configKeys, colors],
  )

  const configEntries = useMemo(() => Object.entries(config), [config])

  return (
    <Chart config={config} data={data} dataKey={dataKey} {...props}>
      {({ onLegendSelect, selectedLegend }) => (
        <LineChartPrimitive
          onClick={() => {
            onLegendSelect(null)
          }}
          data={data}
          margin={{
            bottom: 0,
            left: 0,
            right: 0,
            top: 5,
          }}
          stackOffset={type === "percent" ? "expand" : undefined}
          {...chartProps}
        >
          {!hideGridLines && <CartesianGrid strokeDasharray="4 4" />}
          <XAxis
            hide={hideXAxis}
            displayEdgeLabelsOnly={displayEdgeLabelsOnly}
            intervalType={intervalType}
            {...xAxisProps}
          />
          <YAxis
            hide={hideYAxis}
            tickFormatter={type === "percent" ? valueToPercent : valueFormatter}
            {...yAxisProps}
          />

          {legend && (
            <ChartLegend
              content={typeof legend === "boolean" ? <ChartLegendContent /> : legend}
              {...legendProps}
            />
          )}

          {tooltip && (
            <ChartTooltip
              content={
                typeof tooltip === "boolean" ? <ChartTooltipContent accessibilityLayer /> : tooltip
              }
              {...tooltipProps}
            />
          )}

          {!children
            ? configEntries.map(([category, values]) => {
                const strokeOpacity = selectedLegend && selectedLegend !== category ? 0.1 : 1
                const color = getColorValue(values.color || categoryColors.get(category))

                return (
                  <Line
                    key={category}
                    dot={false}
                    name={category}
                    type="linear"
                    dataKey={category}
                    stroke={color}
                    style={
                      {
                        strokeOpacity,
                        strokeWidth: 2,
                        "--line-color": color,
                      } as React.CSSProperties
                    }
                    strokeLinejoin="round"
                    strokeLinecap="round"
                    connectNulls={connectNulls}
                    {...lineProps}
                  />
                )
              })
            : children}
        </LineChartPrimitive>
      )}
    </Chart>
  )
}