TanStack
shadcn/ui Charts

shadcn Bar Chart

bar

500 lines · 3 files · 14.7 kB

cases/144-shadcn-bar-default/example.tsx142 lines · entry
cases/144-shadcn-bar-default/example.tsx
import { barY, defineChart, type ChartPoint } from '@tanstack/charts'
import { RendererChart } from '@tanstack/charts/react/tooltip'
import { tooltip } from '@tanstack/charts/tooltip'
import { motion } from '@tanstack/charts/motion'
import { scaleBand, scaleLinear } from 'd3-scale'
import { shadcnColors, shadcnMonths } from '@tanstack/charts-data/shadcn'
import './styles.css'
export function createExampleChart() {
  const rows = shadcnMonths
  const marks = [
    barY(rows, {
      id: 'visitor-bars',
      x: 'month',
      y: 'desktop',
      fill: (row) =>
        false && row.desktop < 0 ? shadcnColors[1] : shadcnColors[0],
      radius: 8,
    }),
  ]
  return defineChart(
    {
      marks,
      scales: {
        x: shadcnXAxis(),
        y: { scale: scaleLinear, grid: true, axis: false },
      },

      margin: { top: 5, right: 5, bottom: 35, left: 5 },
      theme: shadcnTheme(),
    },
    {
      svgAnimation: false,
      focus: 'group-x',
      tooltip: {
        use: tooltip,
        className: 'sc-chart-tooltip',
        anchor: 'group-center',
        placement: 'auto',
        sort: 'color-domain',
        content: (points) => shadcnTooltipContent(points),
      },
    },
  )
}
function shadcnXAxis() {
  return {
    scale: () => scaleBand<string>().paddingInner(0.2).paddingOuter(0.1),
    axis: {
      line: false,
      ticks: {
        size: 0,
        padding: 10,
        format: (value: string) => value.slice(0, 3),
      },
    },
  }
}
function shadcnTheme() {
  return {
    foreground: 'var(--muted-foreground, var(--muted))',
    grid: 'var(--border)',
    background: 'transparent',
  }
}
function titleCase(value: string) {
  return value.charAt(0).toUpperCase() + value.slice(1)
}
function shadcnTooltipContent<TDatum>(points: readonly ChartPoint<TDatum>[]) {
  return {
    title: String(points[0]?.xValue ?? ''),
    rows: points.map((point) => ({
      label: titleCase(
        String(
          point.group ??
            point.markId.replace(
              /-?(bars|lines|areas|slices|values|radar)$/u,
              '',
            ),
        ),
      ),
      value: Number(point.yValue ?? point.xValue ?? 0).toLocaleString('en-US'),
      color: point.color,
    })),
  }
}
export const definition = createExampleChart()
const renderer = motion({
  initial: 'always',
  transition: { type: 'spring', stiffness: 170, damping: 18, mass: 1 },
})
export interface ExampleProps {
  width?: number
  height?: number
}
export default function Example({ width = 640, height = 600 }: ExampleProps) {
  const contentWidth = Math.max(1, width - 50)
  const chartWidth = contentWidth
  const chartHeight = (contentWidth * 9) / 16
  return (
    <div className="sc-example" style={{ width, height }}>
      <article className="sc-card sc-default" style={{ width }}>
        <header className="sc-card-header">
          <div className="sc-card-heading">
            <h2>Bar Chart</h2>
            <p>January - June 2024</p>
          </div>
        </header>
        <div className="sc-card-content">
          <div
            className="sc-chart"
            style={{ width: chartWidth, height: chartHeight }}
          >
            <RendererChart
              definition={definition}
              renderer={renderer}
              initialWidth={chartWidth}
              height={chartHeight}
              ariaLabel="Bar Chart"
            />
          </div>
        </div>
        <footer className="sc-card-footer">
          <TrendFooter note="Showing total visitors for the last 6 months" />
        </footer>
      </article>
    </div>
  )
}
function TrendFooter({ note }: { note: string }) {
  return (
    <>
      <div className="sc-trend">
        Trending up by 5.2% this month{' '}
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="m3 17 6-6 4 4 8-8" />
          <path d="M14 7h7v7" />
        </svg>
      </div>
      <div className="sc-footer-note">{note}</div>
    </>
  )
}
cases/144-shadcn-bar-default/styles.css142 lines · dependency
cases/144-shadcn-bar-default/styles.css
.sc-example {
  --background: oklch(1 0 0);
  --foreground: oklch(0 0 0);
  --card: oklch(1 0 0);
  --card-foreground: oklch(0 0 0);
  --muted: oklch(0.97 0 0);
  --muted-foreground: oklch(0.556 0 0);
  --border: oklch(0.922 0 0);
  --chart-1: oklch(0.809 0.105 251.813);
  --chart-2: oklch(0.623 0.214 259.815);
  --chart-3: oklch(0.546 0.245 262.881);
  --chart-4: oklch(0.488 0.243 264.376);
  --chart-5: oklch(0.424 0.199 265.638);
  display: flex;
  justify-content: center;
  align-items: flex-start;
  overflow: hidden;
  color: var(--foreground);
  background: var(--background);
  font-family:
    Inter,
    ui-sans-serif,
    system-ui,
    -apple-system,
    BlinkMacSystemFont,
    'Segoe UI',
    sans-serif;
  text-rendering: geometricPrecision;
}
:root[data-theme='dark'] .sc-example {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  --card: oklch(0.205 0 0);
  --card-foreground: oklch(0.985 0 0);
  --muted: oklch(0.269 0 0);
  --muted-foreground: oklch(0.708 0 0);
  --border: oklch(1 0 0 / 10%);
  --chart-1: oklch(0.809 0.105 251.813);
  --chart-2: oklch(0.623 0.214 259.815);
  --chart-3: oklch(0.546 0.245 262.881);
  --chart-4: oklch(0.488 0.243 264.376);
  --chart-5: oklch(0.424 0.199 265.638);
}
.sc-example,
.sc-example * {
  box-sizing: border-box;
}
.sc-card {
  display: flex;
  flex-direction: column;
  gap: 24px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--card);
  color: var(--card-foreground);
  padding: 24px 0;
}
.sc-card-header {
  display: grid;
  gap: 8px;
  padding: 0 24px;
}
.sc-card-heading {
  display: grid;
  gap: 8px;
}
.sc-card-header h2 {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.01em;
}
.sc-card-header p {
  margin: 0;
  color: var(--muted-foreground);
  font-size: 14px;
  line-height: 20px;
}
.sc-card-content {
  display: flex;
  min-height: 0;
  flex-direction: column;
  padding: 0 24px;
}
.sc-chart {
  position: relative;
  flex: none;
}
.sc-chart > * {
  display: block;
}
.sc-card-footer {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  padding: 0 24px;
  font-size: 14px;
  line-height: 1;
}
.sc-trend {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 500;
}
.sc-trend svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.sc-footer-note {
  color: var(--muted-foreground);
}
.sc-example .ts-chart {
  color: var(--muted-foreground);
}
.sc-example .ts-chart__grid line,
.sc-example .ts-chart__polar-grid path,
.sc-example .ts-chart__polar-grid line {
  stroke: var(--border);
}
.sc-example .ts-chart__axes text,
.sc-example .ts-chart__polar-grid text {
  fill: var(--muted-foreground);
  font-size: 12px;
}
.sc-example .ts-chart-tooltip {
  min-width: 128px;
  padding: 7px 10px !important;
  border: 1px solid var(--border) !important;
  border-radius: 8px !important;
  background: var(--card) !important;
  color: var(--card-foreground) !important;
  box-shadow: 0 2px 6px rgb(0 0 0 / 8%);
  font-size: 12px;
}
packages/charts-demo-data/src/shadcn.ts216 lines · dependency
packages/charts-demo-data/src/shadcn.ts
export type ShadcnChartFamily =
  'area' | 'bar' | 'line' | 'pie' | 'radar' | 'radial' | 'tooltip'

