TanStack
Mark Reference

Violin Marks

violinY draws vertical mirrored profiles around categorical x centers. violinX transposes the contract around categorical y centers.

ts
import { violinY } from '@tanstack/charts/violin'

violinY(profileRows, {
  x: 'category',
  y: 'position',
  width: 'width',
  span: 0.8,
  color: 'category',
})

Both marks are also exported from @tanstack/charts and @tanstack/charts/universal.

Prepared profiles and summaries

The mark mirrors prepared normalized widths. It does not choose bins, estimate a density, normalize values, or calculate summaries.

ts
const bins = binY(rows, {
  value: 'body_mass_g',
  by: 'species',
  thresholds: massBoundaries,
  outputs: { count: { reduce: 'count' } },
})
const profiles = normalize(bins, {
  value: 'count',
  by: 'species',
  basis: 'max',
  as: 'width',
})
const summaries = groupBy(rows, {
  by: 'species',
  outputs: {
    median: { value: 'body_mass_g', reduce: median },
  },
})

defineChart({
  marks: [
    violinY(profiles, {
      x: 'species',
      y: 'y',
      width: 'width',
      span: 0.76,
      color: 'species',
      curve: d3AreaXCurve(curveBasis),
    }),
    tickY(summaries, {
      x: 'species',
      y: 'median',
      span: 0.36,
    }),
    dot(summaries, { x: 'species', y: 'median' }),
  ],
  x: {
    scale: scalePoint<string>().domain(species).padding(0.5),
  },
  y: { scale: scaleLinear },
})

This example is a normalized histogram profile. A kernel density estimate can feed the same mark, but its kernel and bandwidth remain data-preparation policy.

width must be finite and within [0, 1]. Nullish and nonfinite profile positions or widths create gaps. Sort samples into the intended profile order before passing them to the mark.

Signatures

ts
function violinY<TDatum>(
  source: Iterable<TDatum>,
  options: ViolinYOptions<TDatum>,
): ChartMark<TDatum>

function violinX<TDatum>(
  source: Iterable<TDatum>,
  options: ViolinXOptions<TDatum>,
): ChartMark<TDatum>

The profile position is numeric or temporal. The category is a numeric or string ChartKey.

The public type surface includes ViolinPosition, ViolinYCurve, ViolinXCurve, ViolinYOptions, and ViolinXOptions.

Options

OptionTypeDefaultMeaning
xChannel<TDatum, ChartKey?>Required by YCategorical center for violinY
yChannel<TDatum, number | Date?>Required by YVertical profile position for violinY
xChannel<TDatum, number | Date?>Required by XHorizontal profile position for violinX
yChannel<TDatum, ChartKey?>Required by XCategorical center for violinX
widthChannel<TDatum, number?>RequiredNormalized mirrored envelope width
spannumber0.8Full peak width in category-step units
idstringLayer-derivedStable mark ID
keyChannel<TDatum, ChartKey>InferredStable profile-sample identity
colorChannel<TDatum, ChartKey?>CategoryValue sent to the chart color scale
fill, strokeVisualChannel<TDatum, string>Resolved colorEnvelope paint; stroke: null omits the outline
fillOpacity, strokeOpacity, and strokeWidthnumberRenderer defaultEnvelope presentation
strokeDasharraystringNoneOutline dash pattern
curveViolinYCurve or ViolinXCurveStraightOrientation-specific renderer-neutral area path generator
statesreadonly ChartMarkState[]NoneFocus-driven area styles
motionChartMarkMotionOptions<TDatum>['motion']NoneKeyed envelope motion policy

span must be positive and finite. span: 1 makes a peak one complete category step wide. Values above 1 can overlap adjacent categories.

Use d3AreaXCurve(curveBasis) for a curved violinY. Use d3Curve(curveBasis) for a curved violinX.

Category scale, identity, and interaction

The categorical axis must resolve to a point or band scale. Width uses the smallest step in the complete configured domain, including categories without profile rows. A single-category profile uses a bounded plot-relative fallback.

Each valid sample contributes one interaction point at its semantic category center even though the envelope has two painted boundaries. The point retains the exact source datum and index. violinY reports the category as xValue and profile position as yValue; violinX transposes them. Use an explicit key when positions repeat within a category.