---
title: "Create Ternary Plots in R"
author: "Martin R. Smith"
date: "`r Sys.Date()`"
output: rmarkdown::html_document

vignette: >
  %\VignetteIndexEntry{Creating and manipulating ternary plots}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

"Ternary" is an R package to allow the creation of ternary plots in the standard
R graphics environment.  I hope that it proves simple to use.

For simple use cases, generate Ternary plots using the point-and-click
Shiny app:

```{r shiny-app, eval = FALSE}
install.packages("Ternary")
Ternary::TernaryApp()
```

For greater control over your plots, use the full R implementation.

Install the package with:

```{r install-package, eval = FALSE}
install.packages("Ternary")
```

Or if you want the latest development version of the package:

```{R github-package, eval = FALSE}
if (!require("devtools")) install.packages("devtools")
install_github("ms609/Ternary", args = "--recursive")
```

Once the package is installed, load it into the current R session with 
```{R library-ternary}
library("Ternary")
```

There are two stages to creating a ternary plot: first, rendering the plot, styled
as you like it and pointing in any of the four compass directions; secondly, adding data.


## Create a blank plot

At its simplest, all you need to do is type
```{R create-blank-plot, fig.asp = 1}
TernaryPlot()
```

The following charts show which corners are which, under different orientations:

```{R create-simple-plot, fig.width = 7, fig.height = 7}
par(mfrow = c(2, 2), mar = rep(0.5, 4))
for (dir in c("up", "right", "down", "le")) {
  TernaryPlot(point = dir, atip = "A", btip = "B", ctip = "C",
              alab = "Aness", blab = "Bness", clab = "Cness")
  TernaryText(list(A = c(10, 1, 1), B = c(1, 10, 1), C = c(1, 1, 10)),
              labels = c("P1", "P2", "P3"),
              col = cbPalette8[4], font = 2)
}
```

## Plot two stylised plots side by side, and plot data

```{R two-stylised-plots, fig.asp = 1/2}
# Configure plotting area
par(mfrow = c(1, 2), mar = c(0.3, 0.3, 1.3, 0.3))

# Initial plot
TernaryPlot(alab = "Redder \u2192", blab = "\u2190 Greener", clab = "Bluer \u2192",
            lab.col = c("red", "darkgreen", "blue"),
            main = "Colours", # Title
            point = "right", lab.cex = 0.8, grid.minor.lines = 0,
            grid.lty = "solid", col = rgb(0.9, 0.9, 0.9), grid.col = "white", 
            axis.col = rgb(0.6, 0.6, 0.6), ticks.col = rgb(0.6, 0.6, 0.6),
            axis.rotate = FALSE,
            padding = 0.08)
# Colour the background:
cols <- TernaryPointValues(rgb)
ColourTernary(cols, spectrum = NULL)

# Add data points
data_points <- list(
  R = c(255, 0, 0), 
  O = c(240, 180, 52),
  Y = c(210, 222, 102),
  G = c(111, 222, 16),
  B = c(25, 160, 243),
  I = c(92, 12, 243),
  V = c(225, 24, 208)
)

# Compute RGB colours based on point values
PointColour <- function(x) {
  rgb(x[1], x[2], x[3], maxColorValue = 255, # Use point values for RGB
      alpha = 128 # Semitransparency
      )
}
data_col <- sapply(data_points, PointColour)
AddToTernary(graphics::points, data_points, pch = 21, cex = 2.8, bg = data_col)
AddToTernary(text, data_points, names(data_points), cex = 0.8, font = 2)
legend("bottomright", 
       legend = c("Red", "Orange", "Yellow", "Green"),
       cex = 0.8, bty = "n", pch = 21, pt.cex = 1.8,
       pt.bg = c(rgb(255,   0,   0, 128, NULL, 255), 
                 rgb(240, 180,  52, 128, NULL, 255),
                 rgb(210, 222, 102, 128, NULL, 255),
                 rgb(111, 222,  16, 128, NULL, 255)),
       )


###
# Next plot:
###
TernaryPlot("Steam", "Ice", "Water", 
            grid.lines = 5, grid.lty = "dotted",
            grid.minor.lines = 1, grid.minor.lty = "dotted",
            point = "West")

# Another way to add a title
title("Water phases", cex.main = 0.8)

# Add horizontal grid lines
HorizontalGrid()

# Define a polygon
middle_triangle <- matrix(c(
  30, 40, 30,
  30, 30, 40,
  55, 20, 25
), ncol = 3, byrow = TRUE)

# Add polygon to plot
TernaryPolygon(middle_triangle, col = "#aaddfa", border = "grey")

# Connect corners of polygon to plot corners
TernaryLines(list(c(0, 100, 0), middle_triangle[1, ]), col = "grey")
TernaryLines(list(c(0, 0, 100), middle_triangle[2, ]), col = "grey")
TernaryLines(list(c(100, 0, 0), middle_triangle[3, ]), col = "grey")

# Add an arrow
TernaryArrows(c(20, 20, 60), c(30, 30, 40), length = 0.2, col = "darkblue")
```