export interface ShadcnCatalogSpec {
  name: string
  family: ShadcnChartFamily
  variant: string
  title: string
  description: string
  footerNote: string
  square: boolean
  legend: boolean
}

export interface ShadcnMonthDatum {
  month: string
  desktop: number
  mobile: number
  tablet: number
}

export interface ShadcnSeriesDatum {
  month: string
  series: 'desktop' | 'mobile' | 'tablet' | 'other'
  value: number
}

export interface ShadcnBrowserDatum {
  browser: string
  visitors: number
}

export interface ShadcnRadarDatum {
  month: string
  desktop: number
  mobile?: number
}

export interface ShadcnActivityDatum {
  date: string
  activity: 'running' | 'swimming'
  value: number
}

export const shadcnMonths: readonly ShadcnMonthDatum[] = [
  { month: 'January', desktop: 186, mobile: 80, tablet: 44 },
  { month: 'February', desktop: 305, mobile: 200, tablet: 72 },
  { month: 'March', desktop: 237, mobile: 120, tablet: 58 },
  { month: 'April', desktop: 73, mobile: 190, tablet: 91 },
  { month: 'May', desktop: 209, mobile: 130, tablet: 67 },
  { month: 'June', desktop: 214, mobile: 140, tablet: 82 },
]

export const shadcnSeriesRows: readonly ShadcnSeriesDatum[] =
  shadcnMonths.flatMap((row) => [
    { month: row.month, series: 'desktop', value: row.desktop },
    { month: row.month, series: 'mobile', value: row.mobile },
    { month: row.month, series: 'tablet', value: row.tablet },
  ])

