{"id":13711022,"url":"https://github.com/mdhall272/ggarchery","last_synced_at":"2026-02-19T09:02:09.438Z","repository":{"id":41285471,"uuid":"459678431","full_name":"mdhall272/ggarchery","owner":"mdhall272","description":"Flexible segment geoms with arrows for ggplot2","archived":false,"fork":false,"pushed_at":"2025-07-24T12:05:08.000Z","size":1109,"stargazers_count":34,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-28T08:59:54.968Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"R","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mdhall272.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-02-15T17:21:16.000Z","updated_at":"2025-07-30T18:46:45.000Z","dependencies_parsed_at":"2024-03-27T16:00:08.323Z","dependency_job_id":"19bee2cf-55d3-4549-9a87-dd06cb98e475","html_url":"https://github.com/mdhall272/ggarchery","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/mdhall272/ggarchery","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdhall272%2Fggarchery","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdhall272%2Fggarchery/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdhall272%2Fggarchery/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdhall272%2Fggarchery/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mdhall272","download_url":"https://codeload.github.com/mdhall272/ggarchery/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdhall272%2Fggarchery/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29609524,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-19T06:47:36.664Z","status":"ssl_error","status_checked_at":"2026-02-19T06:45:47.551Z","response_time":117,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T23:01:03.518Z","updated_at":"2026-02-19T09:02:09.074Z","avatar_url":"https://github.com/mdhall272.png","language":"R","funding_links":[],"categories":["Plot layers"],"sub_categories":[],"readme":"# ggarchery: Flexible segment geoms with arrows for ggplot2\n\n\nggarchery is intended to extend [ggplot2](https://github.com/tidyverse/ggplot2)'s handling of segments with arrowheads. At present it contains one geom and one position adjustment.\n\n## `geom_arrowsegment()` allows placement of one or more arrowheads at any point on a segment\n\nFirst, let's generate some data that would be understood by [ggplot2](https://github.com/tidyverse/ggplot2)'s normal `geom_segment()`:\n\n```\nlibrary(tidyverse)\nlibrary(ggarchery)\n\ntbl \u003c- tibble(x = c(0.1, 0.2), xend = c(0.1, 0.8), y = c(0.1, 0.5), yend = c(0.7, 0.9))\n```\n\nThe default behaviour of `geom_arrowsegment()` mimics that of `geom_segment(arrow = arrow())`\n\n```\nggplot(tbl) + \n  geom_segment(aes(x = x, xend = xend, y = y, yend = yend), arrow = arrow()) + \n  xlim(c(0,1)) +\n  ylim(c(0,1))\n```\n\n\u003cimg src=\"man/figures/geom_segment_example.png\" width=\"400\"/\u003e\n\n```\nggplot(tbl) + \n  geom_arrowsegment(aes(x = x, xend = xend, y = y, yend = yend)) + \n  xlim(c(0,1)) +\n  ylim(c(0,1))\n```\n\n\u003cimg src=\"man/figures/geom_arrowsegment_example1.png\" width=\"400\"/\u003e\n\nThe `arrows` parameter of `geom_arrowsegment()` also behaves exactly like the `arrow` parameter of `geom_segment`, as a call to `grid::arrow()`:\n\n```\nggplot(tbl) + \n  geom_arrowsegment(aes(x = x, xend = xend, y = y, yend = yend), \n                    arrows = arrow(type = 'closed')) + \n  xlim(c(0,1)) +\n  ylim(c(0,1))\n```\n\n\u003cimg src=\"man/figures/geom_arrowsegment_example2.png\" width=\"400\"/\u003e\n\nNow for the interesting bit. Suppose that we would like the arrowhead to appear at the midpoint of the segment, rather than the end. This can be done by specifying `arrow_positions = 0.5`.\n\n```\nggplot(tbl) + \n  geom_arrowsegment(aes(x = x, xend = xend, y = y, yend = yend), arrow_positions = 0.5) + \n  xlim(c(0,1)) +\n  ylim(c(0,1))\n```\n\n\u003cimg src=\"man/figures/geom_arrowsegment_example3.png\" width=\"400\"/\u003e\n\nControl of the arrow segment works as before:\n\n```\nggplot(tbl) + \n  geom_arrowsegment(aes(x = x, xend = xend, y = y, yend = yend), \n                    arrow_positions = 0.5, \n                    arrows = arrow(type = 'closed')) + \n  xlim(c(0,1)) +\n  ylim(c(0,1))\n```\n\n\u003cimg src=\"man/figures/geom_arrowsegment_example4.png\" width=\"400\"/\u003e\n\nOther aesthetics also work as you would hope. There is a subtle difference in the legend as displayed by `geom_segment` and `geom_arrowsegment`, however:\n\n```\ntbl \u003c- tbl %\u003e% mutate(col = c(\"A\", \"B\"))\n\nggplot(tbl) + \n  geom_arrowsegment(aes(x = x, xend = xend, y = y, yend = yend, col = col), \n                    arrow_positions = 0.5)  + \n  xlim(c(0,1)) +\n  ylim(c(0,1))\n```\n\n\u003cimg src=\"man/figures/geom_arrowsegment_example5.png\" width=\"400\"/\u003e\n\nAnother key way that `geom_arrowsegment` differs from `geom_segment` is that it has a working `fill` aesthetic. This is only visible if the arrowhead is closed. Note that it must be specified as `fill` even if you want it to simply match the `colour` aesthetic, another difference in behaviour from `geom_segment`.\n\n```\nggplot(tbl) + \n  geom_arrowsegment(aes(x = x, xend = xend, y = y, yend = yend, fill = col), \n                    arrow_positions = 0.5, arrows = arrow(type = \"closed\"))  + \n  xlim(c(0,1)) +\n  ylim(c(0,1))\n```\n\n\u003cimg src=\"man/figures/geom_arrowsegment_example6.png\" width=\"400\"/\u003e\n\n```\nggplot(tbl) + \n  geom_arrowsegment(aes(x = x, xend = xend, y = y, yend = yend, col = col), \n                      arrow_positions = 0.5, arrows = arrow(type = \"closed\"))  + \n  xlim(c(0,1)) +\n  ylim(c(0,1))\n```\n\n\u003cimg src=\"man/figures/geom_arrowsegment_example7.png\" width=\"400\"/\u003e\n\n\n```\nggplot(tbl) + \n  geom_arrowsegment(aes(x = x, xend = xend, y = y, yend = yend, \n                        fill = col, col = col), \n                    arrow_positions = 0.5, \n                    arrows = arrow(type = \"closed\"))  + \n  xlim(c(0,1)) +\n  ylim(c(0,1))\n```\n\n\u003cimg src=\"man/figures/geom_arrowsegment_example8.png\" width=\"400\"/\u003e\n\n\n\nYou can also define multiple arrowheads by making `arrow_positions` a vector of length greater than 1. All values are expected to fall between 0 and 1, and not be exactly 0:\n\n```\nggplot(tbl) + \n  geom_arrowsegment(aes(x = x, xend = xend, y = y, yend = yend), \n                    arrow_positions = c(0.25, 0.75))  + \n  xlim(c(0,1)) +\n  ylim(c(0,1))\n```\n\n\u003cimg src=\"man/figures/geom_arrowsegment_example9.png\" width=\"400\"/\u003e\n\nIf one value is 1, then the final arrowhead appears at the end:\n\n```\nggplot(tbl) + \n  geom_arrowsegment(aes(x = x, xend = xend, y = y, yend = yend), \n                    arrow_positions = c(0.25, 1))  + \n  xlim(c(0,1)) +\n  ylim(c(0,1))\n```\n\n\u003cimg src=\"man/figures/geom_arrowsegment_example10.png\" width=\"400\"/\u003e\n\nThe look of each arrow can also be controlled separately by making `arrows` a list:\n\n```\nggplot(tbl) + \n  geom_arrowsegment(aes(x = x, xend = xend, y = y, yend = yend), \n                    arrow_positions = c(0.25, 1), \n                    arrows = list(arrow(angle = 10), \n                                  arrow(type = 'closed')))  + \n  xlim(c(0,1)) +\n  ylim(c(0,1))\n```\n\n\u003cimg src=\"man/figures/geom_arrowsegment_example11.png\" width=\"400\"/\u003e\n\nThe `arrow_fills` option also mimics `arrow.fill` of `geom_segment()` but can be a vector. As with a specification of `fill` outside the aesthetics, this takes precedence over a `fill` aesthetic.\n\n```\nggplot(tbl) + \n  geom_arrowsegment(aes(x = x, xend = xend, y = y, yend = yend), \n                    arrow_positions = c(0.25, 1), \n                    arrow_fills = c(\"indianred3\", \"dodgerblue3\"), \n                    arrows = arrow(type = \"closed\"))  + \n  xlim(c(0,1)) +\n  ylim(c(0,1))\n```\n\n\u003cimg src=\"man/figures/geom_arrowsegment_example12.png\" width=\"400\"/\u003e\n\nFinally, the geom can be used as an annotation:\n\n```\nggplot(mtcars) + \n  geom_point(aes(x = disp, y=hp)) + \n  annotate(geom = \"arrowsegment\", \n           x = 170, \n           y=200, \n           xend = 145, \n           yend = 175, \n           arrow_positions = 0.6, \n           arrows = arrow(type = \"closed\", length = unit(0.1, \"inches\")))\n```\n\u003cimg src=\"man/figures/annotate_example.png\" width=\"800\"/\u003e\n\n\n## `position_attractsegment()` allows you to automatically shave the ends of arrow segments\n\n`position_attractsegment()` is intended to solve the following problem. Suppose you have nicely laid out a set of labelled points:\n\n```\npt.tbl \u003c- tibble(x = c(0.25, 0.5, 0.75), y = c(0.25, 0.5, 0.75), labels = c(\"A\", \"B\", \"C\"))\n\nggplot(pt.tbl) + \n  geom_point(aes(x,y, fill = labels), size =6, shape = 21) +\n  geom_text(aes(x,y, label = labels)) +\n  xlim(c(0, 1)) +\n  ylim(c(0, 1)) +\n  scale_fill_discrete(guide = \"none\")\n```\n\n\u003cimg src=\"man/figures/position_attractsegment_example1.png\" width=\"400\"/\u003e\n\nIf you wish to connect these points using `geom_segment()` with an arrow, the output is a little ugly, as the lines intersect the points:\n\n```\nsg.tbl \u003c- tibble(x = c(0.25, 0.5), y = c(0.25, 0.5), xend = c(0.5, 0.75), yend = c(0.5, 0.75))\n\nggplot(pt.tbl) + \n  geom_point(aes(x,y, fill = labels), size =6, shape = 21) +\n  geom_text(aes(x,y, label = labels)) +\n  geom_segment(data = sg.tbl, \n               aes(x = x, xend = xend, y = y, yend = yend), \n               arrow = arrow()) +\n  xlim(c(0, 1)) +\n  ylim(c(0, 1)) +\n  scale_fill_discrete(guide = \"none\")\n```\n\n\u003cimg src=\"man/figures/position_attractsegment_example2.png\" width=\"400\"/\u003e\n\n`position_attractsegment()` works by shortening the segment at the start and the end (\"attracting\" the start and end points towards each other). It can do this in two ways, as determined by the `type_shave` option. If `type_shave = \"proportion\"` (the default), then it takes the proportions `start_shave` and `end_shave` away:\n\n```\nggplot(pt.tbl) + \n  geom_point(aes(x,y, fill = labels), size =6, shape = 21) +\n  geom_text(aes(x,y, label = labels)) +\n  geom_segment(data = sg.tbl, \n               aes(x = x, xend = xend, y = y, yend = yend), \n               arrow = arrow(), \n               position = position_attractsegment(start_shave = 0.1, \n                                                  end_shave = 0.1)) +\n  xlim(c(0, 1)) +\n  ylim(c(0, 1)) +\n  scale_fill_discrete(guide = \"none\")\n```\n\n\u003cimg src=\"man/figures/position_attractsegment_example3.png\" width=\"400\"/\u003e\n\nAlternatively, if `type_shave = \"distance\"` then the amount removed is in graph units. This allows for finer control, but has strange effects if the dimensions of the x and y axes are not the same and is only really recommended in combination with `coord_fixed()`.\n\n```\nggplot(pt.tbl)+\n  geom_segment(data = sg.tbl, aes(x = x, xend = xend, y = y, yend = yend), arrow = arrow(), \n               position = position_attractsegment(start_shave = 0, \n                                                  end_shave = 0.05, \n                                                  type_shave = \"distance\")) +\n  geom_point(aes(x,y, fill = labels), size =6, shape = 21) +\n  geom_text(aes(x,y, label = labels))  +\n  xlim(c(0, 1)) +\n  ylim(c(0, 1)) +\n  scale_fill_discrete(guide = \"none\") +\n  coord_fixed()\n```\n\n\u003cimg src=\"man/figures/position_attractsegment_example4.png\" width=\"400\"/\u003e\n\n(Note here we shaved only the end of the segment, and drew the segment first.)\n\n`geom_arrowsegment()` and `position_attractsegment()` can naturally be used in combination:\n\n```\nggplot(pt.tbl)+\n  geom_arrowsegment(data = sg.tbl, aes(x = x, xend = xend, y = y, yend = yend), \n                    arrow_positions = c(0.5, 0.6), \n                    arrows = arrow(length = unit(0.1, \"inches\")), \n                    position = position_attractsegment(start_shave = 0.05, \n                                                       end_shave = 0.05, \n                                                       type_shave = \"distance\")) +\n  geom_point(aes(x,y, fill = labels), size =6, shape = 21) +\n  geom_text(aes(x,y, label = labels))  +\n  xlim(c(0, 1)) +\n  ylim(c(0, 1)) +\n  scale_fill_discrete(guide = \"none\") +\n  coord_fixed()\n```\n\n\u003cimg src=\"man/figures/position_attractsegment_example5.png\" width=\"400\"/\u003e\n\n## Limitations\n\nCurrent these replace only `geom_segment()` and work only for linear coordinate systems. I would like to extend to `geom_curve()` but the intricacies of `grid::curveGrob()` make that much more complicated. Also the fact that the specified arrow position corresponds to the arrowhead tip can make lines look a little lopsided; it would be much better if the specified point was the midpoint of the arrowhead instead, but this is very difficult using `grid::arrow()` because of the units specification. To do this I would probably need to draw my own arrowheads. Maybe one day...\n\nNext on my to-do list is extending to the `geom_line()` and `geom_path()` parameterisations.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmdhall272%2Fggarchery","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmdhall272%2Fggarchery","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmdhall272%2Fggarchery/lists"}