### Styling points

More sophisticated plots can be created, for example styling each point 
according to additional properties of the data, in a manner analogous to the
standard plotting functions:

```{r point-styling, fig.asp = 1}
# Configure plotting area
par(mar = rep(0.3, 4))

# Example data object
dat <- data.frame(sio2 = c(2, 4, 10, 20),
                  fe2o3 = c(5, 6, 7, 8),
                  al2o3 = c(12, 11, 10, 9),
                  grain_size = c(20, 16, 12, 8),
                  reflectance = c(80, 63, 51, 20))

# Define a colour spectrum
spectrumBins <- 255 # Number of bins to use
mySpectrum <- rowCol <- hcl.colors(spectrumBins, palette = "viridis")

# Cut our reflectance data into categories
binnedReflectance <- cut(dat$reflectance, spectrumBins)

# Assign each data point a colour from the spectrum
pointCol <- mySpectrum[binnedReflectance]


# Define a size range
maxSize <- 2.4 # Size of largest point, in plotting units
sizes <- dat$grain_size
pointSize <- sizes * maxSize / max(sizes)

# Initialize the plot
TernaryPlot(atip = expression(SiO[2]),
            btip = expression(paste(Fe[2], O[3], " (wt%)")),
            ctip = expression(paste(Al[2], O[3]))
)

# Plot the points
TernaryPoints(dat[, c("sio2", "fe2o3", "al2o3")],
              cex = pointSize, # Point size
              col = pointCol,  # Point colour
              pch = 16         # Plotting symbol (16 = filled circle)
              )

# Legend for colour scale
PlotTools::SpectrumLegend(
  "topleft",
  cex = 0.8, # Font size
  palette = mySpectrum,
  legend = paste(
    seq(from = max(dat$reflectance), to = min(dat$reflectance),
        length.out = 5),
    "%"
  ),
  bty = "n", # No framing box
  xpd = NA, # Don't clip at margins
  # title.font = 2, # Bold.  Argument only available in R>3.6
  title = "Reflectance"
)

# Legend for point size
PlotTools::SizeLegend(
  "topright",
  width = c(0, maxSize),
  lend = "round", # Round end of scale bar
  legend = paste(
    signif(seq(max(sizes), 0, length.out = 5), digits = 3),
    "\u03bcm" # µm
  ),
  title = "Grain size",
  # title.font = 2, # Bold. Argument only available in R>3.6
  bty = "n", # Do not frame with box
  cex = 0.8
)
```

See the "Points" tab of the shiny app for a point-and-click implementation.


### Cartesian coordinates 

It is also possible to use cartesian coordinates to plot onto the graph.

By default, the plotting area is a 1&times;1 square.