export const shadcnBrowsers: readonly ShadcnBrowserDatum[] = [
  { browser: 'chrome', visitors: 275 },
  { browser: 'safari', visitors: 200 },
  { browser: 'firefox', visitors: 187 },
  { browser: 'edge', visitors: 173 },
  { browser: 'other', visitors: 90 },
]

export const shadcnRadarDefault: readonly ShadcnRadarDatum[] = [
  { month: 'January', desktop: 186 },
  { month: 'February', desktop: 305 },
  { month: 'March', desktop: 237 },
  { month: 'April', desktop: 273 },
  { month: 'May', desktop: 209 },
  { month: 'June', desktop: 214 },
]

export const shadcnRadarFilled: readonly ShadcnRadarDatum[] = [
  { month: 'January', desktop: 186 },
  { month: 'February', desktop: 285 },
  { month: 'March', desktop: 237 },
  { month: 'April', desktop: 203 },
  { month: 'May', desktop: 209 },
  { month: 'June', desktop: 264 },
]

export const shadcnRadarMultiple: readonly ShadcnRadarDatum[] =
  shadcnMonths.map(({ month, desktop, mobile }) => ({
    month,
    desktop,
    mobile,
  }))

export const shadcnRadarLines: readonly ShadcnRadarDatum[] = [
  { month: 'January', desktop: 186, mobile: 160 },
  { month: 'February', desktop: 185, mobile: 170 },
  { month: 'March', desktop: 207, mobile: 180 },
  { month: 'April', desktop: 173, mobile: 160 },
  { month: 'May', desktop: 160, mobile: 190 },
  { month: 'June', desktop: 174, mobile: 204 },
]

export const shadcnActivities: readonly ShadcnActivityDatum[] = [
  { date: '2024-07-15', activity: 'running', value: 450 },
  { date: '2024-07-15', activity: 'swimming', value: 300 },
  { date: '2024-07-16', activity: 'running', value: 380 },
  { date: '2024-07-16', activity: 'swimming', value: 420 },
  { date: '2024-07-17', activity: 'running', value: 520 },
  { date: '2024-07-17', activity: 'swimming', value: 120 },
  { date: '2024-07-18', activity: 'running', value: 140 },
  { date: '2024-07-18', activity: 'swimming', value: 550 },
  { date: '2024-07-19', activity: 'running', value: 600 },
  { date: '2024-07-19', activity: 'swimming', value: 350 },
  { date: '2024-07-20', activity: 'running', value: 480 },
  { date: '2024-07-20', activity: 'swimming', value: 400 },
]

export const shadcnColors = [
  'var(--chart-1, var(--ts-chart-1))',
  'var(--chart-2, var(--ts-chart-2))',
  'var(--chart-3, var(--ts-chart-3))',
  'var(--chart-4, var(--ts-chart-4))',
  'var(--chart-5, var(--ts-chart-5))',
] as const

