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_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 = TRUE,
vjust = 1.2) +
ob_label("*F*~2~", e1@focus_2,
plot_point = TRUE,
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 = TRUE, vjust = 1.2) +
ob_label("*F*~2~", e1@focus_2, plot_point = TRUE, 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)Rotated Ellipses
As seen in fig-ellipserotated, rotate an ellipse by setting the angle property with a degree, radian, or turn object or a numeric value (degrees).
bp +
ob_ellipse(a = 2, b = 1, angle = 45)Setting the angle of an ellipse will rotate it about its center. If you want to rotate it around a different point, use the rotate function, setting the origin parameter to any point you wish to rotate the ellipse around.
bp +
{p1 = ob_point(2,0)} +
{e2 <- ob_ellipse(a = 2, b = 1)} +
rotate(e2, theta = degree(45), origin = p1, color = "red") 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>
#> # A tibble: 1 × 2
#> x y
#> <dbl> <dbl>
#> 1 1.59 2.75Code
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() +
scale_x_continuous(expand = expansion(.12))
#> Scale for x is already present.
#> Adding another scale for x, which will replace the existing scale.If the definitional point at t is desired:
ob_ellipse(a = 2)@point_at(degree(60), definitional = TRUE)
#>
#> ── <ob_point>
#> # A tibble: 1 × 2
#> x y
#> <dbl> <dbl>
#> 1 1 1.73Tangent 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 create 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 16, 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 = arrowhead(),
length_fins = 8,
length_head = 8,
resect = 1
) +
ob_label(
label = equation(l1),
center = ob_polar(theta = deg1,
r = e1@point_at(deg1)@r + d),
angle = l1@angle,
vjust = 0
)