Setup
Packages
Base Plot
To avoid repetitive code, we set defaults and make a base plot:
my_font <- "Roboto Condensed"
my_font_size <- 20
my_point_size <- 2
my_arrowhead <- arrowheadr::arrow_head_deltoid(d = 2.3, n = 100)
# my_colors <- viridis::viridis(2, begin = .25, end = .5)
my_colors <- c("#3B528B", "#21908C")
theme_set(
theme_minimal(
base_size = my_font_size,
base_family = my_font) +
theme(axis.title.y = element_text(angle = 0, vjust = 0.5)))
bp <- ggdiagram(
font_family = my_font,
font_size = my_font_size,
point_size = my_point_size,
linewidth = .5,
theme_function = theme_minimal,
axis.title.x = element_text(face = "italic"),
axis.title.y = element_text(
face = "italic",
angle = 0,
hjust = .5,
vjust = .5)) +
scale_x_continuous(labels = signs_centered,
limits = c(-4, 4)) +
scale_y_continuous(labels = signs::signs,
limits = c(-4, 4))
A common way to specify an ellipse is with a center point and the two distances from the center point c to the horizontal and vertical edges, a and b, respectively.
a <- 4
b <- 3
c1 <- ob_point(0,0)
e1 <- ob_ellipse(c1, a = a, b = b)
Foci
A circle has one focus, the center. If a ≠ b, an ellipse has two foci.
bp +
e1 +
ob_label("*F*~1~", e1@focus_1, plot_point = T, vjust = 1.2) +
ob_label("*F*~2~", e1@focus_2, plot_point = T, vjust = 1.2)
For any point P on the ellipse, the sum of PF1 and PF2 is 2a if a > b and 2b if b > a.
deg <- degree(61.5)
bp +
e1 +
ob_label("*F*~1~", e1@focus_1, plot_point = T, vjust = 1.2) +
ob_label("*F*~2~", e1@focus_2, plot_point = T, vjust = 1.2) +
{p <- e1@point_at(deg)} +
p@label("*P*", polar_just = ob_polar(deg, 1.5)) +
ob_segment(e1@focus_1,
p,
label = paste0("*PF*~1~ = ",
distance(e1@focus_1, p) |>
round())) +
ob_segment(p,
e1@focus_2,
label = paste0("*PF*~2~ = ",
distance(e1@focus_2, p) |>
round())) +
ob_label("*PF*~1~ + *PF*~2~ = 2*a* = 8",
center = ob_point(0,4),
size = 20)
Point on the ellipse at a specific angle
The @point_at
property of an ob_ellipse
object is a function that can find a point at a specific angle.
e1@point_at(degree(60))
#> <ob_point>
#> @ x: num 1.59
#> @ y: num 2.75
#> Other props: alpha, color, fill, shape, size, stroke, auto_label,
#> bounding_box, length, r, theta, style, tibble, xy,
#> geom, label, place, aesthetics
Code
deg <- degree(60)
bp +
e1 +
{p45 <- e1@point_at(deg)} +
p45@label(polar_just = ob_polar(deg, 1.5)) +
ob_segment(e1@center, p45) +
ob_arc(
center = e1@center,
radius = 1,
start = degree(0),
end = deg,
label = deg
)
Point on the ellipse using definitional parameter t
The angle expected by the @point_at
function is a true angle. However, the parametric equation for ellipses has a parameter t that looks like an angle, but actually has no direct geometric interpretation:
Code
theta <- degree(seq(0, 350, 30))
bp +
{c1 = ob_circle(radius = 3.6, color = "gray30")} +
{e1 <- ob_ellipse(a = 2.8, b = 1)} +
{p1 <- c1@point_at(theta)} +
ob_label(theta, p1, polar_just = ob_polar(theta, r = 1.5)) +
ob_segment(ob_point(), p1, linewidth = .2) +
{p2 <- e1@point_at(theta, definitional = T, color = "dodgerblue")} +
ob_segment(ob_point(), p2) +
ob_label(theta@degree, p2, polar_just = ob_polar(theta, r = 1.5)) +
theme_void()
If the definitional point at t is desired:
ob_ellipse(a = 2)@point_at(degree(60), definitional = TRUE)
#> <ob_point>
#> @ x: num 1
#> @ y: num 1.73
#> Other props: alpha, color, fill, shape, size, stroke, auto_label,
#> bounding_box, length, r, theta, style, tibble, xy,
#> geom, label, place, aesthetics
Tangent lines
Like the @point_at
property, the @tangent
property is a function that will find the tangent line at a specified angle or point.
bp +
{e1 <- ob_ellipse(a = 3, b = 2)} +
e1@point_at(60, color = "firebrick4") +
e1@tangent_at(60, color = "firebrick4")
The @tangent
function can also take a point instead of an angle.
bp +
e1 +
{p1 <- e1@point_at(60)} +
e1@tangent_at(p1)
If the point is not on the ellipse, the tangent will be at the point’s projection onto the ellipse:
bp +
e1 +
{p1 <- ob_point(3, 2, color = "firebrick4")} +
e1@tangent_at(p1) +
projection(p1, e1)
Superellipses
The standard formula for an ellipse can be altered such that the squared entities can be raised to any positive number.
m2 is set equal to m1 unless otherwise specified.
If m1 is 4, and a and b are equal, we can make a squircle, which is a square-ish circle.
bp +
ob_ellipse(a = 3,
b = 3,
m1 = 4)
If we increase m1 to a high value like 10, we can a rectangle with pleasingly rounded corners.
bp +
ob_ellipse(
a = 3,
b = 3,
m1 = 10,
color = NA,
fill = "dodgerblue",
label = ob_label(
label = "My<br>Variable",
fill = NA,
color = "white",
size = 70
)
)
Connection Paths Among Ellipses
bp +
{e1 <- ob_ellipse(ob_point(-2,0), a = 2)} +
{e2 <- ob_ellipse(ob_point(3,2), b = 2)} +
connect(e1, e2, resect = 2)
Placing Ellipses
The place
function will set an object at a position and distance from another object. Here we set an ellipse to the the right of e1
(i.e., “east” or 0 degrees) with a separation of 2.
bp +
{e1 <- ob_ellipse(center = ob_point(-2, 0),
a = 2)} +
place(ob_ellipse(b = 2),
from = e1,
where = "right",
sep = 2)
The sep
parameter in the place
function is not necessarily the shortest distance between ellipses. Instead, it is the distance between the ellipses on the segment connecting the center points.
deg <- degree(30)
bp +
{e1 <- ob_ellipse(
center = ob_point(-2,-1, color = "dodgerblue4"),
a = 2,
b = 1.5)} +
{e2 <- place(
ob_ellipse(
center = ob_point(color = "orchid4"),
b = 2),
from = e1,
where = deg,
sep = 2)} +
connect(
e1,
e2,
arrow_head = ggarrow::arrow_head_minimal(),
linetype = "dashed",
label = ob_label(2, vjust = 0)
) +
ob_arc(e1@center, end = deg, label = deg) +
ob_segment(e1@center,
e1@point_at(deg)) +
ob_segment(e2@center,
e2@point_at(deg + degree(180))) +
ob_label("*e*~1~", e1@center) +
ob_label("*e*~2~", e2@center)
You can place many ellipses at once. In Figure 12, 12 ellipses are placed around the central ellipse. Connection paths are then drawn to each ellipse.
# Number of ellipses
k <- 12
# Colors
e_fills <- hsv(
h = seq(0, 1 - 1 / k, length.out = k),
s = .4,
v = .6)
bp +
{e_0 <- ob_ellipse(
m1 = 6,
label = ob_label(
"*e*~0~",
size = 40,
color = "white",
fill = "gray20"
),
color = NA,
fill = "gray20"
)} +
{e_x <- place(
x = ob_ellipse(
a = .4,
b = .4,
m1 = 6,
label = ob_label(
paste0("*e*~", seq(k), "~"),
color = "white",
fill = e_fills
),
color = NA,
fill = e_fills
),
from = e_0,
where = degree(seq(0, 360 - 360 / k, 360 / k)),
sep = 2
)} +
connect(e_0, e_x, resect = 2, color = e_fills) +
theme_void()
Lines can be placed in relation to ellipses:
bp +
{e1 <- ob_ellipse(m1 = 4)} +
{l1 <- place(
x = ob_line(),
from = e1,
where = {deg1 <- degree(45)},
sep = {d = 3}
)} +
connect(
e1,
l1,
label = paste0("Distance = ", d),
arrow_fins = arrowheadr::arrow_head_deltoid(),
length_fins = 8,
length_head = 8,
resect = 1
) +
ob_label(
label = l1@equation,
center = ob_polar(theta = deg1,
r = e1@point_at(deg1)@r + d),
angle = l1@angle,
vjust = 0
)