const titleOverrides: Record<string, string> = {
  'chart-area-default': 'Area Chart',
  'chart-area-stacked-expand': 'Area Chart - Stacked Expanded',
  'chart-bar-default': 'Bar Chart',
  'chart-bar-label-custom': 'Bar Chart - Custom Label',
  'chart-bar-stacked': 'Bar Chart - Stacked + Legend',
  'chart-line-default': 'Line Chart',
  'chart-line-dots-custom': 'Line Chart - Custom Dots',
  'chart-line-label-custom': 'Line Chart - Custom Label',
  'chart-pie-simple': 'Pie Chart',
  'chart-pie-donut-text': 'Pie Chart - Donut with Text',
  'chart-pie-label-custom': 'Pie Chart - Custom Label',
  'chart-radar-default': 'Radar Chart',
  'chart-radar-grid-circle-fill': 'Radar Chart - Grid Circle Filled',
  'chart-radar-grid-circle-no-lines': 'Radar Chart - Grid Circle - No lines',
  'chart-radar-grid-fill': 'Radar Chart - Grid Filled',
  'chart-radar-label-custom': 'Radar Chart - Custom Label',
  'chart-radar-radius': 'Radar Chart - Radius Axis',
  'chart-radial-simple': 'Radial Chart',
  'chart-tooltip-indicator-line': 'Tooltip - Line Indicator',
  'chart-tooltip-indicator-none': 'Tooltip - No Indicator',
  'chart-tooltip-label-custom': 'Tooltip - Custom label',
  'chart-tooltip-label-none': 'Tooltip - No Label',
}

export function getShadcnCatalogSpec(name: string): ShadcnCatalogSpec {
  const parts = name.split('-')
  const family = parts[1]
  if (!isShadcnFamily(family)) {
    throw new TypeError(`Unknown shadcn chart family in ${name}`)
  }
  const variant = parts.slice(2).join('-')
  const title =
    titleOverrides[name] ??
    `${family === 'tooltip' ? 'Tooltip' : `${titleCase(family)} Chart`} - ${variant.split('-').map(titleCase).join(' ')}`
  return {
    name,
    family,
    variant,
    title,
    description:
      (family === 'area' || family === 'bar' || family === 'line') &&
      variant === 'interactive'
        ? 'Showing total visitors for the last 3 months'
        : family === 'area' || family === 'radar'
          ? 'Showing total visitors for the last 6 months'
          : family === 'tooltip'
            ? tooltipDescription(variant)
            : 'January - June 2024',
    footerNote:
      family === 'area' || family === 'radar'
        ? 'January - June 2024'
        : 'Showing total visitors for the last 6 months',
    square: family === 'pie' || family === 'radar' || family === 'radial',
    legend:
      variant.includes('legend') ||
      variant === 'icons' ||
      (variant === 'stacked' && family === 'bar') ||
      (family === 'area' && variant === 'interactive'),
  }
}

function tooltipDescription(variant: string): string {
  if (variant === 'advanced') return 'Tooltip with custom formatter and total.'
  if (variant === 'default') return 'Default tooltip with ChartTooltipContent.'
  if (variant === 'formatter') return 'Tooltip with custom formatter.'
  if (variant === 'icons') return 'Tooltip with icons.'
  if (variant === 'indicator-line') return 'Tooltip with line indicator.'
  if (variant === 'indicator-none') return 'Tooltip with no indicator.'
  if (variant === 'label-custom')
    return 'Tooltip with custom label from chartConfig.'
  if (variant === 'label-formatter') return 'Tooltip with label formatter.'
  if (variant === 'label-none') return 'Tooltip with no label.'
  return 'A chart tooltip.'
}

function titleCase(value: string): string {
  return value.charAt(0).toUpperCase() + value.slice(1)
}

function isShadcnFamily(value: string | undefined): value is ShadcnChartFamily {
  return (
    value === 'area' ||
    value === 'bar' ||
    value === 'line' ||
    value === 'pie' ||
    value === 'radar' ||
    value === 'radial' ||
    value === 'tooltip'
  )
}