Skip to content

Commit a537d6b

Browse files
authored
fix: vectorized drawing of arrows (#1904)
1 parent 7e6f575 commit a537d6b

File tree

8 files changed

+302
-212
lines changed

8 files changed

+302
-212
lines changed

R/plot.R

Lines changed: 131 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,8 @@ plot.igraph <- function(
127127
edge.label.color <- params("edge", "label.color")
128128
elab.x <- params("edge", "label.x")
129129
elab.y <- params("edge", "label.y")
130-
arrow.size <- params("edge", "arrow.size")[1]
131-
arrow.width <- params("edge", "arrow.width")[1]
130+
arrow.size <- params("edge", "arrow.size")
131+
arrow.width <- params("edge", "arrow.width")
132132
curved <- params("edge", "curved")
133133
if (is.function(curved)) {
134134
curved <- curved(graph)
@@ -1611,204 +1611,163 @@ rglplot.igraph <- function(x, ...) {
16111611
# slightly modified: code argument added
16121612

16131613
#' @importFrom graphics par xyinch segments xspline lines polygon
1614-
igraph.Arrows <-
1615-
function(
1616-
x1,
1617-
y1,
1618-
x2,
1619-
y2,
1620-
code = 2,
1621-
size = 1,
1622-
width = 1.2 / 4 / cin,
1623-
open = TRUE,
1624-
sh.adj = 0.1,
1625-
sh.lwd = 1,
1626-
sh.col = par("fg"),
1627-
sh.lty = 1,
1628-
h.col = sh.col,
1629-
h.col.bo = sh.col,
1630-
h.lwd = sh.lwd,
1631-
h.lty = sh.lty,
1632-
curved = FALSE
1633-
) {
1634-
## Author: Andreas Ruckstuhl, refined by Rene Locher
1635-
## Version: 2005-10-17
1636-
cin <- size * par("cin")[2]
1637-
width <- width * (1.2 / 4 / cin)
1638-
uin <- 1 / xyinch()
1614+
# Vectorized and modular igraph.Arrows refactor
1615+
igraph.Arrows <- function(
1616+
x1,
1617+
y1,
1618+
x2,
1619+
y2,
1620+
code = 2,
1621+
size = 1,
1622+
width = 1.2 / 4 / par("cin")[2],
1623+
open = TRUE,
1624+
sh.adj = 0.1,
1625+
sh.lwd = 1,
1626+
sh.col = par("fg"),
1627+
sh.lty = 1,
1628+
h.col = sh.col,
1629+
h.col.bo = sh.col,
1630+
h.lwd = sh.lwd,
1631+
h.lty = sh.lty,
1632+
curved = FALSE
1633+
) {
1634+
n <- length(x1)
1635+
1636+
recycle <- function(x) rep(x, length.out = n)
1637+
1638+
x1 <- recycle(x1)
1639+
y1 <- recycle(y1)
1640+
x2 <- recycle(x2)
1641+
y2 <- recycle(y2)
1642+
size <- recycle(size)
1643+
width <- recycle(width)
1644+
curved <- recycle(curved)
1645+
sh.lwd <- recycle(sh.lwd)
1646+
sh.col <- recycle(sh.col)
1647+
sh.lty <- recycle(sh.lty)
1648+
h.col <- recycle(h.col)
1649+
h.col.bo <- recycle(h.col.bo)
1650+
h.lwd <- recycle(h.lwd)
1651+
h.lty <- recycle(h.lty)
1652+
1653+
uin <- 1 / xyinch()
1654+
1655+
label_x <- numeric(n)
1656+
label_y <- numeric(n)
1657+
1658+
for (i in seq_len(n)) {
1659+
cin <- size[i] * par("cin")[2]
1660+
w <- width[i] * (1.2 / 4 / cin)
1661+
delta <- sqrt(h.lwd[i]) * par("cin")[2] * 0.005
1662+
1663+
# Arrowhead shape
16391664
x <- sqrt(seq(0, cin^2, length.out = floor(35 * cin) + 2))
1640-
delta <- sqrt(h.lwd) * par("cin")[2] * 0.005 ## has been 0.05
16411665
x.arr <- c(-rev(x), -x)
1642-
wx2 <- width * x^2
1666+
wx2 <- w * x^2
16431667
y.arr <- c(-rev(wx2 + delta), wx2 + delta)
16441668
deg.arr <- c(atan2(y.arr, x.arr), NA)
16451669
r.arr <- c(sqrt(x.arr^2 + y.arr^2), NA)
16461670

1647-
## backup
1648-
bx1 <- x1
1649-
bx2 <- x2
1650-
by1 <- y1
1651-
by2 <- y2
1652-
1653-
## shaft
1654-
lx <- length(x1)
1655-
r.seg <- rep(cin * sh.adj, lx)
1656-
theta1 <- atan2((y1 - y2) * uin[2], (x1 - x2) * uin[1])
1657-
th.seg1 <- theta1 + rep(atan2(0, -cin), lx)
1658-
theta2 <- atan2((y2 - y1) * uin[2], (x2 - x1) * uin[1])
1659-
th.seg2 <- theta2 + rep(atan2(0, -cin), lx)
1671+
theta1 <- atan2((y1[i] - y2[i]) * uin[2], (x1[i] - x2[i]) * uin[1])
1672+
theta2 <- atan2((y2[i] - y1[i]) * uin[2], (x2[i] - x1[i]) * uin[1])
1673+
r.seg <- cin * sh.adj
1674+
16601675
x1d <- y1d <- x2d <- y2d <- 0
16611676
if (code %in% c(1, 3)) {
1662-
x2d <- r.seg * cos(th.seg2) / uin[1]
1663-
y2d <- r.seg * sin(th.seg2) / uin[2]
1677+
x2d <- r.seg * cos(theta2) / uin[1]
1678+
y2d <- r.seg * sin(theta2) / uin[2]
16641679
}
16651680
if (code %in% c(2, 3)) {
1666-
x1d <- r.seg * cos(th.seg1) / uin[1]
1667-
y1d <- r.seg * sin(th.seg1) / uin[2]
1681+
x1d <- r.seg * cos(theta1) / uin[1]
1682+
y1d <- r.seg * sin(theta1) / uin[2]
16681683
}
1669-
if (
1670-
is.logical(curved) && all(!curved) || is.numeric(curved) && all(!curved)
1671-
) {
1684+
1685+
sx1 <- x1[i] + x1d
1686+
sy1 <- y1[i] + y1d
1687+
sx2 <- x2[i] + x2d
1688+
sy2 <- y2[i] + y2d
1689+
1690+
if (!curved[i]) {
16721691
segments(
1673-
x1 + x1d,
1674-
y1 + y1d,
1675-
x2 + x2d,
1676-
y2 + y2d,
1677-
lwd = sh.lwd,
1678-
col = sh.col,
1679-
lty = sh.lty
1692+
sx1,
1693+
sy1,
1694+
sx2,
1695+
sy2,
1696+
lwd = sh.lwd[i],
1697+
col = sh.col[i],
1698+
lty = sh.lty[i]
16801699
)
1681-
phi <- atan2(y1 - y2, x1 - x2)
1682-
r <- sqrt((x1 - x2)^2 + (y1 - y2)^2)
1683-
lc.x <- x2 + 2 / 3 * r * cos(phi)
1684-
lc.y <- y2 + 2 / 3 * r * sin(phi)
1700+
phi <- atan2(y1[i] - y2[i], x1[i] - x2[i])
1701+
r <- sqrt((x1[i] - x2[i])^2 + (y1[i] - y2[i])^2)
1702+
label_x[i] <- x2[i] + 2 / 3 * r * cos(phi)
1703+
label_y[i] <- y2[i] + 2 / 3 * r * sin(phi)
16851704
} else {
1686-
if (is.numeric(curved)) {
1687-
lambda <- curved
1688-
} else {
1689-
lambda <- as.logical(curved) * 0.5
1705+
lambda <- if (is.numeric(curved)) curved[i] else 0.5
1706+
midx <- (x1[i] + x2[i]) / 2
1707+
midy <- (y1[i] + y2[i]) / 2
1708+
spx <- midx - lambda * 1 / 2 * (sy2 - sy1)
1709+
spy <- midy + lambda * 1 / 2 * (sx2 - sx1)
1710+
1711+
spl <- xspline(
1712+
x = c(sx1, spx, sx2),
1713+
y = c(sy1, spy, sy2),
1714+
shape = 1,
1715+
draw = FALSE
1716+
)
1717+
lines(spl, lwd = sh.lwd[i], col = sh.col[i], lty = sh.lty[i])
1718+
label_x[i] <- spl$x[round(2 / 3 * length(spl$x))]
1719+
label_y[i] <- spl$y[round(2 / 3 * length(spl$y))]
1720+
1721+
if (code %in% c(2, 3)) {
1722+
x1[i] <- spl$x[round(3 / 4 * length(spl$x))]
1723+
y1[i] <- spl$y[round(3 / 4 * length(spl$y))]
16901724
}
1691-
lambda <- rep(lambda, length.out = length(x1))
1692-
c.x1 <- x1 + x1d
1693-
c.y1 <- y1 + y1d
1694-
c.x2 <- x2 + x2d
1695-
c.y2 <- y2 + y2d
1696-
1697-
midx <- (x1 + x2) / 2
1698-
midy <- (y1 + y2) / 2
1699-
spx <- midx - lambda * 1 / 2 * (c.y2 - c.y1)
1700-
spy <- midy + lambda * 1 / 2 * (c.x2 - c.x1)
1701-
sh.col <- rep(sh.col, length.out = length(c.x1))
1702-
sh.lty <- rep(sh.lty, length.out = length(c.x1))
1703-
sh.lwd <- rep(sh.lwd, length.out = length(c.x1))
1704-
lc.x <- lc.y <- numeric(length(c.x1))
1705-
1706-
for (i in seq_len(length(c.x1))) {
1707-
## Straight line?
1708-
if (lambda[i] == 0) {
1709-
segments(
1710-
c.x1[i],
1711-
c.y1[i],
1712-
c.x2[i],
1713-
c.y2[i],
1714-
lwd = sh.lwd[i],
1715-
col = sh.col[i],
1716-
lty = sh.lty[i]
1717-
)
1718-
phi <- atan2(y1[i] - y2[i], x1[i] - x2[i])
1719-
r <- sqrt((x1[i] - x2[i])^2 + (y1[i] - y2[i])^2)
1720-
lc.x[i] <- x2[i] + 2 / 3 * r * cos(phi)
1721-
lc.y[i] <- y2[i] + 2 / 3 * r * sin(phi)
1722-
} else {
1723-
spl <- xspline(
1724-
x = c(c.x1[i], spx[i], c.x2[i]),
1725-
y = c(c.y1[i], spy[i], c.y2[i]),
1726-
shape = 1,
1727-
draw = FALSE
1728-
)
1729-
lines(spl, lwd = sh.lwd[i], col = sh.col[i], lty = sh.lty[i])
1730-
if (code %in% c(2, 3)) {
1731-
x1[i] <- spl$x[3 * length(spl$x) / 4]
1732-
y1[i] <- spl$y[3 * length(spl$y) / 4]
1733-
}
1734-
if (code %in% c(1, 3)) {
1735-
x2[i] <- spl$x[length(spl$x) / 4]
1736-
y2[i] <- spl$y[length(spl$y) / 4]
1737-
}
1738-
lc.x[i] <- spl$x[2 / 3 * length(spl$x)]
1739-
lc.y[i] <- spl$y[2 / 3 * length(spl$y)]
1740-
}
1725+
if (code %in% c(1, 3)) {
1726+
x2[i] <- spl$x[round(1 / 4 * length(spl$x))]
1727+
y2[i] <- spl$y[round(1 / 4 * length(spl$y))]
17411728
}
17421729
}
17431730

1744-
## forward arrowhead
1745-
if (code %in% c(2, 3)) {
1746-
theta <- atan2((by2 - y1) * uin[2], (bx2 - x1) * uin[1])
1747-
Rep <- rep(length(deg.arr), lx)
1748-
p.x2 <- rep(bx2, Rep)
1749-
p.y2 <- rep(by2, Rep)
1750-
ttheta <- rep(theta, Rep) + rep(deg.arr, lx)
1751-
r.arr.rep <- rep(r.arr, lx)
1731+
draw_arrowhead <- function(px, py, theta) {
1732+
px2 <- rep(px, length(deg.arr))
1733+
py2 <- rep(py, length(deg.arr))
1734+
ttheta <- rep(theta, length(deg.arr)) + deg.arr
1735+
1736+
xhead <- px2 + r.arr * cos(ttheta) / uin[1]
1737+
yhead <- py2 + r.arr * sin(ttheta) / uin[2]
1738+
17521739
if (open) {
1753-
lines(
1754-
(p.x2 + r.arr.rep * cos(ttheta) / uin[1]),
1755-
(p.y2 + r.arr.rep * sin(ttheta) / uin[2]),
1756-
lwd = h.lwd,
1757-
col = h.col.bo,
1758-
lty = h.lty
1759-
)
1740+
lines(xhead, yhead, lwd = h.lwd[i], col = h.col.bo[i], lty = h.lty[i])
17601741
} else {
17611742
polygon(
1762-
p.x2 + r.arr.rep * cos(ttheta) / uin[1],
1763-
p.y2 + r.arr * sin(ttheta) / uin[2],
1764-
col = h.col,
1765-
lwd = h.lwd,
1766-
border = h.col.bo,
1767-
lty = h.lty
1743+
xhead,
1744+
yhead,
1745+
col = h.col[i],
1746+
lwd = h.lwd[i],
1747+
border = h.col.bo[i],
1748+
lty = h.lty[i]
17681749
)
17691750
}
17701751
}
17711752

1772-
## backward arrow head
1753+
if (code %in% c(2, 3)) {
1754+
draw_arrowhead(
1755+
x2[i],
1756+
y2[i],
1757+
atan2((y2[i] - y1[i]) * uin[2], (x2[i] - x1[i]) * uin[1])
1758+
)
1759+
}
17731760
if (code %in% c(1, 3)) {
1774-
x1 <- bx1
1775-
y1 <- by1
1776-
tmp <- x1
1777-
x1 <- x2
1778-
x2 <- tmp
1779-
tmp <- y1
1780-
y1 <- y2
1781-
y2 <- tmp
1782-
theta <- atan2((y2 - y1) * uin[2], (x2 - x1) * uin[1])
1783-
lx <- length(x1)
1784-
Rep <- rep(length(deg.arr), lx)
1785-
p.x2 <- rep(x2, Rep)
1786-
p.y2 <- rep(y2, Rep)
1787-
ttheta <- rep(theta, Rep) + rep(deg.arr, lx)
1788-
r.arr.rep <- rep(r.arr, lx)
1789-
1790-
if (open) {
1791-
lines(
1792-
(p.x2 + r.arr.rep * cos(ttheta) / uin[1]),
1793-
(p.y2 + r.arr.rep * sin(ttheta) / uin[2]),
1794-
lwd = h.lwd,
1795-
col = h.col.bo,
1796-
lty = h.lty
1797-
)
1798-
} else {
1799-
polygon(
1800-
p.x2 + r.arr.rep * cos(ttheta) / uin[1],
1801-
p.y2 + r.arr.rep * sin(ttheta) / uin[2],
1802-
col = h.col,
1803-
lwd = h.lwd,
1804-
border = h.col.bo,
1805-
lty = h.lty
1806-
)
1807-
}
1761+
draw_arrowhead(
1762+
x1[i],
1763+
y1[i],
1764+
atan2((y1[i] - y2[i]) * uin[2], (x1[i] - x2[i]) * uin[1])
1765+
)
18081766
}
1767+
}
18091768

1810-
list(lab.x = lc.x, lab.y = lc.y)
1811-
} # Arrows
1769+
list(lab.x = label_x, lab.y = label_y)
1770+
}
18121771

18131772
#' @importFrom graphics xspline
18141773
igraph.polygon <- function(

R/plot.common.R

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -252,24 +252,10 @@
252252
#' The width of the edges. The default value is 1.
253253
#' }
254254
#' \item{arrow.size}{
255-
#' The size of the arrows. Currently this is a constant, so it is the same for every edge.
256-
#' If a vector is submitted then only the first element is used,
257-
#' ie. if this is taken from an edge attribute
258-
#' then only the attribute of the first edge is used for all arrows.
259-
#' This will likely change in the future.
260-
#'
261-
#' The default value is 1.
255+
#' The size of the arrows. The default value is 1.
262256
#' }
263257
#' \item{arrow.width}{
264-
#' The width of the arrows. Currently this is a constant, so it is the same for every edge.
265-
#' If a vector is submitted then only the first element is used,
266-
#' ie. if this is taken from an edge attribute
267-
#' then only the attribute of the first edge is used for all arrows.
268-
#' This will likely change in the future.
269-
#'
270-
#' This argument is currently only used by [plot.igraph()].
271-
#'
272-
#' The default value is 1, which gives the same width as before this option appeared in igraph.
258+
#' The width of the arrows. The default value is 1.
273259
#' }
274260
#' \item{lty}{
275261
#' The line type for the edges. Almost the

man/plot.common.Rd

Lines changed: 2 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)