Ternary Plot

Compositions of three components drawn in a barycentric triangle: alloy and glass compositions, mixing paths, phase-field sampling, or any three-way part-of-whole. Each point is a triple of amounts in the order of the corner labels and is normalized per point, so raw counts, fractions and percentages all land in the right spot. Points and lines sit on the same triangle as the convex hull and the isothermal phase diagram sections: the first component at the right corner, the second at the apex, the third at the left.

Basic ternary

Every series draws one marker per triple. Hover a marker for its fractions, click legend entries to hide series, and use the arrow keys to walk the markers by keyboard.

svelte<script lang="ts">
  import { TernaryPlot, type TernarySeries } from 'matterviz'

  const series: TernarySeries[] = [
    {
      label: `Austenitic`,
      points: [
        [70, 18, 12],
        [68, 20, 12],
        [66, 22, 12],
        [72, 16, 12],
        [64, 24, 12],
      ],
    },
    {
      label: `Ferritic`,
      points: [
        [85, 12, 3],
        [82, 17, 1],
        [88, 11, 1],
        [80, 18, 2],
      ],
    },
    {
      label: `Duplex`,
      points: [
        [70, 22, 8],
        [72, 22, 6],
        [68, 24, 8],
      ],
    },
  ]
</script>

<TernaryPlot {series} labels={[`Fe`, `Cr`, `Ni`]} style="height: 420px" />

Composition path

markers: 'line+points' connects a series’ points in order, so a mixing path or a synthesis trajectory reads as a route through the triangle. line_style takes color, width and dash.

svelte<script lang="ts">
  import { TernaryPlot, type TernarySeries } from 'matterviz'

  // Linear mixing from pure Li2O towards an equal-parts melt, then towards silica
  const path = Array.from({ length: 11 }, (_, idx) => {
    const frac = idx / 10
    return [frac / 3, 1 - frac + frac / 3, frac / 3] as [number, number, number]
  })
  const silica_leg = Array.from({ length: 6 }, (_, idx) => {
    const frac = idx / 5
    return [1 / 3 + (2 / 3) * frac, (1 - frac) / 3, (1 - frac) / 3] as [number, number, number]
  })

  const series: TernarySeries[] = [
    {
      label: `Mixing path`,
      markers: `line+points`,
      points: [...path, ...silica_leg.slice(1)],
      line_style: { width: 2 },
      point_style: { radius: 3 },
    },
    {
      label: `Endmembers`,
      markers: `points`,
      points: [
        [1, 0, 0],
        [0, 1, 0],
        [0, 0, 1],
      ],
      point_style: { radius: 6, symbol_type: `Diamond` },
      color: `#e15759`,
    },
  ]
</script>

<TernaryPlot
  {series}
  labels={[`SiO2`, `Li2O`, `Al2O3`]}
  grid_step={0.2}
  tick_format=".1f"
  style="height: 420px"
/>

Points colored by a property

color_values maps a number per point through color_scale (any d3 interpolator name, an interpolator function or a { fn } mapping values straight to colors) and draws a color bar below the triangle. color_range fixes the span; it defaults to the min/max of every visible series. Null entries fall back to the series color.

svelte<script lang="ts">
  import { TernaryPlot, type TernarySeries } from 'matterviz'

  // A simulated hull-distance landscape sampled on a composition grid
  const points: [number, number, number][] = []
  const color_values: number[] = []
  const step = 0.1
  for (let idx_a = 0; idx_a <= 10; idx_a++) {
    for (let idx_b = 0; idx_b <= 10 - idx_a; idx_b++) {
      const frac_a = idx_a * step
      const frac_b = idx_b * step
      const frac_c = 1 - frac_a - frac_b
      points.push([frac_a, frac_b, frac_c])
      // lowest near the A2B and BC stoichiometries, rising elsewhere
      const to_a2b = Math.hypot(frac_a - 2 / 3, frac_b - 1 / 3, frac_c)
      const to_bc = Math.hypot(frac_a, frac_b - 0.5, frac_c - 0.5)
      color_values.push(Math.min(to_a2b, to_bc) * 0.8)
    }
  }
  const series: TernarySeries[] = [{ label: `Grid`, points, color_values }]
</script>

<TernaryPlot
  {series}
  labels={[`Mg`, `Si`, `O`]}
  color_scale="interpolateMagma"
  color_bar={{ title: `E above hull (eV/atom)`, tick_format: `.2f` }}
  style="height: 460px"
/>

Custom tooltip and click handling

The tooltip snippet and the on_point_click / on_point_hover callbacks receive the point’s series_label, amounts (as given), fractions (normalized), color, color_value and its metadata entry. metadata may be one object per series or an array with one entry per point.

Selected: click a marker

svelte<script lang="ts">
  import { format_num, TernaryPlot, type TernarySeries } from 'matterviz'

  type Sample = { id: string; sintered_at: number }
  let selected = $state<string | null>(null)

  const series: TernarySeries<Sample>[] = [
    {
      label: `Sintered pellets`,
      points: [
        [0.5, 0.3, 0.2],
        [0.4, 0.4, 0.2],
        [0.3, 0.5, 0.2],
        [0.45, 0.25, 0.3],
      ],
      metadata: [
        { id: `P-01`, sintered_at: 1150 },
        { id: `P-02`, sintered_at: 1200 },
        { id: `P-03`, sintered_at: 1250 },
        { id: `P-04`, sintered_at: 1100 },
      ],
      point_style: { radius: 6, symbol_type: `Square` },
    },
  ]
</script>

<TernaryPlot
  {series}
  labels={[`BaO`, `TiO2`, `SrO`]}
  tick_format=".2f"
  on_point_click={({ metadata }) => (selected = metadata?.id ?? null)}
  style="height: 400px"
>
  {#snippet tooltip({ metadata, fractions, series_label })}
    <strong>{metadata?.id}</strong> ({series_label})<br />
    sintered at {metadata?.sintered_at} °C<br />
    Ba : Ti : Sr = {fractions.map((frac) => format_num(frac, `.2f`)).join(` : `)}
  {/snippet}
</TernaryPlot>
<p>Selected: {selected ?? `click a marker`}</p>

Props

PropTypeDefaultDescription
seriesTernarySeries[][]Series of composition triples; see the fields below.
labels[string, string, string]['A', 'B', 'C']Corner labels in triple order: right, top, left.
grid_stepnumber0.1Fraction between grid lines and ticks; 0 draws neither.
show_gridbooleantrueDraw the constant-fraction grid lines.
show_ticksbooleantrueDraw tick marks and labels along the edges.
tick_formatstring'.0%'d3-format spec for tick labels and tooltip fractions (values in [0, 1]).
color_scaleColorBarScale'interpolateViridis'Scale for color_values.
color_range[number, number]data min/maxSpan of the color scale.
color_barColorBar props or null{}Color bar settings; null hides it.
legendLegendConfig or null{}Legend settings; shown automatically with two or more series, show_legend forces.
tooltipSnippet<[TernaryPointProps]>Replaces the default fractions tooltip.
on_point_click(props & { event }) => voidClick or Enter/Space on a marker.
on_point_hover(props & { event } \| null) => voidHover or focus enter/leave; null on leave.
paddingSides{t: 30, b: 34, l: 50, r: 50}Space around the triangle for corner and tick labels.

TernarySeries fields: points (triples), label, color, markers ('points' default, 'line', 'line+points'), point_style (one PointStyle or one per point), line_style, color_values, metadata, visible, id.

The geometry helpers are exported too: ternary_fractions, ternary_to_xy, xy_to_ternary, inside_triangle, ternary_grid_lines and ternary_layout.