Using `ternable` object to draw ternary plots

knitr::opts_chunk$set(
  collapse = TRUE,
  warning = FALSE,
  message = FALSE,
  comment = "#>"
)
#| echo: false
library(prefviz)
library(ggplot2)
library(tourr)
library(dplyr)

This vignette shows you how to build a ternary plot on 2 and higher dimensions, using the ternable object.

Both 2D and high-dimensional (HD) ternary plots require the following 3 components:

You can access all these components conveniently via a ternable object.

ternable object

ternable is a simple S3 object that contains all the data and metadata useful for ternary plots, including the following components:

To create a ternable object, simply call the function as_ternable(). as_ternable() takes 2 arguments:

aecdop22_transformed <- prefviz::aecdop22_transformed |> 
  filter(CountNumber == 0)
head(aecdop22_transformed)

tern22 <- as_ternable(data = aecdop22_transformed, items = ALP:Other)
tern22

ternable helpers - get_tern_*()

While ternable provides you with the essenstial components for building a ternary plot, different plot types (2D or HD) might require slightly different way of representing these commponents. get_tern_*() functions help you do just that.

Under the hood, get_tern_*() perform simple data transformations, i.e., rbind() and cbind(), to help you create the input that are compatible with popular plotting packages, i.e., ggplot2 for 2D ternary plot and tourr for HD ternary plots.

There are 3 get_tern_*() functions:

Drawing a 2D ternary plot

Take the example of the 2022 Australian Federal Election, we would like to take a look at the first preference distribution between the 2 major parties: Labor and the Coalition, and other parties.

The dataset aecdop22_transformed is already in a ternable-friendly format, so we can directly pass it to as_ternable() to create a ternable object.

tern22 <- as_ternable(aecdop22_transformed, ALP:Other)

Now we can use the get_tern_data() function to get the input data for ggplot2.

input_df <- get_tern_data(tern22, plot_type = "2D")
head(input_df)

The output is a data frame where the original columns are combined with the coordinates (x1, x2). These coordinate columns are the observation locations on the plot. We can now use ggplot2 to draw the ternary plot.

p <- ggplot(input_df, aes(x = x1, y = x2)) +
  # Draw the ternary space as an equilateral triangle
  add_ternary_base() + 
  # Plot the observations as points
  geom_point(aes(color = ElectedParty)) + 
  # Add vertex labels, taken from the ternable object
  add_vertex_labels(tern22$simplex_vertices) + 
  labs(title = "First preference in 2022 Australian Federal election")

p

In an election, we would be interested in defining the regions where one party takes the majority over others. We can do that using geom_ternary_region().

This geom takes the barycentric coordinates of a reference point as input, and divides the ternary triangle into 3 regions based on the reference points. These regions are defined by the perpendicular projections of the reference point to the three edges of the triangle. The default reference point is the centroid, which divides the triangle into 3 equal regions.

p + 
  geom_ternary_region(
    x1 = 1/3, x2 = 1/3, x3 = 1/3, # Default reference points. Must sum to 1
    vertex_labels = tern22$vertex_labels, # Labels for the regions
    aes(fill = after_stat(vertex_labels)), 
    alpha = 0.3, color = NA, show.legend = FALSE
  ) +
  scale_fill_manual(
    values = c("ALP" = "red", "LNP" = "blue", "Other" = "grey70"),
    aesthetics = c("fill", "colour")
  )

vertex_labels argument is used to specify the vertex of which the region belongs to. This is helpful when you want to "sync" the aesthetic mapping of geom_ternary_region() with the base layer because you only need to specify the customization once.

Please note that the order in which the labels are provided must match the order of the vertices in the ternary plot. The vertices are listed clockwise, from the right (ALP) to the left (LNP), then ending at the top of the triangle (Other). The best way is to get these labels from ternable$vertex_labels as ternable preserves the vertex orders.

Drawing a high-dimensional ternary plot

Take the example of the 2025 Australian Federal Election, we would like to take a look at the first preference distribution between the 4 major groups: Labor, Coalition, Greens, Independents and the other party. This can be conveniently done using the tourr package, ternable object and the get_tern_*() functions.

A ternary tour requires the following components:

# Load the data
aecdop25_transformed <- prefviz::aecdop25_transformed |> 
  filter(CountNumber == 0)
head(aecdop25_transformed)

tern25 <- as_ternable(aecdop25_transformed, ALP:IND)

# Animate the tour
animate_xy(
  get_tern_data(tern25, plot_type = "HD"), # Dataframe with coordinates of the observations and vertices
  edges = get_tern_edges(tern25), # Edges of the simplex
  obs_labels  = get_tern_labels(tern25), # Labels for the vertices
  axes = "bottomleft"
)

We can add colors to the points.

# Define color mapping
party_colors <- c(
  "ALP" = "#E13940",    # Red
  "LNP" = "#1C4F9C",    # Blue
  "GRN" = "#10C25B",    # Green
  "IND" = "#F39C12",    # Orange
  "Other" = "#95A5A6"   # Gray
)

# Map to your data (assuming your column is called elected_party)
color_vector <- c(rep("black", 5),
  party_colors[aecdop25_transformed$ElectedParty])

# Animate the tour
animate_xy(
  get_tern_data(tern25, plot_type = "HD"), 
  edges = get_tern_edges(tern25),
  obs_labels  = get_tern_labels(tern25),
  col = color_vector,
  axes = "bottomleft"
)


Try the prefviz package in your browser

Any scripts or data that you put into this service are public.

prefviz documentation built on April 13, 2026, 5:07 p.m.