# D3 Ternary Plot

[![npm version](https://badge.fury.io/js/d3-ternary.svg)](https://www.npmjs.com/package/d3-ternary)

d3-ternary is a JavaScript library and [D3.js](https://d3js.org/) module that makes it easy to create ternary plots, its API exposes configurable functions in the manner of other D3 modules.

Ternary plots are a type of triangular diagram that depict components proportions in three-component systems. Each point in the triangle corresponds to a unique composition of those three components.

Try d3-ternary your browser, [view the introductory notebook on Observable](https://observablehq.com/@julesblm/introducing-d3-ternary?collection=@julesblm/ternary-plots) and see the 'Ternary Plots' [notebook collection](https://observablehq.com/collection/@julesblm/ternary-plots) for examples. Or make ternary plots in the browser on [TernaryPlot.com](https://www.ternaryplot.com) which is built using d3-ternary.

<img alt="Example ternary plot" src="img/demoPlot.png" />

## Installing

If you use npm

```bash
npm install d3-ternary
```

You can also download the [latest release](https://github.com/davenquinn/d3-ternary/releases) on GitHub. For vanilla JS in modern browsers, import d3-ternary from [jsDelivr](https://www.skypack.dev/):

```html
<script type="module">
  import {
    barycentric,
    ternaryPlot,
  } from "https://cdn.jsdelivr.net/npm/d3-ternary@3/+esm";

  const b = barycentric();
  const t = ternaryPlot(b);
</script>
```

## API Reference

### `barycentric()`

**barycentric**() [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/barycentric.ts#L7)

Constructs a new default ternary converter that converts ternary data to Cartesian coordinates. By default, it makes an equilateral triangle on the unit circle centered at the origin.

[#](#barycentricConvertDoc) _barycentric_(_data_) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/barycentric.ts#L25)

Computes `[x,y]` coordinates from a ternary values (a single three-element array). Note that the `[x, y]` coordinates here are unscaled (radius of 1). All values are normalized by default.

[#](#barycentricInvertDoc) _barycentric_.**invert**(_coordinates_) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/barycentric.ts#L35)

Computes ternary values from coordinates (a two-element array `[x, y]`). Note that the `[x, y]` coordinates here are unscaled i.e. a radius of 1.

[#](#barycentricADoc) _barycentric_.**a**([_a_]) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/barycentric.ts#L56)

If **a** is specified, sets the a-accessor to the specified function and returns this barycentric converter. If _a_ is not specified, returns the current a-value accessor, which defaults to:

```javascript
const a = (d) => d[0];
```

[#](#barycentricBDoc) _barycentric_.**b**([_b_]) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/barycentric.ts#L60)

If **b** is specified, sets the b-accessor to the specified function and returns this barycentric converter. If _b_ is not specified, returns the current b-value accessor, which defaults to:

```javascript
const b = (d) => d[1];
```

[#](#barycentricCDoc) _barycentric_.**c**([_c_]) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/barycentric.ts#L64)

If _c_ is specified, sets the c-accessor to the specified function and returns this barycentric converter. If _c_ is not specified, returns the current c-value accessor, which defaults to:

```javascript
const c = (d) => d[2];
```

[#](#barycentricDomainsDoc) _barycentric_.**domains**([_domains_]) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/barycentric.ts#L120)

If _domains_ is specified, sets the domains for each axis to the specified domains in order of `[A, B, C]` and returns this barycentric converter. Each domain should be a two-element array `[min, max]`. All domains must have equal lengths. This method allows you to create "partial" ternary plots that zoom in on a specific region of the full triangle.
For example, setting domains to `[[0.2, 0.4], [0.2, 0.4], [0.2, 0.4]]` will show only the portion of the triangle where each component is between 20-40%.

If _domains_ is not specified, returns the current domains for each axis.

```javascript
// Create a zoomed ternary plot showing only values where
// each component is between 20% and 40%
barycentric.domains([
  [0.2, 0.4], // A axis
  [0.2, 0.4], // B axis
  [0.2, 0.4], // C axis
]);
```

[#](#barycentricScalesDoc) _barycentric_.**scales**() [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/barycentric.ts#L146)

Returns an array of the three [d3.scaleLinear()](https://github.com/d3/d3-scale#scaleLinear) scale functions used internally by the barycentric converter, in order of `[A, B, C]`. These scale functions map the input domains to normalized values between 0 and 1.

[#](#barycentricUnscaledDoc) _barycentric_.**unscaled**(_data_) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/barycentric.ts#L77)

Similar to the standard conversion function, but bypasses the domain scaling. Takes a three-element array of ternary values and returns `[x,y]` coordinates on the unit circle. This is primarily used internally for plotting the triangle bounds and grid lines.

[#](#barycentricRotationDoc) _barycentric_.**rotation**([_angle_]) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/barycentric.ts#L123)

If _angle_ is specified, sets the rotation angle in degrees and returns this barycentric converter. If _angle_ is not specified, returns the current rotation angle, which defaults to 0. Positive angles rotate clockwise.

### `ternaryPlot()`

**ternaryPlot**(_barycentric_) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/ternaryPlot.ts)

Constructs a new default ternary plot generator with the default options.

[#](#ternaryPlotConvertDoc) _ternaryPlot_(_data_) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/ternaryPlot.ts#L359)

Computes `[x, y]` coordinates that are scaled by the plot radius from ternary data. Unlike the [_barycentric_](#barycentricConvertDoc) method, this method takes the plot radius into account.

[#](#ternaryPlotInvertDoc) _ternaryPlot_.**invert**(_coordinates_) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/ternaryPlot.ts#L365)

Computes ternary values from `[x, y]` coordinates that are scaled by the radius. Unlike the _barycentric_.[invert()](#barycentricInvertDoc) method this method takes the plot radius into account. Note that for inverting mouse positions, the ternary plot should centered at the origin of the containing SVG element.

#### Configuration methods

[#](#ternaryPlotRadiusDoc) _ternaryPlot_.**radius**([_radius_]) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/ternaryPlot.ts#L330)

If _radius_ is specified, sets the radius of the ternary plot to the specified number. If _radius_ is not specified, returns the current radius, which defaults to 300 (px).

To set domains without these extra checks, use _ternaryPlot_.[setDomains(_domains_)](#ternaryPlotSetDomains).

#### Layout methods

[#](#ternaryPlotLabelsDoc) _ternaryPlot_.**labels**([_labels_]) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/ternaryPlot.ts#L426)

If _labels_ is specified, sets the axis labels to the labels in order of `[A, B, C]` and returns the ternary plot. If _labels_ is not specified, returns the current labels, which defaults to `[`[A, B, C]`]`.

[#](#ternaryPlotLabelAnglesDoc) _ternaryPlot_.**labelAngles**([_angles_]) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/ternaryPlot.ts#L488)

If _angles_ is specified, sets the angles of the axis labels to the specified angles in order of `[A, B, C]` and returns the ternary plot. If _angles_ is not specified, returns the current label angles, which defaults to `[0, 60, -60]`.

[#](#ternaryPlotLabelOffsetsDoc) _ternaryPlot_.**labelOffsets**([_offsets_]) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/ternaryPlot.ts#L469)

If _offsets_ is specified and is an array, sets the axis label offsets to the specified offsets in order of `[A, B, C]` and returns the ternary plot. If _offsets_ is a number, sets all label offsets to that value. If _offsets_ is not specified, returns the current label offsets, which defaults to `[45, 45, 45]` px.

[#](#ternaryPlotTickAnglesDoc) _ternaryPlot_.**tickAngles**([_angles_]) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/ternaryPlot.ts#256)

If _angles_ is specified, sets the angle of the ticks of each axis to the specified angles in order `[A, B, C]` and returns the ternary plot. If _angles_ is not specified, returns the current tick angles, which defaults to `[0, 60, -60]`.

[#](#ternaryPlotTickAnchorsDoc) _ternaryPlot_.**tickTextAnchors**([_textAnchors_]) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/ternaryPlot.ts#410)

If _textAnchors_ is specified, sets the axis tick [text-anchor](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor) to the specified text-anchors in order of `[A, B, C]` and returns the ternary plot. If _textAnchors_ is not specified, returns the current tick text-anchors, which defaults to `["start", "start", "end"]`.

[#](#ternaryPlotTickSizesDoc) _ternaryPlot_.**tickSizes**([_sizes_]) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/ternaryPlot.ts#370)

If _sizes_ is specified and is an array, sets the axis tick sizes to the specified tick sizes in order of `[A, B, C]` and returns the ternary plot. If _sizes_ is a number, sets the tick sizes of all axes to _sizes_. If _sizes_ is not specified, returns the current tick sizes, which defaults to `[6, 6, 6]` (px).

[#](#ternaryPlotTickFormatDoc) _ternaryPlot_.**tickFormat**([_format_]) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/ternaryPlot.ts#387)

If _format_ is specified, sets the tick format. _format_ can either be a [format specifier string](https://github.com/d3/d3-format#format) that is passed to [`d3.tickFormat()`](https://d3js.org/d3-scale/linear#linear_tickFormat). To implement your own tick format function, pass a custom formatter function, for example `const formatTick = (x) => String(x.toFixed(1))`. If _format_ is not specified, returns the current tick format, which defaults to `"%"`.

#### Plot Methods

[#](#ternaryPlotGridLinesDoc) _ternaryPlot_.**gridLines**([_count_]) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/ternaryPlot.ts#L69)

Generates and returns an array of arrays containing grid line coordinates for each axis. If _count_ is not specified, it defaults to 10. _count_ can be a number or an array of numbers, one for each axis in order of `[A, B, C]`. Each array contains _count_ elements of two-element arrays with the start- and end coordinates of the grid line.

Grid lines are generated using [d3._scaleLinear_.ticks()](https://d3js.org/d3-scale/linear#linear_ticks). The specified count is only a **hint**; the scale may return more or fewer values depending on the domain.

[#](#ternaryPlotTicksDoc) _ternaryPlot_.**ticks**([_count_]) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/ternaryPlot.ts#L158)

Generates and returns an array of tick objects for each axis. If _count_ is not specified, it defaults to 10. _count_ can be a number or an array of numbers, one for each axis in order of `[A, B, C]`.

Each tick object contains:

- `tick`: The formatted tick text
- `position`: An array of [x,y] coordinates
- `angle`: The tick rotation angle
- `textAnchor`: The SVG text-anchor value
- `size`: The length of the tick line

Ticks are generated using [d3._scaleLinear_.ticks()](https://d3js.org/d3-scale/linear#linear_ticks). The specified count is only a **hint**; the scale may return more or fewer values depending on the domain.

[#](#ternaryPlotAxisLabelsDoc) _ternaryPlot_.**axisLabels**([_options_]) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/ternaryPlot.ts#L119)

Generates and returns an array containing axis label objects. Each axis label object contains:

- `position`: An array of `[x,y]` coordinates
- `angle`: The rotation angle of the label
- `label`: The axis label text

Takes an optional configuration object:

```javascript
{
  center: false; // If true, places labels at center of axes instead of vertices
}
```

[#](#ternaryPlotTriangleDoc) _ternaryPlot_.**triangle**() [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/ternaryPlot.ts#L330)

Returns an [SVG path command](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d) for a the outer triangle. This is used for the bounds of the ternary plot and its [clipPath](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/clipPath).

### Transform Functions

[#](#domainsFromTransformDoc) **domainsFromTransform**(_transform_) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/transform.ts#L17)

Converts a transform into domain ranges for the ternary plot axes. This can be used to handle zooming and panning using [d3-zoom](https://d3js.org/d3-zoom). The transform object contains

- `k`: The zoom scale factor (1 = no zoom, >1 = zoomed in)
- `x`: The x-translation
- `y`: The y-translation

Returns an array of `[start, end]` domain ranges for axes A, B, and C. For example:

```javascript
const transform = { k: 1.4285, x: -0.3711, y: -0.2142; }
const domains = domainsFromTransform(transform);
// Returns :
// [
//   [0, 0.7],
//   [0, 0.7],
//   [0.3, 1],
// ]
```

Throws an error if the transform would create invalid domains (outside the [0,1] range) or if trying to zoom out beyond the original triangle.

[#](#transformFromDomainsDoc) **transformFromDomains**(_domains_) [<>](https://github.com/davenquinn/d3-ternary/blob/master/src/transform.ts#L89)

The inverse of domainsFromTransform - converts domain ranges into a `d3.zoom` transform. This is useful when you want to programmatically set the zoom/pan state to focus on specific domain ranges.

Takes an array of `[start, end]` domain ranges for axes A, B, and C. Returns a transform object with:

- `k`: The zoom scale factor
- `x`: The x-translation (unscaled by radius)
- `y`: The y-translation (unscaled by radius)

Example usage:

```javascript
// Zoom in to show only values where each component is between 20-70%
const partialDomains = [
  [0, 0.7],
  [0, 0.7],
  [0.3, 1],
];

const b = barycentric().domains(partialDomains);

const { x, y, k } = transformFromDomains(b.domains());

// We need to sync d3-zoom with the tranform of the partial domains
const initialTransform = d3.zoomIdentity
  .translate(x * radius, y * radius)
  .scale(k);

chart.call(zoom).call(zoom.transform, initialTransform);
```

Note that the translations returned are unscaled by the plot radius - they should be scaled by the radius before being used with SVG transforms.

## Rendering Examples

For detailed examples of how to render ternary plots:

- [D3.js Rendering Example](D3_RENDER.md)
- [React Rendering Example](REACT_RENDER.md)

## Acknowledgments

Several projects have served as a starting point for this module.

- The initial [d3-ternary](https://github.com/davenquinn/d3-ternary) module by [Daven Quinn](https://github.com/davenquinn/)
- [D3 Ternary Plot](https://observablehq.com/@toja/d3-ternary-plot) notebook by Torben Jansen
- [Zoomable Ternary Plot](https://observablehq.com/@dixonj13/zoomable-ternary-plot) notebook by dixonj13

All authors are thanked.
