# Adding ordered path to your ternary plot

Adding paths between points in a ternary space can help illustrate transitions or sequences in the data.

In the example of instant-runoff voting in Australian Federal Election, these paths can be used to illustrate the change in preference distribution between rounds of voting in one electorate. This vignette shows how to add paths to both 2D and high-dimensional ternary plots.

# 2D ternary plot

Take the example of the 2022 Australian Federal Election, we would like to take a look at the changes in preference distribution across rounds in three electorates: Higgins, Monash, and Melbourne.

Input data is already in a `ternable`-friendly format, with each row representing the preference distribution in one round of voting in one electorate.

    input_df <- prefviz::aecdop22_transformed |>
       filter(DivisionNm %in% c("Higgins", "Monash", "Melbourne"))
    head(input_df)
    #> # A tibble: 6 × 6
    #>   DivisionNm CountNumber ElectedParty   ALP   LNP Other
    #>   <chr>            <dbl> <chr>        <dbl> <dbl> <dbl>
    #> 1 Higgins              0 ALP          0.285 0.407 0.309
    #> 2 Higgins              1 ALP          0.285 0.407 0.307
    #> 3 Higgins              2 ALP          0.288 0.409 0.303
    #> 4 Higgins              3 ALP          0.292 0.411 0.297
    #> 5 Higgins              4 ALP          0.294 0.416 0.290
    #> 6 Higgins              5 ALP          0.300 0.448 0.252

    # Create ternable object
    tern22 <- as_ternable(input_df, ALP:Other)

For 2D ternary plots, adding ordered paths is straightforward using the `stat_ordered_path()` function, and providing the `order_by` aesthetic. This is equivalent to `geom_path()` when you pre-order your data points in the order you want them to be connected.

In this case, we want to connect the points in the order of round (`order_by = CountNumber`) for each electorate (`group = DivisionNm`).

    # The base plot
    p <- get_tern_data2d(tern22) |>
      ggplot(aes(x = x1, y = x2)) +
      add_ternary_base() +
      geom_ternary_region(
        aes(fill = after_stat(vertex_labels)),
        vertex_labels = tern22$vertex_labels,
        alpha = 0.3, color = "grey50",
        show.legend = FALSE
      ) +
      geom_point(aes(color = ElectedParty)) +
      add_vertex_labels(tern22$simplex_vertices) +
      scale_color_manual(
        values = c("ALP" = "red", "LNP" = "blue", "Other" = "grey70"),
        aesthetics = c("fill", "colour")
      )

    # Add ordered paths
    p + stat_ordered_path(
      aes(group = DivisionNm, order_by = CountNumber, color = ElectedParty),
      size = 0.5
    )

It’s interesting to see such a close win of ALP in Higgins (red line) since the result only flipped in the last round. Meanwhile, wins in Monash and Melbourne were consistent with first preference.

# High-dimensional ternary plot

Let’s take a look at Monash and Melbourne in 2025 election, but this time we will use a high-dimensional ternary plot with 5 parties: ALP, LNP, GRN, IND, and Other.

Unlike 2D ternary plots, we need to group and order the data before plotting by providing the `group` and `order_by` arguments when creating ternable object. Since this data is already ordered by round in the input data frame, we just need to provide the grouping variable (`DivisionNm`).

    input_df2 <- prefviz::aecdop25_transformed |>
      filter(DivisionNm %in% c("Monash", "Melbourne"))
    head(input_df2)
    #> # A tibble: 6 × 8
    #>   DivisionNm CountNumber ElectedParty   ALP   GRN   LNP Other    IND
    #>   <chr>            <dbl> <chr>        <dbl> <dbl> <dbl> <dbl>  <dbl>
    #> 1 Melbourne            0 ALP          0.313     0 0.198 0.439 0.0495
    #> 2 Melbourne            1 ALP          0.316     0 0.199 0.444 0.0408
    #> 3 Melbourne            2 ALP          0.321     0 0.201 0.434 0.0444
    #> 4 Melbourne            3 ALP          0.326     0 0.212 0.408 0.0538
    #> 5 Melbourne            4 ALP          0.343     0 0.236 0.421 0
    #> 6 Melbourne            5 ALP          0.530     0 0     0.470 0

    # Create ternable object
    tern25 <- as_ternable(input_df2, ALP:IND, group = DivisionNm)

Adding paths in a high-dimensional ternary plot is made possible by the `edges` arguments in `display_xy()`. Therefore, besides simplex edges, we need to provide the data edges to the `edges` argument. This can be done by setting `include_data = TRUE` in `get_tern_edges()`.

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

    color_vector <- c(rep("black", 5),
      party_colors[input_df2$ElectedParty])

    edges_color <- c(rep("black", nrow(tern25$simplex_edges)),
      party_colors[input_df2$ElectedParty])

    # Animate the tour
    tourr_data <- get_tern_datahd(tern25)
    animate_xy(
      dplyr::select(tourr_data, starts_with("x")),
      col = color_vector,
      edges = get_tern_edges(tern25, include_data = TRUE),
      edges.col = edges_color,
      obs_labels = tourr_data[["labels"]],
      axes = "bottomleft"
    )