```{r cartesian, fig.asp = 1}
par(mar = rep(0, 4)) # Reduce margins
TernaryPlot(point = "right", clockwise = FALSE)
cat("X range in this orientation:", TernaryXRange())
cat("Y range in this orientation:", TernaryYRange())
arrows(x0 = 0.5, y0 = 0.4, x1 = sqrt(3) / 2, y1 = 0.4, length = 0.1,
       col = cbPalette8[2])
text(x = mean(c(0.5, sqrt(3) / 2)), y = 0.4, "Increasing X", pos = 3,
     col = cbPalette8[2])
text(x = 0.5, y = 0, "(0.5, 0)", col = cbPalette8[3])
text(x = 0.8, y = -0.5, "(0.8, -0.5)", col = cbPalette8[3])
```

Note the anticlockwise axis labelling on this plot, obtained using 
`clockwise = FALSE`.

## Contours

A plot can be coloured and contoured according to the output of a mathematical expression:

```{r contours, fig.asp = 1}
par(mar = rep(0.2, 4))
TernaryPlot(alab = "a", blab = "b", clab = "c")

FunctionToContour <- function(a, b, c) {
  a - c + (4 * a * b) + (27 * a * b * c)
}

# Add contour lines
values <- TernaryContour(FunctionToContour, resolution = 36L, filled = TRUE)
zRange <- range(values$z, na.rm = TRUE)

# Continuous legend for colour scale
PlotTools::SpectrumLegend(
  "topleft",
  legend = round(seq(zRange[1], zRange[2], length.out = 4), 3),
  palette = hcl.colors(265, palette = "viridis", alpha = 0.6),
  bty = "n",    # No framing box
  inset = 0.02,
  xpd = NA      # Do not clip at edge of figure
)
```

or according to the density of points across the plot:

```{r density-contours, fig.asp = 1}
par(mar = rep(0.2, 4))
TernaryPlot(axis.labels = seq(0, 10, by = 1))

nPoints <- 4000L
coordinates <- cbind(abs(rnorm(nPoints, 2, 3)),
                     abs(rnorm(nPoints, 1, 1.5)),
                     abs(rnorm(nPoints, 1, 0.5)))

# Colour plot background
ColourTernary(TernaryDensity(coordinates, resolution = 10L))

# Add points
TernaryPoints(coordinates, col = "red", pch = ".")

# Contour by point density
TernaryDensityContour(coordinates, resolution = 30L)
```

The following image demonstrates the behaviour of the density estimates when 
points fall on boundaries of the triangular grid cells; text denotes the number 
of points within the cell, with cells straddling _n_ cells contributing 1/_n_ 
of a point to each cell straddled.

```{r low-resolution-density-contours, echo = FALSE, fig.asp = 1}
coordinates <- list(middle = c(1, 1, 1),
                 top = c(3, 0, 0),
                 belowTop = c(2, 1, 1),
                 leftSideSolid = c(9, 2, 9),
                 leftSideSolid2 = c(9.5, 2, 8.5),
                 right3way = c(1, 2, 0),
                 rightEdge = c(2.5, 0.5, 0),
                 leftBorder = c(1, 1, 4),
                 topBorder = c(2, 1, 3),
                 rightBorder = c(1, 2, 3)
               )
par(mfrow = c(2, 2), mar = rep(0.2, 4))
TernaryPlot(grid.lines = 3, axis.labels = 1:3, point = "up")
values <- TernaryDensity(coordinates, resolution = 3L)
ColourTernary(values)
TernaryPoints(coordinates, col = "red")
text(values[1, ], values[2, ], paste(values[3, ], "/ 6"), cex = 0.8)

TernaryPlot(grid.lines = 3, axis.labels = 1:3, point = "right")
values <- TernaryDensity(coordinates, resolution = 3L)
ColourTernary(values)
TernaryPoints(coordinates, col = "red")
text(values[1, ], values[2, ], paste(values[3, ], "/ 6"), cex = 0.8)

TernaryPlot(grid.lines = 3, axis.labels = 1:3, point = "down")
values <- TernaryDensity(coordinates, resolution = 3L)
ColourTernary(values)
TernaryPoints(coordinates, col = "red")
text(values[1, ], values[2, ], paste(values[3, ], "/ 6"), cex = 0.8)

TernaryPlot(grid.lines = 3, axis.labels = 1:3, point = "left")
values <- TernaryDensity(coordinates, resolution = 3L)
ColourTernary(values)
TernaryPoints(coordinates, col = "red")
text(values[1, ], values[2, ], paste(values[3, ], "/ 6"), cex = 0.8)

TernaryDensityContour(t(vapply(coordinates, I, double(3L))),
                      resolution = 24L, tolerance = -0.02, col = "orange")


```

