# `Tucan.Geometry`
[🔗](https://github.com/pnezis/tucan/blob/v0.7.0/lib/tucan/geometry.ex#L1)

An API for drawing common geometrical shapes.

`Tucan.Geometry` offers an interface for rendering typical geometric forms, such as
lines, circles, and polygons. Unlike the general Tucan API, these functions require
only the parameters defining the respective geometric shapes, rather than a
dataset and data fields.

You can overlay tucan plots with geometrical shapes through the `Tucan.layers/2` function.

## Examples

```tucan
Tucan.layers([
  Tucan.Geometry.circle({3, 3}, 4, line_color: "red", stroke_width: 3),
  Tucan.Geometry.rectangle({-2, 10}, {7, -3}, line_color: "green"),
  Tucan.Geometry.rectangle({-3.5, 0.1}, {8.1, -4.2},
    fill_color: "pink",
    fill_opacity: 0.3
  ),
  Tucan.Geometry.polyline([{1, 1}, {2, 7}, {5, 3}],
    closed: true,
    fill_color: "green",
    fill_opacity: 0.3
  )
])
|> Tucan.Scale.set_xy_domain(-5, 11)
|> Tucan.set_size(400, 400)
|> Tucan.set_title("Tucan.Geometry examples")
```

# `point`

```elixir
@type point() :: {number(), number()}
```

A cartesian point in the form `{x, y}`

# `arrow`

```elixir
@spec arrow(start :: point(), end_point :: point(), opts :: keyword()) :: VegaLite.t()
```

Draws an arrow from the `start` to the `end` point.

The arrow head points in the direction of the arrow on the screen, taking into
account the scales of the plot.

> #### Concatenated plots {: .warning}
>
> Inside concatenated plots, e.g. `Tucan.hconcat/2`, the scales of the plot cannot
> be referenced and the direction of the head is computed in data units. It matches
> the arrow only if the x and y axes have the same scale.

## Options

### Styling Options

* `:head_size` (`t:pos_integer/0`) - The size of the arrow head. The default value is `60`.
* `:line_color` (`t:String.t/0`) - The color of the line The default value is `"black"`.
* `:opacity` (`t:number/0`) - The overall opacity of the mark
* `:stroke_dash` (list of `t:pos_integer/0`) - An array of alternating stroke, space lengths in pixels for creating dashed
  or dotted lines.
* `:stroke_width` (`t:pos_integer/0`) - The stroke width in pixels The default value is `2`.

## Examples

Pointing to a point of a scatter plot, with an annotation:

```tucan
Tucan.scatter(:cars, "Horsepower", "Miles_per_Gallon")
|> Tucan.Layers.append(Tucan.Geometry.arrow({180, 35}, {225, 16}, line_color: "red"))
|> Tucan.annotate(180, 37, "The most powerful car", color: "red")
```

Arrows in every direction:

```tucan
arrows =
  for angle <- 0..330//30 do
    radians = angle * :math.pi() / 180
    Tucan.Geometry.arrow({0, 0}, {5 * :math.cos(radians), 5 * :math.sin(radians)})
  end

Tucan.layers(arrows)
|> Tucan.set_size(250, 250)
```

# `circle`

```elixir
@spec circle(center :: point(), radius :: number(), opts :: keyword()) :: VegaLite.t()
```

Draws a circle with the given `center` and `radius`.

## Options

### Styling Options

* `:fill_color` (`t:String.t/0`) - The fill color of the marks. This will override the `color` encoding if set.
* `:fill_opacity` (`t:number/0`) - The fill opacity of the plotted elements. The default value is `1`.
* `:line_color` (`t:String.t/0`) - The color of the line
* `:opacity` (`t:number/0`) - The overall opacity of the mark
* `:stroke_dash` (list of `t:pos_integer/0`) - An array of alternating stroke, space lengths in pixels for creating dashed
  or dotted lines.
* `:stroke_opacity` (`t:number/0`) - The opacity of the stroke. The default value is `1`.
* `:stroke_width` (`t:pos_integer/0`) - The stroke width in pixels The default value is `1`.

## Examples

```tucan
Tucan.layers([
  Tucan.Geometry.circle({3, 2}, 5),
  Tucan.Geometry.circle({-1, 6}, 2, line_color: "red"),
  Tucan.Geometry.circle({0, 1}, 4, line_color: "green", stroke_width: 5)
])
|> Tucan.Scale.set_x_domain(-5, 10)
|> Tucan.Scale.set_y_domain(-5, 10)
```

You can also draw filled circles by setting the `:fill_color` option. Opacity of the fill color and
the stroke color can be configured by `:opacity` or independently by `:fill_opacity` and
`:stroke_opacity`.

```tucan
Tucan.layers([
  Tucan.Geometry.circle({3, 2}, 5, stroke_width: 3, stroke_opacity: 0.4),
  Tucan.Geometry.circle({-1, 6}, 2, line_color: "red", fill_color: "pink", opacity: 0.3),
  Tucan.Geometry.circle({0, 1}, 4, line_color: "green", stroke_width: 5, fill_color: "green", fill_opacity: 0.2)
])
|> Tucan.Scale.set_x_domain(-5, 10)
|> Tucan.Scale.set_y_domain(-5, 10)
```

> #### Circles and plot dimensions {: .tip}
>
> Notice that the plot must be square with identical scale domains across
> the two axes for the circle to appear circular. In a different case it will
> look like an ellipsis.
>
> ```tucan
> circle = Tucan.Geometry.circle({0, 0}, 1)
>
> Tucan.hconcat([
>   circle
>   |> Tucan.set_size(150, 150)
>   |> Tucan.set_title("Square frame"),
>   circle
>   |> Tucan.Scale.set_x_domain(-2, 2)
>   |> Tucan.Scale.set_y_domain(-1, 1)
>   |> Tucan.set_size(150, 150)
>   |> Tucan.set_title("Different domains"),
>   circle
>   |> Tucan.set_size(200, 150)
>   |> Tucan.set_title("Different frame dimensions")
> ])
> ```

# `ellipse`

```elixir
@spec ellipse(
  center :: point(),
  x_radius :: number(),
  y_radius :: number(),
  rotation_angle :: number(),
  opts :: keyword()
) :: VegaLite.t()
```

Draws an ellipse with the given `center`, `x_radius`, `y_radius`, and `rotation_angle`.

`x_radius` is expected to be a positive number corresponding to the *major semi-axis* length, `y_radius`
a positive number corresponding to the *minor semi-axis* length and `rotation_angle` the ellipse's
rotation angle with respect to the *x-axis*.

## Options

### Styling Options

* `:fill_color` (`t:String.t/0`) - The fill color of the marks. This will override the `color` encoding if set.
* `:fill_opacity` (`t:number/0`) - The fill opacity of the plotted elements. The default value is `1`.
* `:line_color` (`t:String.t/0`) - The color of the line
* `:opacity` (`t:number/0`) - The overall opacity of the mark
* `:stroke_dash` (list of `t:pos_integer/0`) - An array of alternating stroke, space lengths in pixels for creating dashed
  or dotted lines.
* `:stroke_opacity` (`t:number/0`) - The opacity of the stroke. The default value is `1`.
* `:stroke_width` (`t:pos_integer/0`) - The stroke width in pixels The default value is `1`.

## Examples

```tucan
Tucan.layers([
  Tucan.Geometry.ellipse({0, 0}, 5, 3, 0),
  Tucan.Geometry.ellipse({3, 4}, 5, 4, 45, line_color: "red"),
  Tucan.Geometry.ellipse({4, 1}, 7, 2, -30, line_color: "green", stroke_width: 2)
])
|> Tucan.Scale.set_x_domain(-5, 10)
|> Tucan.Scale.set_y_domain(-5, 10)
```

# `function`

```elixir
@spec function(
  fun :: (number() -&gt; number()),
  range :: {number(), number()},
  opts :: keyword()
) ::
  VegaLite.t()
```

Draws the curve of the function `fun` in the `{min, max}` range of `x`.

The function is sampled at `:samples` equally spaced points. The curve is broken
at the points where the function is not defined, i.e. it raises an arithmetic error
or it returns a value that is not a number, e.g. `:math.sqrt(x * x - 1)` is drawn
as two separate branches.

> #### Asymptotes {: .warning}
>
> The curve connects consecutive samples, so a vertical asymptote between two
> samples, e.g. of `1 / x`, is drawn as a steep line. Split the range around it, or
> return `nil` near it.

## Options

* `:samples` (`t:pos_integer/0`) - The number of points the curve is sampled at. The default value is `200`.

### Styling Options

* `:line_color` (`t:String.t/0`) - The color of the line
* `:opacity` (`t:number/0`) - The overall opacity of the mark
* `:stroke_dash` (list of `t:pos_integer/0`) - An array of alternating stroke, space lengths in pixels for creating dashed
  or dotted lines.
* `:stroke_opacity` (`t:number/0`) - The opacity of the stroke. The default value is `1`.
* `:stroke_width` (`t:pos_integer/0`) - The stroke width in pixels The default value is `2`.

## Examples

```tucan
Tucan.layers([
  Tucan.Geometry.function(&:math.sin/1, {-2 * :math.pi(), 2 * :math.pi()}),
  Tucan.Geometry.function(&:math.cos/1, {-2 * :math.pi(), 2 * :math.pi()}, line_color: "red")
])
|> Tucan.set_width(400)
```

The probability density of a normal distribution on top of a histogram of samples:

```tucan
:rand.seed(:exsss, {1, 2, 3})
samples = for _i <- 1..1000, do: %{x: :rand.normal()}

pdf = fn x -> :math.exp(-x * x / 2) / :math.sqrt(2 * :math.pi()) end

Tucan.layers([
  Tucan.histogram(samples, "x", relative: true, extent: [-4, 4], step: 0.25),
  Tucan.Geometry.function(fn x -> pdf.(x) * 0.25 end, {-4, 4}, line_color: "red")
])
```

# `line`

```elixir
@spec line(start :: point(), end_point :: point(), opts :: keyword()) :: VegaLite.t()
```

Draws a line segment from the `start` to the `end` point.

## Options

### Styling Options

* `:line_color` (`t:String.t/0`) - The color of the line
* `:opacity` (`t:number/0`) - The overall opacity of the mark
* `:stroke_dash` (list of `t:pos_integer/0`) - An array of alternating stroke, space lengths in pixels for creating dashed
  or dotted lines.
* `:stroke_width` (`t:pos_integer/0`) - The stroke width in pixels The default value is `1`.

## Examples

```tucan
Tucan.layers([
  Tucan.Geometry.line({0, 0}, {10, 10}, stroke_dash: [5, 5]),
  Tucan.Geometry.line({0, 10}, {10, 0}, line_color: "red", stroke_width: 3)
])
```

The identity line on top of a scatter plot:

```tucan
data = for i <- 1..50, do: %{actual: i, predicted: i + 8 * :math.sin(i)}

Tucan.layers([
  Tucan.scatter(data, "actual", "predicted"),
  Tucan.Geometry.line({0, 0}, {50, 50}, line_color: "gray", stroke_dash: [4, 4])
])
```

# `polyline`

```elixir
@spec polyline(vertices :: [point()], opts :: keyword()) :: VegaLite.t()
```

Draws a polyline defined by the given vertices.

The order of the vertices defines the order of the line segments that
will be generated.

The polyline or polygon will be added as a new layer to the given plot `vl`.

## Options

* `:closed` (`t:boolean/0`) - Whether a last segment will be added between the last and first points. The default value is `false`.

### Styling Options

* `:fill_color` (`t:String.t/0`) - The fill color of the marks. This will override the `color` encoding if set.
* `:fill_opacity` (`t:number/0`) - The fill opacity of the plotted elements. The default value is `1`.
* `:line_color` (`t:String.t/0`) - The color of the line
* `:opacity` (`t:number/0`) - The overall opacity of the mark
* `:stroke_dash` (list of `t:pos_integer/0`) - An array of alternating stroke, space lengths in pixels for creating dashed
  or dotted lines.
* `:stroke_opacity` (`t:number/0`) - The opacity of the stroke. The default value is `1`.
* `:stroke_width` (`t:pos_integer/0`) - The stroke width in pixels The default value is `1`.

## Examples

```tucan
Tucan.Geometry.polyline([{-1, 1}, {-2, 4}, {-1, 3}, {4, 7}, {8, 2}])
|> Tucan.Scale.set_x_domain(-3, 10)
|> Tucan.Scale.set_y_domain(-1, 9)
```

If `:closed` is set to `true` a line segment is added between the last and
first point.

```tucan
Tucan.Geometry.polyline([{-1, 1}, {-2, 4}, {-1, 3}, {4, 7}, {8, 2}], closed: true)
|> Tucan.Scale.set_x_domain(-3, 10)
|> Tucan.Scale.set_y_domain(-1, 9)
```

You can change the appearance of the polyline/polygon.

```tucan
points = [{-1, 1}, {-2, 4}, {-1, 3}, {4, 7}, {8, 2}]

Tucan.Geometry.polyline(points,
  closed: true,
  fill_color: "red",
  line_color: "green",
  fill_opacity: 0.3,
  stroke_width: 3,
  stroke_dash: [5, 3]
)
|> Tucan.Scale.set_x_domain(-3, 10)
|> Tucan.Scale.set_y_domain(-1, 9)
```

# `rectangle`

```elixir
@spec rectangle(upper_left :: point(), bottom_right :: point(), opts :: keyword()) ::
  VegaLite.t()
```

Draws a rectangle defined by the given `upper_left` and `bottom_right` points.

## Options

See `polyline/2`

## Examples

```tucan
Tucan.layers([
  Tucan.Geometry.rectangle({1, 5}, {5, 1}, line_color: "black", stroke_width: 2, stroke_dash: [5, 5]),
  Tucan.Geometry.rectangle({-2, 10}, {7, -3}, line_color: "green"),
  Tucan.Geometry.rectangle({-3.5, 0.1}, {8.1, -4.2}, fill_color: "pink", fill_opacity: 0.3)
])
|> Tucan.Scale.set_xy_domain(-5, 11)
|> Tucan.set_size(400, 300)
```

# `regular_polygon`

```elixir
@spec regular_polygon(
  center :: point(),
  radius :: number(),
  sides :: pos_integer(),
  opts :: keyword()
) ::
  VegaLite.t()
```

Draws a regular polygon with the given number of `sides`, whose vertices are on a
circle with the given `center` and `radius`.

## Options

* `:rotation` - The rotation of the polygon in degrees, counter clockwise. With `0` the first
  vertex points up. The default value is `0`.

### Styling Options

* `:fill_color` (`t:String.t/0`) - The fill color of the marks. This will override the `color` encoding if set.
* `:fill_opacity` (`t:number/0`) - The fill opacity of the plotted elements. The default value is `1`.
* `:line_color` (`t:String.t/0`) - The color of the line
* `:opacity` (`t:number/0`) - The overall opacity of the mark
* `:stroke_dash` (list of `t:pos_integer/0`) - An array of alternating stroke, space lengths in pixels for creating dashed
  or dotted lines.
* `:stroke_opacity` (`t:number/0`) - The opacity of the stroke. The default value is `1`.
* `:stroke_width` (`t:pos_integer/0`) - The stroke width in pixels The default value is `1`.

## Examples

```tucan
Tucan.layers([
  Tucan.Geometry.regular_polygon({0, 0}, 1, 3, fill_color: "orange", fill_opacity: 0.4),
  Tucan.Geometry.regular_polygon({3, 0}, 1, 4, rotation: 45, line_color: "red"),
  Tucan.Geometry.regular_polygon({6, 0}, 1, 6, fill_color: "teal", stroke_width: 2)
])
|> Tucan.Scale.set_x_domain(-1.5, 7.5)
|> Tucan.Scale.set_y_domain(-1.5, 1.5)
|> Tucan.set_size(360, 120)
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