## Magnification

Perhaps the action on a plot is constrained to a small region of ternary space.
It's possible to "zoom in" -- i.e. magnify and crop the ternary plot to the region
of interest.

To do this you can specify the _x_ and _y_ limits of the region of interest. 

`TernaryCoords` might be useful in establishing the cartesian coordinates of a
particular point in ternary space.  
Ensure that _dx_ = _dy_ if you want an isometric plot.

```{r find-corners}
# Define points corresponding to corners of a region to plot
my_corners <- list(c(22, 66, 12), c(22, 72, 6), c(15, 80, 5), c(12, 76, 12))

# Print Cartesian coordinates of points
vapply(my_corners, TernaryCoords, direction = 1, FUN.VALUE = c(x = 0, y = 0))
```

The `padding` parameter is added as a margin to each side of the region specified
using `xlim` and `ylim`:

```{r close-up, fig.asp = 1}
# Remove plot margins
par(mar = rep(0, 4))

# Create clipped plotting area
TernaryPlot(xlim = c(0.28, 0.38), ylim = c(0.1, 0.2), padding = 0.04)

# Annotate grid lines at user-specified points:
TernaryText(list(c(8, 72, 20), c(8, 82, 10)), c(20, 10), srt = -60, cex = 0.9,
            col = "darkgrey")
TernaryText(list(c(10, 69, 21), c(20, 64, 16)), c(10, 20), srt = 0, cex = 0.9,
            col = "darkgrey")

# Plot desired polygon
TernaryPolygon(my_corners, col = "#2cbe4e88")

# Show xlim, ylim and padding, using Cartesian coordinates
lines(c(0.28, 0.28, 0.38, 0.38, 0.28), c(0.1, 0.2, 0.2, 0.1, 0.1))
text(0.28, 0.15, "xlim[1]", pos = 2, srt = 90)
text(0.38, 0.15, "xlim[2]", pos = 4, srt = 90)
text(0.33, 0.1, "ylim[1]", pos = 1)
text(0.33, 0.2, "ylim[2]", pos = 3)
text(0.38, 0.1, "<padding>", pos = 4, cex = 0.75)
text(0.38, 0.1, "<padding> ", pos = 2, cex = 0.75, srt = 90)
```

An alternative approach is to plot a triangular sub-region of the larger
ternary space, allowing axis annotations to be read more easily.

```{r subregion-plot, fig.asp = 1}
TernaryPlot(region = my_corners) # Fit plotted region to data
TernaryPolygon(my_corners, col = "#2cbe4e88")
```

A region can be defined manually; the smallest triangle that covers the region
will be employed.

```{r manual-subregion, fig.asp = 1}
region <- list(
   c(amin = 10, bmin = 70, cmin = 0),
   c(amax = 30, bmax = 70, cmax = 10)
)
TernaryPlot(region = region)

# Data will be plotted even if it falls outside the plotted axes
TernaryPolygon(my_corners, col = "#2cbe4e88")
```


## Further tutorials

For further examples of usage, see the accompanying vignettes:

- [Introduction for new R users](new-users.html)

- [Interpolating and contouring](interpolation.html)

- [Annotation of points](annotation.html)

- [Plotting Holdridge climate zones](Holdridge.html)


## Bugs and feature requests

I hope the package proves useful. If there"s anything it can"t do that you wish
it could, please let me know by opening a
[Github issue](https://github.com/ms609/Ternary/issues/new?title=Suggestion:+).
