ggraph/0000755000176200001440000000000013617251652011532 5ustar liggesusersggraph/NAMESPACE0000644000176200001440000002516713617226237012765 0ustar liggesusers# Generated by roxygen2: do not edit by hand S3method("[",geometry) S3method("[<-",geometry) S3method(as.data.frame,geometry) S3method(as.data.frame,layout_ggraph) S3method(autograph,default) S3method(c,geometry) S3method(create_layout,default) S3method(create_layout,layout_ggraph) S3method(create_layout,tbl_graph) S3method(format,geometry) S3method(ggplot_build,ggraph) S3method(guide_gengrob,edge_direction) S3method(guide_geom,edge_direction) S3method(guide_merge,edge_direction) S3method(guide_train,edge_colourbar) S3method(guide_train,edge_direction) S3method(is.na,geometry) S3method(layout_to_table,"function") S3method(layout_to_table,character) S3method(layout_to_table,data.frame) S3method(layout_to_table,default) S3method(layout_to_table,matrix) S3method(length,geometry) S3method(makeContent,cappedpathgrob) S3method(makeContent,parallelPath) S3method(makeContent,textalong) S3method(print,geometry) S3method(rep,geometry) S3method(scale_type,geometry) export(FacetEdges) export(FacetGraph) export(FacetNodes) export(GeomAxisHive) export(GeomEdgeBezier) export(GeomEdgeBspline) export(GeomEdgeParallelPath) export(GeomEdgeParallelSegment) export(GeomEdgePath) export(GeomEdgePoint) export(GeomEdgeSegment) export(GeomEdgeSpanPath) export(GeomEdgeSpanSegment) export(GeomEdgeTile) export(GeomNodeTile) export(StatAxisHive) export(StatConnBundle) export(StatConnBundle0) export(StatConnBundle2) export(StatEdgeArc) export(StatEdgeArc0) export(StatEdgeArc2) export(StatEdgeBend) export(StatEdgeBend0) export(StatEdgeBend2) export(StatEdgeDensity) export(StatEdgeDiagonal) export(StatEdgeDiagonal0) export(StatEdgeDiagonal2) export(StatEdgeElbow) export(StatEdgeElbow0) export(StatEdgeElbow2) export(StatEdgeFan) export(StatEdgeFan0) export(StatEdgeFan2) export(StatEdgeHive) export(StatEdgeHive0) export(StatEdgeHive2) export(StatEdgeLink) export(StatEdgeLink2) export(StatEdgeLoop) export(StatEdgeLoop0) export(StatEdgeParallel) export(StatEdgeParallel0) export(StatEdgeParallel2) export(StatFilter) export(StatNodeArcBar) export(StatNodeCircle) export(StatNodeVoronoi) export(autograph) export(circle) export(collect_connections) export(collect_edges) export(create_layout) export(edge_angle) export(ellipsis) export(facet_edges) export(facet_graph) export(facet_nodes) export(geom_axis_hive) export(geom_conn_bundle) export(geom_conn_bundle0) export(geom_conn_bundle2) export(geom_edge_arc) export(geom_edge_arc0) export(geom_edge_arc2) export(geom_edge_bend) export(geom_edge_bend0) export(geom_edge_bend2) export(geom_edge_density) export(geom_edge_diagonal) export(geom_edge_diagonal0) export(geom_edge_diagonal2) export(geom_edge_elbow) export(geom_edge_elbow0) export(geom_edge_elbow2) export(geom_edge_fan) export(geom_edge_fan0) export(geom_edge_fan2) export(geom_edge_hive) export(geom_edge_hive0) export(geom_edge_hive2) export(geom_edge_link) export(geom_edge_link0) export(geom_edge_link2) export(geom_edge_loop) export(geom_edge_loop0) export(geom_edge_parallel) export(geom_edge_parallel0) export(geom_edge_parallel2) export(geom_edge_point) export(geom_edge_span) export(geom_edge_span0) export(geom_edge_span2) export(geom_edge_tile) export(geom_node_arc_bar) export(geom_node_circle) export(geom_node_label) export(geom_node_point) export(geom_node_range) export(geom_node_text) export(geom_node_tile) export(geom_node_voronoi) export(geometry) export(get_con) export(get_edges) export(get_nodes) export(ggraph) export(guide_edge_colorbar) export(guide_edge_colourbar) export(guide_edge_direction) export(is.geometry) export(label_rect) export(layout_to_table) export(node_angle) export(node_rank_fabric) export(pack_circles) export(qgraph) export(rectangle) export(scale_color_viridis) export(scale_edge_alpha) export(scale_edge_alpha_continuous) export(scale_edge_alpha_discrete) export(scale_edge_alpha_identity) export(scale_edge_alpha_manual) export(scale_edge_color_brewer) export(scale_edge_color_continuous) export(scale_edge_color_discrete) export(scale_edge_color_distiller) export(scale_edge_color_gradient) export(scale_edge_color_gradient2) export(scale_edge_color_gradientn) export(scale_edge_color_grey) export(scale_edge_color_hue) export(scale_edge_color_identity) export(scale_edge_color_manual) export(scale_edge_color_viridis) export(scale_edge_colour_brewer) export(scale_edge_colour_continuous) export(scale_edge_colour_discrete) export(scale_edge_colour_distiller) export(scale_edge_colour_gradient) export(scale_edge_colour_gradient2) export(scale_edge_colour_gradientn) export(scale_edge_colour_grey) export(scale_edge_colour_hue) export(scale_edge_colour_identity) export(scale_edge_colour_manual) export(scale_edge_colour_viridis) export(scale_edge_fill_brewer) export(scale_edge_fill_continuous) export(scale_edge_fill_discrete) export(scale_edge_fill_distiller) export(scale_edge_fill_gradient) export(scale_edge_fill_gradient2) export(scale_edge_fill_gradientn) export(scale_edge_fill_grey) export(scale_edge_fill_hue) export(scale_edge_fill_identity) export(scale_edge_fill_manual) export(scale_edge_fill_viridis) export(scale_edge_linetype) export(scale_edge_linetype_continuous) export(scale_edge_linetype_discrete) export(scale_edge_linetype_identity) export(scale_edge_linetype_manual) export(scale_edge_radius) export(scale_edge_shape) export(scale_edge_shape_continuous) export(scale_edge_shape_discrete) export(scale_edge_shape_identity) export(scale_edge_shape_manual) export(scale_edge_size) export(scale_edge_size_area) export(scale_edge_size_continuous) export(scale_edge_size_discrete) export(scale_edge_size_identity) export(scale_edge_size_manual) export(scale_edge_width) export(scale_edge_width_continuous) export(scale_edge_width_discrete) export(scale_edge_width_identity) export(scale_edge_width_manual) export(scale_fill_viridis) export(scale_label_size) export(scale_label_size_continuous) export(scale_label_size_discrete) export(scale_label_size_identity) export(scale_label_size_manual) export(set_graph_style) export(square) export(th_foreground) export(th_no_axes) export(theme_graph) export(unset_graph_style) import(ggplot2) import(tidygraph) importFrom(MASS,bandwidth.nrd) importFrom(MASS,kde2d) importFrom(Rcpp,sourceCpp) importFrom(digest,digest) importFrom(dplyr,"%>%") importFrom(dplyr,arrange) importFrom(dplyr,do) importFrom(dplyr,group_by) importFrom(dplyr,group_by_) importFrom(dplyr,mutate) importFrom(dplyr,n) importFrom(dplyr,pull) importFrom(dplyr,slice) importFrom(dplyr,summarise) importFrom(dplyr,top_n) importFrom(dplyr,transmute) importFrom(dplyr,ungroup) importFrom(ggforce,GeomArcBar) importFrom(ggforce,GeomBezier0) importFrom(ggforce,GeomBspline0) importFrom(ggforce,GeomCircle) importFrom(ggforce,GeomShape) importFrom(ggforce,StatArcBar) importFrom(ggforce,StatBezier) importFrom(ggforce,StatBezier0) importFrom(ggforce,StatBezier2) importFrom(ggforce,StatBspline) importFrom(ggforce,StatBspline2) importFrom(ggforce,StatCircle) importFrom(ggforce,StatLink) importFrom(ggforce,StatLink2) importFrom(ggforce,StatVoronoiTile) importFrom(ggforce,interpolateDataFrame) importFrom(ggforce,radial_trans) importFrom(ggrepel,GeomLabelRepel) importFrom(ggrepel,GeomTextRepel) importFrom(graphlayouts,layout_as_backbone) importFrom(graphlayouts,layout_with_centrality) importFrom(graphlayouts,layout_with_eigen) importFrom(graphlayouts,layout_with_focus) importFrom(graphlayouts,layout_with_pmds) importFrom(graphlayouts,layout_with_sparse_stress) importFrom(graphlayouts,layout_with_stress) importFrom(grid,arrow) importFrom(grid,convertHeight) importFrom(grid,convertWidth) importFrom(grid,convertX) importFrom(grid,convertY) importFrom(grid,gList) importFrom(grid,gTree) importFrom(grid,gpar) importFrom(grid,grob) importFrom(grid,grobHeight) importFrom(grid,grobName) importFrom(grid,grobTree) importFrom(grid,grobWidth) importFrom(grid,is.unit) importFrom(grid,makeContent) importFrom(grid,nullGrob) importFrom(grid,pointsGrob) importFrom(grid,polylineGrob) importFrom(grid,rasterGrob) importFrom(grid,rectGrob) importFrom(grid,segmentsGrob) importFrom(grid,setChildren) importFrom(grid,textGrob) importFrom(grid,unit) importFrom(gtable,gtable) importFrom(gtable,gtable_add_grob) importFrom(igraph,"%--%") importFrom(igraph,"vertex_attr<-") importFrom(igraph,E) importFrom(igraph,V) importFrom(igraph,add_edges) importFrom(igraph,add_vertices) importFrom(igraph,as.undirected) importFrom(igraph,as_edgelist) importFrom(igraph,bfs) importFrom(igraph,components) importFrom(igraph,degree) importFrom(igraph,delete_edges) importFrom(igraph,delete_vertex_attr) importFrom(igraph,distances) importFrom(igraph,edge_attr) importFrom(igraph,ends) importFrom(igraph,gorder) importFrom(igraph,graph_attr) importFrom(igraph,gsize) importFrom(igraph,incident_edges) importFrom(igraph,induced_subgraph) importFrom(igraph,is.directed) importFrom(igraph,is.named) importFrom(igraph,layout_as_bipartite) importFrom(igraph,layout_as_star) importFrom(igraph,layout_as_tree) importFrom(igraph,layout_in_circle) importFrom(igraph,layout_nicely) importFrom(igraph,layout_on_grid) importFrom(igraph,layout_on_sphere) importFrom(igraph,layout_randomly) importFrom(igraph,layout_with_dh) importFrom(igraph,layout_with_drl) importFrom(igraph,layout_with_fr) importFrom(igraph,layout_with_gem) importFrom(igraph,layout_with_graphopt) importFrom(igraph,layout_with_kk) importFrom(igraph,layout_with_lgl) importFrom(igraph,layout_with_mds) importFrom(igraph,layout_with_sugiyama) importFrom(igraph,neighbors) importFrom(igraph,permute) importFrom(igraph,shortest_paths) importFrom(igraph,simplify) importFrom(igraph,unfold_tree) importFrom(igraph,vertex_attr) importFrom(rlang,.data) importFrom(rlang,as_quosure) importFrom(rlang,enquo) importFrom(rlang,eval_tidy) importFrom(rlang,quo_is_null) importFrom(rlang,quo_text) importFrom(rlang,quos) importFrom(rlang,sym) importFrom(scales,abs_area) importFrom(scales,area_pal) importFrom(scales,brewer_pal) importFrom(scales,discard) importFrom(scales,div_gradient_pal) importFrom(scales,gradient_n_pal) importFrom(scales,grey_pal) importFrom(scales,hue_pal) importFrom(scales,identity_pal) importFrom(scales,linetype_pal) importFrom(scales,muted) importFrom(scales,rescale_max) importFrom(scales,rescale_mid) importFrom(scales,rescale_pal) importFrom(scales,seq_gradient_pal) importFrom(scales,shape_pal) importFrom(scales,zero_range) importFrom(stats,setNames) importFrom(tidygraph,.G) importFrom(tidygraph,.register_graph_context) importFrom(tidygraph,activate) importFrom(tidygraph,arrange) importFrom(tidygraph,as_tbl_graph) importFrom(tidygraph,graph_is_forest) importFrom(tidygraph,graph_is_tree) importFrom(tidygraph,node_is_root) importFrom(utils,head) importFrom(utils,tail) importFrom(viridis,scale_color_viridis) importFrom(viridis,scale_fill_viridis) importFrom(viridis,viridis) importFrom(viridis,viridis_pal) useDynLib(ggraph) ggraph/LICENSE.note0000644000176200001440000000351313523247752013507 0ustar liggesusersThe circle packing and minimal enclosing circle algorithms used in the functions layout_igraph_circlepack() and pack_circles() have been implemented with great help and inspiration from the source code of D3.js. The use of any of these function are therefore furthermore under the D3.js license copied below: D3.js license ------------------------------------------------------------------ Copyright 2010-2016 Mike Bostock All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the author nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ggraph/LICENSE0000644000176200001440000000006113523247752012536 0ustar liggesusersYEAR: 2017 COPYRIGHT HOLDER: Thomas Lin Pedersen ggraph/README.md0000644000176200001440000001252313617226142013010 0ustar liggesusers # ggraph [![Travis-CI Build Status](https://travis-ci.org/thomasp85/ggraph.svg?branch=master)](https://travis-ci.org/thomasp85/ggraph) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/thomasp85/ggraph?branch=master&svg=true)](https://ci.appveyor.com/project/thomasp85/ggraph) [![CRAN\_Release\_Badge](http://www.r-pkg.org/badges/version-ago/ggraph)](https://CRAN.R-project.org/package=ggraph) [![CRAN\_Download\_Badge](http://cranlogs.r-pkg.org/badges/ggraph)](https://CRAN.R-project.org/package=ggraph) */dʒiː.dʒɪˈrɑːf/* (or g-giraffe) ## A grammar of graphics for relational data ggraph is an extension of [`ggplot2`](http://ggplot2.tidyverse.org) aimed at supporting relational data structures such as networks, graphs, and trees. While it builds upon the foundation of `ggplot2` and its API it comes with its own self-contained set of geoms, facets, etc., as well as adding the concept of *layouts* to the grammar. ### An example ``` r library(ggraph) #> Loading required package: ggplot2 library(tidygraph) #> #> Attaching package: 'tidygraph' #> The following object is masked from 'package:stats': #> #> filter # Create graph of highschool friendships graph <- as_tbl_graph(highschool) %>% mutate(Popularity = centrality_degree(mode = 'in')) # plot using ggraph ggraph(graph, layout = 'kk') + geom_edge_fan(aes(alpha = stat(index)), show.legend = FALSE) + geom_node_point(aes(size = Popularity)) + facet_edges(~year) + theme_graph(foreground = 'steelblue', fg_text_colour = 'white') ``` ![](man/figures/README-unnamed-chunk-2-1.png) ### The core concepts `ggraph` builds upon three core concepts that are quite easy to understand: 1. [**The Layout**](http://www.data-imaginist.com/2017/ggraph-introduction-layouts/) defines how nodes are placed on the plot, that is, it is a conversion of the relational structure into an x and y value for each node in the graph. `ggraph` has access to all layout functions available in `igraph` and furthermore provides a large selection of its own, such as hive plots, treemaps, and circle packing. 2. [**The Nodes**](http://www.data-imaginist.com/2017/ggraph-introduction-nodes/) are the connected entities in the relational structure. These can be plotted using the `geom_node_*()` family of geoms. Some node geoms make more sense for certain layouts, e.g. `geom_node_tile()` for treemaps and icicle plots, while others are more general purpose, e.g. `geom_node_point()`. 3. [**The Edges**](http://www.data-imaginist.com/2017/ggraph-introduction-edges/) are the connections between the entities in the relational structure. These can be visualized using the `geom_edge_*()` family of geoms that contain a lot of different edge types for different scenarios. Sometimes the edges are implied by the layout (e.g. with treemaps) and need not be plotted, but often some sort of line is warranted. All of the tree concepts have been discussed in detail in dedicated blog posts that are also available as vignettes in the package. Please refer to these for more information. > **Note:** The linked blog posts are based on ggraph v1. After ggraph > v1.1 the underlying implementation was moved to tidygraph and cleaned > up, but this resulted in some breaking changes in the process. > Therefore the vignette versions are generally recommended as they have > been updated. ### Supported data types There are many different ways to store and work with relational data in R. `ggraph` is built upon `tidygraph` and the large swath of data structures it supports are thus natively supported in `ggraph`. In order to get a data type supported by `ggraph`, simply provide an `as_tbl_graph` method for it. ## Installation `ggraph` is available through CRAN and can be installed with `install.packages('ggraph')`. The package is under active development though and the latest set of features can be obtained by installing from this repository using `devtools` ``` r devtools::install_github('thomasp85/ggraph') ``` ## Related work `ggraph` is not the only package to provide some sort of support for relational data in `ggplot2`, though I’m fairly certain that it is the most ambitious. [`ggdendro`](https://CRAN.R-project.org/package=ggdendro) provides support for `dendrogram` and `hclust` objects through conversion of the structures into line segments that can then be plotted with `geom_segment()`. [`ggtree`](http://bioconductor.org/packages/ggtree/) provides more extensive support for all things tree-related, though it lacks some of the layouts and edge types that `ggraph` offers (it has other features that `ggraph` lacks though). For more standard *hairball* network plots [`ggnetwork`](https://CRAN.R-project.org/package=ggnetwork), [`geomnet`](https://CRAN.R-project.org/package=geomnet), and [`GGally`](https://CRAN.R-project.org/package=GGally) all provide some functionality though none of them are as extensive in scope as `ggraph`. ## Code of Conduct Please note that the ‘ggraph’ project is released with a [Contributor Code of Conduct](https://ggraph.data-imaginist.com/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms. ggraph/data/0000755000176200001440000000000013523247752012445 5ustar liggesusersggraph/data/flare.rda0000644000176200001440000001372212653642310014223 0ustar liggesusersBZh91AY&SYq ;GpQ~dM"=zRPSF]6mkm+ aY2jwwhP}q3SWg*d6z4c Ki;&Yy(@B5]!i֔g*b^']~-=Y>;V1=3]f4`w䨠H6Ihd$Y@X(e [ڷg]& r X$cb*+Z((XNUzP-&tJA0b$pʲ\BUFjwQ #0&jf2jdV0fk,(2A "!@"EB)B T78 fִ)($UdTꁛ6-i5sWTZ/ḧC 3 YR IHB `"t\#)_ ÐٌHb0I(ŠV6$̹EM 1S f`ՅQ4*QD]{$1EyAN6YL -攦<ќg#}EjUDg9/m8H{3вjTc"88iQ2 34:\q sczs xf- r86ΘE.r.x/yr6݌!l6pؕA\27(F&`fi&ǽ0+rlh\c8vPvm~)\%/ j%sݤGUZNWs.J.5&Zʏz>\onM9V兢Koko,[dLSdWG7yzuA\洭#j`[Si4C+biqM__#sr- a2r\`.#edd0PVmUb0M5ֲk-i䬎S)q9 n\50ld%S !BeYXe,aeD¼I67l" ڌBX`4BEYX(W[W_=~~x>vG(hh SŜ>^C"$h Rx)xOO~Uh5 txDPf zwӞGΟsu-oY-N":u|w&Mhdy}̼M VSNS_4lL4 U6$XWq^;x 6bZn>ND<_JXO =#@r%*.^ Os! DtB:*TCh}N}%h#HނKX[J!ӢK| Rfdc;ܹi LŠ#x ߿bư^ޚCѡ1 W̸cXA}u.:aK>׎ IHtu;{Mi8CCՓٗw~ P2զPNсѣ&"쀅KlsS+SAXNM#69}.z&Y5ͮ1H+\UIt" b@  uWTubf1՚cH۟AK MD1$6dʇ50LaA& W ;0TkL2vSڮ"Num(󣺼YМT2ı1r+iΰ2N*I6mR0J hf0L*,Y1E ,$j9 CjrL\Ҍpeabp U':2ESy^HS;je] +CDW2^zG w,c,ħìGt"aa\ ʫrp.f8A%l)kq/MZ u7 aq'y|0HSߞNOib<׊>pc ,eUaM:aʐqyh€{]D2Tc2Tc 1,AL 1CD%0M1 -2Ĭ4I1-@DB !(H ))(2,2¨&hjeu q]7ds++FcϖY1uЍOS+BT>RKeQ_탢^ ](oWz7VGW̰ahwC0YIn+mN osK)u萩҄LGtѵe0ʖ K FVG,l9BGD  2" TqDEC&ٲ(?ʇ&ziq-D^]+2NQ)c"ti,Vz2)_Ed$'! !*0'0 jӯvȷ&@)204 ̨$ AO@>D(_^#dd2Xburlb;!!V+B%rE8Pq ggraph/data/highschool.rda0000644000176200001440000000157112654100774015264 0ustar liggesusersBZh91AY&SY`4$sLDUU@/眠@A@@P^o MM"ySSHM440@DFPihm#j$UTd04dhaOP44 4OTBB FbddFAD.`CD9c8cSNAPWhPl}l7JqQ8BN hf1-N"IAGrqHn)wa7B;YFqBĖZuk@E b*ʣ8D'6)ӝ&̇Z? $19X* "Aj"sH{"yGCILt4e=7Gzcv +oQx ɩL,ouN2GCSonY}He0[EY& jb F^p=~52x'\]Q6m(H[F)fE`]F@ AfЕRҍ(/QX "Bg[khl 8D21!4F o ԈTև4B:FR$(ZZDhԀHJiKbIfV,  A#&Ԩa4f)2D,HƖ&*4,05#(f)6T4i @II4҂i6)lFRH(DATD `X1L] _Qm͛u:ҪpWMBxԡozDySU2f[bt0PUnLuwf BhL*0CpY"piK9%i!d|&!2A|a6g$LĈnUqH FPܑN$5pggraph/data/whigs.rda0000644000176200001440000000623512654077261014264 0ustar liggesusersBZh91AY&SYP&1ȉ? `npUH[ Ph P;QCS@Hi1F&dbi@=A~MD$=@h43Phh4F42Ҟ4 zhh4F42 "S%OSAh&'B_"G/_j/꺥1J%*eUDU+/*Sv3ȢJQ@A )r|H'B+VBx(msWP0${]rH~`OB}cT+GއP:Vj5U͵L>2#qr5hҊhUS?5/4.s;똳&vٰgSV Q_:$A*2#2%1fÊVø[ f"gVdz߆\r^$b$ *UT?, ?l*TUIUAbL>ue~Hs'{_2XcNJ4C4b JPȉ**D-պ5ę>iZ]t\>TR|0J׵*$ja '3"SmOs4c޵#o;XN͘,$ʃkAټ|/WhN1z)e9':^~k5̣7 lgQrZw3 ,yƌjsvwUJ'[x!8;'t=;ϽgJ)DZm'$Bt K X3-bdс#[rRD₻FoL#g$dI@Yپ{ otҩc((͙`ݫ ͓xPH&zGlIcf胵DP<8V˸,drԐf:ԮM_yYu1m쳳ћ$) Byt6MuY=l&d1,YI[;6hfM5M(N۩υJlvsj[¬]RgIHxD`Q!T[Xhd) j" JeQrʼn6$if :lae'l4"7pKDeFiR)oHX;RM%j( n˻6;xɢkQw5r[(R$JQ"C ..7#)E".%AN]U 0/D~+OO}zݳoJW MgzXBZ4qw5VC{w}]nǭ =OG9Y:z^uwvዶI&!<S$z~IN 6}mi=&*k1uk{s:|$&?<\4 Vf<+}cpLUy$#<&v}%L2RE4GU (F2Fk::JyGrtщK'Zr{\ǩS)^[tx͑r'w##.\h] W{'ZqKw{GS~[[N~l­h#i ! [e\.Te*c޷ vF\_/^Y/77aBCk;$7DcYp։KB-l0VH-eC$DtM[39ݝg)UJ˞]6㽾TJljr24Mrek*E)ܧ7L:]<i;MpӇq %&X 20& $S8!&Jd+EbcL%ZTֿ.aa!!6$!ɕ˯΍h*#VOPB#VͦuX!ˌ.^3+ݿw1{I#}{[7ǒ̑V[e $ˎ$k+/+;w2F~|iܮVf|MUZͪGN s?tlnTzGޫֵv ,<4jld )e/p…6lDL1rJ2+x*^[ d|DKt0HD=/b{~.mVEٴsokSdVEmZȔTa.qYV]cYb@) sTk*Ȫ@K:2G1jJi)ôcpЇ8R$e!8)uNHK"xtK#E ( p(ﭿL.ft8WunzB-tP g(@L`ْL1&cCL2X4#6V  1kMidnZ;2cSS'o3b\% convert(to_directed) \%>\% mutate(class = sample(letters[1:3], n(), replace = TRUE)) \%>\% activate(edges) \%>\% mutate( class = sample(letters[1:3], n(), replace = TRUE), switch = sample(c(TRUE, FALSE), n(), replace = TRUE) ) \%>\% reroute(from = to, to = from, subset = switch) ggraph(gr, 'linear') + geom_edge_arc(aes(alpha = stat(index))) ggraph(gr, 'linear') + geom_edge_arc2(aes(colour = node.class), strength = 0.6) ggraph(gr, 'linear', circular = TRUE) + geom_edge_arc0(aes(colour = class)) } \seealso{ Other geom_edge_*: \code{\link{geom_edge_bend}()}, \code{\link{geom_edge_density}()}, \code{\link{geom_edge_diagonal}()}, \code{\link{geom_edge_elbow}()}, \code{\link{geom_edge_fan}()}, \code{\link{geom_edge_hive}()}, \code{\link{geom_edge_link}()}, \code{\link{geom_edge_loop}()}, \code{\link{geom_edge_parallel}()}, \code{\link{geom_edge_point}()}, \code{\link{geom_edge_span}()}, \code{\link{geom_edge_tile}()} } \author{ Thomas Lin Pedersen } \concept{geom_edge_*} ggraph/man/geom_node_circle.Rd0000644000176200001440000000554613617226162016061 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geom_node_circle.R \name{geom_node_circle} \alias{geom_node_circle} \title{Show nodes as circles} \usage{ geom_node_circle( mapping = NULL, data = NULL, position = "identity", show.legend = NA, ... ) } \arguments{ \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2:aes]{ggplot2::aes()}} or \code{\link[ggplot2:aes_]{ggplot2::aes_()}}. By default x and y are mapped to x0 and y0 in the node data.} \item{data}{The data to be displayed in this layer. There are three options: If \code{NULL}, the default, the data is inherited from the plot data as specified in the call to \code{\link[=ggplot]{ggplot()}}. A \code{data.frame}, or other object, will override the plot data. All objects will be fortified to produce a data frame. See \code{\link[=fortify]{fortify()}} for which variables will be created. A \code{function} will be called with a single argument, the plot data. The return value must be a \code{data.frame}, and will be used as the layer data. A \code{function} can be created from a \code{formula} (e.g. \code{~ head(.x, 10)}).} \item{position}{Position adjustment, either as a string, or the result of a call to a position adjustment function.} \item{show.legend}{logical. Should this layer be included in the legends? \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE} never includes, and \code{TRUE} always includes. It can also be a named logical vector to finely select the aesthetics to display.} \item{...}{Other arguments passed on to \code{\link[=layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like \code{colour = "red"} or \code{size = 3}. They may also be parameters to the paired geom/stat.} } \description{ This geom is equivalent in functionality to \code{\link[ggforce:geom_circle]{ggforce::geom_circle()}} and allows for plotting of nodes as circles with a radius scaled by the coordinate system. Because of the geoms reliance on the coordinate system it will only produce true circles when combined with \code{\link[ggplot2:coord_fixed]{ggplot2::coord_fixed()}} } \section{Aesthetics}{ \code{geom_node_circle} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x0} \item \strong{y0} \item \strong{r} \item alpha \item colour \item fill \item shape \item size \item stroke \item filter } } \examples{ require(tidygraph) gr <- tbl_graph(flare$vertices, flare$edges) ggraph(gr, 'circlepack', weight = size) + geom_node_circle() + coord_fixed() } \seealso{ Other geom_node_*: \code{\link{geom_node_arc_bar}()}, \code{\link{geom_node_point}()}, \code{\link{geom_node_range}()}, \code{\link{geom_node_text}()}, \code{\link{geom_node_tile}()}, \code{\link{geom_node_voronoi}()} } \author{ Thomas Lin Pedersen } \concept{geom_node_*} ggraph/man/makeContent.cappedpathgrob.Rd0000644000176200001440000000061013276561316020025 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cappedPath.R \name{makeContent.cappedpathgrob} \alias{makeContent.cappedpathgrob} \title{Dynamic capping of paths} \usage{ \method{makeContent}{cappedpathgrob}(x) } \description{ This function takes care of updating which parts of the paths get removed when the device dimensions are updated. } \keyword{internal} ggraph/man/layout_tbl_graph_linear.Rd0000644000176200001440000000410413617226162017462 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/layout_linear.R \name{layout_tbl_graph_linear} \alias{layout_tbl_graph_linear} \title{Place nodes on a line or circle} \usage{ layout_tbl_graph_linear( graph, circular, sort.by = NULL, use.numeric = FALSE, offset = pi/2 ) } \arguments{ \item{graph}{An \code{tbl_graph} object} \item{circular}{Logical. Should the layout be transformed to a circular representation. Defaults to \code{FALSE}.} \item{sort.by}{The name of a node variable to sort the nodes by.} \item{use.numeric}{Logical. Should a numeric sort.by attribute be used as the actual x-coordinates in the layout. May lead to overlapping nodes. Defaults to FALSE} \item{offset}{If \code{circular = TRUE}, where should it begin. Defaults to \code{pi/2} which is equivalent to 12 o'clock.} } \value{ A data.frame with the columns \code{x}, \code{y}, \code{circular} as well as any information stored as node variables in the tbl_graph object. } \description{ This layout puts all nodes on a line, possibly sorted by a node attribute. If \code{circular = TRUE} the nodes will be laid out on the unit circle instead. In the case where the \code{sort.by} attribute is numeric, the numeric values will be used as the x-position and it is thus possible to have uneven spacing between the nodes. } \seealso{ Other layout_tbl_graph_*: \code{\link{layout_tbl_graph_auto}()}, \code{\link{layout_tbl_graph_backbone}()}, \code{\link{layout_tbl_graph_centrality}()}, \code{\link{layout_tbl_graph_circlepack}()}, \code{\link{layout_tbl_graph_dendrogram}()}, \code{\link{layout_tbl_graph_eigen}()}, \code{\link{layout_tbl_graph_fabric}()}, \code{\link{layout_tbl_graph_focus}()}, \code{\link{layout_tbl_graph_hive}()}, \code{\link{layout_tbl_graph_igraph}()}, \code{\link{layout_tbl_graph_manual}()}, \code{\link{layout_tbl_graph_matrix}()}, \code{\link{layout_tbl_graph_partition}()}, \code{\link{layout_tbl_graph_pmds}()}, \code{\link{layout_tbl_graph_stress}()}, \code{\link{layout_tbl_graph_treemap}()}, \code{\link{layout_tbl_graph_unrooted}()} } \concept{layout_tbl_graph_*} ggraph/man/geom_node_arc_bar.Rd0000644000176200001440000000554513617226162016210 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geom_node_arc_bar.R \name{geom_node_arc_bar} \alias{geom_node_arc_bar} \title{Show nodes as thick arcs} \usage{ geom_node_arc_bar( mapping = NULL, data = NULL, position = "identity", show.legend = NA, ... ) } \arguments{ \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2:aes]{ggplot2::aes()}} or \code{\link[ggplot2:aes_]{ggplot2::aes_()}}. By default x and y are mapped to x0 and y0 in the node data.} \item{data}{The data to be displayed in this layer. There are three options: If \code{NULL}, the default, the data is inherited from the plot data as specified in the call to \code{\link[=ggplot]{ggplot()}}. A \code{data.frame}, or other object, will override the plot data. All objects will be fortified to produce a data frame. See \code{\link[=fortify]{fortify()}} for which variables will be created. A \code{function} will be called with a single argument, the plot data. The return value must be a \code{data.frame}, and will be used as the layer data. A \code{function} can be created from a \code{formula} (e.g. \code{~ head(.x, 10)}).} \item{position}{Position adjustment, either as a string, or the result of a call to a position adjustment function.} \item{show.legend}{logical. Should this layer be included in the legends? \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE} never includes, and \code{TRUE} always includes. It can also be a named logical vector to finely select the aesthetics to display.} \item{...}{Other arguments passed on to \code{\link[=layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like \code{colour = "red"} or \code{size = 3}. They may also be parameters to the paired geom/stat.} } \description{ This geom is equivalent in functionality to \code{\link[ggforce:geom_arc_bar]{ggforce::geom_arc_bar()}} and allows for plotting of nodes as arcs with an inner and outer radius scaled by the coordinate system. Its main use is currently in sunburst plots as created with circular partition layouts } \section{Aesthetics}{ \code{geom_node_point} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x0} \item \strong{y0} \item \strong{r0} \item \strong{r} \item \strong{start} \item \strong{end} \item alpha \item colour \item fill \item shape \item size \item stroke \item filter } } \examples{ require(tidygraph) gr <- tbl_graph(flare$vertices, flare$edges) ggraph(gr, 'partition', circular = TRUE, weight = size) + geom_node_arc_bar() } \seealso{ Other geom_node_*: \code{\link{geom_node_circle}()}, \code{\link{geom_node_point}()}, \code{\link{geom_node_range}()}, \code{\link{geom_node_text}()}, \code{\link{geom_node_tile}()}, \code{\link{geom_node_voronoi}()} } \author{ Thomas Lin Pedersen } \concept{geom_node_*} ggraph/man/theme_graph.Rd0000644000176200001440000000771513617226162015067 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/theme_graph.R \name{theme_graph} \alias{theme_graph} \alias{th_foreground} \alias{th_no_axes} \alias{set_graph_style} \alias{unset_graph_style} \title{A theme tuned for graph visualizations} \usage{ theme_graph( base_family = "Arial Narrow", base_size = 11, background = "white", foreground = NULL, border = TRUE, text_colour = "black", bg_text_colour = text_colour, fg_text_colour = text_colour, title_family = base_family, title_size = 18, title_face = "bold", title_margin = 10, title_colour = bg_text_colour, subtitle_family = base_family, subtitle_size = 12, subtitle_face = "plain", subtitle_margin = 15, subtitle_colour = bg_text_colour, strip_text_family = base_family, strip_text_size = 10, strip_text_face = "bold", strip_text_colour = fg_text_colour, caption_family = base_family, caption_size = 9, caption_face = "italic", caption_margin = 10, caption_colour = bg_text_colour, plot_margin = margin(30, 30, 30, 30) ) th_foreground(foreground = "grey80", fg_text_colour = NULL, border = FALSE) th_no_axes() set_graph_style( family = "Arial Narrow", face = "plain", size = 11, text_size = 11, text_colour = "black", ... ) unset_graph_style() } \arguments{ \item{base_size, size, text_size, title_size, subtitle_size, strip_text_size, caption_size}{The size to use for the various text elements. \code{text_size} will be used as geom defaults} \item{background}{The colour to use for the background. This theme sets all background elements except for plot.background to \code{element_blank} so this controls the background for all elements of the plot. Set to \code{NA} to remove the background (thus making the plot transparent)} \item{foreground}{The colour of foreground elements, specifically strip and border. Set to \code{NA} to remove.} \item{border}{Logical. Should border be drawn if a foreground colour is provided?} \item{text_colour, bg_text_colour, fg_text_colour, title_colour, subtitle_colour, strip_text_colour, caption_colour}{The colour of the text in the various text elements} \item{title_margin, subtitle_margin, caption_margin}{The margin to use between the text elements and the plot area} \item{plot_margin}{The plot margin} \item{family, base_family, title_family, subtitle_family, strip_text_family, caption_family}{The font to use for the different elements} \item{face, title_face, subtitle_face, strip_text_face, caption_face}{The fontface to use for the various text elements} \item{...}{Parameters passed on the \code{theme_graph}} } \description{ When plotting graphs, networks, and trees the coordinate values are often of no importance and axes are thus a distraction. \code{ggraph} comes with a build-in theme that removes redundant elements in order to put focus on the data. Furthermore the default behaviour is to use a narrow font so text takes up less space. Theme colour is defined by a background and foreground colour where the background defines the colour of the whole graphics area and the foreground defines the colour of the strip and border. By default strip and border is turned off as it is an unnecessary element unless facetting is used. To add a foreground colour to a plot that is already using \code{theme_graph} the \code{th_foreground} helper is provided. In order to use this appearance as default use the \code{set_graph_style} function. An added benefit of this is that it also changes the default text-related values in the different geoms for a completely coherent look. \code{unset_graph_style} can be used to revert the defaults back to their default settings (that is, they are not necessarily reverted back to what they were prior to calling \code{set_graph_style}). The \code{th_no_axes()} helper is provided to modify an existing theme so that grid and axes are removed. } \examples{ library(tidygraph) graph <- as_tbl_graph(highschool) ggraph(graph) + geom_edge_link() + geom_node_point() + theme_graph() } ggraph/man/pack_circles.Rd0000644000176200001440000000263313226105174015213 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/RcppExports.R \name{pack_circles} \alias{pack_circles} \title{Pack circles together} \usage{ pack_circles(areas) } \arguments{ \item{areas}{A vector of circle areas} } \value{ A matrix with two columns and the same number of rows as the length of the "areas" vector. The matrix has the following attributes added: "enclosing_radius" giving the radius of the smallest enclosing circle, and "front_chain" giving the terminating members of the front chain (see Wang \emph{et al}. 2006). } \description{ This function is a direct interface to the circle packing algorithm used by \code{\link{layout_tbl_graph_circlepack}}. It takes a vector of sizes and returns the x and y position of each circle as a two-column matrix. } \examples{ library(ggforce) sizes <- sample(10, 100, TRUE) position <- pack_circles(sizes) data <- data.frame(x = position[,1], y = position[,2], r = sqrt(sizes/pi)) ggplot() + geom_circle(aes(x0 = x, y0 = y, r = r), data = data, fill = 'steelblue') + geom_circle(aes(x0 = 0, y0 = 0, r = attr(position, 'enclosing_radius'))) + geom_polygon(aes(x = x, y = y), data = data[attr(position, 'front_chain'), ], fill = NA, colour = 'black') } \references{ Wang, W., Wang, H. H., Dai, G., & Wang, H. (2006). \emph{Visualization of large hierarchical data by circle packing}. Chi, 517-520. } ggraph/man/geom_conn_bundle.Rd0000644000176200001440000001075613617226162016100 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geom_conn_bundle.R \name{geom_conn_bundle} \alias{geom_conn_bundle} \alias{geom_conn_bundle2} \alias{geom_conn_bundle0} \title{Create hierarchical edge bundles between node connections} \usage{ geom_conn_bundle( mapping = NULL, data = get_con(), position = "identity", arrow = NULL, lineend = "butt", show.legend = NA, n = 100, tension = 0.8, ... ) geom_conn_bundle2( mapping = NULL, data = get_con(), position = "identity", arrow = NULL, lineend = "butt", show.legend = NA, n = 100, tension = 0.8, ... ) geom_conn_bundle0( mapping = NULL, data = get_con(), position = "identity", arrow = NULL, lineend = "butt", show.legend = NA, tension = 0.8, ... ) } \arguments{ \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2:aes]{ggplot2::aes()}} or \code{\link[ggplot2:aes_]{ggplot2::aes_()}}. By default x, y, xend, yend, group and circular are mapped to x, y, xend, yend, edge.id and circular in the edge data.} \item{data}{The result of a call to \code{\link[=get_con]{get_con()}}} \item{position}{Position adjustment, either as a string, or the result of a call to a position adjustment function.} \item{arrow}{Arrow specification, as created by \code{\link[grid:arrow]{grid::arrow()}}.} \item{lineend}{Line end style (round, butt, square).} \item{show.legend}{logical. Should this layer be included in the legends? \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE} never includes, and \code{TRUE} always includes. It can also be a named logical vector to finely select the aesthetics to display.} \item{n}{The number of points to create along the path.} \item{tension}{How "loose" should the bundles be. 1 will give very tight bundles, while 0 will turn of bundling completely and give straight lines. Defaults to 0.8} \item{...}{Other arguments passed on to \code{\link[ggplot2:layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like \code{colour = "red"} or \code{size = 3}. They may also be parameters to the paired geom/stat.} } \description{ Hierarchical edge bundling is a technique to introduce some order into the hairball structure that can appear when there's a lot of overplotting and edge crossing in a network plot. The concept requires that the network has an intrinsic hierarchical structure that defines the layout but is not shown. Connections between points (that is, not edges) are then drawn so that they loosely follows the underlying hierarchical structure. This results in a flow-like structure where lines that partly move in the same direction will be bundled together. } \note{ In order to avoid excessive typing edge aesthetic names are automatically expanded. Because of this it is not necessary to write \code{edge_colour} within the \code{aes()} call as \code{colour} will automatically be renamed appropriately. } \section{Aesthetics}{ geom_conn_bundle* understands the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item{\strong{x}} \item{\strong{y}} \item{\strong{group}} \item{\strong{circular}} \item{edge_colour} \item{edge_width} \item{edge_linetype} \item{edge_alpha} \item{filter} } } \section{Computed variables}{ \describe{ \item{index}{The position along the path (not computed for the *0 version)} } } \examples{ # Create a graph of the flare class system library(tidygraph) flareGraph <- tbl_graph(flare$vertices, flare$edges) \%>\% mutate( class = map_bfs_chr(node_is_root(), .f = function(node, dist, path, ...) { if (dist <= 1) { return(shortName[node]) } path$result[[nrow(path)]] }) ) importFrom <- match(flare$imports$from, flare$vertices$name) importTo <- match(flare$imports$to, flare$vertices$name) # Use class inheritance for layout but plot class imports as bundles ggraph(flareGraph, 'dendrogram', circular = TRUE) + geom_conn_bundle(aes(colour = stat(index)), data = get_con(importFrom, importTo), edge_alpha = 0.25 ) + geom_node_point(aes(filter = leaf, colour = class)) + scale_edge_colour_distiller('', direction = 1, guide = 'edge_direction') + coord_fixed() + ggforce::theme_no_axes() } \references{ Holten, D. (2006). \emph{Hierarchical edge bundles: visualization of adjacency relations in hierarchical data.} IEEE Transactions on Visualization and Computer Graphics, \strong{12}(5), 741-748. \url{http://doi.org/10.1109/TVCG.2006.147} } \author{ Thomas Lin Pedersen } \concept{geom_conn_*} ggraph/man/geom_node_tile.Rd0000644000176200001440000000650213617226162015546 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geom_node_tile.R \name{geom_node_tile} \alias{geom_node_tile} \title{Draw the rectangles in a treemap} \usage{ geom_node_tile( mapping = NULL, data = NULL, position = "identity", show.legend = NA, ... ) } \arguments{ \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2:aes]{ggplot2::aes()}} or \code{\link[ggplot2:aes_]{ggplot2::aes_()}}. By default x, y, width and height are mapped to x, y, width and height in the node data.} \item{data}{The data to be displayed in this layer. There are three options: If \code{NULL}, the default, the data is inherited from the plot data as specified in the call to \code{\link[ggplot2:ggplot]{ggplot()}}. A \code{data.frame}, or other object, will override the plot data. All objects will be fortified to produce a data frame. See \code{\link[ggplot2:fortify]{fortify()}} for which variables will be created. A \code{function} will be called with a single argument, the plot data. The return value must be a \code{data.frame}, and will be used as the layer data. A \code{function} can be created from a \code{formula} (e.g. \code{~ head(.x, 10)}).} \item{position}{Position adjustment, either as a string, or the result of a call to a position adjustment function.} \item{show.legend}{logical. Should this layer be included in the legends? \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE} never includes, and \code{TRUE} always includes. It can also be a named logical vector to finely select the aesthetics to display.} \item{...}{Other arguments passed on to \code{\link[ggplot2:layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like \code{colour = "red"} or \code{size = 3}. They may also be parameters to the paired geom/stat.} } \description{ A treemap is a space filling layout that recursively divides a rectangle to the children of the node. Often only the leaf nodes are drawn as nodes higher up in the hierarchy would obscure what is below. \code{geom_treemap} is a shorthand for \code{geom_node_treemap} as node is implicit in the case of treemap drawing } \section{Aesthetics}{ \code{geom_treemap} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{y} \item \strong{width} \item \strong{height} \item alpha \item colour \item fill \item size \item stroke \item filter } } \examples{ # Create a graph of the flare class system library(tidygraph) flareGraph <- tbl_graph(flare$vertices, flare$edges) \%>\% mutate( class = map_bfs_chr(node_is_root(), .f = function(node, dist, path, ...) { if (dist <= 1) { return(shortName[node]) } path$result[[nrow(path)]] }) ) ggraph(flareGraph, 'treemap', weight = size) + geom_node_tile(aes(fill = class, filter = leaf, alpha = depth), colour = NA) + geom_node_tile(aes(size = depth), colour = 'white') + scale_alpha(range = c(1, 0.5), guide = 'none') + scale_size(range = c(4, 0.2), guide = 'none') } \seealso{ Other geom_node_*: \code{\link{geom_node_arc_bar}()}, \code{\link{geom_node_circle}()}, \code{\link{geom_node_point}()}, \code{\link{geom_node_range}()}, \code{\link{geom_node_text}()}, \code{\link{geom_node_voronoi}()} } \author{ Thomas Lin Pedersen } \concept{geom_node_*} ggraph/man/layout_tbl_graph_circlepack.Rd0000644000176200001440000000654213617226162020320 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/layout_circlepack.R \name{layout_tbl_graph_circlepack} \alias{layout_tbl_graph_circlepack} \title{Calculate nodes as circles packed within their parent circle} \usage{ layout_tbl_graph_circlepack( graph, weight = NULL, circular = FALSE, sort.by = NULL, direction = "out" ) } \arguments{ \item{graph}{An \code{tbl_graph} object} \item{weight}{An optional node variable to use as weight. Will only affect the weight of leaf nodes as the weight of non-leaf nodes are derived from their children.} \item{circular}{Logical. Should the layout be transformed to a circular representation. Ignored.} \item{sort.by}{The name of a node variable to sort the nodes by.} \item{direction}{The direction of the tree in the graph. \code{'out'} (default) means that parents point towards their children, while \code{'in'} means that children point towards their parent.} } \value{ A data.frame with the columns \code{x}, \code{y}, \code{r}, \code{leaf}, \code{depth}, \code{circular} as well as any information stored as node variables in the tbl_graph object. } \description{ The circle packing algorithm is basically a treemap using circles instead of rectangles. Due to the nature of circles they cannot be packed as efficiently leading to increased amount of "empty space" as compared to a treemap. This can be beneficial though, as the added empty space can aid in visually showing the hierarchy. } \details{ The circle packing is based on the algorithm developed by Weixin Wang and collaborators which tries to find the most dense packing of circles as they are added, one by one. This makes the algorithm very dependent on the order in which circles are added and it is possible that layouts could sometimes be optimized by choosing a different ordering. The algorithm for finding the enclosing circle is the randomized incremental algorithm proposed by Emo Welzl. Both of the above algorithms are the same as used in the D3.js implementation of circle packing and their C++ implementation in ggraph is inspired by Mike Bostocks JavaScript implementation. } \note{ Circle packing is a layout intended for trees, that is, graphs where nodes only have one parent and zero or more children. If the provided graph does not fit this format an attempt to convert it to such a format will be made. } \references{ Wang, W., Wang, H. H., Dai, G., & Wang, H. (2006). \emph{Visualization of large hierarchical data by circle packing}. Chi, 517-520. Welzl, E. (1991). \emph{Smallest enclosing disks (balls and ellipsoids)}. New Results and New Trends in Computer Science, 359-370. } \seealso{ Other layout_tbl_graph_*: \code{\link{layout_tbl_graph_auto}()}, \code{\link{layout_tbl_graph_backbone}()}, \code{\link{layout_tbl_graph_centrality}()}, \code{\link{layout_tbl_graph_dendrogram}()}, \code{\link{layout_tbl_graph_eigen}()}, \code{\link{layout_tbl_graph_fabric}()}, \code{\link{layout_tbl_graph_focus}()}, \code{\link{layout_tbl_graph_hive}()}, \code{\link{layout_tbl_graph_igraph}()}, \code{\link{layout_tbl_graph_linear}()}, \code{\link{layout_tbl_graph_manual}()}, \code{\link{layout_tbl_graph_matrix}()}, \code{\link{layout_tbl_graph_partition}()}, \code{\link{layout_tbl_graph_pmds}()}, \code{\link{layout_tbl_graph_stress}()}, \code{\link{layout_tbl_graph_treemap}()}, \code{\link{layout_tbl_graph_unrooted}()} } \concept{layout_tbl_graph_*} ggraph/man/scale_type.geometry.Rd0000644000176200001440000000061713276561316016564 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geometry.R \name{scale_type.geometry} \alias{scale_type.geometry} \title{Define default scale type for geometry} \usage{ \method{scale_type}{geometry}(x) } \description{ This function is quite useless as geometry is not meant to be scaled, but it is a requirement for ggplot2 to handle it correctly. } \keyword{internal} ggraph/man/geom_edge_density.Rd0000644000176200001440000000644113617226162016251 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geom_edge_density.R \name{geom_edge_density} \alias{geom_edge_density} \title{Show edges as a density map} \usage{ geom_edge_density( mapping = NULL, data = get_edges("short"), position = "identity", show.legend = NA, n = 100, ... ) } \arguments{ \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2:aes]{ggplot2::aes()}} or \code{\link[ggplot2:aes_]{ggplot2::aes_()}}. By default x, y, xend, yend, group and circular are mapped to x, y, xend, yend, edge.id and circular in the edge data.} \item{data}{The return of a call to \code{get_edges()} or a data.frame giving edges in correct format (see details for for guidance on the format). See \code{\link[=get_edges]{get_edges()}} for more details on edge extraction.} \item{position}{Position adjustment, either as a string, or the result of a call to a position adjustment function.} \item{show.legend}{logical. Should this layer be included in the legends? \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE} never includes, and \code{TRUE} always includes. It can also be a named logical vector to finely select the aesthetics to display.} \item{n}{The number of points to estimate in the x and y direction, i.e. the resolution of the raster.} \item{...}{Other arguments passed on to \code{\link[ggplot2:layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like \code{colour = "red"} or \code{size = 3}. They may also be parameters to the paired geom/stat.} } \description{ This geom makes it possible to add a layer showing edge presence as a density map. Each edge is converted to \code{n} points along the line and a jitter is applied. Based on this dataset a two-dimensional kernel density estimation is applied and plotted as a raster image. The density is mapped to the alpha level, making it possible to map a variable to the fill. } \section{Aesthetics}{ \code{geom_edge_density} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \strong{x} \strong{y} \strong{xend} \strong{yend} edge_fill filter } \section{Computed variables}{ \describe{ \item{x, y}{The coordinates for each pixel in the raster} \item{density}{The density associated with the pixel} } } \section{Edge aesthetic name expansion}{ In order to avoid excessive typing edge aesthetic names are automatically expanded. Because of this it is not necessary to write \code{edge_colour} within the \code{aes()} call as \code{colour} will automatically be renamed appropriately. } \examples{ require(tidygraph) gr <- create_notable('bull') \%>\% activate(edges) \%>\% mutate(class = sample(letters[1:3], n(), replace = TRUE)) ggraph(gr, 'stress') + geom_edge_density(aes(fill = class)) + geom_edge_link() + geom_node_point() } \seealso{ Other geom_edge_*: \code{\link{geom_edge_arc}()}, \code{\link{geom_edge_bend}()}, \code{\link{geom_edge_diagonal}()}, \code{\link{geom_edge_elbow}()}, \code{\link{geom_edge_fan}()}, \code{\link{geom_edge_hive}()}, \code{\link{geom_edge_link}()}, \code{\link{geom_edge_loop}()}, \code{\link{geom_edge_parallel}()}, \code{\link{geom_edge_point}()}, \code{\link{geom_edge_span}()}, \code{\link{geom_edge_tile}()} } \author{ Thomas Lin Pedersen } \concept{geom_edge_*} ggraph/man/layout_tbl_graph_matrix.Rd0000644000176200001440000000431413617226162017517 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/layout_matrix.R \name{layout_tbl_graph_matrix} \alias{layout_tbl_graph_matrix} \title{Place nodes on a diagonal} \usage{ layout_tbl_graph_matrix(graph, circular = FALSE, sort.by = NULL) } \arguments{ \item{graph}{An \code{tbl_graph} object} \item{circular}{Ignored} \item{sort.by}{An expression providing the sorting of the nodes. If \code{NULL} the nodes will be ordered by their index in the graph.} } \value{ A data.frame with the columns \code{x}, \code{y}, \code{circular} as well as any information stored as node variables in the tbl_graph object. } \description{ This layout puts all nodes on a diagonal, thus preparing the layout for use with \code{\link[=geom_edge_point]{geom_edge_point()}} resulting in a matrix layout. While matrix layouts excel in scalability, the interpretation of the visual is very dependent on the sorting of the nodes. Different sorting algorithms have been implemented in \code{tidygraph} and these can be used directly. Behrisch \emph{et al.} (2016) have provided a nice overview of some of the different sorting algorithms and what insight they might bring, along with a rundown of different patterns to look out for. } \references{ Behrisch, M., Bach, B., Riche, N. H., Schreck, T., Fekete, J.-D. (2016). \emph{Matrix Reordering Methods for Table and Network Visualization}. Computer Graphics Forum, 35: 693–716. \url{http://doi.org/10.1111/cgf.12935} } \seealso{ Other layout_tbl_graph_*: \code{\link{layout_tbl_graph_auto}()}, \code{\link{layout_tbl_graph_backbone}()}, \code{\link{layout_tbl_graph_centrality}()}, \code{\link{layout_tbl_graph_circlepack}()}, \code{\link{layout_tbl_graph_dendrogram}()}, \code{\link{layout_tbl_graph_eigen}()}, \code{\link{layout_tbl_graph_fabric}()}, \code{\link{layout_tbl_graph_focus}()}, \code{\link{layout_tbl_graph_hive}()}, \code{\link{layout_tbl_graph_igraph}()}, \code{\link{layout_tbl_graph_linear}()}, \code{\link{layout_tbl_graph_manual}()}, \code{\link{layout_tbl_graph_partition}()}, \code{\link{layout_tbl_graph_pmds}()}, \code{\link{layout_tbl_graph_stress}()}, \code{\link{layout_tbl_graph_treemap}()}, \code{\link{layout_tbl_graph_unrooted}()} } \concept{layout_tbl_graph_*} ggraph/man/get_edges.Rd0000644000176200001440000000741013617226162014522 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/edges.R \name{get_edges} \alias{get_edges} \title{Create edge extractor function} \usage{ get_edges(format = "short", collapse = "none", ...) } \arguments{ \item{format}{Either \code{'short'} (the default) or \code{'long'}. See details for a descriptions of the differences} \item{collapse}{Either \code{'none'} (the default), \code{'all'} or \code{'direction'}. Specifies whether parallel edges should be merged. See details for more information} \item{...}{Additional data that will be cbind'ed together with the returned edge data.} } \value{ A data.frame with columns dependent on format as well as the graph type. In addition to the columns discussed in the details section, the data.frame will always contain the columns \code{from}, \code{to} and \code{circular}, the two former giving the indexes of the start and end node and the latter if the layout is circular (needed for correct formatting of some \verb{geom_edge_*}). The graph dependent information is: \describe{ \item{dendrogram}{A \code{label} column will hold the value of the \code{edgetext} attribute. In addition any value stored in the \code{edgePar} attribute will be added. Lastly a \code{direction} column will hold the relative position between the start and end nodes (needed for correct formatting of \code{\link[=geom_edge_elbow]{geom_edge_elbow()}}).} \item{igraph}{All edge attributes of the original graph object is added as columns to the data.frame} } } \description{ This function returns another function that can extract edges from a ggraph_layout object. The functionality of the returned function is decided by the arguments to \code{get_edges}. The need for \code{get_edges} is mainly to pass to the \code{data} argument of the different \verb{geom_edge_*} functions in order to present them with the right kind of data. In general each \verb{geom_edge_*} has the default set correctly so there is only need to modify the data argument if parallel edges should be collapsed. } \details{ There are two types of return formats possible for the result of the returned function: \describe{ \item{short}{In this format each edge is described in one line in the format expected for \code{\link[ggplot2:geom_segment]{ggplot2::geom_segment()}}, that is, the start node position is encoded in the \code{x} and \code{y} column and the end node position is encoded in the \code{xend} and \code{yend} column. If node parameters are added to the edge the name of the parameters will be prefixed with \code{node1.} for the start node and \code{node2.} for the end node.} \item{long}{In this format each edge consists of two rows with matching \code{edge.id} value. The start and end position are both encoded in the \code{x} and \code{y} column. The relative position of the rows determines which is the start and end node, the first occurring being the start node. If node parameters are added to the edge data the name of the parameters will be prefixed with \code{node.}.} } Node parameters are automatically added so it is possible to format edge aesthetics according to start or end node parameters, or interpolate edge aesthetics between start and end node parameters. Node parameters will be prefixed to avoid name clash with edge parameters. The prefix depends on the format (see above). If the graph is not simple (it contains at most one edge between each node pair) it can be collapsed so either all edges between two nodes or all edges of the same direction between two nodes are merged. The edge parameters are taken from the first occurring edge, so if some more sophisticated summary is needed it is suggested that the graph be tidied up before plotting with ggraph. } \seealso{ Other extractors: \code{\link{get_con}()}, \code{\link{get_nodes}()} } \concept{extractors} ggraph/man/layout_tbl_graph_manual.Rd0000644000176200001440000000272713617226162017476 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/layout_manual.R \name{layout_tbl_graph_manual} \alias{layout_tbl_graph_manual} \title{Manually specify a layout for layout_tbl_graph} \usage{ layout_tbl_graph_manual(graph, x, y, circular) } \arguments{ \item{graph}{An \code{tbl_graph} object} \item{x, y}{Expressions with the x and y positions of the nodes} \item{circular}{Ignored} } \value{ A data.frame with the columns \code{x}, \code{y}, \code{circular} as well as any information stored as node variables in the tbl_graph. } \description{ This layout function lets you pass the node positions in manually. The supplied positions must match the order of the nodes in the tbl_graph } \seealso{ Other layout_tbl_graph_*: \code{\link{layout_tbl_graph_auto}()}, \code{\link{layout_tbl_graph_backbone}()}, \code{\link{layout_tbl_graph_centrality}()}, \code{\link{layout_tbl_graph_circlepack}()}, \code{\link{layout_tbl_graph_dendrogram}()}, \code{\link{layout_tbl_graph_eigen}()}, \code{\link{layout_tbl_graph_fabric}()}, \code{\link{layout_tbl_graph_focus}()}, \code{\link{layout_tbl_graph_hive}()}, \code{\link{layout_tbl_graph_igraph}()}, \code{\link{layout_tbl_graph_linear}()}, \code{\link{layout_tbl_graph_matrix}()}, \code{\link{layout_tbl_graph_partition}()}, \code{\link{layout_tbl_graph_pmds}()}, \code{\link{layout_tbl_graph_stress}()}, \code{\link{layout_tbl_graph_treemap}()}, \code{\link{layout_tbl_graph_unrooted}()} } \concept{layout_tbl_graph_*} ggraph/man/makeContent.textalong.Rd0000644000176200001440000000054613276561316017057 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/textAlong.R \name{makeContent.textalong} \alias{makeContent.textalong} \title{Text angled according to line} \usage{ \method{makeContent}{textalong}(x) } \description{ This function takes care of recalculating the angle of the text as the device size changes } \keyword{internal} ggraph/man/geom_edge_point.Rd0000644000176200001440000000656513617226162015732 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geom_edge_point.R \name{geom_edge_point} \alias{geom_edge_point} \title{Draw edges as glyphs} \usage{ geom_edge_point( mapping = NULL, data = get_edges(), position = "identity", mirror = FALSE, show.legend = NA, ... ) } \arguments{ \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2:aes]{ggplot2::aes()}} or \code{\link[ggplot2:aes_]{ggplot2::aes_()}}. By default x, y, xend, yend, group and circular are mapped to x, y, xend, yend, edge.id and circular in the edge data.} \item{data}{The return of a call to \code{get_edges()} or a data.frame giving edges in correct format (see details for for guidance on the format). See \code{\link[=get_edges]{get_edges()}} for more details on edge extraction.} \item{position}{Position adjustment, either as a string, or the result of a call to a position adjustment function.} \item{mirror}{Logical. Should edge points be duplicated on both sides of the diagonal. Intended for undirected graphs. Default to \code{FALSE}} \item{show.legend}{logical. Should this layer be included in the legends? \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE} never includes, and \code{TRUE} always includes. It can also be a named logical vector to finely select the aesthetics to display.} \item{...}{Other arguments passed on to \code{\link[ggplot2:layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like \code{colour = "red"} or \code{size = 3}. They may also be parameters to the paired geom/stat.} } \description{ This geom draws edges as glyphs with their x-position defined by the x-position of the start node, and the y-position defined by the y-position of the end node. As such it will result in a matrix layout when used in conjunction with \code{\link[=layout_tbl_graph_matrix]{layout_tbl_graph_matrix()}} } \section{Aesthetics}{ \code{geom_edge_point} understands the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{y} \item edge_shape \item edge_colour \item edge_size \item edge_alpha \item filter } } \section{Edge aesthetic name expansion}{ In order to avoid excessive typing edge aesthetic names are automatically expanded. Because of this it is not necessary to write \code{edge_colour} within the \code{aes()} call as \code{colour} will automatically be renamed appropriately. } \examples{ require(tidygraph) gr <- create_notable('zachary') \%>\% mutate(group = group_infomap()) \%>\% morph(to_split, group) \%>\% activate(edges) \%>\% mutate(edge_group = as.character(.N()$group[1])) \%>\% unmorph() ggraph(gr, 'matrix', sort.by = node_rank_hclust()) + geom_edge_point(aes(colour = edge_group), mirror = TRUE, edge_size = 3) + scale_y_reverse() + coord_fixed() + labs(edge_colour = 'Infomap Cluster') + ggtitle("Zachary' Karate Club") } \seealso{ Other geom_edge_*: \code{\link{geom_edge_arc}()}, \code{\link{geom_edge_bend}()}, \code{\link{geom_edge_density}()}, \code{\link{geom_edge_diagonal}()}, \code{\link{geom_edge_elbow}()}, \code{\link{geom_edge_fan}()}, \code{\link{geom_edge_hive}()}, \code{\link{geom_edge_link}()}, \code{\link{geom_edge_loop}()}, \code{\link{geom_edge_parallel}()}, \code{\link{geom_edge_span}()}, \code{\link{geom_edge_tile}()} } \author{ Thomas Lin Pedersen } \concept{geom_edge_*} ggraph/man/geom_edge_diagonal.Rd0000644000176200001440000002211013617226162016337 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geom_edge_diagonal.R \name{geom_edge_diagonal} \alias{geom_edge_diagonal} \alias{geom_edge_diagonal2} \alias{geom_edge_diagonal0} \title{Draw edges as diagonals} \usage{ geom_edge_diagonal( mapping = NULL, data = get_edges(), position = "identity", arrow = NULL, strength = 1, flipped = FALSE, n = 100, lineend = "butt", linejoin = "round", linemitre = 1, label_colour = "black", label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = "rot", force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ... ) geom_edge_diagonal2( mapping = NULL, data = get_edges("long"), position = "identity", arrow = NULL, strength = 1, flipped = FALSE, n = 100, lineend = "butt", linejoin = "round", linemitre = 1, label_colour = "black", label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = "rot", force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ... ) geom_edge_diagonal0( mapping = NULL, data = get_edges(), position = "identity", arrow = NULL, strength = 1, flipped = FALSE, lineend = "butt", show.legend = NA, ... ) } \arguments{ \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2:aes]{ggplot2::aes()}} or \code{\link[ggplot2:aes_]{ggplot2::aes_()}}. By default x, y, xend, yend, group and circular are mapped to x, y, xend, yend, edge.id and circular in the edge data.} \item{data}{The return of a call to \code{get_edges()} or a data.frame giving edges in correct format (see details for for guidance on the format). See \code{\link[=get_edges]{get_edges()}} for more details on edge extraction.} \item{position}{Position adjustment, either as a string, or the result of a call to a position adjustment function.} \item{arrow}{Arrow specification, as created by \code{\link[grid:arrow]{grid::arrow()}}.} \item{strength}{The strength of the curvature of the diagonal. \code{0} will result in a straight line while \code{1} will give the familiar S-shape.} \item{flipped}{Logical, Has the layout been flipped by reassigning the mapping of x, y etc?} \item{n}{The number of points to create along the path.} \item{lineend}{Line end style (round, butt, square).} \item{linejoin}{Line join style (round, mitre, bevel).} \item{linemitre}{Line mitre limit (number greater than 1).} \item{label_colour}{The colour of the edge label. If \code{NA} it will use the colour of the edge.} \item{label_alpha}{The opacity of the edge label. If \code{NA} it will use the opacity of the edge.} \item{label_parse}{If \code{TRUE}, the labels will be parsed into expressions and displayed as described in \code{\link[grDevices:plotmath]{grDevices::plotmath()}}.} \item{check_overlap}{If \code{TRUE}, text that overlaps previous text in the same layer will not be plotted.} \item{angle_calc}{Either 'none', 'along', or 'across'. If 'none' the label will use the angle aesthetic of the geom. If 'along' The label will be written along the edge direction. If 'across' the label will be written across the edge direction.} \item{force_flip}{Logical. If \code{angle_calc} is either 'along' or 'across' should the label be flipped if it is on it's head. Default to \code{TRUE}.} \item{label_dodge}{A \code{\link[grid:unit]{grid::unit()}} giving a fixed vertical shift to add to the label in case of \code{angle_calc} is either 'along' or 'across'} \item{label_push}{A \code{\link[grid:unit]{grid::unit()}} giving a fixed horizontal shift to add to the label in case of \code{angle_calc} is either 'along' or 'across'} \item{show.legend}{logical. Should this layer be included in the legends? \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE} never includes, and \code{TRUE} always includes. It can also be a named logical vector to finely select the aesthetics to display.} \item{...}{Other arguments passed on to \code{\link[ggplot2:layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like \code{colour = "red"} or \code{size = 3}. They may also be parameters to the paired geom/stat.} } \description{ This geom draws edges as diagonal bezier curves. The name comes from D3.js where this shape was called diagonals until it was renamed to \href{https://github.com/d3/d3-shape/blob/v1.3.5/README.md#links}{links}. A diagonal in this context is a quadratic bezier with the control points positioned halfway between the start and end points but on the same axis. This produces a pleasing fan-in, fan-out line that is mostly relevant for hierarchical layouts as it implies an overall directionality in the plot. } \section{Aesthetics}{ \code{geom_edge_diagonal} and \code{geom_edge_diagonal0} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{y} \item \strong{xend} \item \strong{yend} \item \strong{circular} \item edge_colour \item edge_width \item edge_linetype \item edge_alpha \item filter } \code{geom_edge_diagonal2} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{y} \item \strong{group} \item \strong{circular} \item edge_colour \item edge_width \item edge_linetype \item edge_alpha \item filter } \code{geom_edge_diagonal} and \code{geom_edge_diagonal2} furthermore takes the following aesthetics. \itemize{ \item start_cap \item end_cap \item label \item label_pos \item label_size \item angle \item hjust \item vjust \item family \item fontface \item lineheight } } \section{Computed variables}{ \describe{ \item{index}{The position along the path (not computed for the *0 version)} } } \section{Edge variants}{ Many geom_edge_* layers comes in 3 flavors depending on the level of control needed over the drawing. The default (no numeric postfix) generate a number of points (\code{n}) along the edge and draws it as a path. Each point along the line has a numeric value associated with it giving the position along the path, and it is therefore possible to show the direction of the edge by mapping to this e.g. \code{colour = stat(index)}. The version postfixed with a "2" uses the "long" edge format (see \code{\link[=get_edges]{get_edges()}}) and makes it possible to interpolate node parameter between the start and end node along the edge. It is considerable less performant so should only be used if this is needed. The version postfixed with a "0" draws the edge in the most performant way, often directly using an appropriate grob from the grid package, but does not allow for gradients along the edge. Often it is beneficial to stop the drawing of the edge before it reaches the node, for instance in cases where an arrow should be drawn and the arrowhead shouldn't lay on top or below the node point. geom_edge_* and geom_edge_*2 supports this through the start_cap and end_cap aesthetics that takes a \code{\link[=geometry]{geometry()}} specification and dynamically caps the termini of the edges based on the given specifications. This means that if \code{end_cap = circle(1, 'cm')} the edges will end at a distance of 1cm even during resizing of the plot window. All \verb{geom_edge_*} and \code{geom_edge_*2} have the ability to draw a label along the edge. The reason this is not a separate geom is that in order for the label to know the location of the edge it needs to know the edge type etc. Labels are drawn by providing a label aesthetic. The label_pos can be used to specify where along the edge it should be drawn by supplying a number between 0 and 1. The label_size aesthetic can be used to control the size of the label. Often it is needed to have the label written along the direction of the edge, but since the actual angle is dependent on the plot dimensions this cannot be calculated beforehand. Using the angle_calc argument allows you to specify whether to use the supplied angle aesthetic or whether to draw the label along or across the edge. } \section{Edge aesthetic name expansion}{ In order to avoid excessive typing edge aesthetic names are automatically expanded. Because of this it is not necessary to write \code{edge_colour} within the \code{aes()} call as \code{colour} will automatically be renamed appropriately. } \examples{ require(tidygraph) gr <- create_tree(20, 4) \%>\% mutate(class = sample(letters[1:3], n(), replace = TRUE)) \%>\% activate(edges) \%>\% mutate(class = sample(letters[1:3], n(), replace = TRUE)) ggraph(gr, 'tree') + geom_edge_diagonal(aes(alpha = stat(index))) ggraph(gr, 'tree') + geom_edge_diagonal2(aes(colour = node.class)) ggraph(gr, 'tree') + geom_edge_diagonal0(aes(colour = class)) } \seealso{ Other geom_edge_*: \code{\link{geom_edge_arc}()}, \code{\link{geom_edge_bend}()}, \code{\link{geom_edge_density}()}, \code{\link{geom_edge_elbow}()}, \code{\link{geom_edge_fan}()}, \code{\link{geom_edge_hive}()}, \code{\link{geom_edge_link}()}, \code{\link{geom_edge_loop}()}, \code{\link{geom_edge_parallel}()}, \code{\link{geom_edge_point}()}, \code{\link{geom_edge_span}()}, \code{\link{geom_edge_tile}()} } \author{ Thomas Lin Pedersen } \concept{geom_edge_*} ggraph/man/scale_label_size.Rd0000644000176200001440000001112613617226162016053 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/scale_label_size.R \name{scale_label_size} \alias{scale_label_size} \alias{scale_label_size_continuous} \alias{scale_label_size_discrete} \alias{scale_label_size_manual} \alias{scale_label_size_identity} \title{Edge label size scales} \usage{ scale_label_size_continuous(..., range = c(1, 6)) scale_label_size(..., range = c(1, 6)) scale_label_size_discrete(..., range = c(2, 6)) scale_label_size_manual(..., values) scale_label_size_identity(..., guide = "none") } \arguments{ \item{...}{Arguments passed on to \code{continuous_scale} \describe{ \item{name}{The name of the scale. Used as the axis or legend title. If \code{waiver()}, the default, the name of the scale is taken from the first mapping used for that aesthetic. If \code{NULL}, the legend title will be omitted.} \item{breaks}{One of: \itemize{ \item \code{NULL} for no breaks \item \code{waiver()} for the default breaks computed by the transformation object \item A numeric vector of positions \item A function that takes the limits as input and returns breaks as output }} \item{minor_breaks}{One of: \itemize{ \item \code{NULL} for no minor breaks \item \code{waiver()} for the default breaks (one minor break between each major break) \item A numeric vector of positions \item A function that given the limits returns a vector of minor breaks. }} \item{labels}{One of: \itemize{ \item \code{NULL} for no labels \item \code{waiver()} for the default labels computed by the transformation object \item A character vector giving labels (must be same length as \code{breaks}) \item A function that takes the breaks as input and returns labels as output }} \item{limits}{One of: \itemize{ \item \code{NULL} to use the default scale range \item A numeric vector of length two providing limits of the scale. Use \code{NA} to refer to the existing minimum or maximum \item A function that accepts the existing (automatic) limits and returns new limits }} \item{oob}{Function that handles limits outside of the scale limits (out of bounds). The default replaces out of bounds values with \code{NA}.} \item{na.value}{Missing values will be replaced with this value.} \item{trans}{Either the name of a transformation object, or the object itself. Built-in transformations include "asn", "atanh", "boxcox", "date", "exp", "hms", "identity", "log", "log10", "log1p", "log2", "logit", "modulus", "probability", "probit", "pseudo_log", "reciprocal", "reverse", "sqrt" and "time". A transformation object bundles together a transform, its inverse, and methods for generating breaks and labels. Transformation objects are defined in the scales package, and are called \code{name_trans}, e.g. \code{\link[scales:boxcox_trans]{scales::boxcox_trans()}}. You can create your own transformation with \code{\link[scales:trans_new]{scales::trans_new()}}.} \item{guide}{A function used to create a guide or its name. See \code{\link[ggplot2:guides]{guides()}} for more info.} \item{position}{The position of the axis. "left" or "right" for vertical scales, "top" or "bottom" for horizontal scales} \item{super}{The super class to use for the constructed scale} \item{expand}{Vector of range expansion constants used to add some padding around the data, to ensure that they are placed some distance away from the axes. Use the convenience function \code{\link[ggplot2:expand_scale]{expand_scale()}} to generate the values for the \code{expand} argument. The defaults are to expand the scale by 5\% on each side for continuous variables, and by 0.6 units on each side for discrete variables.} }} \item{range}{a numeric vector of length 2 that specifies the minimum and maximum size of the plotting symbol after transformation.} \item{values}{a set of aesthetic values to map data values to. If this is a named vector, then the values will be matched based on the names. If unnamed, values will be matched in order (usually alphabetical) with the limits of the scale. Any data values that don't match will be given \code{na.value}.} \item{guide}{A function used to create a guide or its name. See \code{\link[ggplot2:guides]{guides()}} for more info.} } \value{ A ggproto object inheriting from \code{Scale} } \description{ This set of scales defines new size scales for edge labels in order to allow for separate sizing of edges and their labels. } \seealso{ Other scale_edge_*: \code{\link{scale_edge_alpha}()}, \code{\link{scale_edge_colour}}, \code{\link{scale_edge_fill}}, \code{\link{scale_edge_linetype}()}, \code{\link{scale_edge_shape}()}, \code{\link{scale_edge_size}()}, \code{\link{scale_edge_width}()} } \concept{scale_edge_*} ggraph/man/geom_edge_span.Rd0000644000176200001440000002226613617226162015536 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geom_edge_span.R \name{geom_edge_span} \alias{geom_edge_span} \alias{geom_edge_span2} \alias{geom_edge_span0} \title{Draw edges as vertical spans} \usage{ geom_edge_span( mapping = NULL, data = get_edges("short"), position = "identity", end_shape = NA, arrow = NULL, n = 100, lineend = "butt", linejoin = "round", linemitre = 1, label_colour = "black", label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = "rot", force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ... ) geom_edge_span2( mapping = NULL, data = get_edges("long"), position = "identity", end_shape = NA, arrow = NULL, n = 100, lineend = "butt", linejoin = "round", linemitre = 1, label_colour = "black", label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = "rot", force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ... ) geom_edge_span0( mapping = NULL, data = get_edges(), position = "identity", end_shape = NA, arrow = NULL, lineend = "butt", show.legend = NA, ... ) } \arguments{ \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2:aes]{ggplot2::aes()}} or \code{\link[ggplot2:aes_]{ggplot2::aes_()}}. By default x, y, xend, yend, group and circular are mapped to x, y, xend, yend, edge.id and circular in the edge data.} \item{data}{The return of a call to \code{get_edges()} or a data.frame giving edges in correct format (see details for for guidance on the format). See \code{\link[=get_edges]{get_edges()}} for more details on edge extraction.} \item{position}{Position adjustment, either as a string, or the result of a call to a position adjustment function.} \item{end_shape}{The adornment to put at the ends of the span. The naming follows the conventions of the shape aesthetic in \code{\link[ggplot2:geom_point]{ggplot2::geom_point()}}} \item{arrow}{Arrow specification, as created by \code{\link[grid:arrow]{grid::arrow()}}.} \item{n}{The number of points to create along the path.} \item{lineend}{Line end style (round, butt, square).} \item{linejoin}{Line join style (round, mitre, bevel).} \item{linemitre}{Line mitre limit (number greater than 1).} \item{label_colour}{The colour of the edge label. If \code{NA} it will use the colour of the edge.} \item{label_alpha}{The opacity of the edge label. If \code{NA} it will use the opacity of the edge.} \item{label_parse}{If \code{TRUE}, the labels will be parsed into expressions and displayed as described in \code{\link[grDevices:plotmath]{grDevices::plotmath()}}.} \item{check_overlap}{If \code{TRUE}, text that overlaps previous text in the same layer will not be plotted.} \item{angle_calc}{Either 'none', 'along', or 'across'. If 'none' the label will use the angle aesthetic of the geom. If 'along' The label will be written along the edge direction. If 'across' the label will be written across the edge direction.} \item{force_flip}{Logical. If \code{angle_calc} is either 'along' or 'across' should the label be flipped if it is on it's head. Default to \code{TRUE}.} \item{label_dodge}{A \code{\link[grid:unit]{grid::unit()}} giving a fixed vertical shift to add to the label in case of \code{angle_calc} is either 'along' or 'across'} \item{label_push}{A \code{\link[grid:unit]{grid::unit()}} giving a fixed horizontal shift to add to the label in case of \code{angle_calc} is either 'along' or 'across'} \item{show.legend}{logical. Should this layer be included in the legends? \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE} never includes, and \code{TRUE} always includes. It can also be a named logical vector to finely select the aesthetics to display.} \item{...}{Other arguments passed on to \code{\link[ggplot2:layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like \code{colour = "red"} or \code{size = 3}. They may also be parameters to the paired geom/stat.} } \description{ This edge geom is mainly intended for use with \link[=layout_tbl_graph_fabric]{fabric} layouts. It draws edges as vertical segments with an optional end shape adornment. Due to the special nature of fabric layouts where nodes are not a single point in space but a line, this geom doesn't derive the x position from the location of the terminal nodes, but defaults to using the \code{edge_x} variable calculated by the fabric layout. If this geom is used with other layouts \code{x}and \code{xend} must be given explicitly. } \section{Aesthetics}{ \code{geom_edge_span} and \code{geom_edge_span0} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{y} \item \strong{xend} \item \strong{yend} \item edge_colour \item edge_width \item edge_linetype \item edge_alpha \item filter } \code{geom_edge_span2} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{y} \item \strong{group} \item edge_colour \item edge_width \item edge_linetype \item edge_alpha \item filter } \code{geom_edge_span} and \code{geom_edge_span2} furthermore takes the following aesthetics. \itemize{ \item start_cap \item end_cap \item label \item label_pos \item label_size \item angle \item hjust \item vjust \item family \item fontface \item lineheight } } \section{Computed variables}{ \describe{ \item{index}{The position along the path (not computed for the *0 version)} } } \section{Edge variants}{ Many geom_edge_* layers comes in 3 flavors depending on the level of control needed over the drawing. The default (no numeric postfix) generate a number of points (\code{n}) along the edge and draws it as a path. Each point along the line has a numeric value associated with it giving the position along the path, and it is therefore possible to show the direction of the edge by mapping to this e.g. \code{colour = stat(index)}. The version postfixed with a "2" uses the "long" edge format (see \code{\link[=get_edges]{get_edges()}}) and makes it possible to interpolate node parameter between the start and end node along the edge. It is considerable less performant so should only be used if this is needed. The version postfixed with a "0" draws the edge in the most performant way, often directly using an appropriate grob from the grid package, but does not allow for gradients along the edge. Often it is beneficial to stop the drawing of the edge before it reaches the node, for instance in cases where an arrow should be drawn and the arrowhead shouldn't lay on top or below the node point. geom_edge_* and geom_edge_*2 supports this through the start_cap and end_cap aesthetics that takes a \code{\link[=geometry]{geometry()}} specification and dynamically caps the termini of the edges based on the given specifications. This means that if \code{end_cap = circle(1, 'cm')} the edges will end at a distance of 1cm even during resizing of the plot window. All \verb{geom_edge_*} and \code{geom_edge_*2} have the ability to draw a label along the edge. The reason this is not a separate geom is that in order for the label to know the location of the edge it needs to know the edge type etc. Labels are drawn by providing a label aesthetic. The label_pos can be used to specify where along the edge it should be drawn by supplying a number between 0 and 1. The label_size aesthetic can be used to control the size of the label. Often it is needed to have the label written along the direction of the edge, but since the actual angle is dependent on the plot dimensions this cannot be calculated beforehand. Using the angle_calc argument allows you to specify whether to use the supplied angle aesthetic or whether to draw the label along or across the edge. } \section{Edge aesthetic name expansion}{ In order to avoid excessive typing edge aesthetic names are automatically expanded. Because of this it is not necessary to write \code{edge_colour} within the \code{aes()} call as \code{colour} will automatically be renamed appropriately. } \examples{ require(tidygraph) gr <- play_smallworld(n_dim = 3, dim_size = 3, order = 1, p_rewire = 0.6) # Standard use ggraph(gr, 'fabric', sort.by = node_rank_fabric()) + geom_node_range(colour = 'grey80') + geom_edge_span() # Add end shapes ggraph(gr, 'fabric', sort.by = node_rank_fabric()) + geom_node_range(colour = 'grey80') + geom_edge_span(end_shape = 'circle') # If the layout include shadow edges these can be styled differently ggraph(gr, 'fabric', sort.by = node_rank_fabric(), shadow.edges = TRUE) + geom_node_range(colour = 'grey80') + geom_edge_span(aes(colour = shadow_edge), end_shape = 'square') + scale_edge_colour_manual(values = c('FALSE' = 'black', 'TRUE' = 'grey')) } \seealso{ Other geom_edge_*: \code{\link{geom_edge_arc}()}, \code{\link{geom_edge_bend}()}, \code{\link{geom_edge_density}()}, \code{\link{geom_edge_diagonal}()}, \code{\link{geom_edge_elbow}()}, \code{\link{geom_edge_fan}()}, \code{\link{geom_edge_hive}()}, \code{\link{geom_edge_link}()}, \code{\link{geom_edge_loop}()}, \code{\link{geom_edge_parallel}()}, \code{\link{geom_edge_point}()}, \code{\link{geom_edge_tile}()} } \author{ Thomas Lin Pedersen } \concept{geom_edge_*} ggraph/man/layout_to_table.Rd0000644000176200001440000000106313226105174015753 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/tbl_graph.R \name{layout_to_table} \alias{layout_to_table} \title{Convert a layout to a table} \usage{ layout_to_table(layout, graph, ...) } \arguments{ \item{layout}{A supported object} \item{graph}{A \code{tbl_graph}} \item{...}{passed on to implementations} } \value{ A valid data.frame } \description{ This generic takes care of dispatching various layout types (names, functions, tables) to their respective functions that will return a valid layout table. } \keyword{internal} ggraph/man/layout_tbl_graph_partition.Rd0000644000176200001440000000716413617226162020232 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/layout_partition.R \name{layout_tbl_graph_partition} \alias{layout_tbl_graph_partition} \title{Calculate nodes as areas dividing their parent} \usage{ layout_tbl_graph_partition( graph, weight = NULL, circular = FALSE, height = NULL, sort.by = NULL, direction = "out", offset = pi/2, const.area = TRUE ) } \arguments{ \item{graph}{An \code{tbl_graph} object} \item{weight}{An optional node variable to use as weight. Will only affect the weight of leaf nodes as the weight of non-leaf nodes are derived from their children.} \item{circular}{Logical. Should the layout be transformed to a circular representation. If \code{TRUE} the resulting layout will be a sunburst diagram.} \item{height}{An optional node variable to use as height. If \code{NULL} all nodes will be given a height of 1.} \item{sort.by}{The name of a node variable to sort the nodes by.} \item{direction}{The direction of the tree in the graph. \code{'out'} (default) means that parents point towards their children, while \code{'in'} means that children point towards their parent.} \item{offset}{If \code{circular = TRUE}, where should it begin. Defaults to \code{pi/2} which is equivalent to 12 o'clock.} \item{const.area}{Logical. Should 'height' be scaled for area proportionality when using \code{circular = TRUE}. Defaults to \code{TRUE}.} } \value{ If \code{circular = FALSE} A data.frame with the columns \code{x}, \code{y}, \code{width}, \code{height}, \code{leaf}, \code{depth}, \code{circular} as well as any information stored as node variables in the tbl_graph object. If \code{circular = TRUE} A data.frame with the columns \code{x}, \code{y}, \code{r0}, \code{r}, \code{start}, \code{end}, \code{leaf}, \code{depth}, \code{circular} as well as any information stored as node variables in the tbl_graph object. } \description{ The partition layout is a way to show hierarchical data in the same way as \code{\link[=layout_tbl_graph_treemap]{layout_tbl_graph_treemap()}}. Instead of subdividing the parent area the partition layout shows the division of a nodes children next to the area of the node itself. As such the node positions will be very reminiscent of a reingold-tilford tree layout but by plotting nodes as areas it better communicate the total weight of a node by summing up all its children. Often partition layouts are called icicle plots or sunburst diagrams (in case a radial transform is applied). } \note{ partition is a layout intended for trees, that is, graphs where nodes only have one parent and zero or more children. If the provided graph does not fit this format an attempt to convert it to such a format will be made. } \references{ Kruskal, J. B., Landwehr, J. M. (1983). \emph{Icicle Plots: Better Displays for Hierarchical Clustering}. American Statistician Vol 37(2), 162-168. https://doi.org/10.2307/2685881 } \seealso{ Other layout_tbl_graph_*: \code{\link{layout_tbl_graph_auto}()}, \code{\link{layout_tbl_graph_backbone}()}, \code{\link{layout_tbl_graph_centrality}()}, \code{\link{layout_tbl_graph_circlepack}()}, \code{\link{layout_tbl_graph_dendrogram}()}, \code{\link{layout_tbl_graph_eigen}()}, \code{\link{layout_tbl_graph_fabric}()}, \code{\link{layout_tbl_graph_focus}()}, \code{\link{layout_tbl_graph_hive}()}, \code{\link{layout_tbl_graph_igraph}()}, \code{\link{layout_tbl_graph_linear}()}, \code{\link{layout_tbl_graph_manual}()}, \code{\link{layout_tbl_graph_matrix}()}, \code{\link{layout_tbl_graph_pmds}()}, \code{\link{layout_tbl_graph_stress}()}, \code{\link{layout_tbl_graph_treemap}()}, \code{\link{layout_tbl_graph_unrooted}()} } \concept{layout_tbl_graph_*} ggraph/man/layout_tbl_graph_focus.Rd0000644000176200001440000000426213617226162017334 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/layout_focus.R \name{layout_tbl_graph_focus} \alias{layout_tbl_graph_focus} \title{Place nodes in circles based on distance to a specific node} \usage{ layout_tbl_graph_focus( graph, focus, weights = NULL, niter = 500, tolerance = 1e-04, circular = TRUE ) } \arguments{ \item{graph}{a tbl_graph object} \item{focus}{An expression evaluating to a selected node. Can either be a single integer or a logical vector with a single \code{TRUE} element.} \item{weights}{An expression evaluated on the edge data to provide edge weights for the layout. Currently ignored for the sparse version} \item{niter}{number of iterations during stress optimization} \item{tolerance}{stopping criterion for stress optimization} \item{circular}{ignored} } \value{ A data.frame with the columns \code{x}, \code{y}, \code{circular}, \code{distance} as well as any information stored as node variables in the tbl_graph object. } \description{ This layout constrains node placement to a radius relative to its distance to a given node. It then uses stress majorisation to find an optimal node distribution according to this constraint. } \references{ Brandes, U., & Pich, C. (2011). \emph{More flexible radial layout.} Journal of Graph Algorithms and Applications, 15(1), 157-173. } \seealso{ Other layout_tbl_graph_*: \code{\link{layout_tbl_graph_auto}()}, \code{\link{layout_tbl_graph_backbone}()}, \code{\link{layout_tbl_graph_centrality}()}, \code{\link{layout_tbl_graph_circlepack}()}, \code{\link{layout_tbl_graph_dendrogram}()}, \code{\link{layout_tbl_graph_eigen}()}, \code{\link{layout_tbl_graph_fabric}()}, \code{\link{layout_tbl_graph_hive}()}, \code{\link{layout_tbl_graph_igraph}()}, \code{\link{layout_tbl_graph_linear}()}, \code{\link{layout_tbl_graph_manual}()}, \code{\link{layout_tbl_graph_matrix}()}, \code{\link{layout_tbl_graph_partition}()}, \code{\link{layout_tbl_graph_pmds}()}, \code{\link{layout_tbl_graph_stress}()}, \code{\link{layout_tbl_graph_treemap}()}, \code{\link{layout_tbl_graph_unrooted}()} } \author{ The underlying algorithm is implemented in the graphlayouts package by David Schoch } \concept{layout_tbl_graph_*} ggraph/man/geom_edge_parallel.Rd0000644000176200001440000002163613617226162016371 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geom_edge_parallel.R \name{geom_edge_parallel} \alias{geom_edge_parallel} \alias{geom_edge_parallel2} \alias{geom_edge_parallel0} \title{Draw multi edges as parallel lines} \usage{ geom_edge_parallel( mapping = NULL, data = get_edges(), position = "identity", arrow = NULL, sep = unit(2, "mm"), n = 100, lineend = "butt", linejoin = "round", linemitre = 1, label_colour = "black", label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = "rot", force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ... ) geom_edge_parallel2( mapping = NULL, data = get_edges("long"), position = "identity", arrow = NULL, sep = unit(2, "mm"), n = 100, lineend = "butt", linejoin = "round", linemitre = 1, label_colour = "black", label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = "rot", force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ... ) geom_edge_parallel0( mapping = NULL, data = get_edges(), position = "identity", arrow = NULL, sep = unit(2, "mm"), lineend = "butt", show.legend = NA, ... ) } \arguments{ \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2:aes]{ggplot2::aes()}} or \code{\link[ggplot2:aes_]{ggplot2::aes_()}}. By default x, y, xend, yend, group and circular are mapped to x, y, xend, yend, edge.id and circular in the edge data.} \item{data}{The return of a call to \code{get_edges()} or a data.frame giving edges in correct format (see details for for guidance on the format). See \code{\link[=get_edges]{get_edges()}} for more details on edge extraction.} \item{position}{Position adjustment, either as a string, or the result of a call to a position adjustment function.} \item{arrow}{Arrow specification, as created by \code{\link[grid:arrow]{grid::arrow()}}.} \item{sep}{The separation between parallel edges, given as a \code{\link[grid:unit]{grid::unit()}}} \item{n}{The number of points to create along the path.} \item{lineend}{Line end style (round, butt, square).} \item{linejoin}{Line join style (round, mitre, bevel).} \item{linemitre}{Line mitre limit (number greater than 1).} \item{label_colour}{The colour of the edge label. If \code{NA} it will use the colour of the edge.} \item{label_alpha}{The opacity of the edge label. If \code{NA} it will use the opacity of the edge.} \item{label_parse}{If \code{TRUE}, the labels will be parsed into expressions and displayed as described in \code{\link[grDevices:plotmath]{grDevices::plotmath()}}.} \item{check_overlap}{If \code{TRUE}, text that overlaps previous text in the same layer will not be plotted.} \item{angle_calc}{Either 'none', 'along', or 'across'. If 'none' the label will use the angle aesthetic of the geom. If 'along' The label will be written along the edge direction. If 'across' the label will be written across the edge direction.} \item{force_flip}{Logical. If \code{angle_calc} is either 'along' or 'across' should the label be flipped if it is on it's head. Default to \code{TRUE}.} \item{label_dodge}{A \code{\link[grid:unit]{grid::unit()}} giving a fixed vertical shift to add to the label in case of \code{angle_calc} is either 'along' or 'across'} \item{label_push}{A \code{\link[grid:unit]{grid::unit()}} giving a fixed horizontal shift to add to the label in case of \code{angle_calc} is either 'along' or 'across'} \item{show.legend}{logical. Should this layer be included in the legends? \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE} never includes, and \code{TRUE} always includes. It can also be a named logical vector to finely select the aesthetics to display.} \item{...}{Other arguments passed on to \code{\link[ggplot2:layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like \code{colour = "red"} or \code{size = 3}. They may also be parameters to the paired geom/stat.} } \description{ This geom draws multi edges as parallel lines. The edges are first sorted by direction and then shifted a fixed amount so that all edges are visible. } \section{Aesthetics}{ \code{geom_edge_parallel} and \code{geom_edge_parallel0} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{y} \item \strong{xend} \item \strong{yend} \item \strong{from} \item \strong{to} \item edge_colour \item edge_width \item edge_linetype \item edge_alpha \item filter } \code{geom_edge_parallel2} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{y} \item \strong{group} \item \strong{from} \item \strong{to} \item edge_colour \item edge_width \item edge_linetype \item edge_alpha \item filter } \code{geom_edge_parallel} and \code{geom_edge_parallel2} furthermore takes the following aesthetics. \itemize{ \item start_cap \item end_cap \item label \item label_pos \item label_size \item angle \item hjust \item vjust \item family \item fontface \item lineheight } } \section{Computed variables}{ \describe{ \item{index}{The position along the path (not computed for the *0 version)} } } \section{Edge variants}{ Many geom_edge_* layers comes in 3 flavors depending on the level of control needed over the drawing. The default (no numeric postfix) generate a number of points (\code{n}) along the edge and draws it as a path. Each point along the line has a numeric value associated with it giving the position along the path, and it is therefore possible to show the direction of the edge by mapping to this e.g. \code{colour = stat(index)}. The version postfixed with a "2" uses the "long" edge format (see \code{\link[=get_edges]{get_edges()}}) and makes it possible to interpolate node parameter between the start and end node along the edge. It is considerable less performant so should only be used if this is needed. The version postfixed with a "0" draws the edge in the most performant way, often directly using an appropriate grob from the grid package, but does not allow for gradients along the edge. Often it is beneficial to stop the drawing of the edge before it reaches the node, for instance in cases where an arrow should be drawn and the arrowhead shouldn't lay on top or below the node point. geom_edge_* and geom_edge_*2 supports this through the start_cap and end_cap aesthetics that takes a \code{\link[=geometry]{geometry()}} specification and dynamically caps the termini of the edges based on the given specifications. This means that if \code{end_cap = circle(1, 'cm')} the edges will end at a distance of 1cm even during resizing of the plot window. All \verb{geom_edge_*} and \code{geom_edge_*2} have the ability to draw a label along the edge. The reason this is not a separate geom is that in order for the label to know the location of the edge it needs to know the edge type etc. Labels are drawn by providing a label aesthetic. The label_pos can be used to specify where along the edge it should be drawn by supplying a number between 0 and 1. The label_size aesthetic can be used to control the size of the label. Often it is needed to have the label written along the direction of the edge, but since the actual angle is dependent on the plot dimensions this cannot be calculated beforehand. Using the angle_calc argument allows you to specify whether to use the supplied angle aesthetic or whether to draw the label along or across the edge. } \section{Edge aesthetic name expansion}{ In order to avoid excessive typing edge aesthetic names are automatically expanded. Because of this it is not necessary to write \code{edge_colour} within the \code{aes()} call as \code{colour} will automatically be renamed appropriately. } \examples{ require(tidygraph) gr <- create_notable('bull') \%>\% convert(to_directed) \%>\% bind_edges(data.frame(from = c(1, 2, 2, 3), to = c(2, 1, 3, 2))) \%E>\% mutate(class = sample(letters[1:3], 9, TRUE)) \%N>\% mutate(class = sample(c('x', 'y'), 5, TRUE)) ggraph(gr, 'stress') + geom_edge_parallel(aes(alpha = stat(index))) ggraph(gr, 'stress') + geom_edge_parallel2(aes(colour = node.class)) ggraph(gr, 'stress') + geom_edge_parallel0(aes(colour = class)) # Use capping and sep to fine tune the look ggraph(gr, 'stress') + geom_edge_parallel(start_cap = circle(1), end_cap = circle(1), arrow = arrow(length = unit(2, 'mm')), sep = unit(4, 'mm')) + geom_node_point(size = 12) } \seealso{ Other geom_edge_*: \code{\link{geom_edge_arc}()}, \code{\link{geom_edge_bend}()}, \code{\link{geom_edge_density}()}, \code{\link{geom_edge_diagonal}()}, \code{\link{geom_edge_elbow}()}, \code{\link{geom_edge_fan}()}, \code{\link{geom_edge_hive}()}, \code{\link{geom_edge_link}()}, \code{\link{geom_edge_loop}()}, \code{\link{geom_edge_point}()}, \code{\link{geom_edge_span}()}, \code{\link{geom_edge_tile}()} } \author{ David Schoch and Thomas Lin Pedersen } \concept{geom_edge_*} ggraph/man/layout_tbl_graph_igraph.Rd0000644000176200001440000001277013617226162017472 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/layout_igraph.R \name{layout_tbl_graph_igraph} \alias{layout_tbl_graph_igraph} \title{Use igraph layout algorithms for layout_tbl_graph} \usage{ layout_tbl_graph_igraph( graph, algorithm, circular, offset = pi/2, use.dummy = FALSE, ... ) } \arguments{ \item{graph}{A \code{tbl_graph} object.} \item{algorithm}{The type of layout algorithm to apply. See \emph{Details} or \code{\link[igraph:layout_]{igraph::layout_()}} for links to the layouts supplied by igraph.} \item{circular}{Logical. Should the layout be transformed to a circular representation. Defaults to \code{FALSE}. Only applicable to \code{algorithm = 'tree'} and \code{algorithm = 'sugiyama'}.} \item{offset}{If \code{circular = TRUE}, where should it begin. Defaults to \code{pi/2} which is equivalent to 12 o'clock.} \item{use.dummy}{Logical. In the case of \code{algorithm = 'sugiyama'} should the dummy-infused graph be used rather than the original. Defaults to \code{FALSE}.} \item{...}{Arguments passed on to the respective layout functions} } \value{ A data.frame with the columns \code{x}, \code{y}, \code{circular} as well as any information stored as node variables in the tbl_graph object. } \description{ This layout function makes it easy to apply one of the layout algorithms supplied in igraph when plotting with ggraph. Layout names are auto completed so there is no need to write \code{layout_with_graphopt} or \code{layout_as_tree}, just \code{graphopt} and \code{tree} (though the former will also work if you want to be super explicit). Circular layout is only supported for tree-like layout (\code{tree} and \code{sugiyama}) and will throw an error when applied to other layouts. } \details{ igraph provides a huge amount of possible layouts. They are all briefly described below: \strong{Hierarchical layouts} \describe{ \item{\code{tree}}{Uses the \emph{Reingold-Tilford} algorithm to place the nodes below their parent with the parent centered above its children. See \code{\link[igraph:as_tree]{igraph::as_tree()}}} \item{\code{sugiyama}}{Designed for directed acyclic graphs (that is, hierarchies where multiple parents are allowed) it minimizes the number of crossing edges. See \code{\link[igraph:with_sugiyama]{igraph::with_sugiyama()}}} } \strong{Standard layouts} \describe{ \item{\code{bipartite}}{Minimize edge-crossings in a simple two-row (or column) layout for bipartite graphs. See \code{\link[igraph:as_bipartite]{igraph::as_bipartite()}}} \item{\code{star}}{Place one node in the center and the rest equidistantly around it. See \code{\link[igraph:as_star]{igraph::as_star()}}} \item{\code{circle}}{Place nodes in a circle in the order of their index. Consider using \code{\link[=layout_tbl_graph_linear]{layout_tbl_graph_linear()}} with \code{circular=TRUE} for more control. See \code{\link[igraph:in_circle]{igraph::in_circle()}}} \item{\code{nicely}}{Tries to pick an appropriate layout. See \code{\link[igraph:nicely]{igraph::nicely()}} for a description of the simple decision tree it uses} \item{\code{dh}}{Uses \emph{Davidson and Harels} simulated annealing algorithm to place nodes. See \code{\link[igraph:with_dh]{igraph::with_dh()}}} \item{\code{gem}}{Place nodes on the plane using the GEM force-directed layout algorithm. See \code{\link[igraph:with_gem]{igraph::with_gem()}}} \item{\code{graphopt}}{Uses the Graphopt algorithm based on alternating attraction and repulsion to place nodes. See \code{\link[igraph:with_graphopt]{igraph::with_graphopt()}}} \item{\code{grid}}{Place nodes on a rectangular grid. See \code{\link[igraph:on_grid]{igraph::on_grid()}}} \item{\code{mds}}{Perform a multidimensional scaling of nodes using either the shortest path or a user supplied distance. See \code{\link[igraph:with_mds]{igraph::with_mds()}}} \item{\code{sphere}}{Place nodes uniformly on a sphere - less relevant for 2D visualizations of networks. See \code{\link[igraph:on_sphere]{igraph::on_sphere()}}} \item{\code{randomly}}{Places nodes uniformly random. See \code{\link[igraph:randomly]{igraph::randomly()}}} \item{\code{fr}}{Places nodes according to the force-directed algorithm of Fruchterman and Reingold. See \code{\link[igraph:with_fr]{igraph::with_fr()}}} \item{\code{kk}}{Uses the spring-based algorithm by Kamada and Kawai to place nodes. See \code{\link[igraph:with_kk]{igraph::with_kk()}}} \item{\code{drl}}{Uses the force directed algorithm from the DrL toolbox to place nodes. See \code{\link[igraph:with_drl]{igraph::with_drl()}}} \item{\code{lgl}}{Uses the algorithm from Large Graph Layout to place nodes. See \code{\link[igraph:with_lgl]{igraph::with_lgl()}}} } } \note{ This function is not intended to be used directly but by setting \code{layout = 'igraph'} in \code{\link[=create_layout]{create_layout()}} } \seealso{ Other layout_tbl_graph_*: \code{\link{layout_tbl_graph_auto}()}, \code{\link{layout_tbl_graph_backbone}()}, \code{\link{layout_tbl_graph_centrality}()}, \code{\link{layout_tbl_graph_circlepack}()}, \code{\link{layout_tbl_graph_dendrogram}()}, \code{\link{layout_tbl_graph_eigen}()}, \code{\link{layout_tbl_graph_fabric}()}, \code{\link{layout_tbl_graph_focus}()}, \code{\link{layout_tbl_graph_hive}()}, \code{\link{layout_tbl_graph_linear}()}, \code{\link{layout_tbl_graph_manual}()}, \code{\link{layout_tbl_graph_matrix}()}, \code{\link{layout_tbl_graph_partition}()}, \code{\link{layout_tbl_graph_pmds}()}, \code{\link{layout_tbl_graph_stress}()}, \code{\link{layout_tbl_graph_treemap}()}, \code{\link{layout_tbl_graph_unrooted}()} } \concept{layout_tbl_graph_*} ggraph/man/ggraph-package.Rd0000644000176200001440000000205513617226162015435 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/ggraph-package.R \docType{package} \name{ggraph-package} \alias{ggraph-package} \alias{_PACKAGE} \title{ggraph: An Implementation of Grammar of Graphics for Graphs and Networks} \description{ \if{html}{\figure{logo.png}{options: align='right' alt='logo' width='120'}} The grammar of graphics as implemented in ggplot2 is a poor fit for graph and network visualizations due to its reliance on tabular data input. ggraph is an extension of the ggplot2 API tailored to graph visualizations and provides the same flexible approach to building up plots layer by layer. } \seealso{ Useful links: \itemize{ \item \url{https://ggraph.data-imaginist.com} \item \url{https://github.com/thomasp85/ggraph} \item Report bugs at \url{https://github.com/thomasp85/ggraph/issues} } } \author{ \strong{Maintainer}: Thomas Lin Pedersen \email{thomasp85@gmail.com} (\href{https://orcid.org/0000-0002-5147-4711}{ORCID}) Other contributors: \itemize{ \item RStudio [copyright holder] } } ggraph/man/guide_edge_colourbar.Rd0000644000176200001440000001115013617226162016721 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/edge_colourbar.R \name{guide_edge_colourbar} \alias{guide_edge_colourbar} \alias{guide_edge_colorbar} \title{Colourbar legend for edges} \usage{ guide_edge_colourbar( title = waiver(), title.position = NULL, title.theme = NULL, title.hjust = NULL, title.vjust = NULL, label = TRUE, label.position = NULL, label.theme = NULL, label.hjust = NULL, label.vjust = NULL, barwidth = NULL, barheight = NULL, nbin = 20, raster = TRUE, ticks = TRUE, draw.ulim = TRUE, draw.llim = TRUE, direction = NULL, default.unit = "line", reverse = FALSE, order = 0, ... ) guide_edge_colorbar( title = waiver(), title.position = NULL, title.theme = NULL, title.hjust = NULL, title.vjust = NULL, label = TRUE, label.position = NULL, label.theme = NULL, label.hjust = NULL, label.vjust = NULL, barwidth = NULL, barheight = NULL, nbin = 20, raster = TRUE, ticks = TRUE, draw.ulim = TRUE, draw.llim = TRUE, direction = NULL, default.unit = "line", reverse = FALSE, order = 0, ... ) } \arguments{ \item{title}{A character string or expression indicating a title of guide. If \code{NULL}, the title is not shown. By default (\code{\link[ggplot2:waiver]{waiver()}}), the name of the scale object or the name specified in \code{\link[ggplot2:labs]{labs()}} is used for the title.} \item{title.position}{A character string indicating the position of a title. One of "top" (default for a vertical guide), "bottom", "left" (default for a horizontal guide), or "right."} \item{title.theme}{A theme object for rendering the title text. Usually the object of \code{\link[ggplot2:element_text]{element_text()}} is expected. By default, the theme is specified by \code{legend.title} in \code{\link[ggplot2:theme]{theme()}} or theme.} \item{title.hjust}{A number specifying horizontal justification of the title text.} \item{title.vjust}{A number specifying vertical justification of the title text.} \item{label}{logical. If \code{TRUE} then the labels are drawn. If \code{FALSE} then the labels are invisible.} \item{label.position}{A character string indicating the position of a label. One of "top", "bottom" (default for horizontal guide), "left", or "right" (default for vertical guide).} \item{label.theme}{A theme object for rendering the label text. Usually the object of \code{\link[ggplot2:element_text]{element_text()}} is expected. By default, the theme is specified by \code{legend.text} in \code{\link[ggplot2:theme]{theme()}}.} \item{label.hjust}{A numeric specifying horizontal justification of the label text.} \item{label.vjust}{A numeric specifying vertical justification of the label text.} \item{barwidth}{A numeric or a \code{\link[grid:unit]{grid::unit()}} object specifying the width of the colourbar. Default value is \code{legend.key.width} or \code{legend.key.size} in \code{\link[ggplot2:theme]{theme()}} or theme.} \item{barheight}{A numeric or a \code{\link[grid:unit]{grid::unit()}} object specifying the height of the colourbar. Default value is \code{legend.key.height} or \code{legend.key.size} in \code{\link[ggplot2:theme]{theme()}} or theme.} \item{nbin}{A numeric specifying the number of bins for drawing the colourbar. A smoother colourbar results from a larger value.} \item{raster}{A logical. If \code{TRUE} then the colourbar is rendered as a raster object. If \code{FALSE} then the colourbar is rendered as a set of rectangles. Note that not all graphics devices are capable of rendering raster image.} \item{ticks}{A logical specifying if tick marks on the colourbar should be visible.} \item{draw.ulim}{A logical specifying if the upper limit tick marks should be visible.} \item{draw.llim}{A logical specifying if the lower limit tick marks should be visible.} \item{direction}{A character string indicating the direction of the guide. One of "horizontal" or "vertical."} \item{default.unit}{A character string indicating \code{\link[grid:unit]{grid::unit()}} for \code{barwidth} and \code{barheight}.} \item{reverse}{logical. If \code{TRUE} the colourbar is reversed. By default, the highest value is on the top and the lowest value is on the bottom} \item{order}{positive integer less than 99 that specifies the order of this guide among multiple guides. This controls the order in which multiple guides are displayed, not the contents of the guide itself. If 0 (default), the order is determined by a secret algorithm.} \item{...}{ignored.} } \value{ A guide object } \description{ This function is equivalent to \code{\link[ggplot2:guide_colourbar]{ggplot2::guide_colourbar()}} but works for edge aesthetics. } ggraph/man/layout_tbl_graph_centrality.Rd0000644000176200001440000000431113617226162020366 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/layout_centrality.R \name{layout_tbl_graph_centrality} \alias{layout_tbl_graph_centrality} \title{Place nodes in circles according to centrality measure} \usage{ layout_tbl_graph_centrality( graph, centrality, scale = TRUE, niter = 500, tolerance = 1e-04, tseq = seq(0, 1, 0.2), circular = FALSE ) } \arguments{ \item{graph}{A tbl_graph object} \item{centrality}{An expression evaluating to a centrality measure for the nodes. See the different \verb{centrality_*()} algorithms in tidygraph for a selection.} \item{scale}{Should the centrality measure be scaled between 0 and 100} \item{niter}{number of iterations during stress optimization} \item{tolerance}{stopping criterion for stress optimization} \item{tseq}{Transitioning steps} \item{circular}{ignored} } \value{ A data.frame with the columns \code{x}, \code{y}, \code{circular}, \code{centrality} as well as any information stored as node variables in the tbl_graph object. } \description{ This layout places nodes in circles with the radii relative to a given centrality measure. Under the hood it use stress majorisation to place nodes optimally given the radius constraint. } \references{ Brandes, U., & Pich, C. (2011). \emph{More flexible radial layout.} Journal of Graph Algorithms and Applications, 15(1), 157-173. } \seealso{ Other layout_tbl_graph_*: \code{\link{layout_tbl_graph_auto}()}, \code{\link{layout_tbl_graph_backbone}()}, \code{\link{layout_tbl_graph_circlepack}()}, \code{\link{layout_tbl_graph_dendrogram}()}, \code{\link{layout_tbl_graph_eigen}()}, \code{\link{layout_tbl_graph_fabric}()}, \code{\link{layout_tbl_graph_focus}()}, \code{\link{layout_tbl_graph_hive}()}, \code{\link{layout_tbl_graph_igraph}()}, \code{\link{layout_tbl_graph_linear}()}, \code{\link{layout_tbl_graph_manual}()}, \code{\link{layout_tbl_graph_matrix}()}, \code{\link{layout_tbl_graph_partition}()}, \code{\link{layout_tbl_graph_pmds}()}, \code{\link{layout_tbl_graph_stress}()}, \code{\link{layout_tbl_graph_treemap}()}, \code{\link{layout_tbl_graph_unrooted}()} } \author{ The underlying algorithm is implemented in the graphlayouts package by David Schoch } \concept{layout_tbl_graph_*} ggraph/man/layout_tbl_graph_pmds.Rd0000644000176200001440000000366613617226162017167 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/layout_pmds.R \name{layout_tbl_graph_pmds} \alias{layout_tbl_graph_pmds} \title{Place nodes based on a multidimensional scaling of a set of pivot nodes} \usage{ layout_tbl_graph_pmds(graph, pivots, weights = NULL, circular = FALSE) } \arguments{ \item{graph}{A tbl_graph object} \item{pivots}{The number of pivot nodes} \item{weights}{An expression evaluated on the edge data to provide edge weights for the layout. Currently ignored for the sparse version} \item{circular}{ignored} } \value{ A data.frame with the columns \code{x}, \code{y}, \code{circular} as well as any information stored as node variables in the tbl_graph object. } \description{ This layout is similar to the 'mds' layout but uses only a subset of pivot nodes for the mds calculation, making it considerably faster and thus suited for large graphs } \references{ Brandes, U. and Pich, C. (2006). \emph{Eigensolver Methods for Progressive Multidimensional Scaling of Large Data.} In International Symposium on Graph Drawing (pp. 42-53). Springer } \seealso{ Other layout_tbl_graph_*: \code{\link{layout_tbl_graph_auto}()}, \code{\link{layout_tbl_graph_backbone}()}, \code{\link{layout_tbl_graph_centrality}()}, \code{\link{layout_tbl_graph_circlepack}()}, \code{\link{layout_tbl_graph_dendrogram}()}, \code{\link{layout_tbl_graph_eigen}()}, \code{\link{layout_tbl_graph_fabric}()}, \code{\link{layout_tbl_graph_focus}()}, \code{\link{layout_tbl_graph_hive}()}, \code{\link{layout_tbl_graph_igraph}()}, \code{\link{layout_tbl_graph_linear}()}, \code{\link{layout_tbl_graph_manual}()}, \code{\link{layout_tbl_graph_matrix}()}, \code{\link{layout_tbl_graph_partition}()}, \code{\link{layout_tbl_graph_stress}()}, \code{\link{layout_tbl_graph_treemap}()}, \code{\link{layout_tbl_graph_unrooted}()} } \author{ The underlying algorithm is implemented in the graphlayouts package by David Schoch } \concept{layout_tbl_graph_*} ggraph/man/layout_tbl_graph_hive.Rd0000644000176200001440000000765313617226162017157 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/layout_hive.R \name{layout_tbl_graph_hive} \alias{layout_tbl_graph_hive} \title{Place nodes in a Hive Plot layout} \usage{ layout_tbl_graph_hive( graph, axis, axis.pos = NULL, sort.by = NULL, divide.by = NULL, divide.order = NULL, normalize = TRUE, center.size = 0.1, divide.size = 0.05, use.numeric = FALSE, offset = pi/2, split.axes = "none", split.angle = pi/6, circular = FALSE ) } \arguments{ \item{graph}{An \code{tbl_graph} object} \item{axis}{The node attribute to use for assigning nodes to axes} \item{axis.pos}{The relative distance to the prior axis. Default (\code{NULL}) places axes equidistant.} \item{sort.by}{The node attribute to use for placing nodes along their axis. Defaults (\code{NULL}) places nodes sequentially.} \item{divide.by}{An optional node attribute to subdivide each axis by.} \item{divide.order}{The order the axis subdivisions should appear in} \item{normalize}{Logical. Should axis lengths be equal or reflect the number of nodes in each axis. Defaults to \code{TRUE}.} \item{center.size}{The size of the blank center, that is, the start position of the axes.} \item{divide.size}{The distance between subdivided axis segments.} \item{use.numeric}{Logical, If the \code{sort.by} attribute is numeric, should these values be used directly in positioning the nodes along the axes. Defaults to \code{FALSE} which sorts the numeric values and positions them equidistant from each other.} \item{offset}{Change the overall rotation of the hive plot by changing the offset of the first axis.} \item{split.axes}{Should axes be split to show edges between nodes on the same axis? One of: \describe{ \item{\code{'none'}}{Do not split axes and show in-between edges} \item{\code{'loops'}}{Only split axes that contain in-between edges} \item{\code{'all'}}{Split all axes} }} \item{split.angle}{The angular distance between the two axes resulting from a split.} \item{circular}{Ignored.} } \value{ A data.frame with the columns \code{x}, \code{y}, \code{r}, \code{center_size}, \code{split}, \code{axis}, \code{section}, \code{angle}, \code{circular} as well as any information stored as node variables in the tbl_graph object. } \description{ Hive plots were invented by Martin Krzywinski as a perceptually uniform and scalable alternative to standard node-edge layouts. In hive plots nodes are positioned on axes radiating out from a center based on their own information e.g. membership of a class, size of neighborhood, etc. Edges are then drawn between nodes as bezier curves. As the placement of nodes is not governed by convoluted algorithms but directly reflects the qualities of the nodes itself the resulting plot can be easier to interpret as well as compare to other graphs. } \details{ In order to be able to draw all edges without edges crossing axes you should not assign nodes to axes based on a variable with more than three levels. } \references{ Krzywinski, M., Birol, I., Jones, SJM., and Marra, MA. (2012). \emph{Hive plots-rational approach to visualizing networks}. Brief Bioinform 13 (5): 627-644. https://doi.org/10.1093/bib/bbr069 \url{http://www.hiveplot.net} } \seealso{ Other layout_tbl_graph_*: \code{\link{layout_tbl_graph_auto}()}, \code{\link{layout_tbl_graph_backbone}()}, \code{\link{layout_tbl_graph_centrality}()}, \code{\link{layout_tbl_graph_circlepack}()}, \code{\link{layout_tbl_graph_dendrogram}()}, \code{\link{layout_tbl_graph_eigen}()}, \code{\link{layout_tbl_graph_fabric}()}, \code{\link{layout_tbl_graph_focus}()}, \code{\link{layout_tbl_graph_igraph}()}, \code{\link{layout_tbl_graph_linear}()}, \code{\link{layout_tbl_graph_manual}()}, \code{\link{layout_tbl_graph_matrix}()}, \code{\link{layout_tbl_graph_partition}()}, \code{\link{layout_tbl_graph_pmds}()}, \code{\link{layout_tbl_graph_stress}()}, \code{\link{layout_tbl_graph_treemap}()}, \code{\link{layout_tbl_graph_unrooted}()} } \concept{layout_tbl_graph_*} ggraph/man/internal_extractors.Rd0000644000176200001440000000121313524251132016651 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/connections.R, R/edges.R \name{internal_extractors} \alias{internal_extractors} \alias{collect_connections} \alias{collect_edges} \title{Internal data extractors} \usage{ collect_connections(layout, from, to, ...) collect_edges(layout) } \arguments{ \item{layout}{The layout data} \item{from, to}{A numeric vector giving the indexes of the start and end nodes} \item{...}{Additional parameters passed on to the specific method} } \description{ These functions exists for supporting different data structures. There is no need to call these directly } \keyword{internal} ggraph/man/scale_edge_size.Rd0000644000176200001440000001240213617226162015676 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/scale_edge_size.R \name{scale_edge_size} \alias{scale_edge_size} \alias{scale_edge_size_continuous} \alias{scale_edge_radius} \alias{scale_edge_size_discrete} \alias{scale_edge_size_area} \alias{scale_edge_size_manual} \alias{scale_edge_size_identity} \title{Edge size scales} \usage{ scale_edge_size_continuous(..., range = c(1, 6)) scale_edge_radius(..., range = c(1, 6)) scale_edge_size(..., range = c(1, 6)) scale_edge_size_discrete(..., range = c(2, 6)) scale_edge_size_area(..., max_size = 6) scale_edge_size_manual(..., values) scale_edge_size_identity(..., guide = "none") } \arguments{ \item{...}{Arguments passed on to \code{continuous_scale} \describe{ \item{name}{The name of the scale. Used as the axis or legend title. If \code{waiver()}, the default, the name of the scale is taken from the first mapping used for that aesthetic. If \code{NULL}, the legend title will be omitted.} \item{breaks}{One of: \itemize{ \item \code{NULL} for no breaks \item \code{waiver()} for the default breaks computed by the transformation object \item A numeric vector of positions \item A function that takes the limits as input and returns breaks as output }} \item{minor_breaks}{One of: \itemize{ \item \code{NULL} for no minor breaks \item \code{waiver()} for the default breaks (one minor break between each major break) \item A numeric vector of positions \item A function that given the limits returns a vector of minor breaks. }} \item{labels}{One of: \itemize{ \item \code{NULL} for no labels \item \code{waiver()} for the default labels computed by the transformation object \item A character vector giving labels (must be same length as \code{breaks}) \item A function that takes the breaks as input and returns labels as output }} \item{limits}{One of: \itemize{ \item \code{NULL} to use the default scale range \item A numeric vector of length two providing limits of the scale. Use \code{NA} to refer to the existing minimum or maximum \item A function that accepts the existing (automatic) limits and returns new limits }} \item{oob}{Function that handles limits outside of the scale limits (out of bounds). The default replaces out of bounds values with \code{NA}.} \item{na.value}{Missing values will be replaced with this value.} \item{trans}{Either the name of a transformation object, or the object itself. Built-in transformations include "asn", "atanh", "boxcox", "date", "exp", "hms", "identity", "log", "log10", "log1p", "log2", "logit", "modulus", "probability", "probit", "pseudo_log", "reciprocal", "reverse", "sqrt" and "time". A transformation object bundles together a transform, its inverse, and methods for generating breaks and labels. Transformation objects are defined in the scales package, and are called \code{name_trans}, e.g. \code{\link[scales:boxcox_trans]{scales::boxcox_trans()}}. You can create your own transformation with \code{\link[scales:trans_new]{scales::trans_new()}}.} \item{guide}{A function used to create a guide or its name. See \code{\link[ggplot2:guides]{guides()}} for more info.} \item{position}{The position of the axis. "left" or "right" for vertical scales, "top" or "bottom" for horizontal scales} \item{super}{The super class to use for the constructed scale} \item{expand}{Vector of range expansion constants used to add some padding around the data, to ensure that they are placed some distance away from the axes. Use the convenience function \code{\link[ggplot2:expand_scale]{expand_scale()}} to generate the values for the \code{expand} argument. The defaults are to expand the scale by 5\% on each side for continuous variables, and by 0.6 units on each side for discrete variables.} }} \item{range}{a numeric vector of length 2 that specifies the minimum and maximum size of the plotting symbol after transformation.} \item{max_size}{Size of largest points.} \item{values}{a set of aesthetic values to map data values to. If this is a named vector, then the values will be matched based on the names. If unnamed, values will be matched in order (usually alphabetical) with the limits of the scale. Any data values that don't match will be given \code{na.value}.} \item{guide}{A function used to create a guide or its name. See \code{\link[ggplot2:guides]{guides()}} for more info.} } \value{ A ggproto object inheriting from \code{Scale} } \description{ This set of scales defines new size scales for edge geoms equivalent to the ones already defined by ggplot2. See \code{\link[ggplot2:scale_size]{ggplot2::scale_size()}} for more information. The different geoms will know whether to use edge scales or the standard scales so it is not necessary to write \code{edge_size} in the call to the geom - just use \code{size}. } \note{ In ggplot2 size conflates both line width and point size into one scale. In ggraph there is also a width scale (\code{\link[=scale_edge_width]{scale_edge_width()}}) that is used for linewidth. As edges are often represented by lines the width scale is the most common. } \seealso{ Other scale_edge_*: \code{\link{scale_edge_alpha}()}, \code{\link{scale_edge_colour}}, \code{\link{scale_edge_fill}}, \code{\link{scale_edge_linetype}()}, \code{\link{scale_edge_shape}()}, \code{\link{scale_edge_width}()}, \code{\link{scale_label_size}()} } \concept{scale_edge_*} ggraph/man/ggraph-extensions.Rd0000644000176200001440000000637313617226162016250 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/aaa.R, R/facet_edges.R, R/facet_graph.R, % R/facet_nodes.R, R/geom_axis_hive.R, R/geom_conn_bundle.R, R/geom_edge.R, % R/geom_edge_arc.R, R/geom_edge_bend.R, R/geom_edge_density.R, % R/geom_edge_diagonal.R, R/geom_edge_elbow.R, R/geom_edge_fan.R, % R/geom_edge_hive.R, R/geom_edge_link.R, R/geom_edge_loop.R, % R/geom_edge_parallel.R, R/geom_node_arc_bar.R, R/geom_node_circle.R, % R/geom_node_tile.R, R/geom_node_voronoi.R, R/ggproto-classes.R \docType{data} \name{StatFilter} \alias{StatFilter} \alias{FacetEdges} \alias{FacetGraph} \alias{FacetNodes} \alias{StatAxisHive} \alias{GeomAxisHive} \alias{StatConnBundle} \alias{StatConnBundle2} \alias{StatConnBundle0} \alias{GeomEdgePath} \alias{GeomEdgeParallelPath} \alias{GeomEdgeSegment} \alias{GeomEdgeParallelSegment} \alias{GeomEdgeSpanPath} \alias{GeomEdgeSpanSegment} \alias{GeomEdgePoint} \alias{GeomEdgeTile} \alias{GeomEdgeBezier} \alias{GeomEdgeBspline} \alias{StatEdgeArc} \alias{StatEdgeArc2} \alias{StatEdgeArc0} \alias{StatEdgeBend} \alias{StatEdgeBend2} \alias{StatEdgeBend0} \alias{StatEdgeDensity} \alias{GeomEdgeDensity} \alias{StatEdgeDiagonal} \alias{StatEdgeDiagonal2} \alias{StatEdgeDiagonal0} \alias{StatEdgeElbow} \alias{StatEdgeElbow2} \alias{StatEdgeElbow0} \alias{StatEdgeFan} \alias{StatEdgeFan2} \alias{StatEdgeFan0} \alias{StatEdgeHive} \alias{StatEdgeHive2} \alias{StatEdgeHive0} \alias{StatEdgeLink} \alias{StatEdgeLink2} \alias{StatEdgeLoop} \alias{StatEdgeLoop0} \alias{StatEdgeParallel} \alias{StatEdgeParallel2} \alias{StatEdgeParallel0} \alias{StatNodeArcBar} \alias{StatNodeCircle} \alias{GeomNodeTile} \alias{StatNodeVoronoi} \alias{ggraph-extensions} \title{ggraph extensions to ggplot2} \description{ This help page lists all exported ggproto classes defined by ggraph. In general these should be of no concern to the user as the main interface to the functionality is, as with ggplot2, the \verb{geom_*} format. As opposed to ggplot2 there really aren't any use for separate \verb{stat_*} functions as they are intimately linked to each geom and mixing and matching stats and geoms would only cause a lot of trouble. } \details{ Many of the \verb{geom_edge_*} geoms comes in different flavors dependent on the functionality required. There will always be a base geom and some will have a \code{geom_edge_*0} and \code{geom_edge_*2} version. The base geom will, in the case of multiple versions, draw the edge as a sequence of small segments. The different aesthetics will be repeated for each segment and a counter will be added the enumerates the progression of segments, so that a gradient of colour or size can be added along the edge by assigning the respective aesthetic to \code{stat(index)}. \code{geom_edge_*2} will also draw the edge as segments but will interpolate between the aesthetics at the end points. This makes it possible for an edge to interpolate node properties of its end nodes. \code{geom_edge_*2} is less performant than the base geom so use only when interpolation is needed. \code{geom_edge_*0} is a high-performance version that usually maps directly to a grid grob. It does not allow for drawing gradients or interpolations though, so use only for simple edge drawings. } \keyword{datasets} \keyword{internal} ggraph/man/layout_tbl_graph_treemap.Rd0000644000176200001440000001015613617226162017651 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/layout_treemap.R \name{layout_tbl_graph_treemap} \alias{layout_tbl_graph_treemap} \title{Calculate nodes as rectangles subdividing that of their parent} \usage{ layout_tbl_graph_treemap( graph, algorithm = "split", weight = NULL, circular = FALSE, sort.by = NULL, direction = "out", height = 1, width = 1 ) } \arguments{ \item{graph}{A \code{tbl_graph} object} \item{algorithm}{The name of the tiling algorithm to use. Defaults to 'split'} \item{weight}{An optional node variable to use as weight. Will only affect the weight of leaf nodes as the weight of non-leaf nodes are derived from their children.} \item{circular}{Logical. Should the layout be transformed to a circular representation. Ignored.} \item{sort.by}{The name of a node variables to sort the nodes by.} \item{direction}{The direction of the tree in the graph. \code{'out'} (default) means that parents point towards their children, while \code{'in'} means that children point towards their parent.} \item{height}{The height of the bounding rectangle} \item{width}{The width of the bounding rectangle} } \value{ A data.frame with the columns \code{x}, \code{y}, \code{width}, \code{height}, \code{leaf}, \code{depth}, \code{circular} as well as any information stored as node variables in the tbl_graph object. } \description{ A treemap is a space filling hierarchical layout that maps nodes to rectangles. The rectangles of the children of a node is packed into the rectangle of the node so that the size of a rectangle is a function of the size of the children. The size of the leaf nodes can be mapped arbitrarily (defaults to 1). Many different algorithms exists for dividing a rectangle into smaller bits, some optimizing the aspect ratio and some focusing on the ordering of the rectangles. See details for more discussions on this. The treemap layout was first developed by Ben Shneiderman for visualizing disk usage in the early '90 and has seen many improvements since. } \details{ Different approaches to dividing the rectangles in a treemap exists; all with their strengths and weaknesses. Currently only the split algorithm is implemented which strikes a good balance between aspect ratio and order preservation, but other, more well-known, algorithms such as squarify and slice-and-dice will eventually be implemented. \strong{Algorithms} \emph{Split} (default) The Split algorithm was developed by Bjorn Engdahl in order to address the downsides of both the original slice-and-dice algorithm (poor aspect ratio) and the popular squarify algorithm (no ordering of nodes). It works by finding the best cut in the ordered list of children in terms of making sure that the two rectangles associated with the split will have optimal aspect ratio. } \note{ Treemap is a layout intended for trees, that is, graphs where nodes only have one parent and zero or more children. If the provided graph does not fit this format an attempt to convert it to such a format will be made. } \references{ Engdahl, B. (2005). \emph{Ordered and unordered treemap algorithms and their applications on handheld devices}. Master's Degree Project. Johnson, B., & Ben Shneiderman. (1991). \emph{Tree maps: A Space-Filling Approach to the Visualization of Hierarchical Information Structures}. IEEE Visualization, 284-291. \url{http://doi.org/10.1109/VISUAL.1991.175815} } \seealso{ Other layout_tbl_graph_*: \code{\link{layout_tbl_graph_auto}()}, \code{\link{layout_tbl_graph_backbone}()}, \code{\link{layout_tbl_graph_centrality}()}, \code{\link{layout_tbl_graph_circlepack}()}, \code{\link{layout_tbl_graph_dendrogram}()}, \code{\link{layout_tbl_graph_eigen}()}, \code{\link{layout_tbl_graph_fabric}()}, \code{\link{layout_tbl_graph_focus}()}, \code{\link{layout_tbl_graph_hive}()}, \code{\link{layout_tbl_graph_igraph}()}, \code{\link{layout_tbl_graph_linear}()}, \code{\link{layout_tbl_graph_manual}()}, \code{\link{layout_tbl_graph_matrix}()}, \code{\link{layout_tbl_graph_partition}()}, \code{\link{layout_tbl_graph_pmds}()}, \code{\link{layout_tbl_graph_stress}()}, \code{\link{layout_tbl_graph_unrooted}()} } \concept{layout_tbl_graph_*} ggraph/man/geom_edge_bend.Rd0000644000176200001440000002131613617226162015500 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geom_edge_bend.R \name{geom_edge_bend} \alias{geom_edge_bend} \alias{geom_edge_bend2} \alias{geom_edge_bend0} \title{Draw edges as diagonals} \usage{ geom_edge_bend( mapping = NULL, data = get_edges(), position = "identity", arrow = NULL, strength = 1, flipped = FALSE, n = 100, lineend = "butt", linejoin = "round", linemitre = 1, label_colour = "black", label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = "rot", force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ... ) geom_edge_bend2( mapping = NULL, data = get_edges("long"), position = "identity", arrow = NULL, strength = 1, flipped = FALSE, n = 100, lineend = "butt", linejoin = "round", linemitre = 1, label_colour = "black", label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = "rot", force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ... ) geom_edge_bend0( mapping = NULL, data = get_edges(), position = "identity", arrow = NULL, strength = 1, flipped = FALSE, lineend = "butt", show.legend = NA, ... ) } \arguments{ \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2:aes]{ggplot2::aes()}} or \code{\link[ggplot2:aes_]{ggplot2::aes_()}}. By default x, y, xend, yend, group and circular are mapped to x, y, xend, yend, edge.id and circular in the edge data.} \item{data}{The return of a call to \code{get_edges()} or a data.frame giving edges in correct format (see details for for guidance on the format). See \code{\link[=get_edges]{get_edges()}} for more details on edge extraction.} \item{position}{Position adjustment, either as a string, or the result of a call to a position adjustment function.} \item{arrow}{Arrow specification, as created by \code{\link[grid:arrow]{grid::arrow()}}.} \item{strength}{The strength of the curvature of the bend. \code{0} will result in a straight line while \code{1} will give a strong arc.} \item{flipped}{Logical, Has the layout been flipped by reassigning the mapping of x, y etc?} \item{n}{The number of points to create along the path.} \item{lineend}{Line end style (round, butt, square).} \item{linejoin}{Line join style (round, mitre, bevel).} \item{linemitre}{Line mitre limit (number greater than 1).} \item{label_colour}{The colour of the edge label. If \code{NA} it will use the colour of the edge.} \item{label_alpha}{The opacity of the edge label. If \code{NA} it will use the opacity of the edge.} \item{label_parse}{If \code{TRUE}, the labels will be parsed into expressions and displayed as described in \code{\link[grDevices:plotmath]{grDevices::plotmath()}}.} \item{check_overlap}{If \code{TRUE}, text that overlaps previous text in the same layer will not be plotted.} \item{angle_calc}{Either 'none', 'along', or 'across'. If 'none' the label will use the angle aesthetic of the geom. If 'along' The label will be written along the edge direction. If 'across' the label will be written across the edge direction.} \item{force_flip}{Logical. If \code{angle_calc} is either 'along' or 'across' should the label be flipped if it is on it's head. Default to \code{TRUE}.} \item{label_dodge}{A \code{\link[grid:unit]{grid::unit()}} giving a fixed vertical shift to add to the label in case of \code{angle_calc} is either 'along' or 'across'} \item{label_push}{A \code{\link[grid:unit]{grid::unit()}} giving a fixed horizontal shift to add to the label in case of \code{angle_calc} is either 'along' or 'across'} \item{show.legend}{logical. Should this layer be included in the legends? \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE} never includes, and \code{TRUE} always includes. It can also be a named logical vector to finely select the aesthetics to display.} \item{...}{Other arguments passed on to \code{\link[ggplot2:layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like \code{colour = "red"} or \code{size = 3}. They may also be parameters to the paired geom/stat.} } \description{ This geom draws edges as cubic bezier curves with the control points positioned along the elbow edge. It has the appearance of a softened elbow edge with the hard angle substituted by a tapered bend. } \section{Aesthetics}{ \code{geom_edge_bend} and \code{geom_edge_bend0} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{y} \item \strong{xend} \item \strong{yend} \item \strong{circular} \item edge_colour \item edge_width \item edge_linetype \item edge_alpha \item filter } \code{geom_edge_bend2} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{y} \item \strong{group} \item \strong{circular} \item edge_colour \item edge_width \item edge_linetype \item edge_alpha \item filter } \code{geom_edge_bend} and \code{geom_edge_bend2} furthermore takes the following aesthetics. \itemize{ \item start_cap \item end_cap \item label \item label_pos \item label_size \item angle \item hjust \item vjust \item family \item fontface \item lineheight } } \section{Computed variables}{ \describe{ \item{index}{The position along the path (not computed for the *0 version)} } } \section{Edge variants}{ Many geom_edge_* layers comes in 3 flavors depending on the level of control needed over the drawing. The default (no numeric postfix) generate a number of points (\code{n}) along the edge and draws it as a path. Each point along the line has a numeric value associated with it giving the position along the path, and it is therefore possible to show the direction of the edge by mapping to this e.g. \code{colour = stat(index)}. The version postfixed with a "2" uses the "long" edge format (see \code{\link[=get_edges]{get_edges()}}) and makes it possible to interpolate node parameter between the start and end node along the edge. It is considerable less performant so should only be used if this is needed. The version postfixed with a "0" draws the edge in the most performant way, often directly using an appropriate grob from the grid package, but does not allow for gradients along the edge. Often it is beneficial to stop the drawing of the edge before it reaches the node, for instance in cases where an arrow should be drawn and the arrowhead shouldn't lay on top or below the node point. geom_edge_* and geom_edge_*2 supports this through the start_cap and end_cap aesthetics that takes a \code{\link[=geometry]{geometry()}} specification and dynamically caps the termini of the edges based on the given specifications. This means that if \code{end_cap = circle(1, 'cm')} the edges will end at a distance of 1cm even during resizing of the plot window. All \verb{geom_edge_*} and \code{geom_edge_*2} have the ability to draw a label along the edge. The reason this is not a separate geom is that in order for the label to know the location of the edge it needs to know the edge type etc. Labels are drawn by providing a label aesthetic. The label_pos can be used to specify where along the edge it should be drawn by supplying a number between 0 and 1. The label_size aesthetic can be used to control the size of the label. Often it is needed to have the label written along the direction of the edge, but since the actual angle is dependent on the plot dimensions this cannot be calculated beforehand. Using the angle_calc argument allows you to specify whether to use the supplied angle aesthetic or whether to draw the label along or across the edge. } \section{Edge aesthetic name expansion}{ In order to avoid excessive typing edge aesthetic names are automatically expanded. Because of this it is not necessary to write \code{edge_colour} within the \code{aes()} call as \code{colour} will automatically be renamed appropriately. } \examples{ require(tidygraph) gr <- create_tree(20, 4) \%>\% mutate(class = sample(letters[1:3], n(), replace = TRUE)) \%>\% activate(edges) \%>\% mutate(class = sample(letters[1:3], n(), replace = TRUE)) ggraph(gr, 'tree') + geom_edge_bend(aes(alpha = stat(index))) ggraph(gr, 'tree') + geom_edge_bend2(aes(colour = node.class)) ggraph(gr, 'tree') + geom_edge_bend0(aes(colour = class)) } \seealso{ Other geom_edge_*: \code{\link{geom_edge_arc}()}, \code{\link{geom_edge_density}()}, \code{\link{geom_edge_diagonal}()}, \code{\link{geom_edge_elbow}()}, \code{\link{geom_edge_fan}()}, \code{\link{geom_edge_hive}()}, \code{\link{geom_edge_link}()}, \code{\link{geom_edge_loop}()}, \code{\link{geom_edge_parallel}()}, \code{\link{geom_edge_point}()}, \code{\link{geom_edge_span}()}, \code{\link{geom_edge_tile}()} } \author{ Thomas Lin Pedersen } \concept{geom_edge_*} ggraph/man/geom_node_text.Rd0000644000176200001440000000777013617226162015605 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geom_node_text.R \name{geom_node_text} \alias{geom_node_text} \alias{geom_node_label} \title{Annotate nodes with text} \usage{ geom_node_text( mapping = NULL, data = NULL, position = "identity", parse = FALSE, nudge_x = 0, nudge_y = 0, check_overlap = FALSE, show.legend = NA, repel = FALSE, ... ) geom_node_label( mapping = NULL, data = NULL, position = "identity", parse = FALSE, nudge_x = 0, nudge_y = 0, label.padding = unit(0.25, "lines"), label.r = unit(0.15, "lines"), label.size = 0.25, show.legend = NA, repel = FALSE, ... ) } \arguments{ \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2:aes]{ggplot2::aes()}} or \code{\link[ggplot2:aes_]{ggplot2::aes_()}}. By default x and y are mapped to x and y in the node data.} \item{data}{The data to be displayed in this layer. There are three options: If \code{NULL}, the default, the data is inherited from the plot data as specified in the call to \code{\link[ggplot2:ggplot]{ggplot()}}. A \code{data.frame}, or other object, will override the plot data. All objects will be fortified to produce a data frame. See \code{\link[ggplot2:fortify]{fortify()}} for which variables will be created. A \code{function} will be called with a single argument, the plot data. The return value must be a \code{data.frame}, and will be used as the layer data. A \code{function} can be created from a \code{formula} (e.g. \code{~ head(.x, 10)}).} \item{position}{Position adjustment, either as a string, or the result of a call to a position adjustment function.} \item{parse}{If \code{TRUE}, the labels will be parsed into expressions and displayed as described in \code{?plotmath}.} \item{nudge_x, nudge_y}{Horizontal and vertical adjustment to nudge labels by. Useful for offsetting text from points, particularly on discrete scales.} \item{check_overlap}{If \code{TRUE}, text that overlaps previous text in the same layer will not be plotted.} \item{show.legend}{logical. Should this layer be included in the legends? \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE} never includes, and \code{TRUE} always includes. It can also be a named logical vector to finely select the aesthetics to display.} \item{repel}{If \code{TRUE}, text labels will be repelled from each other to avoid overlapping, using the \code{GeomTextRepel} geom from the ggrepel package.} \item{...}{Other arguments passed on to \code{\link[ggplot2:layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like \code{colour = "red"} or \code{size = 3}. They may also be parameters to the paired geom/stat.} \item{label.padding}{Amount of padding around label. Defaults to 0.25 lines.} \item{label.r}{Radius of rounded corners. Defaults to 0.15 lines.} \item{label.size}{Size of label border, in mm.} } \description{ These geoms are equivalent in functionality to \code{\link[ggplot2:geom_text]{ggplot2::geom_text()}} and \code{\link[ggplot2:geom_label]{ggplot2::geom_label()}} and allows for simple annotation of nodes. } \section{Aesthetics}{ \code{geom_node_text} understands the following aesthetics. Bold aesthetics are automatically set, but can be overridden. Italic aesthetics are required but not set by default \itemize{ \item \strong{x} \item \strong{y} \item \emph{label} \item alpha \item angle \item colour \item family \item fontface \item hjust \item lineheight \item size \item vjust } } \examples{ require(tidygraph) gr <- create_notable('bull') \%>\% mutate(class = sample(letters[1:3], n(), replace = TRUE)) ggraph(gr, 'stress') + geom_node_point(aes(label = class)) ggraph(gr, 'stress') + geom_node_label(aes(label = class), repel = TRUE) } \seealso{ Other geom_node_*: \code{\link{geom_node_arc_bar}()}, \code{\link{geom_node_circle}()}, \code{\link{geom_node_point}()}, \code{\link{geom_node_range}()}, \code{\link{geom_node_tile}()}, \code{\link{geom_node_voronoi}()} } \author{ Thomas Lin Pedersen } \concept{geom_node_*} ggraph/man/figures/0000755000176200001440000000000013533136725013751 5ustar liggesusersggraph/man/figures/logo.png0000644000176200001440000002203013226105173015404 0ustar liggesusersPNG  IHDRyXiCCPsRGB IEC61966-2.1(uKBA?jabP$]@B >jAנ үCu :AQt\ԥ5O%rwgYJAoAXҢ!\|m EWG#0 ^nZTZW"n\uzRUcz:|GwoLm69l󲑹aE`hSG#%flˬl|qUb{iM bҟfy3g`$n" ԃBjxUK_ּW&GPk+kv&%h42gƠR^\h4.^P([%qY"RW ɇ7-Z[Eyz~5nuY%v~߻OC79<>Ydz3>K|mDZεe*ZtY#tˋʏ%e P`cT 4 d2+ybƢ 4  ͌CeF3928\_SKZbz@}]B`ȸ,qMbhji<8!:_{Hd֐R&igvjޒ2mp%04[e83)SsZ{:!FeL094$~{ $ v)ff}WoPDM~ KILo擘L5ў@RG"RY\ 59gUN&ܸ۴ 5UtbB_kk*[k/wƃ i<8߰o39l˲9i9yF`DZҹpQW(E#NXژpl츤\e/2&ۺܬ,&=9D"6fΟ^C"yCHimU,^ZBچzJ>axt Hqs t=ҤÃ藟o֦^n "/2R(KyV^h- 5%Yv"51cH$:OŠ #Uti47Fኑ"C 1y(Ԅ T a?<`1޾sy=ko@,Hkh~M>ﬔ=Z^K`h!Pw!q*̨?sgr392{&/6$&.ܝ L6 f֠ҨJV. r1< $;ۤ?!gᩉ|fbD 1K"s !_ ;KtM q 00o '-:X1W_ry3脳9ށ!spcم 5˾/8=TcU[Qܾ̑ 8z_ϲ֕?톛ܤ9a`joǥYD&zeʊ9Ϫ*JMAtOqtDzc]*<~ٍأNAMU-jj+~ ؄GOr[78pů~FUe $b R$g\BƼ0pplRRQQ^wK(+͂a:s񏡫Aǀٿ;JP\XJ)+FmmMn[ S;+@@eI9n;6^| R!F`PQ^vb[l[S?.^usO7 ;,׌_gcHz/;'7`P9T*}[N{{Wn]^C&x.AI^!XlPi4W¥t 叁Pc1!`hnS gKPOܶB$ɖ$0zxByh4*~=A&( 7.\wAK7z lW!畸?my>Z!H^a"?5 OcIz:Fo^>mlL[09HiHfvC3cl[#r 7B]O>66T|. 5TG?B6c!pc_)gfiaB$!H4FL3OLl-`bkޓlM RXX``#c} 3KPJ,: cs0٠P)hkh(-BZ}P(,\49rǺ^syhkckh?ۣ 5551Ycw(),E1Cajg+pi8{p0o"Lw/g=b d 8!K@Q?^#[@Zr [p|T[G+51~ 31bX1[sWťSWpv3hͨ{y%02ևYWHcCH Csc /6|ԬW>#$nXL4sy J]܆FL~%3bn7ojARFULM8NOcv ={f00Es~%9|tÕӗU(-¨#bDd2WW!SHwaێY9ca(/LXoJS'|pS spmk! r1>Fd 06ʒZĆkPYR}ӖW3dU/+ڐupኄ>3 {pp 4u͘?KIj Ā{Xu)c5[^ihh`M;_Giq9Ο¬\  ~W>SD 7 k.bBhji`r`"[]R;M pW>#R&GX,.FAĞaػ;G1eeJ8 }$tBM}$0;Xegdyʖ +~YSPVZ $5T: Lw5htFΜ(KH{l)/qq(+*ŒVkeDtA+6fA w(2^Hʖ҆®x$ k`~9H~[cUPH1p0dV~Z!Wrinaܪ{#U6qa3'ʉs}Ku6xkUZ8ve`iglI1;m./E߭SK5R֬Ke{k">,T"GBLNW2/=&6`hk|mt5LOlm(bhOGW =>ioP|Ême1o"}dHy+f½ m}^F8 6VgM]Wv}-=A~Mܾx 99.@C}# 0yTl yi½_Oh"U8'Xl=滑o umu,d>}u>T+o[mXSQ#oYr7ΠL!C,[!9JULH9@Kd=j 3`ՅO\M&IH;o{6n7%5 8|A =1M5P`C$T5gq1J&I(*Eʟ!$uD;{qg[&khbKFR2P4tt 5jj`,XnC# 5rk A"BV6rwC[B` W7vg5B"(|RlYg:BFbB"Ba} ~#w L #vӿ!$(|7ZHc0bvAH$hjhrTx`糱燐NJ~zvDžd3-mM0_fbmܯ# <}"uy$'MoL&ggY"Te(C⃧  N K)Wt\_AIҨmZ7K~TIu>+gSZX&u ):M7m͎+/ (dkGkZ:U59'5FC]%t ^-eK4J[F&Rر!5ҽ%F.~E&t,^\j}ݯJmO@QrX5k-U"lZO3ܹp _͐縼Ms{k읤[\UY_'e·đty Mu,]݇Q _ah~bb(-RNm[aޟ^@/p,,xd^$5)/k;s  N;Kd'j%ߐmmM=D&cЈp&0mC❇H{̔lTᖼӆ%lo`h(T׀#p] u4ݜ~&gGS3& D>cRYRH{ARaja  ]r"Kp5< \.tpy4^-w)-Ju"@^}6)m EhC}MjjQ_ۀNUSCB5&L6L6 L6jll0lkΆHJ @ML&^7;ӒY)Ji[,;阊RD C/M ]]ؿ˾d  W&PHP>q)]#!pX MugJ׎5hr+eeQiID"441m4 ?K [p:4ARx;0Jؖe "s2- >K`.HGvR"v0``o`{T ɭaѲ̭Ϫ v92ZUTxx z\GwU)y6"5{S\Πcԑ?g:Y{@GOҩ8XLs7N"3ٽɭK҂>zژ1 ~  `jA&Sqobi:sW{Wldr+M -+6{',Z 2s3Yi kr\ovAɭܸ`}Zb:zk~ }䚡 +@tNoڐ,<6,&&xYU>DH$b ` nNCd:yM&ܩ8(:3%Dž#x.iC&#D9JH$88ۦY]]IDn&r%Y)Y{J˵T" K3-ݕQkwEʳ] HҒ2V5P)*`1޾sy8}8pbZ"'w5uV))Kbr+EeQ#ZCL'FC.BB8s4 hprw327 QB*Jx "r2>K`YZEfB "wEQ~ :w _̥[9imU,9;A oWS^axtecp#&O٣,MJ5"5{Rqxd@Ln%l˲ѹ鹡EF`DZҹpTiAz"w";=`ic^fñf%*{3|]MU-D"%.|'7۬lmCeMZBUe)|q0sTHDb\: QZBj ŃgCDMn%R:ksg#$Sl4t? % V46nI6"dΘ83OEP|1Ŀ (>/ % (>P苦;3ie|{g蹪X-2s=+WQ+]L6O w[C{_F qb Uvz?Zb1@/zcs>~if,ӈUSjF 1_Mjbuݠpamhmçϙ>a\+5%QKFkm}ۖ?ޚD\!~6,-7SثŜvķ5Z;[rmS5{yDyH}r9|-ăFAJjI.[/]mK 7KRDrYQO-Q||6 (0 MXd(@h2_f<:”_δ*d>e\c?~,7?& ك^2Iq2"y@g|U\ @IDATx$URA̺:5 iQAPT\DĀ@E#A b@QVDY@1puM&Ĭۿg,j:~nwsso]=RD HD HEBTOV$@"$@"捐$@"$@"$]TD HD H$y$@"$@",*I@,HD HD h@"$@"$@E;+KD HD H@"$@"$"tQD HD H=$@"$@"$]TD HD H$y$@"$@",*I@,HD HD h@"$@"$@E;+KD HD H@"$@"$"tQD HD H=$@"$@"$]TD HD H$y$@"$@",*I@,HD HD h@"$@"$@E;+KD HD H@"$@"$"tQD HD H=$@"$@"$]TD HD H$y$@"$@",*I@,HD HD h@"$@"$@E;+KD HD H@"$@"$"tQD HD H=$@"$@"$]TD HD H$y$@"$@",*I@,HD HD h@"$@"$zZۘ]U?c'RE`YlW\Y<D`Fש݉lP=ݟ_cyR"7޵ |Bgßt@"6;.{t; S$yլwTY}ʃ@"gf&@"$@"0/$_2$@"$ tF~lf"$@"$@y%@"$@"I@gf&@"$@"0/6Lz^l\W}[oxS+S{e&wyu[d5l\]owe?wO",MлU|%\Vզ+1Su}u7c5:! 4w|c7/w.兀n3.og>׸5~|{߳޷]︤bxvᬏ}-}|ޯ:S\M@^}s-Uz =E/Do>ԛ\{ ]{",q.hޚ~o}j]]oUxoݹ;&eYu[ި<%^r;lTTRJ",m߲jF#q;T_y2GBBↅ|]]}'W~WT{>~jc5P/@.f.[v\z}nW/\˯UzgVc$6F}S/}O_VK(^U}^'HO\~O^V{Ə7ǿ2 6߸t?~OG|߫?WyJ{tHYz=%@I@P閭jϫ>|wV7zy7u+ oxrnP]׬v{佫Sw!pqw!H.cxGK߶ZC6_t7Nu':mGW<3?B>v|ל}I@ lNsJ3a|['yu10~ ߩ}ƏJNG?Eo_2$Ko^>V?EG{C{e^uy/߮WoT]snuU/_jyK٪D #F~^k_<<D()?3BBprCC6m>7D`F8_/BUuvow rEoG nÅs^}?vՅţ^9-'D X$]?ϺӸB꿿EnEOǿ^wj(X1/TٯgsdOnò+ƾ=poPW&`;^zIq/ʫ~WCSD`6U;}+o~U?uou>Wy|/v^>Oz{~=8⥔D msoU-H:KIp^~o~7x@~{竝qY[*IIFjߺ<+߹b"!Mrj7[M"$P-/gl}" pަ0t'K>Вnd6n,-TJ]uk~yrJ<_#G;y`i/.)7E?횺l WMgʱHXœFjzl_J"7mSR.}rكD HD Hf $3secD HD }o=HD HD`H:S?W66HD HG`!]uzg-NI9D`շWwn3'&SoSc?͔&QoSVeSyaeYnoxVF}Ƀ@"0gGE`y$۽l~@"0ur\:p='&l"pW.MBgg62 .HD HD`j$Yp"$@"$@I@PD HD H@ЩA'@"$@"! .HD HD`j$Yp"$@"$@I@PD HD H@ЩA'@"$@"! .HD HD`j$Yp"$@"$@3(ζNw^xaw\m;\w.gD?N?Duk_b-'>zΛ$HfbK~.bȹ[|է?jM7]-Ϧ%\q&lRW^Y;3]wݵu{qD Xkd~A߿ګ:VOg-=y4Hfs6o]]רjOS|@"I@OvUWUoy[*m?:w@"0s9 np֞_D Hf$KWZjUwK:D m.ҫy>2 D%OYB ,6^[MJI p\5uI!HD`tki֚eENwSuް 6ؠfm:D mvo t@",u._կ~ukx>,Fzszrv_7]Ӡ|z&#WXQ/TLݱ[]ve ^L?I@iUG?׿~!_qڲ?U?VՓvO]rᇗIuQW~_ɱ7˜$@|?=/j]w||򓟼ףYg/~??4ϻ:/JwyWx>.^|F)qhמ{8_aF&?%X[\q_\6)cΊwx8}n 8łvx+_YFf=ƥ慀G?zvwI@|O$03׻U6x5Λ{?A^ud 8z> >7)+.yY{՗B)DwOHnV"ַU.w:{|EOVޟB 4MFJ//w/)> sywu{#׽ue[+Ʈ僦s(gy E;u;߹XOx$0c9<=T:묒v wms$k$k .(Kzo^~"j}x+}{+%_jM7:e"ߟ}٥;CgqkѿfJ"b5G>Rs&rfOoM66wS}_9rb\5&ć!K}}V&ҏ)V^y &fYv"0-cj;-tXC!7STy{^Qꪫ%Pƫ˸av:{Q/zы g% #!PrAgB &A:O,R`>W|}Cz) Fݖi*c~i %"SNwӴZ]o{ՐD` t-p"Z\}cOTRX%gqFO,Q("Qa^y3>O;#6$mMIG "ƒ)X! 6۬K7`YsG9nӡZr0y!ȗs&*fs9Qj{jiww &,dn0,~-wE䕗' iQ1+R߱b&Y3]&t\LpcIOzRʂ=^c޼x#r[A?ZO^SKw^wnzիJ*x{ il!f!-bܴA}s;~2 0":iO9w tJ"pr)%R܃|a -'&jc$ "t ?:\zꭾd ni)~ mE/?q{\Y)?f#"/G?GB=qq>:,Ƙ0sZg&W"?e'yֻ}BxJHnV[mU|#YV}k6ɷDO}j!̀yR o8C[h3HrR[㎫Y"i /KxwYO0%'m8?O*hj1cyƿ&xEe{몍EMؕPc!0yp=} G7(>+y }zxylx,$P82xܧz-걠2+_J`KmQ4JwhJA{ォC9sԤ=e$^a"$<"P'*_0 B}^<+cj;u DF!<^(Hp1^ܷ^>2"&d4?rv1O-NڐK2Y B'~iG> t:B2bOABJN Ӟ6TTDY 390v$#G`n (//>JHb2Xx Q{B,`xHrj/< !u1 nm #Qnb 9I$<6p1対/(^N8 :C܅`+sX0L0Yf0ޑ<Ԉ(iH:/b dw/ ^w&&/R?x<+VH'"مB=67F"z⳾6"u3}cFGcD`e q0좮|OGoiJ9{}{ģ׼l33a3ʙrdE:/FkbןtIe/!-DKx.N>ՇIa\Ie ۼV.V 3^ yצ bepABݭꮺHFQDV$@FFY#KYdxi;D!&wƿNLhZcQ&ƻE=zU)<&wH.zbѶQ᭼K~y}嘀g}5COxc"O~eR*Kʤ$us" 0D Ii3*.C4"xC*ߊwn,x5|GQϏ2.Di`nAxQ޾ƀ::ɽg<85Nc8 EQ&R&Ɯf(2;Cc.w&ΡWȨGʫ}[Z&m]Y1{ _u YLŔ7lk"0i撀"W=JT8ȋr$</%!HcY{SbDN8 NJܹ<rǰZuow݅ k?˛aD1<$bAaAP7) mׯg^%^DQir!Pn,Sm2cs-yƃcǃK?,@b Qk,sܻƻI!s?5r%$|!{êw߽<1EPt|[:w藫Ն -G9Rhg!X%th[3:, bEB'Ĕw,r誏2+^QBt]}[ݢ)V <ƪ_?{ۚx$yl {Zi8J@@ _cgvcCFQZmK#&sy?=- EɎIaUc3ܳSSD0ee{Z0"b :4qF4ʥgHcTgW&m k Ry:QzTI8R b)|Jюh[[=K;:$,pD \Pʌ'Jb|(&~W>/%F+gCDuP:^ 4@P8NtE&yÈ{C!&DzA_ E=[PI1-cRߍ- `;Ϗ=] ڥ0N Ʒ+l~“/6D5]lj_pե^tۇ$2dt(fAԄ("ǃpP>/b8)+ǒQϼ;ԧgagD`FbFiE\k |6w~Fmz[۵bѽ电b D* ":26L6oSEdQ4Ez "kR[]º1FƀkLZ :$ϫڵ0Ƭ~#"'T E1@bMM@&>zrpJDȏWGJOç$p-et(:LDqqEA6!r˓u!ьI}}/ƍWuʕ@ ٍc ɑ_`,dp;#AqDxÊا,=ٷՁXHATAxma%Hmaue6GƆ{+c8"P]Arm/5NB2j,MnAp^!yR7, 4~#>;S?H@`alh)ue>ɶ 0$]?,Y( b2IDΕJ`ETEa3v5#!q fS/k"= ~}T6EnBsZ}t?~eV ?<}b`pU/Ɓq%L g<Ș!Ɠ Gn O-=|cUD&]vYW5X: &2DiNVuHwyoqd=) ݁.21/z2$i:9H#P,z+y< A#VSgiE&oGS*z$vGd@mo+ U[D!qŊ;A^-'5S%8 #%R-«4:+oM)Ӭ3^8&QM "=N[ED=TlDG!&jƬwObFcIY=kl*\"ay%~b :B0>`[.O66qW).dg10$e3T%jiכ/ Q 0Ƅ`O&sc9} _X iH$/Iv=m(dzYxѡ&,}1BRIxDLݟB?4,QhDBľ<&ȮpTxP҉AA}S$@ IS&TE) uaF[  &t%7XH OwfQw``[`¨{Phq:tfRe{&Us#l m9ƣ(Dq'ˆNH֨*¦|?ars0$I8TR8.7O"lxJEI?<ݸя~t!r\LXє#&L7i=ՙ$I@'{SR4-6"P1L귈cL`E  "?UyXxZ{ 1ʓ ^i/2`y(OY@~e H腕O9B&P)265F h“i !1t?W]Q2K=@F"H0?<6(KQgꀕ|Q0]h}y/:uoYh" D>, k7ڈ"z&Ʊ1)]Bʈqk)=ƒT)-ÈLȶE[Y%\N[o)K5^W3cBO{6/G E1 e%,XaѐY f#TV0P`sC_W>;jOMOSkE3.4B&^H ; lxDM@>HE{&]5UJM mLٔc8\>S/-e<"{?mONe^*D N7ǣ2LsL^Gam᳦1$^El4 SD "r K60"ʔνjlERXWu=HragYBÈZ-@-)?&D ONw)eM5Ʀ N^FQĢa ##%<,i{(!6^Q^D`7H~Xg'H6cNSxi{Yهul׼y7:vV!ȓF@yMT !jQB>>&g!x" Dڌ+)e c[4 ''Ri~uHzB~ Hկ.*+RD` ['XlkQC($Bo\1"z.a<16~ھR cð02 2n+ %{ы^T?3#6;L" mbUeqDG9F$G{CVHAI]cm"nB-AẒp1Ϥ.""cbLm"#u݅B\/c^E+!}Ʈ.ȭ5>3(oW?r&d@6K;D\6U~w_-1!!gĉį.#i8 5YDضbe˩^asaPoTR'lݼbܨS.JCk7"&^B>}gRUESx7i3(=d4vD`~C$vg7sR1 bzg0\~oK=yg0! Gz}(H"1 mƉQ:,?gDY=JvyMx4MyH_s"FrՅ\E9&^ug$Tv ]ׯ1t_"!3FylHex?\~JD*yQ[% 7V'-<ը]9uay:ȣKEN@ oxC^{ʃ=/kJ"I@'; wQzϋR OۮDyXRwO}j?W^ap0 f^07E>ޟ9?M|Ie,]DWp}/LׅWA@BuZ/g$T!i4VM91YK96B{:exY15Avq`]絕C!GydګmkQD Nw\#B,ƶG<GʹBo M88n%W͓VМL F)$aD0u<<9MR= 0teSfy]Pq`(Q-!*?!G.M8&$IȼDu+YVn"DZ,4 k 'P&8Zmq7(Uv&>w l']mD`|a)RM^ F< _Mah-Vp~_auy"e9O*@=yN4|Fڤy@Bczd@@9M`ܯA3 c$"qOG#wHa}2-Ա3(MM,lF]]3 6<˗//:6ɦg6M׮\ \qkD`#tB/҄ZXϦ<~.pVaH#CW /'uQQ~;{'CmFNlTh紽SoRmBwEyO$Hύ=ߜ"[S[x =Ryjm"qV*֣&OHc >HE@2J>0T(Q~3K5rAB78Gk5D`H:ߌr(cb /- YYbT,1^^4#˰3QE ° #r9 CߝvjX]a#ã%v~PtHuy֢ /a[~vMڼ↴< "Rz?MލvءL,-hd[#56Bm0@@MEB'ynBHț#z@/\F6nB:tX$@ 3J~]]C#h?*U}?+cv+(7U2< !O*ٖ6` ynIM,k 6&H oɗ%x^(Kƈ4RKb" 1J7Dθ2 qڢ3H#^xfI$I@1$fubVΰ!cxE,O3[3<&Ԗ|9Hbi '< eJ-a|9&Dj#ݼzԣ 4<$m۵D`nH:V8zRf!a|Gyg< 1XmS8 Zމȳd +oe=G_B )W7#UgqP\׺Gxx!c {Cڗ~ RnShc=,c-5v+RԳ{18J>50-S/O&R$ /kLꬂY|e,4H yt lR]D`~hϪM'nt]k @x(߾xsX_>1ȂƓ2 ʊ+JxFQ``y+ƒKԶy\zz? 2 ҠN$ faЛX<AZ^ ! BBu /~b"V(BRW?:n-, !}ަvHA=@"86MIu3@ax,ny'FB*χ{A?)@IDAT#!5-yrA;d!cx%| Dk $1ax1 2f$yr"$-, )sZ/^u0? A<[,/8DL 2G9nRSiqnԖ me#Igt-7R߶f]bVڮ_J[yp*+OG 蘿!" e*㩣Б~UxB$ɞ}6G H;ctFxGDy6b9U?Mi <-Ӷ2ZufA9Ԁ$/ IxwoCbI4..Ҟ~mn3.T%/yIكؓËk%)+;j]o>lZİuy@"0[$ȇg`ᰈ`[PG{srx>B2B!Ȭs0Ķsa+7%nfi4e)gUei+6./x ʓ|裏.76DУA&ʈ=oP?9̢>D`>H:JU^B_yUB1׸T8ѴP\"0N\2&RO^LkT RD9V3~T)sBhا8-hpOCv;8X`(l)O?2( #'sHg9.w߽fZE衇7?dZ)nd}Z/$S\# &rs"I@M-YG؛G4'cY ѵ(*g=Y1nmY=/.7؆-~|?j;yZY҅DQ[t1\P [j#0G˧$򩥎#Yy:KMJi(1E@0W6J<@]*R߅?e|Ux 뾬硭/VB@D XuSYyig!*ׅ@h."oʷr+y[EJy~lɻ걞,6YHA\4pP$QB?e>m&"LDIN-ɘMތmw4M.Azǝ*8M`fb1Lv&Dٔ o K'Ciu{",]:o$-r(jK%#f-qEș.%Cg<,Hl.>rsyJGȅm{KAy0M?9)/#"qЯ2[P$PK/^#(=~ m{^F):P'w - "P`!sGVMƧGS!ڮ"$㷧An*ָY;1 Nݨ0&zWH'4HD^IfM)>EzWu<y~]-bܔGA~-|F M=MΉlLJ E#GSy~y$hH F:kbÈd6S^$@1~WaٍQH s#"34#דW ^ Z3(]St\i5Ѷ''#"?T߳v#`2EoǍ!^=?yDMF"]/&A&&]LSX,%?&o|N:l<TQ<-Nr7 h$w[ORg4=uߏ+G{5r#=->NZ۶Dێ3T6fdiu-d2ʞQdS$酋zcލ ^vm Ucw|d'm'0J T~._S ~k_/pB6j[a=H!hVޅangtvJ]T9cA-WtP] t}_#f-D- YPPcxT^Q?=>S`zHƿ"rr\˅9pQG@|vyR:SC^$A qE(qH‰"V|3("fp°orTA`nWc,ZO{\i)zՏx'lb zAe 0v!<ߛ^zb1uQ]eK>L1$lXqWL+ӘV=KP3x:EG&^-oy[l Zcؘ*`rI_ 4x7#Pb=ެ{Ǜ=zmеy<H|0R!dR`(rP4$0*axF-@TFRbrPq!;ߩZ+o<< < cزV[I{xC򐇔T{2 q!r[3LM" P1bB%@x+[<%T.Ƹq(شJ4&oZ0P{ы^T o(ѕ(I7vVlt:蘅4o$@)FH kd1䔭WpF&g%&OE6~[huib]3"^'xb1d"ˋ/z^&&pI^Av'$poPń_{ԃd 7D {!nnJ1B>C['CZ=0BDX'wS8L9 tV"D)@"E)V$KNӟ5wOO#wG3^fK3 Ű&vie#@GQ CuE(edWLa5^=9] c!:jc i`H@$͢(Yƈ9,|+' 28->(ه0g#M3> uyWmo+&gB)6;yV=714IDAXT`vCLt"*SȞ7Wڰԧ>;7&RʰS3<,LbBl ":y.'xWsF|[Zexu,R>2P+W,ae$y`Ÿ!S?RTl&yM~V`m`"ǔ1jG<;Xxs '6OJ{y qOL 38 8=dž(NlŢ(.bKq$pJ(o',5XavI ص8C=TMГȤPqK~ѡ/mzO/UzS: QpZK.H!1+S!8oEIwᥔ[5^(Ck.Db%{Ub;&H~z +2P2jrEy2=`/=yI.rbR^FFG(x&-m602)c:yg ߀UBz>1ͭ3as 1 uPYQrӴGhm-h/mUO?A:7DͽS^pOc3ܯȚ16Įƻ5DO^A𻉡_<Ƣg 7y&=Em=+VT{ik&kXIOaF #i 7ژ߭=7~Sc=.| GxW\)V\#c\$qbm MDD}AM~7ERJC|JAjy<reu"yuzqd!08m0TH%bB=Jag+<I̥S)/IP ʛb7*,iv]v)ge:nqE]orHyHCwwQނ!k(] 3pb'  (\Fh[)Ƞ* wH z}%7ۂ.) a.wK!R 8^-mPZƈ! K꿭V`Ɖ{c7aAO ?xTB&K"ogiӃ:dt2LZWIو={gĔ(8B|+.H)#tA>񄜪ZB:$k]otA fhzD :33QԔ.w i(bB"$}K^Rꅼ"SG2Y;w1Dd|A (m=u]gS]9<#`{e1Fa\>rI  p>#$1$4N=2THGYxY}dy@zDnCy~>(y! uo<爎{WϤ )'$D}As?fgLZMO7MX:t"tHS8(beu~z9LCД˫J3{ 5FRCDYt _HTC)7KAx=b+YysidI/wܱ*f$ FnÈ=Ga3<`pAHw>}`]z -DVQ0b2 ?Aڐ&!d̘w6Eɬ6{Yr{ˊ0+Ƥ!&icH E˾3 +28OɵlM%B萉$"D3&ṳD9 (ܑ U|"vkPzN&HwzYl_WH]u\A?"&2 ;C5oI3`<>36"*\ƋB#B "F!`WAeTuӠ]CH&JYp=K} b (Á2̌wҜuUX `I/?=xX,ƅ Ir*rDŽxF&y&"&ȘIq{+=-o˓: 5J^Gc„јɯi+D D(mT ]s&Ư oȐb #fƲ1$‡?1%E?1m.<!ַe=-V+۶N:i~e#Þۯ<$".X腻,])DE B($ÆL֓yBCG&<\;/&F킭r`SՏ(h䫎ZWpL[MqoŢg=2׸g958]  Cvcb+ou=!&&iem`NDPbǃHzQ'&ƩxG`Չ;0Vcx1_m^†'GyAFPXNuD 05h1"nBDNy@`!_ Z3"h0 y)\/aJS/+ ['Nhoe=G QU C1]eׯC&Fc-.F^.(J",ɁרmaxN*9t:<z& bRCJ"LuAatxG,=ØAHGA{-_(r$tQ)+p0f1xx'~z#|uzE'3h[0Ɠ!Ǫx 8 :2)96H&-b3djd Ạ-%sޙ>xyv+? wmVONA. |x(7bhM*x`p}ݰv!M+$}>EvuBLGyu 8#T-pO"]YYc(:=kk.m;h' #]Nc%RU$ո#tW`cLdMBa,d k7 %P ZZݾ L AF#%H: ~XhNX! aQb9Q^|"`c>7 Y36M5E[~ ꧷.dx On lUχu 4.c9A:% {,\bE(7$[x׾{*NQbK]> MD  I@DT_򢙉 Qf J2"ȕY7uI'0@q"` ōQv cO)#Ppk c8:MD9 FXAڄ/ y1NHeSb0 Bx<CE@FyAE$i7"V[\P'ǪZP\.^cO^exRL 6^)/v\!ړ2 J){8FLH[- OWc=Mo F3"iwcODHɮ>>E) 1]SDi1f%朘:Ük1N]7'ʦ5!Bېj.ƫVo@'E[!ߔBq: =&&5٧+l"+l7DpIZEs"0Ӿө{JxmD3.|+D  t ${RB>S>!A<HJ^;ȧ;W_Q2)~!F50]!(}C]J_o,{e7~"N1 g]eF'9#?H%UH3iSV"lX7~gDc+i0b(F$ct:ђR~o &o1&b H-lk&ȝ _i lƤ7Ru}U/)4ݥs|Bcbð# iSؾTZ; iCB%-!iР"ĈhRc\2@xxxgKh`! hX@Hdb ?){J)$z~AVcVV&8 rsc{Fݧߏi \ڥ>61L^cW?A17dΗxwvт":'> 렾qK;tdX)@"0=!מ)Y~[yΏ(n$'U(My"feؘ6Hrq{آd60z&3rC:S r;ǛuhR}ali-HfГz˴ږ&F +=E/$K Y d1@]њϊyJBPPf<>3fF@e?{w+q_ɹn b D8JB^ !Q|+@$A1R>O\ǹ:qaC k*59cY%A{z\=w}wv}Vקwo,rShe4ɣ( ^!&\ޒж~”~a  `; gBh{M 3L9reVcXP1HLe4| EaWc\2^ -1z[t;mK]h{ô[dqQ 1^~Y8#B[ƫ5G`+|9Y?  6s# 4<"V+Ƕhr]@ӞJz/Tҙt GgE@T$NsBo ~[z.%ڵv -ȴn)N|4m(a4Mޗ )Fb(ּ(GӋCR"Cw7hQdu.6j[J#> hb)䔃Jݗsqi )7KxL[u1CkaR8 tڪK?,Aݫ4ȹ8aZ aQq[xѽ-T1mŎ:iaZ>gi\/ ;2V;'{żo}ok;L\ݫ{q;G;~9^{09`Ŗ\؀j8@gCJW#&&\K߇tٚmIJ \E9^l;ʽfM |a-JuQγVo"it"mP^u(U9W-]` %)s/DŽ$L,0*)wޙsٖ4'ŭ\Nng)hACrA'!p )SEa|ᇯ} _.;>eQ ZcShl;6'?4Ů`5i[En. $LFl^[%Di# XWGl*]'i@SU)J7#1Io+}%#u{vC 5Ȟc66|y[`_84 eEO!89?`<hS\p+? gL-OX/\<޴ P9ҧ徥[w0oQ퀍q:5!o/Rq [Sc'cSJrwi1кQw?O9-D:-s".b' ucZc1O.3}sӉ8tvށSTwz8rqzemeb[Y^= Q-0(1[fy::*B\} hb,]奮 B]pɈ`-DX# Ʒ MNt}ʛBx* & 0V7F&u[XfOB'kkg]9- p|õX`@9w7Oxq\Γ)3RezIk4鞸wϥ:3/O@ \W8@VɮZZ.ՅO7]DG,@LS7|s>guwKXh~W/^̻#K{8~VGRצ = 쀶޵{pN-0>UDҴg@}p~ N<.aG8r=%/1EC8hX,8yeÀ(Jʰ`<}&ۨ)pF#0Pҿοaߺjw^՛|pXV@IDATWNᾚI[V) z3VbBH~ ϧ0KQm^N=3Ii/.d Fh ]K10azZ CT }dHy\Y_֌6EsP @ȣ CM!.m]U{n@N@3^SD[NaC~v~/B!@|9cًo޽Ëp6@+:)я޴㴏K:seܫ?^퀁\K12796Ih鲔\!t+ټ ׀/݅E9DA?v̯'œ3m㾴w> [_>~/[X-K]Sg>3߾E^5#/Xz:걻$F<-W0=2/QBm ->GS՟&BydLJ=1F.@wv PFYCJuE<5 m)9@/PkP?'#`dLӽy c9!*|xgﱫm00reZ ;ά49> 3&Ȅʻ?4;ܬ]J=5̸j#mE%^1ڌá#e^`-t:d_ 0.yX`zŗ\PpW d̬P+xc"0.10>>{̩67As'lNخKq_  D X(G C:N?݁E+8~3E{241tܣ PkPA|ɤr=Vn_L0l1`&3F;BkFI1c$F)c4F+͐B[>V =&@ܣ`E}QeYK2!Z7 7^[!OK?,zaKtπl,0h(0pp:A~Ch;XY3:KooD|PNLآ4vJ;18:۟yoQw#~@\@מeMf멯q8e;8" Ʈ7vx5/1Awjs ;ї腱'" =kD@<Ә\L ']ؠZuf9 POL% sQl E$ 4HM@J%Ee)yhׄd0-@#X'[ZC`{Ex99 wk|!u@R4\SXچ(k}4-N>ʃ79{k?~t 00_;*>c7F#D1"&Z͌/R6™&ø55DQ 5aG\ʍ3o8ՙt Z[t$DZ vp6 y9%[9n-5#kz uzOX@.f,`W1aZWӟ~>Z@ī\F1o"xd*sC}j^8h>>4x8D"%8P$Ե@OA0bR$rxB. aDuQGExˉغZ S-&)/Iv#[qХD?|:_]DYsW*PNwTy|_z.wDWH" nށyB?$*c-8#hbd;3}fO@{)EzP"AlyGH]\7ȭO9`x;<%ٳŽOX9 3D~+1d/$f3 Oʥ"ZapEb=QD7(2`0 򣜄x~v\2f)eI֎?~d x17L|Ud`c9۹P %uo蒨1 s=tY0 4`ΈH!(C&*%cgm`SYY/ݻ>Ae!〳ٵ6>p$vߞ%Lw08XO UB"E,SjX- `+ oEu-"0'`@=0a(J5_ !3Y乴KB`kW2%LQ.ʔ+SʜXnH6)%lȒr8iF}FW(8;^Bז>ɴ~?[o apv~XF8@8T#W =)0Dt v/OEK47s!\*a 0f@8\81e 7{Rv'G8jhis \P⳶#j$ĸx 8oIlLy8}00lc\*'Z?"D{p@LX.vd-@OEI`1I>y>&$BU [l3JE2(Fa/92$RGJS1 `8|并ח@Pd@sWUqiz~_3b9nsʥu^^ɞ8www3( 2Eis<'"'ܣUP&a͖3UTv|x˜jsE8x% X0{諥r0_3]KTKc]"{J߷9%XlY9-gw"t5.TExYbSK)K{S 9 h,9`Q%b82Hr#Q00}'ڰsSK#֐Q2MTe9 2y zaj!RhL c((ȵ1eB;MJM`̈3Z/fʯߍ9 )^}WF[/,;#!xהSS0%͂$! bF}x4αFgr,B2}H(:F311-\5V;˜wNKmΛ+]QU%vƚ ɫ;精2iY mJwjyY%[["`[%`}zB+S RN -`T.@Xr:.drLr1E 2 @1bKcSip I%t6)T3=%S!k8ϧ$`4+= @s">.k=޷1C<kv@= #2,O({*Ɠĺ[~[0/< $"Oxd=8##-^1Lױ[E[Yv2/J`#CG(~Ml(q[HWp/I Ժ/hNu_vRt <`%5s'01N`AE$9?hc|2'gHrpcGgq樛*rC׸cQ @tϮcZ5up=)139B[ޘVߙ)p0[Z:ﯣ<z-4WY8ܷ`@8RW/×-QGbJ9 1x'T=ˆ`\Qʶ̀G߽aUM13C@Z:(%2f% TOS2| (rlfR uAYk/?'ԌcgT[3gkm cs)`ٮPG;q̡p9{byI/`h[s\\o9s]9;;Ƌp.@Kȹ`#g˽rBF#o1hvbqG Z`|~a(&cOS%BOFXmJ%:Z4@c/LgMqOC ŽBra)~{ 2* # @\0*\Еw88a-!xmF=cm0 +r[ \YSE*ãa>N?B/1[h%N9),ޓoLPŀ u @4%S<+F ]W^6]LR D54lyΦ+`W?TJCV_[JבmS@_oWx8 vn"lٱf,q}^?ߚ4Zbg񸅲zƴvD>P<} Kqȗ~> u@/c'/wh&Em]O+&7 2H @}'/e![+WC(FN9$t \a͏>brmLc?L `1GrOke4:֓S({1OHt@+]kй^p )'@9 ]&]F<QYc@!=Q?09~;ZZ=օo;F! N^Y[~x_x1N.Ob0Er"OGLٻ2~|7aZQ+2ՕQ[c $ '+(bLwm<%">n<uQp dlPmz X>'T}΅4uɸd Q"%|!רBP}aIJN5lRTZ3 {nh'ܧR.%'[N,stD5"51v/z=wp',#℗..F+ QJz 2RljJзP@xۛnxuTg@1eZ@2mQR![lYK 6r6;cE/w~3q/Zp KFasGuttnDZjp<[1q3b;$Ԏ{B26e7KwpDay0cOǺ/T1EC@fYOq 9 \G/;߷0L㣢R-8hjز1r ,0!m} qj$V^bQpLَEP2%GbTGDXyB ]Ch`ُM yV޵Our@c1`-Qm r!>p hI]Z` W2BXBª-ǁeʨ7~[,1k35a+qĮYƙokVW>gnh~R[8:Ny{gt6B4`gCoQ `G8qΰ=q)k֦^-p-p{X]Kа B0lK{a}k3o|cR> x2B5aE!5jb|]$O,Cb4mb @1[_dzL=dQ9{c P^3klL@۸z>-1鱟9z?./Jذ~i˿+F@ґ@9P\k7s#Jᢧg>I .7Uݸ_@SƷr;9 P5 l^TgSkEŎ}D"\)+z0&ǀO [skrHˤu[5}gTΘ6o'cKtB9v bC8I)U__4Np*ap-) G;2Ns}j)xz믵_8È#Ðo,yˏԞax 5u0x.cr+uݰ5!aL$ڋe]& Ľ{ڐOfUc)bqtߞC|I4}űct'ek?y8<~ᐣ.zbl px؆=*cQ|92D/<1 FYQۈgH(fʵ0m"|}c%N-GQDµz\=`g6g-F515X UX:4 /A*h@ٲv0-aH|o6ߥOOc5uYL7o aE#I@@[b9gVC_g`ļWFƞb? Τ=jIڻ Ts|XJq:YDT^sHyXk'bպY;t6E@<]ӊy\]=9HC+:>)[W˹@OQJa'^l'VJ{z +8\dñ%* 6%VrDvq^ӾSw ϧi\8vR t"'ł8"9+ו--}ѓHG߀Y>\~NX`iRv`)cCD<%,81 $3G]K@dSg6+bG Qʖ[bA֮Pϫ^\S+@ R'ݔh v3a#{0|a8!Xd((Rx@zaBfj -3-nՏ!*vsF6~Svy"^'c-|68W[[h{,~)Vc@v浱Umj>P[^cs@\(u} )r:U ҹα2Ч/-R4= ;֪w)=+ӛRjv j^z9\|70y=*< (X}Xhyj h8}(J6Mż9U{v4@wWl~n C ^ Tp"oZZ8~ wFVh(Z3D#UZ,ՠJilV} <%YF@PXD?aw&0(1&%q =9ڑ1f܀O-X0n9/R=ߺ{甿#u"_d3 pahHhݟ6Nxb 8~#b0[rsW4p(` 11iV똏`}g_\ZT Mmg~ڊJ> UF!nl \ΞO{XkSH5K9qw_k䑳9X G3} C;azGĜpo@{0݇~r- _:9qyΔN-{9Э-6/|MJ ػsA}(2FC^hc%a0%+|5,1#x)xִɶ03?{`0(Wi0k2ƕ[I_)QC;iqf戮{g>Ҝ@`Ƒ6&m2.!Ƃ0z+ܼF=AjD Fr7N9XˀK΄Tz,:8)A 5i-چ-(}v)%E}Vg"lUd&{ TeO%r :4<%h 6I=9P(FI02ԇ

v dQ,}-_[=) 1 `<}ksl8Α(RX^?hgu o%#?0a C鞁cԻ':vD(څJ7=1&9@K©jP hї\5D1=) m\0-${X``3f[/|a@Q $͘mt/gc%0' lO j[!oO 2CZD:􅶏({|-J7c.mt];/twyg=M݇} !;)vЍLr|bds FJR(A,/R(~#S_-1śE1qLc0ZZe/u\DΨ,E@3( e^g#t:울Nש➲ y,c="c_оkȽpj97wߍRE7q[ &*/l/o kM9z tν?я_֥C=,H '˜##:(pg#mıhutv57Lla-uX'uyϱDZlcBU}K,Wٴ>8:67'G,o)떎=nXeQgWQ kA1b91>(Ậgy>%k} #.~5sŰX )e,ߤ`r1 Hb=OƟ1Ab'6Ξ=5Yi#b@~v]C_,{m1bKb\-F-'朤9< 񛱌q\lϧ|D^#~6$@! ~~#ǎ!٣pPl-LQ.ϱ5s>q[ڜO"q)o~躥Hk C[ P+"-4~Y2O7%="&0e xYIQQ>{71ǺBA5CA90Ps  )Q>Cy߉GèxlȽ0 X(S6ͰȆP $̰&Ȩ[÷`!nXNF~&1*&@1 H}` cJ10q9JV"c[~/+*ߗ@Ty%g)lM/vͱX5fuۄmg|/c!e,P 3Vϗ`TOh>38R[ڷ唶 |Na"K<}J?3xK"MQR7+-"gx WC8'kmg.aX#?cy9{B҈G.MNf_ ,"_N|;j 1z:^w]3׼V+79Kɐ`K0| YXUGi @ ͸QD̎ m=C$]Cn"0ly"펄Vk=sPf,qݷ/smf˧ɚ_-c]v7>[b,'2"7Ӝom#է~L)C:*tQs[ǙSzsM{P,݈.uRoC&tcOQq `*C=cNsV__)  [/[9/çOZOvhyAŚZ-6QW(=44Jр._ MǕX@3æl'$cm~>Jb4'oSzWZa)cL\(gm_sRF1g%Q-Bz7x 7xcsNa2ϮeAF uO5qb P_>JUޭt 8r'uӏlϘΠm^uT@6= }fУw;d(iFFlN*x`mof(9ء$(kF@(G>CA4FRBk1\(^9P_j`oŽ98U mM<)ev3@|1Q{EƤu13XQ }"<|N3vFa̬PcmC[#ZoT:3G9|ҚXܓ̻Ky-1Zj@p-X^Klo}7+vwsijCO:曹^py7Li7 O8r:]_yR~m*瘲KNQ)Dt htzm-h-Q^VRyX kӾ[RS FNKXR۲ʖ(I?*e1-%ډkn#ÂJ-1#}^?g3֍7v݃_[mP)w.3fɵİQp3 {ƂBj02 pB$Do5w ^EI@ߩ`gS^V+|`0džƴKx4ãߔuKb\[04δÎUbv'C1N; U)eD`K@VOH ;>{ƃ۰{ |_K "!cl%ݣvwMηPwl6<푹f|c(i@F?.:^2ƈBg0 0%#NKJrp H[J#ɸo"g֌5å[*3O\yPk~6]C:(Z3c:FGYV3&@X @βz`]z?7{>; |= %ѧXt ˥^&ͱRa է9NHԹkIJ8ck,b@2yd[Phc,|K&tN kc e4ce&-c^nHQC>M[ݣT̥.a1ڙ>~qxV?}c/㰞tgKR̛VwOt;* 8 y % (O^6S`##&RKPc@krQs&'"DWJ>;c@ cl % h|Eo SqE)" Ecy4 zQr1h2uD=, KE5z # e+S]3ܣ1:G4`JtrrLϊVt/P֘2c\'XKa=3o">ޕ\ezB/.sSgC Prc{>2cONvu0{%cZK-s|V4ʪJ d`x E[08v%,g3#h.!Gcݴ{rʝЗDy}k&Ê1@?iXQ ~ȞX}hgF|t +@=_A:Vi>)W]?|m `??4Rz3&azse/L1ic/Җt(.1]Xi8e\r0mflGa}NW)r?"'Zm_KKbγ9]@?X}BOpTm9+b@ar-IpjiO($J]lJʐm)ۇG[A 2J ب0,{7 X]EFkД>o ;JXKaj{VXRS܂3^P}cM{c9&l7*GCW~/0y6Pd-1QsΨCtUر\oVg!\3iHTB}tVhbbKJжwTs"RQ"09cX ig?Q^ #n髭bYP:*Bmkb O/[(}9 i;- !@VwOQAa(F:[`Q6%Jq*h 3DR EUp$ mc`  0]I,̐cR$Mݭf.  \Q.vOނ- pj@ٳgO{y8_r2J -?;GhQm L,-H?6 2$ ໭Q@/IyƋ(ŒucH45CLO2 U0y$usd+ү|+s|`PL(gm{ ``QNnqmThk̡(SX$&VcKuÞrzqfn8^F)M =gX,q/twE Y>,c8Vk^ }>T^ܫ9J3ÅO7d*V)K)™Osnmf'q+1Vkq`u[Lӑ hKR׹#^>%w8Ut-o%I87Љt Ϝa,NxwEQ Q E ,lc=6(ގHIQX1xƚpkӇ6$aVy9/wT{[>8tXWyRJ8t1 s9X&xaT9"ٹ@ȡ0G0rdb;Ƌmp>\.i;i iGSL/75^ o'nݍ߈;p+ӇPޟ7qPl\`O>՞59g) J^xQҗ4vnq(7K-SFh8V= -ļ`P  r?% h~C%YFd{m' 'O ]hoɱ0R8Ƙ~Z m Bz3% nP(D_`֗pGMiϹ38Me]+OXk8o8@s `wV@~?,𽚢;ѹ{Q )?@V1L~^" T40a[lzač環u23> s9@ksD8grA[mJM`1-c 5l=HqK޼{aH: iB{X0l IIQ@E eXZ%3^H `|jC،p>:K̀o1C{KPQ7mΈiG^0IFcFqq3@1ƀkL ('@bL`8KY`-K G\Tk6[a0)i g X4釈" }Wyۭ9\`gP5lEs1~7{Try b׳N>Gr1 I-/seDܷ>#lrdvh{}{-(u^&/FD>G*T/V{B[gA K6 cL(gϋa0w(ɯ(l1+1;k`-!=7VTQrEMRXyp,3 %Dh[#YĈHu6‘9=%X@ (-o-TnMuKk c0~VUڵ'`Rԃ`Ϯa.]Y+4.K1#IOXb3V6:e X~:BYP_q@ЯqG/?k¬,?{ ֟!ްKSNX>瞲dŌ)mcfa36:)*w1%,AOg R)ս. C,qq\B}KР(w18QyÉ,s{​Kh7ߘʒq ?;Z֚1W~qzP7Oaf|},"',q9i?Ã(i@(#`=,"UNY Q(gQ'o1Bh»n--ul)2f͠_``pDܿvBԀ{&XP"•lqMK0rN|@(PTF Y)Eh;%HC/-P$7ZSy݇O!p`a/ +',nxqB7[YZ'6W݋]GнG<)Z9ks P ka^%bg "),H"FAK"?G@x`0;d}ᇯ}ߜ2K $80ޮ// [HLZ fWPbTڷf]cՋ~H_t *L@LYF%^@H {ɃA%}@1"8,'fN_΍r g c#1{ 5&ȱ旅HpPSx(wF7sԗy=u(gTj  5ɘ0O7t,ֆ=X_+m/ @v-'UG7/8yFGR}HWkZh.lq"DZta{2c; R.fc { at<'DY;gJw@b|o[81@-CH`"cˀIm*€a 0IAdT9_wW^!>O9lZLⳉA"sMŚvд!30Y<0+}&2|}(ZM8R6u+ł?`vڪԒ%f"Rzc|>5F5m (d> ;@6q! 7n3S,c1Sh C:x0Wd9m\3kc a}͇ X 6!Z?mo8?XC~=FU=L%'k O4%]og ʳ ()xVe&+sߥ~6Nu}W~N+uZ5jú0J~g ? d$dW  0j%Øb1tOq=l2ԅ (b| v F~0隗 hwwmkD.>vڑC;1rۨ`B_ɭU x!&9X/@.='tr/#b4)н{y@_U[lrH:pK_}fNۉD8*nRw,{SܗehkGl[%OlK^% c*!N))_ dXhԮX4Qd7 lzzb "KS^Vq38Hy&/zC}] S bZ0O O^Ɣ?56 @:f|]4-8e{ugu$´{/ fl;䅍0>1dm(6?0B vչgۢ`s@bSKEei`R^M!r1޲N9O05,ˤ'lIeR8tDzScN}WO]1rj X(I޸ҧψcw@%Ph\E0s'5"LLv_3o|>zRfʪ!jj)0R,>h#΍0`jD^ƱbA4+ P}9EYDyƖsKݤ:rZ rFye[˛U. ]7L9em -xanBgf) !2\c1%X`#؅Q%3$`b=/FJ. @1p7f`an0Ԍt x3Wm<>hka H0Uip@%`(#UNrV7fuO m`8kcOx{^P-63$~^o,b>0qS2Ns0Z}+<P'gco{Xw4`L{0Kxۇ6?)8и+=j^1?9Z`n&ӚPN&5P "x!qkV&2@ %{P`hr+YDX%@(f 4)Z(p#C5" uMct}啅.6ƀ 3cCC21x{DyHaRJfΜsV8h{IHA`R.36z뭷fpȱB1>PvhB%&ZAvP'nt]r4;̷fm[Sss+bz}8W7:쳻jG o7sey 9y]oH6aXqjd`w3r(W+P4̓p,0a+q r052_bvUޛA'Cb>\{9g,kbCF*{Ŗ4[g-O=G%mP@i/`¸s6~K*Ws/MPSAJ h?oqJs c ;Gc<ͽ|o.Zjk8nJ}mek<L. H4-cO}9"3E`,smzD/6Otʿߵ'?q͆uhs P21gbc(_r?$lC!]`քR2:<]k QRD ¸~b'2HaN!.@X`Ӧ cʳe@@d&̎wvcb#c9#6BtV:FHx4E't5{ 0%N2`O漀=.]b*K0; tҚ0V}~0tݝ65ah>ˆ"_?vпƽ9s`9+l†Xer~5ǁ`؜p}̣95'9G/C_-UG߳CHvplmPP_rB-]|eaIѶ#{r%:yx,Pq)uz,PlF -K!t^&.EQ8|45)֎=~o`c$d<0ހ AcL(1P#db,g ,#cr„y aT0͉I(Bi +GH d]va܌qFP}Fc\c273߫_@c2`ykk 0rS$g/9F9,ߵzR^f,D߭Jf\4!2k{l՞~Ԟ6OfQ9z0ff:;b~S )dё"nh0yv*Z}·l Yb)gO[?'`ΟO[٣>sP9m9w\CaLyL~&|g%m.]hj`N=!i2IsU,@HI0:B-J1[(\*kY/ЦD(Cr,BZ cƘEփAN@I&K0ǴQz-p1 R{Q4Ļ x7 H!ã֘ƶ잌3sXbԏ6^_}3Ӽ1(z6H19sHr ')GiCaFH ^}όנJGpjZc9Y[Tİrz\҄ڠpw,龁/du onpƳs3P˾8Vg~ƥ{ FL!0c&ZwU(x¬oX^/Taa>02h apBD)#fl(Ũ2 /`xX`{[z9~03.-g/Y.3"ښb ޼@v_>bx1TaQ}>""M?@ qG(F q^Ag-reIe ^-e}2N>夙W5>{4gBsyt`[DV-i?ʺ嫻q&QhlJْWWcƚ〾<;g@K(!J1/b%²2JQ Cygq.oU"%Qtmo+(7,voeʇW;tmMQ ߱o q 6XZJXZtMĪ\(=c8xŵ @Q¼ԢNZp&nbM+H®1J7oB{cQ/l,Z%ae+S:zs`~sn49ޕS`QKOO'c@ hբO`&C_Ӂ ï8<=n cѦ<_@(dԶ)P50sdL p5dH)z\0cPJF1Dž9+( 8`~A&G?~4x1R^٦99 ZB(Dn,abԑor-b1UB=:0\藌iu~5li>Ƭ- |0?B9ƶ[tooN6ԮХD5 uXq0:l 1w3N8B[+m%tۤ`h9ڧ$#Q܏e,>c-?8tn\sOMeXJf{0V@YRE0Fxd(:J R!3(Eѻ-\Rx7<,`fhKQ//?m[{| >ƃQ`+ K4~02lj-a1h1s 11 @MK"9 l+$M"bxq6Cz#|7݄Hsm ˅@swe]N[,^ؘƲ_lIHlate,{a#yc/¬EYnB $,L:==]tOwթj'R*25/ I^*aB+Jʑ@([i9u) &XEл~CJqrcN9/ch (̤AH(RI1愠t^γBx%Gȭ'Zm| lE+m-?H ~p.lj#g1쭣V8G›s#"YH&!lIWD@ #}ew.J|df)õ@>@wn~|&8ȣH.*PC p#9sfwkX|ghI1!6$ .1)J^"" s1Fң ?>SH #l^d!d!- O%qe7|s,Ǹ 9fXpWf%Q&r|p AruGP$m}mGq'}rDCD QCc, y๟s;pJL8V\}!wChՉ-խl/-ƺC 2 q:yXQȹq6 5%&+2GL| e=tߩRn[wSa{/"_%X#9<]a-_`ZF RTY <)u*b 3HP%BGn+Ƽ4=+K1&'hcxʦ6ptf#" $ @ߵ)r$HW[~y3Ŝ|HQx4HBZX!!^΁~S":(!.?G贍xo cD^=b5׷<DH~r-#71DrJmI׼o\^誺؂rHrEcZ4}yAgK|NuǗ^zS:=؇3`.sc}s$6ȑt Lžܪ\[rBͽwMur$<,H572\Bowq`E=1PD|8+t#+HG9YRHQ1/`+K>[ȘcZEDDZ۴.5j}&j( iCYC9GrWj'ÈF+*bmH# "\OAj9%uhkRns @5J8pR6;sG!nlnk9V!s_O=*E$ȚpT[dS!(esr}ϫ$΅ TCzR->wRvYJҔyLZO=׳$@`0=⭘|=Ӻ\4x$g:FQ[k2* 1`?@zDXz;Xۥ0zsX1A-'# ́GD8]3ԁ|@Zy:/9}KuY.1w6 .Mi Wt|EtpVoD1崨3f"ly9ӄsG] Ük}}79Q_n7vQ_u<{t3~5 c##&3ņ"G~ZUi@+5'Ȱh/YH&.bi+&HY/e.3weY`n#ȁM9\ 'K1tD"s`lIyt![-r޻c r*_$DxPzs1ӿ_˙GplD,=5Ə UGRMc&F o,'khKH>@ #R{{EYu(BCU "JjiR5@R"~A=Db cșYI)|U˼"IiExeu%su1C e:+ڭpwt كEED2\QDTP) Q p~DJ1o@soYPDP|k`:R2UP]cK]KskeLa~̕U~  #:plPA=](3ð!#9QQhz"L 6 j`|*#lzK-zeH)T^%%LTH&FKH#r)Yg6'U?sϐ2"׺&y2?߭& GXN0|s yKw+ S4O Q?"_P7t?WfbǻqxGFۤCH7j~t"0j =Du2[)u5+)N,QױYP] lvYJ7) wDO/:<{DNT_[eEKH *1րr]nL}3ж܅SQg$E%v"kǸ9d X6 cLd<\ G8Ve0mHޚ4U6] p ±C%@,eB{Z ug >a<טێRPm3{QΩX0`_sӒ,K\r# 2xbGg ;W b''xh20 zĚE{!>'?hH|ۛgK@E.bL rȕ9R*WW9YgᏲ!GP^/AK";?X{X =pވR]z(c6W*r!eV]MaFFlw^z[y\Ed8[ʳkyFpk*UHh)'kfЀߤGb$}TP_(XF6P` 8)@@`,Ӵ >T S/7xc$x'"L"ekx_ ,uFFqtVD5C-ꕁw^OOQC\`x.\i:v/HG! !)sv$2iA͇2 &7$՜!r?p*3NGlK@_OBHWZe}Q$Q<2«'a,1GJ78%4f"b"pY!R9%/I{sn8V]~UBD5rJ$0ބ.+AW%1DF903V\otmϝgK@ `aYe4zՂ!rtHP";2`Y.U# G"` +ѭZLx$:)Dc4>To]9p]˽u@YP@P`,RxajЩl1|d-1 g q1_:&z;k+1)f}h>s2ҩ?K*7O.H1Wkcalo{\It-bzsr{#Sns-jU<= #}`!aL:f*7y1o8~%Kr9r!=_+0M0Έh)tEGxt#m9\>r@IDAT 뇵l=Ζڸ,mކlD>óDD8vEyCD%yo:rGkax 0NYz(k6a N^*XwI!Zk*W^+d-H4#%1RG,&b3EQ`ꗃ8oN" qlhdA?$g"C)]EҮ}zE#5$!S GFTI. YU 3(Ľ)6V#>jS{c>Bs\3{@@QS:E7$6ri]^Gz[ekӖX_t6959G..}؁]]s=pw}*Q%Kf <mn\`)>~5{8D>@D@C (KY2q9etW6@608[~4H[@ܯ{ B-r!7-F/N?QӓGG(?#u]QRYw=+bIA,_tNq}9gN|Y@:R3_g'Nmt#F؀ *3 W]zT{%9Ld<kAjou#l% ,4FMdl["_N#hg?:* c)Wʅ5ʁ*Dy a,_7ƕ}m2@Q޿~y2֊M큳e(+ ~C 0BLI,Zn%#;n,!x(7d+rhU*Mݯ~=(#.uA1(<,#2R\L'XtdV y嗻'p޾E?2⻈o@R]\c .mfCvUGc#cdN'sWNnGOQ;Md# /xOY!9W;UK0@K Щ =Q#fՎ}v"B;( ,r=A$0U@D$B8_7)DctZuℴss\KD Qd"uG+`Q\ch$PjC~QS1կ~_ad}HE=4=ꕾ1$#6R- =)5K|0LuuMQswď!ߵ~i;--QH2~BQ)Ry1D%D+8W-l)kÑl9, 2 bDO"沾N"DDrN5Ut#5v;dXΉkE9ۢCdBEa~zuuu5}iUi"ѯ{;c.8+^&sE AUxvo4O'Q<PΔQ*FRc2{9,1PDqL@3;/{3h^")}S[zG8BvD: #٢ֈ}x,5HI7<Cx< FJ#}IhkAlDzD2ypN瘿Ɓn{>y))Z,E,!=ܘ`iq 1ΓGD?V4:@DrL) Y~6>ʫn)mۘis~#H| p|JEXqq-0=#nWezeވԲuuV[Ba[ebAEխY-&G^.~z~I/Kecyi>Nc5E8/w^{`bRǃ"'' 4& cRSh%#ʴLnG)k"F#d)p5Xs.{Գ:v#i^'@nHD/TF@Socl$[je@8 ' \+@JDSDN]YK.#>uHD'hm)_64K>;y(> (UDˊ4= 1C88ILٜ#}2澯^> 1wn. Rwϭ3ISGwi~{:ug$,Nl?DC {s]"JGFX|s]T&c>>W''"OwvJV/z<!BM4aBf-ω~0:s93@rAj8F+qk3<3F2ri!-iL)kE>gBc,$1< s=`{z5˽цZϸrvCScx(z!@Za@"h^ Z^kϷčy\eWȣV=g}(cLJ,U?D1k#~zcgY5^c`,erh9~fS2"B|pdzMj}E%KK|-yj=o[u..39<'q\ ++6Y3lt/c0['N$TT\`mCH8rB{pjDqǕIUNTv 3O[c[Kƣ:k8 VH=*pʡ}({(-ZF0>K[ [ȳ#g޹WG&kW9h %DLv.[&ǰ_#~ 芆"#-?%W 9I1$)W9=㋼~Ge۾{d 2 T2fF! hOH+}- ',V0@z RC,9AYr|@dOjTT6W\N Xr=o<3+VsqA$Ҩc! %2ΦdJ}R^俗νWڄZR/")! m pt^#13aϗ|fYRr`:qԉHU=]彆*R %>79Ll+Vn@8x"kIBi,Ϲemp:fz iZVW胧-p88:6h+;`o 8)mt_Vآ,Ɛ}VT~n:)Spq!vT<#6)@E4!BHEU=oCK#Y#W(ٽ_Fbhe(6o~6d@Mdx +iQzLs?l+2I_ k~^oIEXD.PȬ!!W Hact֘Qڤs9Y `U=s -ۣuء7W:2}iUtcQkyꅀsbgp}6%ڢ\V"^\D=PpvNlݧBgY֛ﳦ/# i2?h@.5D8 #'Od"~ #Y:E<^V!\dĎiR }_=2*Nyied9=3H9ǙtzXU#'GY޹hh)HHy/;ZU@y 3)1loC䍸kOJ]#09;Xb#r>Fc$p/ P7LDa08:\rVtR>Dh=pfD? ?ӭs_T5w-LʵOkḍ'=D Ez["* 8$~2!E8A"@DQ!,$rMc)By[7 b9_ynl˂q"RI`sҪ%q%5CSrαs"I Ǔ>Lωv  q[A2$Egf igm|tӘFXCMȧqV;G>JyB97Vs&G@% UH([%D.MNHGHaxK]݄r%JDUE7SẎ#-IGG,+]'h3roj0N rH'8񓐛"HEd1N;(H?iD`ED<pD`t1|3ϢnqRkV7gdFT?F>WG?yi#t8PlS%q1[$K b\vXL QFl=t x)|/& +%Py,zgOecjs)oÇM.$CNBA#&f)Y&J)`zT%`.gp,F !R$I 9/ YDUZҪ vx>kQoJFWg%2UxqDW^ye|ED"D5ǀx  ;h7`t) &z|b==p d8A] ({+WCsi>]EcK8dA* yqc|v38V&҇X tK[:ό6Tr$(&"/Ir!)629S::{5g_bdQQD!>,n윋8G`Fp*&647M?vtMnlūuRG'I-z#ecR2@ x'8*RVW%"DƄwj9.Y-ڬNNVa?Sg \> "ɐPToF Hc`# Φ!f Eyazt!E4WWWˬ6(݇cpȶ.y/_N6~Ow%PGos[a3>sDab|8wH{:j`d09L wsfE.Υ/E ߭]{]yH']a^gz=\=QoO &PzV9ɍ}G/E 9c$@ |s|1޻{_a }=p,u lceO9N:c 熳B܇I8$+c"qt.Aޭ tBayGG/?*5XNA[~ã?VN, w VF4 &*aJ<޲iDDZrn~sMgOwޅYA #"R!򈤊+V+{!V?.//G]z5FM\3MNo4H 1q@Vא?[;g"`㌶=A<s EgS7U8ؘy~58FTU[- ڒVN3$& xp uKslQwH'TRm|*c9=B_ Nz@'"&H]aI,=[mW'ueŜhg} Q^zQ.F6z.{PgšTd,A'xMFY>CrC ѸbDEU#jG,x/gBvDQ|ꥌp+{ߩ$5"{*S=[x{) ckb/e@?S)Z %}E"\|E)""]Ƈ!&BD_dh sQ8 x̠ SEՌtU$ײ%r 9!Z&-Dޢ\@vpr>՗h%TQ?G<'R]Vo<i=bx(?ؤOGز$0į<@z68p(QpZR]Dᖍag1_PqpVnr25^H`2߲tJB}كH0=7,0[>d( `VM>LjĔ8 F ` B)V<$D^8CN(paMVgX ș:zøh ,K;JR;"-otl4ei|LӲcEH]~<_ aUDz=`ICcV(+asмBU sQ i`YSzz9cVF9`"dnۙM|UyBE8V?p qEf}")"H-g8@J9|SKN`ΨH,k;p@c8'!D EaW.F#@H-=>ᰈ NMe9\ݤh]TD#e#ם˫! 9]1=^ss wؒz6!5 cDܗCd#Q\6K F%5  bIyPJ ,& h`{0V1, бDցKͰqH̉<v|1s;dWK"St$9u?FZ:0R&;`QFǘ#z6i\!^w}lB*No85澍] s3Q  F 9Nl3(bg׼5t"°Kqtcw38%2g^t3Q?8E@}iaA 8e{8ˑ "akLR}ct^r$< ό\Y0>\!;ayiF"7Oĕ>q z{:@O u?f>ڈ^#B?- ɞVج)g&Xlxtm،D@2 n y0-`Aj ,:2y׉h2Z%1&o 08@as迨%o^}eyuY޼Zr$i8%%C9fGcOr"ecGW+lcS`7(E}/Wy:a:Do{"| gvjTlVZA䍥z`#E\PO$g)f0b`l,8FƁqX1`$MnH(`S#n!U@=P[+uNg\0#DMtG;U)AZ@D=_@(" ̀|3ƅ!kZ*{9|U("ی'AEd@SMN sdms<@`5.ba('E$^HEw.CO` =525VV8"n-"b]#,Zsٽ+jhY#D#aUSc0F߂ pp h|2-=l \C@n)v0 -AM狊gvu18`u9yg}1R8BkVN)7 ~F@a(ӈHD#D^yޛ @T$d4THc{MG9k)@y6@^:2v}_D-cʛ!hrM)&]t=%poc$K^ |oL@V-x|"0Na=jM8QD(:ABxL `1'çss18ktDީy DDFo'~M Wg<=V'wpOw_"EsKy" !ġN4psV80A52>5CNg,D$yȌR).E޵'(874vsYPxbՈ gˉQĦB%sOj9|%!xIPO->G(pL$&(R0 ~!>oag,Ғ/2:ϙqu_GJvMX}<@i 1Ɛ Z,*a,* ,@)(ӯx ,2DaL<2D1`|/f%i%5HYrc(W94fA7DHG2e{s\z*JWjHr堃Va/_@|"H.éA?W6q{p ~E2ߚ"^9%`x9D9Ǧ"T/A@?X+pMsKRZR;دssk϶^ٳ!WqͯuO/o<@&:E b0FșCϲOYa((I-~H.f+trkYw= iRhKR`m k#Fix0dTk.^i HL b,cvq48ꩾ%.}/2[͏/G#[6ip$JNVY^=ckr3 hIUd\4v"m[KE=bK-80aD릟Xv6nyN}9VYNȧ o3R} A;#w{ Rya 8Pp\ʨ&L0=8*VKD@EU96Th3PGӼ' !_qJ3Z5uC\_!Qxόpy?Zd9հω)OЏz00YOmA0?dI[o aQ!X""F9@j{7גKCLs;baP6&2 -{E6kjC8k)C)&ueؼfz*!:v1>n2t>͉"挨(E`~ 3Sr m>( F`>V<3<(]s/QqͮG/ӧ YvV? Ȕ~Ja~4cfNǝI\4+ŹC/E9{q8S}pOn(y#+p-8#ZA kiӮs-HbLl׬NI.aPN鼳'&|9*: ` ($B," ȕ`͵D$:$Uw'60! a"C:X[WK䩬epZ=/nu@j-IPD[:`@\J `DϐOJ#`R?: eas0Vr\NboD' :h_Wu>^;H%XR7{ ~Jk):A}ȩ=]Qt9(eFr2kQ_.RQUGʂC^zlb5Oea~8~pg9Ƣ1x\'Erƞ~8׷{zgEp!FJ9^RNsҏb% Y%)1`cwcheWqkEkNA_uR'u6͍#}[B83 #Af0xRG>6K]$lɫMY1& u$} gegύ ,6闟fmP&W{3[5zMTGĹPx, BBSD 5"*n5>l7Cr@Aܛ }bجv=DR u? (TeY9ܟbL)w E ]t=]#Ѣ&b̖] izTz"עHk|&:ʍh]!2nyYkl"r&s" b@QS! $ɒQ!eܓN]ѳ,7xH#&+at{= d[b`SQAz [ߎ%a@ 3iErwn 㽴Laܰt 'b|]93k_Owa"0G8~^3d>yuʇ-Vt[N8F4m!+f>SDEgK;a(3 2&~A*`&1ٌđ"'C g©GVO`{tAbO:ʭ?3qeIO-&0P;UMsSӗM'|dҒxctx!VsJxߵ-|3f4G$ X & y @vh LM F^ R&&']  i#G0v&jY$]mBJ!KV܏q0Dz:DGm>S's0}g!)Fȹڈ,2Be qQ}.*=dQkυxc;%C1A:ðG7,!9yȶ8s9HMH3C&CiN?M߹m&B,.BN.uIy3n!~2ϼO6渾|PX!r92%CVM8H op3 #v`~G-ګ_kҺkDH!t`]U!jҙMngO@u!oO) C:(i->q">½)Q> @ԗChBOh>A>| |\b 6dZ)H>jbzđ0۽&#@ :@;~QJ!hRb Q!EŘ"K9A"HW5D@k_"R`5@6OUTma hLNh2PH'kY#?@^}Տ4^8 '@~#x;uk;}0d6i5i|oZkϯ!AEr" 㞌H X-1-[:O9c H+e2):&gBKtK13O`96157/  hEߍy*iI-!W:CSu9v' s hm}ڠ{,Dθ " <ol5u"0Vk,Ʈ1GeJ/WJ=2ȱv 6u{c,1X8 d=ЏD@ 71-A"=Ad`9-0uS@EHˁt w$G9t=g}v}u:!ph|OC$kY=KꌝHC9QstytC$Ms%>pn"" s1+QrΖI3`%nj^ȱ鿆|*_9W1} ^:8__H[Yh δ{(JO mehJ2l>GUȨ/g2!uL>R>MlgV-NߊL A;&DˍJ9$aeg G!(E;DD"z`X9CH91AF%R$N}H Θ#Xze|,"Z@Se9UGG"+0@0JBI_QRGG8"Sn;sRc$ ?q#C~0K1 0'[<sAT=q0oIE(wIm^KprHA'3'}V*\]`B p'z Obȷ !ԡj1ǝNs Җ8T+BQɇ"ҋ\Auח}zE8*=!"Ė5"jTD+vu'"`o|q\<=+4&" JOP L0Ea  )+kzh;=45OSYȜO_aHc[- =þ뗾WoơgHø(397."%O\Uߙӧ)lœ[#zPܵo\Ϲ_6%:9$AI^hQd?̀Dd+Nq5 5K@yr Gw䂡 p2H {/05I}A\ry#adlM=S<į xᄁ&\Oh~Rcs˟!S&"ƛn(a {EYƨX8aKa dSVQo9HHBD@6H1DDJ~!%ļQ%x;ݟ<*:fD"Hz"8ω!:"+0`(!ekN~D9jvޑ X9ʉN)=X=|6Zڂ v.$Z7zzjtu"LU' J 0DxMrc>@0~XFQ. (%o]ΕH2dirz$[dS˨JF"UڨBy;KGYD,!H*8'gO2XeIڡ=gQϡY#g,{ѦpV8(6_$L# `:N t 46Gw.$tz~tEv%tkŠ?^r):b׈v!.2P w>u0B>GQMx$d7ɽ0 ShAHT <_c&Ztdy{_.GTոhG<rS3¤e|ċ$믏ad}J9S^'e,mrЇ\"~>J(.X@{9KBw,1 [JYB̙GZbx(+&E+7C2,\I;\CB]*ye0 1O aPa,985_Ԙ3?i¦F}0SvC'} "@0( ȐO~iUe@`$dIt=C Gt\@ӒCE{E2:0zDY|9D=QV@"WXEeϼ"6||nR$UFȶXD^[.T]?AE Y#,0֢#~crHYQ6!U1x|wZlm}+ZfMv~&ϼ帘?Σ/1E'9uKRVA|wV@Dԡ)E@-BV9ȧ?ȈzED>DK:o'6g\ϐy `Ko_x/8 #qLsTs%ϖ?$Zx!|Zq꒶?&[Wl9=ըO #Ĩ {́ÜX•x3iNC\n 2#AxyW<^Krݟa#dDDaD}>v\H:/r\6!b (EIԛu/C hk;fiQ76_W(" gf/#&Z8@GD}F)A?m`DDE?EEgtED9T%!ˌ"ʎЏW}|Ek{mBT8 j C8_L& tG#cuMPs6#湹u /SZ3sޘaEBRyO}BGH1iuo2D4b Hu' #G>.zl;nd$$)8!N"s8,gv%6]825^8d &GV6B"X&;"rQFH 9KcܹP' h &-$, @!m QwK1z S"_r7͸$XlQ j,^2yDYHytߩVR!xoetRud`Msђ2 GLjC iD+zRՒD"\uy睱<nYm5g5""ƘSsziL>3oAhh ,R7g̟sO |%[u#5 t /=NϑʺܹndkIP pU97N6| /SKG}k1.ɓʔ\ASk[B80MDo#hZND1rJÐs?#Vg{@! GU2[@(`Vd\px J*L,{@ͫʶ,;v[=rK"e +d 0aPõ$е~HHfi(O$)`4e#Ù DvL苌 \ #!*!B5E(U̥x}á)۾tݹpYf&@IDVv=sڲ薟}#"uW!mEDIXx"ϢRѵu$`NyȩIO<JqZQ\ZGyϡƫע? WGqtmsJ9CzCZQu3UŶuz`=Љ  @ΉltN>όjlH8 %bl(`) `mExռ}nHBm[(z ݓQ7D '$RP 2E-Di&]=OX\ \H% $U"@RWi6s'?1@$KI[@{Iy2ȹ =cD(j)Y ʪEM9.M <3M{@w1]ew?aC?Ld!H☐aD%ĈZڴb-HXRQ#D% *ѐ@~0cL&1qoWY{}?+yy{"}}Оi\qĤg {Usg̊y)e~+@0 ĕeڀ88\ C')0h輱d쉫٢sTdj]1}́RUǚ#~/:94礣U,-1=KGueB-<*c_gב_%Ədӱ><#M\FD yRS"5&"sn K{s`gȩM4cENA bDBȐxEdy.3v/(i}!@"*m7Fw-?ƾ8828$Tk輩8 CJ>#&C?I+D_E?k(0#A`@Rf4d8()]C@DIrPDlcRdEQ32>HR1DU_ ZBZ<om7r"TQ% %KSCeulfZ\XfH`;qD *X3(#i cD!ޖ ֌Z-Xu݊+[W~EXm+@yEBES-L #`lɘj8_I X}pD-CR9oHCq^fc .*?֦wt ǕS'E~(18 Q􆱈s t} 18]c;z^?N 9~*8z3XbCrgz$v@ t*$*1!̉@fLDkQ6;%F d?l\-%=m3lK}UDEJ(35e0(D?QmqV8)BMa RF c"fJ=cDDt1:h^k7[ʉ_ojϿ MF([ce"I9玃ƙG}9ml"0DQb\xͫH#!T..آQVpcUW(k.?SbK_v-<6{V/M]k#tA9C'pLF Ǯ1 EU:g sN)~(=ԅ_?s$3ߍ9TXD#ȥzicHVTRwG.1.Tqqa4DɌ};%p SǼ+',~^os!tXfa5 4/|_./' !}2~QfURh_[n7:S}`+.*n;6 0=砫xL#."A7O!&)BJ(.7M~D4AAKDD!9""RlcP1rM}D9`}l3R"4<$"j }[`XjLWN}vdli6IcY:Nd8׸DgdҎ"pB*eȨ/OT hYDsʐtC7,HfB!~_!`̫E!76GdC͠vLzmBe M> vdU\JyBە98wEB1}VСtYCtȑ:X0ސH6!#2BLH oQ:ʍ/("XMR"Z V%ȥcZɋ`HRE8HzKI=edhvJF fȕ Z(0&%_ek;+[ĐO^_!F6EA(fe"}0uS1{T ٵPiNJQCQj~Wc cOn)c aj>@p`fe>i3wWfl]|c,"7VsJDD8QcЧ}cw]׳?_ajM1q*CP koLor?w kzO߿m[o- !Sd#% bq= ўr1J#m":&ETQ:CV9heu)q( Y1.#Yӕw Vcns`ڭ݆ u]b |DT%>i.opsm_B/gy [sYT8Ryj"06#pDƳu񴤖|y(B@ ߔGzW%" 厬J 8!."&s[vc(GTu^䪭_2Bʨ=Djl1th*A<7=JoC3Έ1D" u~wPVZq""b>RAL-` MjO{s,PgAT3!Qpl*Q[هԫQV01ҬȪ(U1~D?(wx2Owwz{ ]dd+0ݯX"ƳA hcM >Dt-qTi<8ixGw*G <㱉4s|=)}پ{AQ* G[E cb'%lc?N!(rA}`#)=oJ8a#$ kqDQ՟s̐T "2Γzl r ~~} X:wl3oN anCs;Ș%E\1C7s:9,L!:;QaL(j!^8 hբ(~J/ D( E6KZe`Q,SaDE+""Z`Q̐Iy s]ACDH\\s\Y}cEETF34'* ⷅ=p,^gߢwqGXIIKt!e?NEgp!:" F.Ea5$29^WItc"VS8¸!F_7Zb13ɵKX]pe[!ﹴUtQkw"(qD"r8Mc~#E[UY}2~#7^  I";%lW;=6}~oXedYBj˵}s1#JDXQ!qMft0B%j= E7i1K7C~"T1PN"'#"ƪ9!r)Bn#2 um"}Cq>!8"Ȩ1;$gn7]:o j0k*O3ƏI~NgmcW@wʎ ⸡H`K@9zT6GFϹ(Hx~EZa4D+RjID!nG6E=\(EAEVG"4+V&/aEnJ)w!;2s)xmGORde#Z4QA (`&uU"R-/GٵaWx 66 +FE/""M w~ >?cbgh,! dS6n=~4POO{6Ph]"m+3Gj3xc8^SstmdڭAK"}ǪڄxB78"SW67[߶s^)3'oܸ9-5~$H>k<1=#R2 s6ևu=13#tQ=K ]g3sXה Wm߁ܫ56v ":ɌڗϏQEzE0)k$Rc|D9f0." {I 3"M;@ PÓ6RZT\^(z-Οǖw*Ik\MW426߃qW; k1Xv}Џ P!MR勇HtJ ^Uڪ%ڃT+k\  ϶n O\vs,F_&V/$o<{vQK'"n=(ymJ[Ʃ'n̆mzqĉ4Dj8W;n<~0\ϼO:3@vX}gl" }|#R"Uh!ҿޮgh|B-pΩv j խ2.z^NǺ"J+vza"A`>_hgx V)Ri1)1d̈́EP"wm-5jzD\ݰ>]g\"EO Aq?+s?onmfTbFI1+ %OԢ DTͩO?ق  w Ѣ䵝r.#msu.2# 0J~[̉H[`cJdi334Ʈ7 ]8s,Rπ# Uv.b@OUGh^!w{fc΋@K`L`l,pD#rj^lLwOs\Vu_2c^9ZeھsPwJq`# /0 y=1cl^r -NM`(kԖ_ xns=R>52} Kmbs"a@ <(A8EjCX #/RPޮ5%xOm@0DO];rb3]_{K<;Ӓ߹)-"Fk 'uf gf[UFo~sGz,,pRhr=/c;x9gG\Q+ &Z6Oʋ]O,rR;ϏضHH(3#S@Oԝq;uarTZ1E=F ĜDGDV#5P}EfFBmG?rsݧt1 5'8bZy9~C3JԷnzP7C M9vSAeA2JM:cctg[6}4uXҰ L]7~Q?2Fy#Jׯ_ 3% B  BmU1V"S.sEdS{.ʇudPxĔglU-q='G)$e7FhJ jEj`}7̤< maEIԨ~a .țAEE~sQQ&R]x9s㲆\W]T֘:_H=hq.}3'c >~`lHc9s t6C2&YAzo6::u9G(.B_ud 91@hӿdE$LL? /U) BJF [E>+4uvmB14!ڍ9gL+m 9cDpPY))pL<(2#JqJM B />t?1F{E+>3IDATUmFDDDD0ME#3[RbȄ1??obLgѿMWxDR˻l.-jXeƪ18""GH:Y);$_])["ޏgr-?s=SDlMZu:Ǽ5~95}}jOmgh,=axrkUb 5}ʡB3N1K'KMϮ!󪓚3c> AMpHH:E(ߔ80EG ȇ{QȂϪmc/RCWUH"8y̠դyf!"ڰJK=>ݔyQ+0<" 8}6&H'Ɉ)m砼af_ͫ>*B30/Gx}3BҙhU℀2BSDzm]zlu]1Ea۫qlV8D?򁸚G}[Bb?V JpVڣ.ٵ` ^¡L吢摨`90Gt>L%QCG͢rcsғKYi+zͱ2ݰXmD$L!9V!KDF(3~B_YyXMbPE4WOyX4"8xiZLSH8BAATD ճ:Ozc,R nRO;eJ0~t{ :9N{D<)]c|Pޢ7h>r!Aa~<۾6K"7mՏyӎ@^5sߟqR?ƚFJpq:ݸ}Mqϐac%܈yl׉)'eH"ٲg=kI~E9zkn ^i:L}ft? qV~ƾ p8>tL ǎnU?);fRG@:+)@1H~Zbɘ9~9e,("u%P/N );E7$By g)tQ:ZqH))1FPhH(6)[ѕ2h ]<i`iZ9G4q(kj(JKJwʜ叅pTkW5`*A2_3:Q%moF~0(}OvD15C3;} sE_ֱ$}LwQ϶s ꁑ?s}K ֦m23k;!;;HCX?? T2Zz6z#n>X9v}E~zG:َV,C]eF`w!~YxEL(uaDVqNZZkU #kDy^=#ӕT "ED-dY%R6\F)ٞ',8*{'0|S{PWo+-# rejE=ZUoocõX|V35l&>7FB9ۮ Ki"RT:)|QkdOd\ާp̍|ɖZܟ&ZM/džNf 8scdf 6 g<)rHQ4I4a JX6#ߢE>r-)9C) i11)ekI3"iEe:}c.E⬂E #F m4H8+Fa}&x2TU,ׯ3S>V}[Ϥ=~˜32A!F|a 1EraLDi\6q}^ƀx׏GN{^YEj=_/:ByIz&elU3m^u x1#qޘRg1g8BEy }"}mu}NAz_իu_w>x!q͛09/t@sk: jks?ݲJ(Uu@h? 2GPyMkW(a7+[B6v6RjU#,*h2nKĪuP&+bɈDP8Ȇ\XA!KEQ$"GeډΩs ʖe!32@go{:`L R0wXaA~TqScyM6XE[ jXIs_jw~42ٽAyA:Fs$c<4-:BDH(ءOb~~g}gs/k: c@nz(抈ҩ!^X'οsDeI2OUWsX,`Λeswq9vœdm{:~Zׯ?҄EDQo~7۲ ,@EU~iﵫ;{ur*qOsXuȹgD:@u"[d u_ 8>z=C;\c1 ,L"W?OfSe"@7^ hՔS k} CR=b}PsrytbS/++|z iAD?Qm* j_v=uo% } `x*CQWa1ޞ[ W޷elDT{תVs֪ksFs2Jq.q"9<櫔%IH6(3R} vL(ᄺO׭|}89h7,N-FfO tH1f&µwݓ.N ܧ\8296E˸G\/- 1G 'RٔzJCaRSfo}`Y h}MdT))q}Qsq3VB({ļE(1" /Dob$b Ϧ/]pq&nu#0# :Pc[pFEFΗEƪUt@C y9V+<%2UnB@|pE/j.Gsb}T JNg٧3Ezvk>)Jji$< >;E@澍o"PV[a;UX-RCcsmQ Fw\"% >cb+! )͍0֡uhOx_j79|s(yDMĂE%=9%Zw>ؽszks׹KYJMl{S^+ V ?$B9_푆 }-DPf DEԫ4b33FfC \׮t*ZoFr[#`6W֫wau}PGG'GI1 DO9ijECoVN'D)"{M:mRf#MT6KȥE>CD2s! ^d|&7uϕ "kPU7‰clz{l&"%.=&Qu׵/&ADJmeOE@_t7idmH{ߡDmw7}g0?4Q@"C:G(|Dea}HѾ!ۛ{m"M4׼/-"؅Pr >t_3Vf3V:ڎC)r)E$2"2R6ÿ{.յǹ۹GE<"Zd\=$d|U]\^u}cCh؏XW?OkܽڻI#>яvQF׋&К[HmonBQ:ݧݖj%cJqՆ7 C!ܗAP'"KAD%E"U @*5J\w}4X }':GphuLci0isShKEB@ۑxz# 9v̪(*tٷHZm8TDgP]=ēgzI,( um|VF#ʏf,GAS@lv>Oǀ@1e%ܥv)R| K_X$q-Y ̭>J뮻# E# c x2DSWYE,oH}#~yB⦈>nAv~TrƆVΧit^4S{%5V6(ʈ D:7n\ xjZL;[(ӑUt{+-$Q] E@ΝzVQ}X7ŞQTx_d s_h>Lj@S.)}0H&C8T&`Sݘ,yD;-B'p>gx8"ysߜdO@}% fO[E&!6HFz_&nPSΒADՓ-Ʀ+YF ujjۡ샼IJqd2鱜sY/*ڧE#.+&9u_FAEzv) ̹E%G]Qh mZJۋ *!|ۥ(=]ȓLMuT'n]al"V5y{6sQ?Y4xfC)u)+TRVԛҾa!R┲l9 w"AG.i[~ fEZ핹kN9[H%C::ۛnX~kr,RvKM!SB tǽ%">;SRZ }[.I[ (SFPz>GD OjHVbzRv"j:R4gğ:T3)kBgi썚PbR3+7{nCԽҽR\_3s(nj@{ǖ jj5l  G>/x :oV/ hJnۛBՆZIH#ݵ I#w}wu(w)C!\ano(r+.ƳۗH-$j'K/{s1tw9BaqЦ[]er9S+w)ҨưP xuiwX\axh̺>傤zXm\+lmٻ4\KJ:Lq z}Ҁ z(JlEέTRӮ0u[9Cs[-hz^bkW_v jR.nG ]觍oLb!A'xbgb\#AaBEzjH=vm]v+Q?u){)f- ]Ͼ{guնIܹ陊3B>Kع3>;}xmC"C~ۉ4O9j@s"{dgEZ۾'iFUm=zsҚPoy[lkxׯ_z݊YuVK7ZD^W&C6o`w6(h*"Iosϵt>l__D}(⃤Iˮ oxÅN(ОW]. [u/bKJ|ٗ}}UɶcD tǽ;^RSm)"zի.wǾ술*3FԧȄ}KyIn[lbOUյ4lc=mDzz)6O~1[r=?"$4%(ֱ,6:ѓX%U.A8,\!Qt*)p~Z(i1R/i~j-m{me|zf"S PgRG}͡UOnoê}V}sDuI Eu픒v=>:(.B)#8!$TM]#Kd{+˽]Z[9"`|F sYΠ|Ih+-:]=.6rrg[Ux"Kju~B&]B@Hex՝% U}ET[gzMEcd GRY.T`:|K^VPsQ>~뻪uZNϸOM!v^iI.;kX(=0zW<;׼#0'XG8 |"kUtB?FުCЅ,Զm]/ @ ~¢dSV !P#%u'BLH 2|Q׹s(Z0St)]5FI<\%RUzNϙg$EA8v0O#x[N:z=E`9nU>RHdal}ZuYb](۳/8`(i[ Սz']k2/`_{HdD3*꺞“d1QrT;P2h"9 E:-2_z.?%Ǐ# h#TqcޚW.R ĺGWD菺7Sڃt*1DTMk՜qEzi[貚\{H^v[/WO(Uy"^_te.* , d]{ы^, [=gz9:'\% Ҙ 0PQ*^*qm?bd!ԯگuѫDHrs|HwwGaM77~H"ھsv/6`Dz6 M ~hZ"Mb[yw_{+2hF9J,[n[$"zsܶ`܌jSW80j>k%Ost[t숈%(iJcW',_cx jDeFDM"r, ڳ37O=uHR,4-" P I!nQ}H}g~-T֨'͙Cjյ1r`sF@'"V!\FJUAtmyX~s?hWM }Z$rQTw%:Slc7|s-o*!"OOE=tuH6(A18_IcD)|iO] sD9- pX:=V޳,* zysF!-b}e_,d#"jG U-)i@<-xzmdk5;>#Jh}YuV[:%}""8&rݴt>s3w[i!:u@(7(ٗ[V6K",^9đ/Ci0 J2"E tkH {t[wۿ=c}N*s)Y\ؔZ EԂ$Vū)Tp6e[Dp-P>P#Ml Vk4:,ϼATH Tzsm~ZȀƥƄD2)a[|~k8Bm։Z!C\u/iSdRnt^v#R}Α^ԦHJ5=>C^ʒGqվp So[D EJLya:,c@D?QSYdAT GpNEU"/տ]u؟hrL$MD@#4C C늈س. HnCD8NzַzT4DM|"zGt_K>D^զ|Zx0짎S?D<%"RqEECEV,`fSSid=_җvQJZ- \"ȧԺU[ս_u1 vsz3m~EW$jNh*yjs?t|vU=ўF,b(%b $ͧE?CEXEY,/@Zשu螿Da>v;@@-(∀C"Ҩc JRBEeGɡZ9_z#6 ?ul" S 9J\r9#z½ %)PֿSj]& "ȯd|X\ucE|+w̽\g c˨=^.T)i 󌣹QP;j;T;Oʋ"]n^jY)cK6#C)86Qt*Sm,-*3qb}|j–EYJ#*mK [Xh'kի=GE D=~~ WQk@[:38!pJ(l[;%ƝE3RRRwqG"HEꞺNt+e8]F_} Y{{޳ѻocԴsetgD{;cA 0KOَoo.f1y}ʟѢ8!6DwJ22>CBł#뮻vH]v~闺γ}6ykq2˅_4`xZx_-&4Ɣ(yv=tQPoVh srѴ|1_zA֣:n}CGuB{1LҫnZ[ҭj *!'jdiڼ^DAݥ7i& ""XtS*z07!F|<) S}T@$TԇY@J!TW/*Rވtf WQ%DX0 .~{Wj½6s59!Q;㸉V͜DE]WVorc9WmE\p[0UeKϥ@ oo6_ !7hR?DDHԈIޑNT[9Ir]cEkN}̆~bD-Pgs@_us)t=;;*oEuqqH9XȦoucQuwK h!}U=6*>k^/a|.0N ԖG`Q+fE-Lr"^ӟtw=-4U -sD@iR|.U86o.'tx$ʀpJ-b_NYq.wED O$\UB@Ϥ[Xm=>Ot7K 9Q@ 3.|<ًzW;C%<xˆ# L|r-9{ω4@A$*}9';3JN9}'=pϞB 8d|,4G`ѰRւ "!eK QR+ei$jUȋ /yKz'N/,u 9'E QPnܸq?jxQJsUK 3cȧ話>#ȺubՀO@ F% D)? _`0sHH+Jy73E!`qfZ渭~'fMMTEGܕ&nwh10 iU8Y ;yE9CHI#oxխ.QA2Y[Tg=YuH< "HZ/-7 pF/b]T_܊TE1ndUC9g?.2ϱT#ŞTq漿e7SH÷uw3W6ヤ1bHD;mFX,K!s (EX!xJFY@Ű//g|!O]n}ZGuzߙ߶ ch 7~7v=V`+Y#ضE#_EB@Ϡznk!>cU77w?RTlϤbT*Z"eTQS=]?72WN[MG.EKeG86tg&tp09JYeKѫ*zA|W5QPRBVz"꼤D9 1@"Lc3?R);GD[ojoN|ι9f=,ERlI ]5gG Њumm>-3}J[4:9n}->9xÑbdl $}3ʛD$`sF t]t~^ʱwlD|w@s8 ).<!l*^uz3?*xkn"VU;PB "P+97s_?zᇟ~v-P W D@O1걤~~Y. %MoocrBNK9kitV@xS^?S"x$4!g{o?ԣH}7|CԱ~/Uoۻ=V{\ TȏUhBO wc|3P UBI=裫;DK+9t }ς>c]=w^wNhн^:$Y8kB@Ϥ{y䑋ᄏ[/iG__ۥ}H@q3!aRDE;s.G,]{TEUC`"^5yW?'>V_$:Nl*:r&0cvmo{+' 0G>r~i6k/x w~Yh t<F[ W 3nB-r\|W}UG)vpڵk]Z֜*M3k7wxSJm>=yy Е ]w|ɋB Aj OOxIDUD *z`HW>~"A#EHW}@A  gB@ xn@A @Uy @A = @A#zG@? @{F tπvA  @^A  3]A  :!W}@A  gB@ xn@A ɽ/z"/>?oOce_}iz>)o/>{ڜQ#_v|";ƧWR@A  trjA  rB@c3@A  6@ trjA  rB@c3@A  6@ trjA  rB@c3@A  6@ trjA  rB@c3@A  6@ trjA  rB@c3@A  6@ trjA  rB@c3@A  6@ trjA  rB@c3@A  6@ trjA  rB@c3@A  6@ trjA  rB@c3@A  6@ trjA  rB@c3@A  6@ trjA  rB@c3@A  6@ trjA  rB@c3@A  6@ trjA  rB@c3@A  6@ trjA  rB@c3@A  6@ trjA  rB@c3@A  6@ trjA  rB@c3@A  6@ trjA  rB@c3@A  6@ trjA  rB@c3@A  6@ trjA  rB@c3@A  6@ trjA  rB@c3@A  6@ trjA  rB@c3@A  6@<IENDB`ggraph/man/reexports.Rd0000644000176200001440000000075213226105174014624 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/utils.R \docType{import} \name{reexports} \alias{reexports} \alias{scale_color_viridis} \alias{scale_fill_viridis} \title{Objects exported from other packages} \keyword{internal} \description{ These objects are imported from other packages. Follow the links below to see their documentation. \describe{ \item{viridis}{\code{\link[viridis]{scale_color_viridis}}, \code{\link[viridis]{scale_fill_viridis}}} }} ggraph/man/geom_edge_link.Rd0000644000176200001440000002045113617226162015524 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geom_edge_link.R \name{geom_edge_link} \alias{geom_edge_link} \alias{geom_edge_link2} \alias{geom_edge_link0} \title{Draw edges as straight lines between nodes} \usage{ geom_edge_link( mapping = NULL, data = get_edges("short"), position = "identity", arrow = NULL, n = 100, lineend = "butt", linejoin = "round", linemitre = 1, label_colour = "black", label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = "rot", force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ... ) geom_edge_link2( mapping = NULL, data = get_edges("long"), position = "identity", arrow = NULL, n = 100, lineend = "butt", linejoin = "round", linemitre = 1, label_colour = "black", label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = "rot", force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ... ) geom_edge_link0( mapping = NULL, data = get_edges(), position = "identity", arrow = NULL, lineend = "butt", show.legend = NA, ... ) } \arguments{ \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2:aes]{ggplot2::aes()}} or \code{\link[ggplot2:aes_]{ggplot2::aes_()}}. By default x, y, xend, yend, group and circular are mapped to x, y, xend, yend, edge.id and circular in the edge data.} \item{data}{The return of a call to \code{get_edges()} or a data.frame giving edges in correct format (see details for for guidance on the format). See \code{\link[=get_edges]{get_edges()}} for more details on edge extraction.} \item{position}{Position adjustment, either as a string, or the result of a call to a position adjustment function.} \item{arrow}{Arrow specification, as created by \code{\link[grid:arrow]{grid::arrow()}}.} \item{n}{The number of points to create along the path.} \item{lineend}{Line end style (round, butt, square).} \item{linejoin}{Line join style (round, mitre, bevel).} \item{linemitre}{Line mitre limit (number greater than 1).} \item{label_colour}{The colour of the edge label. If \code{NA} it will use the colour of the edge.} \item{label_alpha}{The opacity of the edge label. If \code{NA} it will use the opacity of the edge.} \item{label_parse}{If \code{TRUE}, the labels will be parsed into expressions and displayed as described in \code{\link[grDevices:plotmath]{grDevices::plotmath()}}.} \item{check_overlap}{If \code{TRUE}, text that overlaps previous text in the same layer will not be plotted.} \item{angle_calc}{Either 'none', 'along', or 'across'. If 'none' the label will use the angle aesthetic of the geom. If 'along' The label will be written along the edge direction. If 'across' the label will be written across the edge direction.} \item{force_flip}{Logical. If \code{angle_calc} is either 'along' or 'across' should the label be flipped if it is on it's head. Default to \code{TRUE}.} \item{label_dodge}{A \code{\link[grid:unit]{grid::unit()}} giving a fixed vertical shift to add to the label in case of \code{angle_calc} is either 'along' or 'across'} \item{label_push}{A \code{\link[grid:unit]{grid::unit()}} giving a fixed horizontal shift to add to the label in case of \code{angle_calc} is either 'along' or 'across'} \item{show.legend}{logical. Should this layer be included in the legends? \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE} never includes, and \code{TRUE} always includes. It can also be a named logical vector to finely select the aesthetics to display.} \item{...}{Other arguments passed on to \code{\link[ggplot2:layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like \code{colour = "red"} or \code{size = 3}. They may also be parameters to the paired geom/stat.} } \description{ This geom draws edges in the simplest way - as straight lines between the start and end nodes. Not much more to say about that... } \section{Edge variants}{ Many geom_edge_* layers comes in 3 flavors depending on the level of control needed over the drawing. The default (no numeric postfix) generate a number of points (\code{n}) along the edge and draws it as a path. Each point along the line has a numeric value associated with it giving the position along the path, and it is therefore possible to show the direction of the edge by mapping to this e.g. \code{colour = stat(index)}. The version postfixed with a "2" uses the "long" edge format (see \code{\link[=get_edges]{get_edges()}}) and makes it possible to interpolate node parameter between the start and end node along the edge. It is considerable less performant so should only be used if this is needed. The version postfixed with a "0" draws the edge in the most performant way, often directly using an appropriate grob from the grid package, but does not allow for gradients along the edge. Often it is beneficial to stop the drawing of the edge before it reaches the node, for instance in cases where an arrow should be drawn and the arrowhead shouldn't lay on top or below the node point. geom_edge_* and geom_edge_*2 supports this through the start_cap and end_cap aesthetics that takes a \code{\link[=geometry]{geometry()}} specification and dynamically caps the termini of the edges based on the given specifications. This means that if \code{end_cap = circle(1, 'cm')} the edges will end at a distance of 1cm even during resizing of the plot window. All \verb{geom_edge_*} and \code{geom_edge_*2} have the ability to draw a label along the edge. The reason this is not a separate geom is that in order for the label to know the location of the edge it needs to know the edge type etc. Labels are drawn by providing a label aesthetic. The label_pos can be used to specify where along the edge it should be drawn by supplying a number between 0 and 1. The label_size aesthetic can be used to control the size of the label. Often it is needed to have the label written along the direction of the edge, but since the actual angle is dependent on the plot dimensions this cannot be calculated beforehand. Using the angle_calc argument allows you to specify whether to use the supplied angle aesthetic or whether to draw the label along or across the edge. } \section{Edge aesthetic name expansion}{ In order to avoid excessive typing edge aesthetic names are automatically expanded. Because of this it is not necessary to write \code{edge_colour} within the \code{aes()} call as \code{colour} will automatically be renamed appropriately. } \section{Aesthetics}{ \code{geom_edge_link} and \code{geom_edge_link0} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{y} \item \strong{xend} \item \strong{yend} \item edge_colour \item edge_width \item edge_linetype \item edge_alpha \item filter } \code{geom_edge_link2} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{y} \item \strong{group} \item edge_colour \item edge_width \item edge_linetype \item edge_alpha \item filter } \code{geom_edge_link} and \code{geom_edge_link2} furthermore takes the following aesthetics. \itemize{ \item start_cap \item end_cap \item label \item label_pos \item label_size \item angle \item hjust \item vjust \item family \item fontface \item lineheight } } \section{Computed variables}{ \describe{ \item{index}{The position along the path (not computed for the *0 version)} } } \examples{ require(tidygraph) gr <- create_notable('bull') \%>\% mutate(class = sample(letters[1:3], n(), replace = TRUE)) \%>\% activate(edges) \%>\% mutate(class = sample(letters[1:3], n(), replace = TRUE)) ggraph(gr, 'stress') + geom_edge_link(aes(alpha = stat(index))) ggraph(gr, 'stress') + geom_edge_link2(aes(colour = node.class)) ggraph(gr, 'stress') + geom_edge_link0(aes(colour = class)) } \seealso{ Other geom_edge_*: \code{\link{geom_edge_arc}()}, \code{\link{geom_edge_bend}()}, \code{\link{geom_edge_density}()}, \code{\link{geom_edge_diagonal}()}, \code{\link{geom_edge_elbow}()}, \code{\link{geom_edge_fan}()}, \code{\link{geom_edge_hive}()}, \code{\link{geom_edge_loop}()}, \code{\link{geom_edge_parallel}()}, \code{\link{geom_edge_point}()}, \code{\link{geom_edge_span}()}, \code{\link{geom_edge_tile}()} } \author{ Thomas Lin Pedersen } \concept{geom_edge_*} ggraph/man/geom_node_voronoi.Rd0000644000176200001440000001176513617226162016313 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geom_node_voronoi.R \name{geom_node_voronoi} \alias{geom_node_voronoi} \title{Show nodes as voronoi tiles} \usage{ geom_node_voronoi( mapping = NULL, data = NULL, position = "identity", show.legend = NA, bound = NULL, eps = 1e-09, max.radius = NULL, normalize = FALSE, asp.ratio = 1, expand = 0, radius = 0, ... ) } \arguments{ \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2:aes]{ggplot2::aes()}} or \code{\link[ggplot2:aes_]{ggplot2::aes_()}}. By default x and y are mapped to x and y in the node data and group set to \code{-1}.} \item{data}{The data to be displayed in this layer. There are three options: If \code{NULL}, the default, the data is inherited from the plot data as specified in the call to \code{\link[=ggplot]{ggplot()}}. A \code{data.frame}, or other object, will override the plot data. All objects will be fortified to produce a data frame. See \code{\link[=fortify]{fortify()}} for which variables will be created. A \code{function} will be called with a single argument, the plot data. The return value must be a \code{data.frame}, and will be used as the layer data. A \code{function} can be created from a \code{formula} (e.g. \code{~ head(.x, 10)}).} \item{position}{Position adjustment, either as a string, or the result of a call to a position adjustment function.} \item{show.legend}{logical. Should this layer be included in the legends? \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE} never includes, and \code{TRUE} always includes. It can also be a named logical vector to finely select the aesthetics to display.} \item{bound}{The bounding rectangle for the tesselation or a custom polygon to clip the tesselation to. Defaults to \code{NULL} which creates a rectangle expanded 10\% in all directions. If supplied as a bounding box it should be a vector giving the bounds in the following order: xmin, xmax, ymin, ymax. If supplied as a polygon it should either be a 2-column matrix or a data.frame containing an \code{x} and \code{y} column.} \item{eps}{A value of epsilon used in testing whether a quantity is zero, mainly in the context of whether points are collinear. If anomalous errors arise, it is possible that these may averted by adjusting the value of eps upward or downward.} \item{max.radius}{The maximum distance a tile can extend from the point of origin. Will in effect clip each tile to a circle centered at the point with the given radius. If \code{normalize = TRUE} the radius will be given relative to the normalized values} \item{normalize}{Should coordinates be normalized prior to calculations. If \code{x} and \code{y} are in wildly different ranges it can lead to tesselation and triangulation that seems off when plotted without \code{\link[ggplot2:coord_fixed]{ggplot2::coord_fixed()}}. Normalization of coordinates solves this. The coordinates are transformed back after calculations.} \item{asp.ratio}{If \code{normalize = TRUE} the x values will be multiplied by this amount after normalization.} \item{expand}{A numeric or unit vector of length one, specifying the expansion amount. Negative values will result in contraction instead. If the value is given as a numeric it will be understood as a proportion of the plot area width.} \item{radius}{As \code{expand} but specifying the corner radius.} \item{...}{Other arguments passed on to \code{\link[=layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like \code{colour = "red"} or \code{size = 3}. They may also be parameters to the paired geom/stat.} } \description{ This geom is equivalent in functionality to \code{\link[ggforce:geom_voronoi_tile]{ggforce::geom_voronoi_tile()}} and allows for plotting of nodes as tiles from a voronoi tesselation. As with \code{\link[ggforce:geom_voronoi_tile]{ggforce::geom_voronoi_tile()}} it is possible to restrict the size of the tile to a fixed radius, as well as round corners and expand/contract the tile. } \section{Aesthetics}{ \code{geom_node_voronoi} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{y} \item alpha \item colour \item fill \item shape \item size \item stroke \item filter } } \examples{ require(tidygraph) gr <- create_notable('meredith') \%>\% mutate(group = sample(letters[1:4], n(), TRUE)) ggraph(gr) + geom_node_voronoi(aes(fill = group, colour = group), alpha = 0.3) + geom_edge_link(alpha = 0.3) + geom_node_point() # Use max.radius to make the tesselation more "node"-like ggraph(gr) + geom_node_voronoi(aes(fill = group, colour = group), alpha = 0.3, max.radius = 1) + geom_edge_link(alpha = 0.3) + geom_node_point() } \seealso{ Other geom_node_*: \code{\link{geom_node_arc_bar}()}, \code{\link{geom_node_circle}()}, \code{\link{geom_node_point}()}, \code{\link{geom_node_range}()}, \code{\link{geom_node_text}()}, \code{\link{geom_node_tile}()} } \author{ Thomas Lin Pedersen } \concept{geom_node_*} ggraph/man/whigs.Rd0000644000176200001440000000170613050776432013720 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/data_whigs.R \docType{data} \name{whigs} \alias{whigs} \title{Membership network of American Whigs} \format{The data is stored as an incidence matrix with persons as rows and organizations as columns. A 0 means no membership while a one means membership.} \source{ \url{https://github.com/kjhealy/revere/blob/master/data/PaulRevereAppD.csv} adapted from: Fischer, David H. (1995) \emph{Paul Revere's Ride}. Oxford University Press } \usage{ whigs } \description{ This dataset shows the membership of 136 colonial Americans in 5 whig organization and is a bipartite graph. The data appeared in the appendix to David Hackett Fischer's \emph{Paul Revere's Ride} (Oxford University Press, 1995) and compiled by Kieran Healy for the blog post \href{http://kieranhealy.org/blog/archives/2013/06/09/using-metadata-to-find-paul-revere/}{Using Metadata to Find Paul Revere}. } \keyword{datasets} ggraph/man/autograph.Rd0000644000176200001440000000334313617214151014562 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/autograph.R \name{autograph} \alias{autograph} \alias{autograph.default} \title{Quickplot wrapper for networks} \usage{ autograph(graph, ...) \method{autograph}{default}( graph, ..., node_colour = NULL, edge_colour = NULL, node_size = NULL, edge_width = NULL, node_label = NULL, edge_label = NULL ) } \arguments{ \item{graph}{An object coercible to a tbl_graph} \item{...}{arguments passed on to methods} \item{node_colour, edge_colour}{Colour mapping for nodes and edges} \item{node_size, edge_width}{Size/width mapping for nodes and edges} \item{node_label, edge_label}{Label mapping for nodes and edges} } \description{ This function is intended to quickly show an overview of your network data. While it returns a ggraph object that layers etc can be added to it is limited in use and should not be used as a foundation for more complicated plots. It allows colour, labeling and sizing of nodes and edges, and the exact combination of layout and layers will depend on these as well as the features of the network. The output of this function may be fine-tuned at any release and should not be considered stable. If a plot should be reproducible it should be created manually. } \examples{ library(tidygraph) gr <- create_notable('herschel') \%>\% mutate(class = sample(letters[1:3], n(), TRUE)) \%E>\% mutate(weight = runif(n())) # Standard graph autograph(gr) # Adding node labels will cap edges autograph(gr, node_label = class) # Use tidygraph calls for mapping autograph(gr, node_size = centrality_pagerank()) # Trees are plotted as dendrograms iris_tree <- hclust(dist(iris[1:4], method = 'euclidean'), method = 'ward.D2') autograph(iris_tree) } ggraph/man/layout_tbl_graph_eigen.Rd0000644000176200001440000000345713617226162017311 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/layout_eigen.R \name{layout_tbl_graph_eigen} \alias{layout_tbl_graph_eigen} \title{Place nodes according to their eigenvalues} \usage{ layout_tbl_graph_eigen( graph, type = "laplacian", eigenvector = "smallest", circular = FALSE ) } \arguments{ \item{graph}{A tbl_graph object} \item{type}{The type of matrix to extract the eigenvectors from. Either \code{'laplacian'} or \code{'adjacency'}} \item{eigenvector}{The eigenvector to use for coordinates. Either \code{'smallest'} or \code{'largest'}} \item{circular}{ignored} } \value{ A data.frame with the columns \code{x}, \code{y}, \code{circular} as well as any information stored as node variables in the tbl_graph object. } \description{ This layout is based on the idea of spectral layouts where node coordinates are calculated directly by decomposing a matrix representation of the graph and extracting the eigenvectors. } \seealso{ Other layout_tbl_graph_*: \code{\link{layout_tbl_graph_auto}()}, \code{\link{layout_tbl_graph_backbone}()}, \code{\link{layout_tbl_graph_centrality}()}, \code{\link{layout_tbl_graph_circlepack}()}, \code{\link{layout_tbl_graph_dendrogram}()}, \code{\link{layout_tbl_graph_fabric}()}, \code{\link{layout_tbl_graph_focus}()}, \code{\link{layout_tbl_graph_hive}()}, \code{\link{layout_tbl_graph_igraph}()}, \code{\link{layout_tbl_graph_linear}()}, \code{\link{layout_tbl_graph_manual}()}, \code{\link{layout_tbl_graph_matrix}()}, \code{\link{layout_tbl_graph_partition}()}, \code{\link{layout_tbl_graph_pmds}()}, \code{\link{layout_tbl_graph_stress}()}, \code{\link{layout_tbl_graph_treemap}()}, \code{\link{layout_tbl_graph_unrooted}()} } \author{ The underlying algorithm is implemented in the graphlayouts package by David Schoch } \concept{layout_tbl_graph_*} ggraph/man/qgraph.Rd0000644000176200001440000000037213617226162014056 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/autograph.R \name{qgraph} \alias{qgraph} \title{Deprecated autograph predecessor} \usage{ qgraph(...) } \description{ Deprecated autograph predecessor } \keyword{internal} ggraph/man/geom_edge_loop.Rd0000644000176200001440000001772313617226162015550 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geom_edge_loop.R \name{geom_edge_loop} \alias{geom_edge_loop} \alias{geom_edge_loop0} \title{Draw edges as diagonals} \usage{ geom_edge_loop( mapping = NULL, data = get_edges(), position = "identity", arrow = NULL, n = 100, lineend = "butt", linejoin = "round", linemitre = 1, label_colour = "black", label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = "rot", force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ... ) geom_edge_loop0( mapping = NULL, data = get_edges(), position = "identity", arrow = NULL, lineend = "butt", show.legend = NA, ... ) } \arguments{ \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2:aes]{ggplot2::aes()}} or \code{\link[ggplot2:aes_]{ggplot2::aes_()}}. By default x, y, xend, yend, group and circular are mapped to x, y, xend, yend, edge.id and circular in the edge data.} \item{data}{The return of a call to \code{get_edges()} or a data.frame giving edges in correct format (see details for for guidance on the format). See \code{\link[=get_edges]{get_edges()}} for more details on edge extraction.} \item{position}{Position adjustment, either as a string, or the result of a call to a position adjustment function.} \item{arrow}{Arrow specification, as created by \code{\link[grid:arrow]{grid::arrow()}}.} \item{n}{The number of points to create along the path.} \item{lineend}{Line end style (round, butt, square).} \item{linejoin}{Line join style (round, mitre, bevel).} \item{linemitre}{Line mitre limit (number greater than 1).} \item{label_colour}{The colour of the edge label. If \code{NA} it will use the colour of the edge.} \item{label_alpha}{The opacity of the edge label. If \code{NA} it will use the opacity of the edge.} \item{label_parse}{If \code{TRUE}, the labels will be parsed into expressions and displayed as described in \code{\link[grDevices:plotmath]{grDevices::plotmath()}}.} \item{check_overlap}{If \code{TRUE}, text that overlaps previous text in the same layer will not be plotted.} \item{angle_calc}{Either 'none', 'along', or 'across'. If 'none' the label will use the angle aesthetic of the geom. If 'along' The label will be written along the edge direction. If 'across' the label will be written across the edge direction.} \item{force_flip}{Logical. If \code{angle_calc} is either 'along' or 'across' should the label be flipped if it is on it's head. Default to \code{TRUE}.} \item{label_dodge}{A \code{\link[grid:unit]{grid::unit()}} giving a fixed vertical shift to add to the label in case of \code{angle_calc} is either 'along' or 'across'} \item{label_push}{A \code{\link[grid:unit]{grid::unit()}} giving a fixed horizontal shift to add to the label in case of \code{angle_calc} is either 'along' or 'across'} \item{show.legend}{logical. Should this layer be included in the legends? \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE} never includes, and \code{TRUE} always includes. It can also be a named logical vector to finely select the aesthetics to display.} \item{...}{Other arguments passed on to \code{\link[ggplot2:layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like \code{colour = "red"} or \code{size = 3}. They may also be parameters to the paired geom/stat.} } \description{ This geom draws edge loops (edges starting and ending at the same node). Loops are drawn as bezier curves starting and ending at the position of the node and with control points protruding at an angle and in a direction specified in the call. As the start and end node is always the same no *2 method is provided. Loops can severely clutter up your visualization which is why they are decoupled from the other edge drawings. Only plot them if they are of importance. If the graph doesn't contain any loops the geom adds nothing silently. } \section{Aesthetics}{ \code{geom_edge_loop} and \code{geom_edge_loop0} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{y} \item \strong{from} \item \strong{to} \item \strong{span} \emph{90} \item \strong{direction} \emph{45} \item \strong{strength} \emph{1} \item edge_colour \item edge_width \item edge_linetype \item edge_alpha \item filter } \code{geom_edge_loop} furthermore takes the following aesthetics. \itemize{ \item start_cap \item end_cap \item label \item label_pos \item label_size \item angle \item hjust \item vjust \item family \item fontface \item lineheight } } \section{Computed variables}{ \describe{ \item{index}{The position along the path (not computed for the *0 version)} } } \section{Edge variants}{ Many geom_edge_* layers comes in 3 flavors depending on the level of control needed over the drawing. The default (no numeric postfix) generate a number of points (\code{n}) along the edge and draws it as a path. Each point along the line has a numeric value associated with it giving the position along the path, and it is therefore possible to show the direction of the edge by mapping to this e.g. \code{colour = stat(index)}. The version postfixed with a "2" uses the "long" edge format (see \code{\link[=get_edges]{get_edges()}}) and makes it possible to interpolate node parameter between the start and end node along the edge. It is considerable less performant so should only be used if this is needed. The version postfixed with a "0" draws the edge in the most performant way, often directly using an appropriate grob from the grid package, but does not allow for gradients along the edge. Often it is beneficial to stop the drawing of the edge before it reaches the node, for instance in cases where an arrow should be drawn and the arrowhead shouldn't lay on top or below the node point. geom_edge_* and geom_edge_*2 supports this through the start_cap and end_cap aesthetics that takes a \code{\link[=geometry]{geometry()}} specification and dynamically caps the termini of the edges based on the given specifications. This means that if \code{end_cap = circle(1, 'cm')} the edges will end at a distance of 1cm even during resizing of the plot window. All \verb{geom_edge_*} and \code{geom_edge_*2} have the ability to draw a label along the edge. The reason this is not a separate geom is that in order for the label to know the location of the edge it needs to know the edge type etc. Labels are drawn by providing a label aesthetic. The label_pos can be used to specify where along the edge it should be drawn by supplying a number between 0 and 1. The label_size aesthetic can be used to control the size of the label. Often it is needed to have the label written along the direction of the edge, but since the actual angle is dependent on the plot dimensions this cannot be calculated beforehand. Using the angle_calc argument allows you to specify whether to use the supplied angle aesthetic or whether to draw the label along or across the edge. } \section{Edge aesthetic name expansion}{ In order to avoid excessive typing edge aesthetic names are automatically expanded. Because of this it is not necessary to write \code{edge_colour} within the \code{aes()} call as \code{colour} will automatically be renamed appropriately. } \examples{ require(tidygraph) gr <- as_tbl_graph( data.frame(from = c(1, 1, 2, 2, 3, 3, 3), to = c(1, 2, 2, 3, 3, 1, 2)) ) ggraph(gr, 'stress') + geom_edge_loop(aes(alpha = stat(index))) + geom_edge_fan(aes(alpha = stat(index))) ggraph(gr, 'stress') + geom_edge_loop0() + geom_edge_fan0() } \seealso{ Other geom_edge_*: \code{\link{geom_edge_arc}()}, \code{\link{geom_edge_bend}()}, \code{\link{geom_edge_density}()}, \code{\link{geom_edge_diagonal}()}, \code{\link{geom_edge_elbow}()}, \code{\link{geom_edge_fan}()}, \code{\link{geom_edge_hive}()}, \code{\link{geom_edge_link}()}, \code{\link{geom_edge_parallel}()}, \code{\link{geom_edge_point}()}, \code{\link{geom_edge_span}()}, \code{\link{geom_edge_tile}()} } \author{ Thomas Lin Pedersen } \concept{geom_edge_*} ggraph/man/highschool.Rd0000644000176200001440000000155313276561316014731 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/data_highschool.R \docType{data} \name{highschool} \alias{highschool} \title{Friendship among high school boys} \format{The graph is stored as an unnamed edgelist with a year attribute. \describe{ \item{from}{The boy answering the question} \item{to}{The boy being the answer to the question} \item{year}{The year the friendship was reported} }} \source{ Coleman, J. S. \emph{Introduction to Mathematical Sociology}. New York: Free Press, pp.450-451. } \usage{ highschool } \description{ This dataset shows the friendship among high school boys as assessed by the question: "What fellows here in school do you go around with most often?". The question was posed twice, with one year in between (1957 and 1958) and shows the evolution in friendship between the two timepoints. } \keyword{datasets} ggraph/man/scale_edge_fill.Rd0000644000176200001440000001576713617226162015673 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/scale_edge_fill.R \name{scale_edge_fill} \alias{scale_edge_fill} \alias{scale_edge_fill_hue} \alias{scale_edge_fill_brewer} \alias{scale_edge_fill_distiller} \alias{scale_edge_fill_gradient} \alias{scale_edge_fill_gradient2} \alias{scale_edge_fill_gradientn} \alias{scale_edge_fill_grey} \alias{scale_edge_fill_identity} \alias{scale_edge_fill_manual} \alias{scale_edge_fill_viridis} \alias{scale_edge_fill_continuous} \alias{scale_edge_fill_discrete} \title{Edge fill scales} \usage{ scale_edge_fill_hue( ..., h = c(0, 360) + 15, c = 100, l = 65, h.start = 0, direction = 1, na.value = "grey50" ) scale_edge_fill_brewer(..., type = "seq", palette = 1, direction = 1) scale_edge_fill_distiller( ..., type = "seq", palette = 1, direction = -1, values = NULL, space = "Lab", na.value = "grey50", guide = "edge_colourbar" ) scale_edge_fill_gradient( ..., low = "#132B43", high = "#56B1F7", space = "Lab", na.value = "grey50", guide = "edge_colourbar" ) scale_edge_fill_gradient2( ..., low = muted("red"), mid = "white", high = muted("blue"), midpoint = 0, space = "Lab", na.value = "grey50", guide = "edge_colourbar" ) scale_edge_fill_gradientn( ..., colours, values = NULL, space = "Lab", na.value = "grey50", guide = "edge_colourbar", colors ) scale_edge_fill_grey(..., start = 0.2, end = 0.8, na.value = "red") scale_edge_fill_identity(..., guide = "none") scale_edge_fill_manual(..., values) scale_edge_fill_viridis( ..., alpha = 1, begin = 0, end = 1, discrete = FALSE, option = "D", direction = 1 ) scale_edge_fill_continuous( ..., low = "#132B43", high = "#56B1F7", space = "Lab", na.value = "grey50", guide = "edge_colourbar" ) scale_edge_fill_discrete( ..., h = c(0, 360) + 15, c = 100, l = 65, h.start = 0, direction = 1, na.value = "grey50" ) } \arguments{ \item{...}{Arguments passed on to \code{discrete_scale} \describe{ \item{palette}{A palette function that when called with a single integer argument (the number of levels in the scale) returns the values that they should take.} \item{breaks}{One of: \itemize{ \item \code{NULL} for no breaks \item \code{waiver()} for the default breaks computed by the transformation object \item A character vector of breaks \item A function that takes the limits as input and returns breaks as output }} \item{limits}{A character vector that defines possible values of the scale and their order.} \item{drop}{Should unused factor levels be omitted from the scale? The default, \code{TRUE}, uses the levels that appear in the data; \code{FALSE} uses all the levels in the factor.} \item{na.translate}{Unlike continuous scales, discrete scales can easily show missing values, and do so by default. If you want to remove missing values from a discrete scale, specify \code{na.translate = FALSE}.} \item{na.value}{If \code{na.translate = TRUE}, what value aesthetic value should missing be displayed as? Does not apply to position scales where \code{NA} is always placed at the far right.} \item{scale_name}{The name of the scale} \item{name}{The name of the scale. Used as the axis or legend title. If \code{waiver()}, the default, the name of the scale is taken from the first mapping used for that aesthetic. If \code{NULL}, the legend title will be omitted.} \item{labels}{One of: \itemize{ \item \code{NULL} for no labels \item \code{waiver()} for the default labels computed by the transformation object \item A character vector giving labels (must be same length as \code{breaks}) \item A function that takes the breaks as input and returns labels as output }} \item{expand}{Vector of range expansion constants used to add some padding around the data, to ensure that they are placed some distance away from the axes. Use the convenience function \code{\link[ggplot2:expand_scale]{expand_scale()}} to generate the values for the \code{expand} argument. The defaults are to expand the scale by 5\% on each side for continuous variables, and by 0.6 units on each side for discrete variables.} \item{guide}{A function used to create a guide or its name. See \code{\link[ggplot2:guides]{guides()}} for more info.} \item{position}{The position of the axis. "left" or "right" for vertical scales, "top" or "bottom" for horizontal scales} \item{super}{The super class to use for the constructed scale} }} \item{h}{range of hues to use, in [0, 360]} \item{c}{chroma (intensity of colour), maximum value varies depending on combination of hue and luminance.} \item{l}{luminance (lightness), in [0, 100]} \item{h.start}{hue to start at} \item{direction}{direction to travel around the colour wheel, 1 = clockwise, -1 = counter-clockwise} \item{na.value}{Colour to use for missing values} \item{type}{One of seq (sequential), div (diverging) or qual (qualitative)} \item{palette}{If a string, will use that named palette. If a number, will index into the list of palettes of appropriate \code{type}} \item{values}{if colours should not be evenly positioned along the gradient this vector gives the position (between 0 and 1) for each colour in the \code{colours} vector. See \code{\link[=rescale]{rescale()}} for a convenience function to map an arbitrary range to between 0 and 1.} \item{space}{colour space in which to calculate gradient. Must be "Lab" - other values are deprecated.} \item{guide}{Type of legend. Use \code{"colourbar"} for continuous colour bar, or \code{"legend"} for discrete colour legend.} \item{low, high}{Colours for low and high ends of the gradient.} \item{mid}{colour for mid point} \item{midpoint}{The midpoint (in data value) of the diverging scale. Defaults to 0.} \item{colours, colors}{Vector of colours to use for n-colour gradient.} \item{start}{grey value at low end of palette} \item{end}{grey value at high end of palette} \item{alpha}{pass through parameter to \code{viridis}} \item{begin}{The (corrected) hue in [0,1] at which the viridis colormap begins.} \item{discrete}{generate a discrete palette? (default: \code{FALSE} - generate continuous palette)} \item{option}{A character string indicating the colormap option to use. Four options are available: "magma" (or "A"), "inferno" (or "B"), "plasma" (or "C"), "viridis" (or "D", the default option) and "cividis" (or "E").} } \value{ A ggproto object inheriting from \code{Scale} } \description{ This set of scales defines new fill scales for edge geoms equivalent to the ones already defined by ggplot2. The parameters are equivalent to the ones from ggplot2 so there is nothing new under the sun. The different geoms will know whether to use edge scales or the standard scales so it is not necessary to write \code{edge_fill} in the call to the geom - just use \code{fill}. } \seealso{ Other scale_edge_*: \code{\link{scale_edge_alpha}()}, \code{\link{scale_edge_colour}}, \code{\link{scale_edge_linetype}()}, \code{\link{scale_edge_shape}()}, \code{\link{scale_edge_size}()}, \code{\link{scale_edge_width}()}, \code{\link{scale_label_size}()} } \concept{scale_edge_*} ggraph/man/geometry.Rd0000644000176200001440000000463313617226162014433 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geometry.R \name{geometry} \alias{geometry} \alias{circle} \alias{square} \alias{ellipsis} \alias{rectangle} \alias{label_rect} \alias{is.geometry} \title{Define simple shapes for line capping} \usage{ geometry( type = "circle", width = 1, height = width, width_unit = "cm", height_unit = width_unit ) circle(radius = 1, unit = "cm") square(length = 1, unit = "cm") ellipsis(a = 1, b = 1, a_unit = "cm", b_unit = a_unit) rectangle(width = 1, height = 1, width_unit = "cm", height_unit = width_unit) label_rect(label, padding = margin(1, 1, 1.5, 1, "mm"), ...) is.geometry(x) } \arguments{ \item{type}{The type of geometry to use. Currently \code{'circle'} and \code{'rect'} is supported.} \item{width, height, length, radius, a, b}{The dimensions of the shape.} \item{unit, width_unit, height_unit, a_unit, b_unit}{The unit for the numbers given.} \item{label}{The text to be enclosed} \item{padding}{extra size to be added around the text using the \code{\link[ggplot2:margin]{ggplot2::margin()}} function} \item{...}{Passed on to \code{\link[grid:gpar]{grid::gpar()}}} \item{x}{An object to test for geometry inheritance} } \value{ A geometry object encoding the specified shape. } \description{ This set of functions makes it easy to define shapes at the terminal points of edges that are used to shorten the edges. The shapes themselves are not drawn, but the edges will end at the boundary of the shape rather than at the node position. This is especially relevant when drawing arrows at the edges as the arrows will be partly obscured by the node unless the edge is shortened. Edge shortening is dynamic and will update as the plot is resized, making sure that the capping remains at an absolute distance to the end point. } \details{ \code{geometry} is the base constructor, while the rest are helpers to save typing. \code{circle} creates circles width a given radius, \code{square} creates squares at a given side length, \code{ellipsis} creates ellipses with given a and b values (width and height radii), and \code{rectangle} makes rectangles of a given width and height. label_rect is a helper that, given a list of strings and potentially formatting options creates a rectangle that encloses the string. } \examples{ geometry(c('circle', 'rect', 'rect'), 1:3, 3:1) circle(1:4, 'mm') label_rect(c('some', 'different', 'words'), fontsize = 18) } ggraph/man/layout_tbl_graph_dendrogram.Rd0000644000176200001440000000540013617226162020332 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/layout_dendrogram.R \name{layout_tbl_graph_dendrogram} \alias{layout_tbl_graph_dendrogram} \title{Apply a dendrogram layout to layout_tbl_graph} \usage{ layout_tbl_graph_dendrogram( graph, circular = FALSE, offset = pi/2, height = NULL, length = NULL, repel = FALSE, ratio = 1, direction = "out" ) } \arguments{ \item{graph}{A \code{tbl_graph} object} \item{circular}{Logical. Should the layout be transformed to a circular representation. Defaults to \code{FALSE}.} \item{offset}{If \code{circular = TRUE}, where should it begin. Defaults to \code{pi/2} which is equivalent to 12 o'clock.} \item{height}{The node variable holding the height of each node in the dendrogram. If \code{NULL} it will be calculated as the maximal distance to a leaf.} \item{length}{An edge parameter giving the length of each edge. The node height will be calculated from the maximal length to the root node (ignored if \code{height} does not evaluate to \code{NULL})} \item{repel}{Should leafs repel each other relative to the height of their common ancestor. Will emphasize clusters} \item{ratio}{The strength of repulsion if \code{repel = TRUE}. Higher values will give more defined clusters} \item{direction}{The direction to the leaves. Defaults to 'out'} } \value{ A data.frame with the columns \code{x}, \code{y}, \code{circular}, \code{depth} and \code{leaf} as well as any information stored as node variables on the tbl_graph } \description{ This layout mimics the \code{\link[igraph:layout_as_tree]{igraph::layout_as_tree()}} algorithm supplied by igraph, but puts all leaves at 0 and builds it up from there, instead of starting from the root and building it from there. The height of branch points are related to the maximum distance to an edge from the branch node, or read from a node variable. } \note{ This function is not intended to be used directly but by setting \code{layout = 'dendrogram'} in \code{\link[=create_layout]{create_layout()}} } \seealso{ Other layout_tbl_graph_*: \code{\link{layout_tbl_graph_auto}()}, \code{\link{layout_tbl_graph_backbone}()}, \code{\link{layout_tbl_graph_centrality}()}, \code{\link{layout_tbl_graph_circlepack}()}, \code{\link{layout_tbl_graph_eigen}()}, \code{\link{layout_tbl_graph_fabric}()}, \code{\link{layout_tbl_graph_focus}()}, \code{\link{layout_tbl_graph_hive}()}, \code{\link{layout_tbl_graph_igraph}()}, \code{\link{layout_tbl_graph_linear}()}, \code{\link{layout_tbl_graph_manual}()}, \code{\link{layout_tbl_graph_matrix}()}, \code{\link{layout_tbl_graph_partition}()}, \code{\link{layout_tbl_graph_pmds}()}, \code{\link{layout_tbl_graph_stress}()}, \code{\link{layout_tbl_graph_treemap}()}, \code{\link{layout_tbl_graph_unrooted}()} } \concept{layout_tbl_graph_*} ggraph/man/geom_edge_hive.Rd0000644000176200001440000002200313617226162015515 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geom_edge_hive.R \name{geom_edge_hive} \alias{geom_edge_hive} \alias{geom_edge_hive2} \alias{geom_edge_hive0} \title{Draw edges in hive plots} \usage{ geom_edge_hive( mapping = NULL, data = get_edges(), position = "identity", arrow = NULL, strength = 1, n = 100, lineend = "butt", linejoin = "round", linemitre = 1, label_colour = "black", label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = "rot", force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ..., curvature ) geom_edge_hive2( mapping = NULL, data = get_edges("long"), position = "identity", arrow = NULL, strength = 1, n = 100, lineend = "butt", linejoin = "round", linemitre = 1, label_colour = "black", label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = "rot", force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ..., curvature ) geom_edge_hive0( mapping = NULL, data = get_edges(), position = "identity", arrow = NULL, strength = 1, lineend = "butt", show.legend = NA, ..., curvature ) } \arguments{ \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2:aes]{ggplot2::aes()}} or \code{\link[ggplot2:aes_]{ggplot2::aes_()}}. By default x, y, xend, yend, group and circular are mapped to x, y, xend, yend, edge.id and circular in the edge data.} \item{data}{The return of a call to \code{get_edges()} or a data.frame giving edges in correct format (see details for for guidance on the format). See \code{\link[=get_edges]{get_edges()}} for more details on edge extraction.} \item{position}{Position adjustment, either as a string, or the result of a call to a position adjustment function.} \item{arrow}{Arrow specification, as created by \code{\link[grid:arrow]{grid::arrow()}}.} \item{strength}{The curvature of the bezier. Defines the distance from the control points to the midpoint between the start and end node. 1 means the control points are positioned halfway between the nodes and the middle of the two axes, while 0 means it coincide with the nodes (resulting in straight lines)} \item{n}{The number of points to create along the path.} \item{lineend}{Line end style (round, butt, square).} \item{linejoin}{Line join style (round, mitre, bevel).} \item{linemitre}{Line mitre limit (number greater than 1).} \item{label_colour}{The colour of the edge label. If \code{NA} it will use the colour of the edge.} \item{label_alpha}{The opacity of the edge label. If \code{NA} it will use the opacity of the edge.} \item{label_parse}{If \code{TRUE}, the labels will be parsed into expressions and displayed as described in \code{\link[grDevices:plotmath]{grDevices::plotmath()}}.} \item{check_overlap}{If \code{TRUE}, text that overlaps previous text in the same layer will not be plotted.} \item{angle_calc}{Either 'none', 'along', or 'across'. If 'none' the label will use the angle aesthetic of the geom. If 'along' The label will be written along the edge direction. If 'across' the label will be written across the edge direction.} \item{force_flip}{Logical. If \code{angle_calc} is either 'along' or 'across' should the label be flipped if it is on it's head. Default to \code{TRUE}.} \item{label_dodge}{A \code{\link[grid:unit]{grid::unit()}} giving a fixed vertical shift to add to the label in case of \code{angle_calc} is either 'along' or 'across'} \item{label_push}{A \code{\link[grid:unit]{grid::unit()}} giving a fixed horizontal shift to add to the label in case of \code{angle_calc} is either 'along' or 'across'} \item{show.legend}{logical. Should this layer be included in the legends? \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE} never includes, and \code{TRUE} always includes. It can also be a named logical vector to finely select the aesthetics to display.} \item{...}{Other arguments passed on to \code{\link[ggplot2:layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like \code{colour = "red"} or \code{size = 3}. They may also be parameters to the paired geom/stat.} \item{curvature}{Deprecated. Use \code{strength} instead.} } \description{ This geom is only intended for use together with the hive layout. It draws edges between nodes as bezier curves, with the control points positioned at the same radii as the start or end point, and at a distance defined by the curvature argument. } \section{Aesthetics}{ \code{geom_edge_hive} and \code{geom_edge_hive0} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{y} \item \strong{xend} \item \strong{yend} \item edge_colour \item edge_width \item edge_linetype \item edge_alpha \item filter } \code{geom_edge_hive2} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{y} \item \strong{group} \item edge_colour \item edge_width \item edge_linetype \item edge_alpha \item filter } \code{geom_edge_hive} and \code{geom_edge_hive2} furthermore takes the following aesthetics. \itemize{ \item start_cap \item end_cap \item label \item label_pos \item label_size \item angle \item hjust \item vjust \item family \item fontface \item lineheight } } \section{Computed variables}{ \describe{ \item{index}{The position along the path (not computed for the *0 version)} } } \section{Edge variants}{ Many geom_edge_* layers comes in 3 flavors depending on the level of control needed over the drawing. The default (no numeric postfix) generate a number of points (\code{n}) along the edge and draws it as a path. Each point along the line has a numeric value associated with it giving the position along the path, and it is therefore possible to show the direction of the edge by mapping to this e.g. \code{colour = stat(index)}. The version postfixed with a "2" uses the "long" edge format (see \code{\link[=get_edges]{get_edges()}}) and makes it possible to interpolate node parameter between the start and end node along the edge. It is considerable less performant so should only be used if this is needed. The version postfixed with a "0" draws the edge in the most performant way, often directly using an appropriate grob from the grid package, but does not allow for gradients along the edge. Often it is beneficial to stop the drawing of the edge before it reaches the node, for instance in cases where an arrow should be drawn and the arrowhead shouldn't lay on top or below the node point. geom_edge_* and geom_edge_*2 supports this through the start_cap and end_cap aesthetics that takes a \code{\link[=geometry]{geometry()}} specification and dynamically caps the termini of the edges based on the given specifications. This means that if \code{end_cap = circle(1, 'cm')} the edges will end at a distance of 1cm even during resizing of the plot window. All \verb{geom_edge_*} and \code{geom_edge_*2} have the ability to draw a label along the edge. The reason this is not a separate geom is that in order for the label to know the location of the edge it needs to know the edge type etc. Labels are drawn by providing a label aesthetic. The label_pos can be used to specify where along the edge it should be drawn by supplying a number between 0 and 1. The label_size aesthetic can be used to control the size of the label. Often it is needed to have the label written along the direction of the edge, but since the actual angle is dependent on the plot dimensions this cannot be calculated beforehand. Using the angle_calc argument allows you to specify whether to use the supplied angle aesthetic or whether to draw the label along or across the edge. } \section{Edge aesthetic name expansion}{ In order to avoid excessive typing edge aesthetic names are automatically expanded. Because of this it is not necessary to write \code{edge_colour} within the \code{aes()} call as \code{colour} will automatically be renamed appropriately. } \examples{ # Plot the flare import graph as a hive plot library(tidygraph) flareGr <- as_tbl_graph(flare$imports) \%>\% mutate( type = dplyr::case_when( centrality_degree(mode = 'in') == 0 ~ 'Source', centrality_degree(mode = 'out') == 0 ~ 'Sink', TRUE ~ 'Both' ) ) \%>\% activate(edges) \%>\% mutate( type = dplyr::case_when( grepl('flare.analytics', paste(.N()$name[from], .N()$name[to])) ~ 'Analytics', TRUE ~ 'Other' ) ) ggraph(flareGr, 'hive', axis = type) + geom_edge_hive(aes(colour = type), edge_alpha = 0.1) + coord_fixed() } \seealso{ Other geom_edge_*: \code{\link{geom_edge_arc}()}, \code{\link{geom_edge_bend}()}, \code{\link{geom_edge_density}()}, \code{\link{geom_edge_diagonal}()}, \code{\link{geom_edge_elbow}()}, \code{\link{geom_edge_fan}()}, \code{\link{geom_edge_link}()}, \code{\link{geom_edge_loop}()}, \code{\link{geom_edge_parallel}()}, \code{\link{geom_edge_point}()}, \code{\link{geom_edge_span}()}, \code{\link{geom_edge_tile}()} } \author{ Thomas Lin Pedersen } \concept{geom_edge_*} ggraph/man/layout_tbl_graph_stress.Rd0000644000176200001440000000561213617226162017540 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/layout_stress.R \name{layout_tbl_graph_stress} \alias{layout_tbl_graph_stress} \alias{layout_tbl_graph_sparse_stress} \title{Place nodes using stress majorisation} \usage{ layout_tbl_graph_stress( graph, weights = NULL, niter = 500, tolerance = 1e-04, mds = TRUE, bbox = 50, circular = FALSE ) layout_tbl_graph_sparse_stress( graph, pivots, weights = NULL, niter = 500, circular = FALSE ) } \arguments{ \item{graph}{a tbl_graph object} \item{weights}{An expression evaluated on the edge data to provide edge weights for the layout. Currently ignored for the sparse version} \item{niter}{number of iterations during stress optimization} \item{tolerance}{stopping criterion for stress optimization} \item{mds}{should an MDS layout be used as initial layout (default: TRUE)} \item{bbox}{constrain dimension of output. Only relevant to determine the placement of disconnected graphs.} \item{circular}{ignored} \item{pivots}{The number of pivot nodes.} } \value{ A data.frame with the columns \code{x}, \code{y}, \code{circular} as well as any information stored as node variables in the tbl_graph object. } \description{ This layout is related to the stress-minimization algorithm known as Kamada-Kawai (available as the 'kk' layout), but uses another optimization strategy. It generally have better runtime, quality, and stability compared to the Kamada-Kawai layout and is thus generally preferred. The sparse version of the layout have better performance (especially on larger networks) at the expense of layout quality, but will generally outperform many other algorithms for large graphs in both runtime and quality (e.g. the 'drl' layout from igraph). } \references{ Gansner, E. R., Koren, Y., & North, S. (2004). \emph{Graph drawing by stress majorization.} In International Symposium on Graph Drawing (pp. 239-250). Springer, Berlin, Heidelberg. Ortmann, M. and Klimenta, M. and Brandes, U. (2016). \emph{A Sparse Stress Model.} https://arxiv.org/pdf/1608.08909.pdf } \seealso{ Other layout_tbl_graph_*: \code{\link{layout_tbl_graph_auto}()}, \code{\link{layout_tbl_graph_backbone}()}, \code{\link{layout_tbl_graph_centrality}()}, \code{\link{layout_tbl_graph_circlepack}()}, \code{\link{layout_tbl_graph_dendrogram}()}, \code{\link{layout_tbl_graph_eigen}()}, \code{\link{layout_tbl_graph_fabric}()}, \code{\link{layout_tbl_graph_focus}()}, \code{\link{layout_tbl_graph_hive}()}, \code{\link{layout_tbl_graph_igraph}()}, \code{\link{layout_tbl_graph_linear}()}, \code{\link{layout_tbl_graph_manual}()}, \code{\link{layout_tbl_graph_matrix}()}, \code{\link{layout_tbl_graph_partition}()}, \code{\link{layout_tbl_graph_pmds}()}, \code{\link{layout_tbl_graph_treemap}()}, \code{\link{layout_tbl_graph_unrooted}()} } \author{ The underlying algorithm is implemented in the graphlayouts package by David Schoch } \concept{layout_tbl_graph_*} ggraph/man/scale_edge_width.Rd0000644000176200001440000001200113617226162016036 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/scale_edge_width.R \name{scale_edge_width} \alias{scale_edge_width} \alias{scale_edge_width_continuous} \alias{scale_edge_width_discrete} \alias{scale_edge_width_manual} \alias{scale_edge_width_identity} \title{Edge width scales} \usage{ scale_edge_width_continuous(..., range = c(1, 6)) scale_edge_width(..., range = c(1, 6)) scale_edge_width_discrete(..., range = c(2, 6)) scale_edge_width_manual(..., values) scale_edge_width_identity(..., guide = "none") } \arguments{ \item{...}{Arguments passed on to \code{continuous_scale} \describe{ \item{name}{The name of the scale. Used as the axis or legend title. If \code{waiver()}, the default, the name of the scale is taken from the first mapping used for that aesthetic. If \code{NULL}, the legend title will be omitted.} \item{breaks}{One of: \itemize{ \item \code{NULL} for no breaks \item \code{waiver()} for the default breaks computed by the transformation object \item A numeric vector of positions \item A function that takes the limits as input and returns breaks as output }} \item{minor_breaks}{One of: \itemize{ \item \code{NULL} for no minor breaks \item \code{waiver()} for the default breaks (one minor break between each major break) \item A numeric vector of positions \item A function that given the limits returns a vector of minor breaks. }} \item{labels}{One of: \itemize{ \item \code{NULL} for no labels \item \code{waiver()} for the default labels computed by the transformation object \item A character vector giving labels (must be same length as \code{breaks}) \item A function that takes the breaks as input and returns labels as output }} \item{limits}{One of: \itemize{ \item \code{NULL} to use the default scale range \item A numeric vector of length two providing limits of the scale. Use \code{NA} to refer to the existing minimum or maximum \item A function that accepts the existing (automatic) limits and returns new limits }} \item{oob}{Function that handles limits outside of the scale limits (out of bounds). The default replaces out of bounds values with \code{NA}.} \item{na.value}{Missing values will be replaced with this value.} \item{trans}{Either the name of a transformation object, or the object itself. Built-in transformations include "asn", "atanh", "boxcox", "date", "exp", "hms", "identity", "log", "log10", "log1p", "log2", "logit", "modulus", "probability", "probit", "pseudo_log", "reciprocal", "reverse", "sqrt" and "time". A transformation object bundles together a transform, its inverse, and methods for generating breaks and labels. Transformation objects are defined in the scales package, and are called \code{name_trans}, e.g. \code{\link[scales:boxcox_trans]{scales::boxcox_trans()}}. You can create your own transformation with \code{\link[scales:trans_new]{scales::trans_new()}}.} \item{guide}{A function used to create a guide or its name. See \code{\link[ggplot2:guides]{guides()}} for more info.} \item{position}{The position of the axis. "left" or "right" for vertical scales, "top" or "bottom" for horizontal scales} \item{super}{The super class to use for the constructed scale} \item{expand}{Vector of range expansion constants used to add some padding around the data, to ensure that they are placed some distance away from the axes. Use the convenience function \code{\link[ggplot2:expand_scale]{expand_scale()}} to generate the values for the \code{expand} argument. The defaults are to expand the scale by 5\% on each side for continuous variables, and by 0.6 units on each side for discrete variables.} }} \item{range}{a numeric vector of length 2 that specifies the minimum and maximum size of the plotting symbol after transformation.} \item{values}{a set of aesthetic values to map data values to. If this is a named vector, then the values will be matched based on the names. If unnamed, values will be matched in order (usually alphabetical) with the limits of the scale. Any data values that don't match will be given \code{na.value}.} \item{guide}{A function used to create a guide or its name. See \code{\link[ggplot2:guides]{guides()}} for more info.} } \value{ A ggproto object inheriting from \code{Scale} } \description{ This set of scales defines width scales for edge geoms. Of all the new edge scales defined in ggraph, this is the only one not having an equivalent in ggplot2. In essence it mimics the use of size in \code{\link[ggplot2:geom_line]{ggplot2::geom_line()}} and related. As almost all edge representations are lines of some sort, edge_width will be used much more often than edge_size. It is not necessary to spell out that it is an edge scale as the geom knows if it is drawing an edge. Just write \code{width} and not \code{edge_width} in the call to geoms. } \seealso{ Other scale_edge_*: \code{\link{scale_edge_alpha}()}, \code{\link{scale_edge_colour}}, \code{\link{scale_edge_fill}}, \code{\link{scale_edge_linetype}()}, \code{\link{scale_edge_shape}()}, \code{\link{scale_edge_size}()}, \code{\link{scale_label_size}()} } \concept{scale_edge_*} ggraph/man/guide_edge_direction.Rd0000644000176200001440000000637413617226162016725 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/edge_direction.R \name{guide_edge_direction} \alias{guide_edge_direction} \title{Edge direction guide} \usage{ guide_edge_direction( title = waiver(), title.position = NULL, title.theme = NULL, title.hjust = NULL, title.vjust = NULL, arrow = TRUE, arrow.position = NULL, barwidth = NULL, barheight = NULL, nbin = 500, direction = NULL, default.unit = "line", reverse = FALSE, order = 0, override.aes = list(), ... ) } \arguments{ \item{title}{A character string or expression indicating a title of guide. If \code{NULL}, the title is not shown. By default (\code{\link[ggplot2:waiver]{waiver()}}), the name of the scale object or the name specified in \code{\link[ggplot2:labs]{labs()}} is used for the title.} \item{title.position}{A character string indicating the position of a title. One of "top" (default for a vertical guide), "bottom", "left" (default for a horizontal guide), or "right."} \item{title.theme}{A theme object for rendering the title text. Usually the object of \code{\link[ggplot2:element_text]{element_text()}} is expected. By default, the theme is specified by \code{legend.title} in \code{\link[ggplot2:theme]{theme()}} or theme.} \item{title.hjust}{A number specifying horizontal justification of the title text.} \item{title.vjust}{A number specifying vertical justification of the title text.} \item{arrow}{Logical. Should an arrow be drawn to illustrate the direction. Defaults to \code{TRUE}} \item{arrow.position}{The position of the arrow relative to the example edge.} \item{barwidth}{A numeric or a \code{\link[grid:unit]{grid::unit()}} object specifying the width of the colourbar. Default value is \code{legend.key.width} or \code{legend.key.size} in \code{\link[ggplot2:theme]{theme()}} or theme.} \item{barheight}{A numeric or a \code{\link[grid:unit]{grid::unit()}} object specifying the height of the colourbar. Default value is \code{legend.key.height} or \code{legend.key.size} in \code{\link[ggplot2:theme]{theme()}} or theme.} \item{nbin}{A numeric specifying the number of bins for drawing the colourbar. A smoother colourbar results from a larger value.} \item{direction}{A character string indicating the direction of the guide. One of "horizontal" or "vertical."} \item{default.unit}{A character string indicating \code{\link[grid:unit]{grid::unit()}} for \code{barwidth} and \code{barheight}.} \item{reverse}{logical. If \code{TRUE} the colourbar is reversed. By default, the highest value is on the top and the lowest value is on the bottom} \item{order}{positive integer less than 99 that specifies the order of this guide among multiple guides. This controls the order in which multiple guides are displayed, not the contents of the guide itself. If 0 (default), the order is determined by a secret algorithm.} \item{override.aes}{A list specifying aesthetic parameters of legend key.} \item{...}{ignored.} } \description{ This guide is intended to show the direction of edges based on the aesthetics mapped to its progression, such as changing width, colour and opacity. } \examples{ gr <- tidygraph::as_tbl_graph(highschool) ggraph(gr, layout = 'kk') + geom_edge_fan(aes(alpha = stat(index))) + guides(edge_alpha = guide_edge_direction()) } ggraph/man/layout_tbl_graph_unrooted.Rd0000644000176200001440000000560113617226162020052 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/layout_unrooted.R \name{layout_tbl_graph_unrooted} \alias{layout_tbl_graph_unrooted} \title{Create an unrooted layout using equal-angle or equal-daylight} \usage{ layout_tbl_graph_unrooted( graph, daylight = TRUE, length = NULL, tolerance = 0.05, rotation_mod = 1, maxiter = 100, circular = FALSE ) } \arguments{ \item{graph}{A tbl_graph object} \item{daylight}{Should equal-daylight adjustments be made} \item{length}{An expression evaluating to the branch length of each edge} \item{tolerance}{The threshold for mean angular adjustment before terminating the daylight adjustment} \item{rotation_mod}{A modifier for the angular adjustment of each branch. Set it below 1 to let the daylight adjustment progress more slowly} \item{maxiter}{The maximum number of iterations in the the daylight adjustment} \item{circular}{ignored} } \value{ A data.frame with the columns \code{x}, \code{y}, \code{circular}, \code{leaf} as well as any information stored as node variables in the tbl_graph object. } \description{ When drawing unrooted trees the standard dendrogram layout is a bad fit as it implicitly creates a visual root node. Instead it is possible to spread the leafs out on the plane without putting any special emphasis on a particular node using an unrooted layout. The standard algorithm is the equal angle algorithm, but it can struggle with optimising the leaf distribution for large trees trees with very uneven branch length. The equal daylight algorithm modifies the output of the equal angle algorithm to better disperse the leaves, at the cost of higher computational cost and the possibility of edge crossings for very large unbalanced trees. For standard sized trees the daylight algorithm is far superior and not too heavy so it is the default. } \note{ Unrooted is a layout intended for undirected trees, that is, graphs with no cycles. If the provided graph does not fit this format an attempt to convert it to such a format will be made. } \references{ Felsenstein, J. (2004) \emph{Drawing Trees}, in Inferring Phylogenies. Sinauer Assoc., pp 573-584 } \seealso{ Other layout_tbl_graph_*: \code{\link{layout_tbl_graph_auto}()}, \code{\link{layout_tbl_graph_backbone}()}, \code{\link{layout_tbl_graph_centrality}()}, \code{\link{layout_tbl_graph_circlepack}()}, \code{\link{layout_tbl_graph_dendrogram}()}, \code{\link{layout_tbl_graph_eigen}()}, \code{\link{layout_tbl_graph_fabric}()}, \code{\link{layout_tbl_graph_focus}()}, \code{\link{layout_tbl_graph_hive}()}, \code{\link{layout_tbl_graph_igraph}()}, \code{\link{layout_tbl_graph_linear}()}, \code{\link{layout_tbl_graph_manual}()}, \code{\link{layout_tbl_graph_matrix}()}, \code{\link{layout_tbl_graph_partition}()}, \code{\link{layout_tbl_graph_pmds}()}, \code{\link{layout_tbl_graph_stress}()}, \code{\link{layout_tbl_graph_treemap}()} } \concept{layout_tbl_graph_*} ggraph/man/scale_edge_shape.Rd0000644000176200001440000000722513617226162016033 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/scale_edge_shape.R \name{scale_edge_shape} \alias{scale_edge_shape} \alias{scale_edge_shape_discrete} \alias{scale_edge_shape_continuous} \alias{scale_edge_shape_manual} \alias{scale_edge_shape_identity} \title{Edge shape scales} \usage{ scale_edge_shape(..., solid = TRUE) scale_edge_shape_discrete(..., solid = TRUE) scale_edge_shape_continuous(...) scale_edge_shape_manual(..., values) scale_edge_shape_identity(..., guide = "none") } \arguments{ \item{...}{Arguments passed on to \code{discrete_scale} \describe{ \item{palette}{A palette function that when called with a single integer argument (the number of levels in the scale) returns the values that they should take.} \item{breaks}{One of: \itemize{ \item \code{NULL} for no breaks \item \code{waiver()} for the default breaks computed by the transformation object \item A character vector of breaks \item A function that takes the limits as input and returns breaks as output }} \item{limits}{A character vector that defines possible values of the scale and their order.} \item{drop}{Should unused factor levels be omitted from the scale? The default, \code{TRUE}, uses the levels that appear in the data; \code{FALSE} uses all the levels in the factor.} \item{na.translate}{Unlike continuous scales, discrete scales can easily show missing values, and do so by default. If you want to remove missing values from a discrete scale, specify \code{na.translate = FALSE}.} \item{na.value}{If \code{na.translate = TRUE}, what value aesthetic value should missing be displayed as? Does not apply to position scales where \code{NA} is always placed at the far right.} \item{aesthetics}{The names of the aesthetics that this scale works with} \item{scale_name}{The name of the scale} \item{name}{The name of the scale. Used as the axis or legend title. If \code{waiver()}, the default, the name of the scale is taken from the first mapping used for that aesthetic. If \code{NULL}, the legend title will be omitted.} \item{labels}{One of: \itemize{ \item \code{NULL} for no labels \item \code{waiver()} for the default labels computed by the transformation object \item A character vector giving labels (must be same length as \code{breaks}) \item A function that takes the breaks as input and returns labels as output }} \item{guide}{A function used to create a guide or its name. See \code{\link[ggplot2:guides]{guides()}} for more info.} \item{super}{The super class to use for the constructed scale} }} \item{solid}{Should the shapes be solid, \code{TRUE}, or hollow, \code{FALSE}?} \item{values}{a set of aesthetic values to map data values to. If this is a named vector, then the values will be matched based on the names. If unnamed, values will be matched in order (usually alphabetical) with the limits of the scale. Any data values that don't match will be given \code{na.value}.} \item{guide}{Guide to use for this scale.} } \value{ A ggproto object inheriting from \code{Scale} } \description{ This set of scales defines new shape scales for edge geoms equivalent to the ones already defined by ggplot2. See \code{\link[ggplot2:scale_shape]{ggplot2::scale_shape()}} for more information. The different geoms will know whether to use edge scales or the standard scales so it is not necessary to write \code{edge_shape} in the call to the geom - just use \code{shape}. } \seealso{ Other scale_edge_*: \code{\link{scale_edge_alpha}()}, \code{\link{scale_edge_colour}}, \code{\link{scale_edge_fill}}, \code{\link{scale_edge_linetype}()}, \code{\link{scale_edge_size}()}, \code{\link{scale_edge_width}()}, \code{\link{scale_label_size}()} } \concept{scale_edge_*} ggraph/man/layout_tbl_graph_backbone.Rd0000644000176200001440000000431413617226162017757 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/layout_backbone.R \name{layout_tbl_graph_backbone} \alias{layout_tbl_graph_backbone} \title{Place node to emphasize group structure} \usage{ layout_tbl_graph_backbone(graph, keep = 0.2, circular = FALSE) } \arguments{ \item{graph}{A tbl_graph object} \item{keep}{The fraction of edges to use for creating the backbone} \item{circular}{ignored} } \value{ A data.frame with the columns \code{x}, \code{y}, \code{circular} as well as any information stored as node variables in the tbl_graph object. Further an edge attribute called \code{backbone} is added giving whether the edge was selected as backbone. } \description{ This layout is optimised for drawing small-world types of graphs often found in social networks, where distinct groups are still highly connected to the remaining graph. Typical layouts struggle with this as they attempt to minimise the edge length of all edges equally. The backbone layout is based on weighing edges based on how well they hold together communities. The end result is that communities tend to stick together despite high interconnectivity. } \references{ Nocaj, A., Ortmann, M., & Brandes, U. (2015). \emph{Untangling the hairballs of multi-centered, small-world online social media networks.} Journal of Graph Algorithms and Applications: JGAA, 19(2), 595-618. } \seealso{ Other layout_tbl_graph_*: \code{\link{layout_tbl_graph_auto}()}, \code{\link{layout_tbl_graph_centrality}()}, \code{\link{layout_tbl_graph_circlepack}()}, \code{\link{layout_tbl_graph_dendrogram}()}, \code{\link{layout_tbl_graph_eigen}()}, \code{\link{layout_tbl_graph_fabric}()}, \code{\link{layout_tbl_graph_focus}()}, \code{\link{layout_tbl_graph_hive}()}, \code{\link{layout_tbl_graph_igraph}()}, \code{\link{layout_tbl_graph_linear}()}, \code{\link{layout_tbl_graph_manual}()}, \code{\link{layout_tbl_graph_matrix}()}, \code{\link{layout_tbl_graph_partition}()}, \code{\link{layout_tbl_graph_pmds}()}, \code{\link{layout_tbl_graph_stress}()}, \code{\link{layout_tbl_graph_treemap}()}, \code{\link{layout_tbl_graph_unrooted}()} } \author{ The underlying algorithm is implemented in the graphlayouts package by David Schoch } \concept{layout_tbl_graph_*} ggraph/man/geom_node_point.Rd0000644000176200001440000000527113617226162015744 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geom_node_point.R \name{geom_node_point} \alias{geom_node_point} \title{Show nodes as points} \usage{ geom_node_point( mapping = NULL, data = NULL, position = "identity", show.legend = NA, ... ) } \arguments{ \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2:aes]{ggplot2::aes()}} or \code{\link[ggplot2:aes_]{ggplot2::aes_()}}. By default x and y are mapped to x and y in the node data.} \item{data}{The data to be displayed in this layer. There are three options: If \code{NULL}, the default, the data is inherited from the plot data as specified in the call to \code{\link[ggplot2:ggplot]{ggplot()}}. A \code{data.frame}, or other object, will override the plot data. All objects will be fortified to produce a data frame. See \code{\link[ggplot2:fortify]{fortify()}} for which variables will be created. A \code{function} will be called with a single argument, the plot data. The return value must be a \code{data.frame}, and will be used as the layer data. A \code{function} can be created from a \code{formula} (e.g. \code{~ head(.x, 10)}).} \item{position}{Position adjustment, either as a string, or the result of a call to a position adjustment function.} \item{show.legend}{logical. Should this layer be included in the legends? \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE} never includes, and \code{TRUE} always includes. It can also be a named logical vector to finely select the aesthetics to display.} \item{...}{Other arguments passed on to \code{\link[ggplot2:layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like \code{colour = "red"} or \code{size = 3}. They may also be parameters to the paired geom/stat.} } \description{ This geom is equivalent in functionality to \code{\link[ggplot2:geom_point]{ggplot2::geom_point()}} and allows for simple plotting of nodes in different shapes, colours and sizes. } \section{Aesthetics}{ \code{geom_node_point} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{y} \item alpha \item colour \item fill \item shape \item size \item stroke \item filter } } \examples{ require(tidygraph) gr <- create_notable('bull') \%>\% mutate(class = sample(letters[1:3], n(), replace = TRUE)) ggraph(gr, 'stress') + geom_node_point() } \seealso{ Other geom_node_*: \code{\link{geom_node_arc_bar}()}, \code{\link{geom_node_circle}()}, \code{\link{geom_node_range}()}, \code{\link{geom_node_text}()}, \code{\link{geom_node_tile}()}, \code{\link{geom_node_voronoi}()} } \author{ Thomas Lin Pedersen } \concept{geom_node_*} ggraph/man/get_con.Rd0000644000176200001440000000365213617226162014216 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/connections.R \name{get_con} \alias{get_con} \title{Create a connection extractor function} \usage{ get_con( from = integer(), to = integer(), paths = NULL, ..., weight = NULL, mode = "all" ) } \arguments{ \item{from, to}{The index of the start and end nodes for the connections} \item{paths}{A list of integer vectors giving the index of nodes defining connections} \item{...}{Additional information to be added to the final data output} \item{weight}{An expression to be evaluated on the edge data to provide weights for the shortest path calculations} \item{mode}{Character constant, gives whether the shortest paths to or from the given vertices should be calculated for directed graphs. If \code{out} then the shortest paths \emph{from} the vertex, if \code{in} then \emph{to} it will be considered. If \code{all}, the default, then the corresponding undirected graph will be used, ie. not directed paths are searched. This argument is ignored for undirected graphs.} } \value{ A function that takes a layout_ggraph object and returns the given connections } \description{ Connections within the ggraph terminology are links between nodes that are not part of the network structure itself. In that sense connections do not affect the layout calculation in any way and will not be drawn by the standard \verb{geom_edge_*} functions. A connection does not need to only be defined by a start and end node, but can include intermediary nodes. \code{get_con} helps in creating connection data by letting you specify start and end nodes and automatically finds the shortest path within the graph structure that connects the given points. If this is not what is needed it is also possible to supply a list of vectors giving node indices that define a connection. } \seealso{ Other extractors: \code{\link{get_edges}()}, \code{\link{get_nodes}()} } \concept{extractors} ggraph/man/facet_nodes.Rd0000644000176200001440000000652113617226162015050 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/facet_nodes.R \name{facet_nodes} \alias{facet_nodes} \title{Create small multiples based on node attributes} \usage{ facet_nodes( facets, nrow = NULL, ncol = NULL, scales = "fixed", shrink = TRUE, labeller = "label_value", as.table = TRUE, switch = NULL, drop = TRUE, dir = "h", strip.position = "top" ) } \arguments{ \item{facets}{A set of variables or expressions quoted by \code{\link[ggplot2:vars]{vars()}} and defining faceting groups on the rows or columns dimension. The variables can be named (the names are passed to \code{labeller}). For compatibility with the classic interface, can also be a formula or character vector. Use either a one sided formula, \code{~a + b}, or a character vector, \code{c("a", "b")}.} \item{nrow}{Number of rows and columns.} \item{ncol}{Number of rows and columns.} \item{scales}{Should scales be fixed (\code{"fixed"}, the default), free (\code{"free"}), or free in one dimension (\code{"free_x"}, \code{"free_y"})?} \item{shrink}{If \code{TRUE}, will shrink scales to fit output of statistics, not raw data. If \code{FALSE}, will be range of raw data before statistical summary.} \item{labeller}{A function that takes one data frame of labels and returns a list or data frame of character vectors. Each input column corresponds to one factor. Thus there will be more than one with formulae of the type \code{~cyl + am}. Each output column gets displayed as one separate line in the strip label. This function should inherit from the "labeller" S3 class for compatibility with \code{\link[ggplot2:labeller]{labeller()}}. See \code{\link[ggplot2:label_value]{label_value()}} for more details and pointers to other options.} \item{as.table}{If \code{TRUE}, the default, the facets are laid out like a table with highest values at the bottom-right. If \code{FALSE}, the facets are laid out like a plot with the highest value at the top-right.} \item{switch}{By default, the labels are displayed on the top and right of the plot. If \code{"x"}, the top labels will be displayed to the bottom. If \code{"y"}, the right-hand side labels will be displayed to the left. Can also be set to \code{"both"}.} \item{drop}{If \code{TRUE}, the default, all factor levels not used in the data will automatically be dropped. If \code{FALSE}, all factor levels will be shown, regardless of whether or not they appear in the data.} \item{dir}{Direction: either \code{"h"} for horizontal, the default, or \code{"v"}, for vertical.} \item{strip.position}{By default, the labels are displayed on the top of the plot. Using \code{strip.position} it is possible to place the labels on either of the four sides by setting \code{strip.position = c("top", "bottom", "left", "right")}} } \description{ This function is equivalent to \code{\link[ggplot2:facet_wrap]{ggplot2::facet_wrap()}} but only facets nodes. Edges are drawn if their terminal nodes are both present in a panel. } \examples{ library(tidygraph) gr <- as_tbl_graph(highschool) \%>\% mutate(popularity = as.character(cut(centrality_degree(mode = 'in'), breaks = 3, labels = c('low', 'medium', 'high') ))) ggraph(gr) + geom_edge_link() + geom_node_point() + facet_nodes(~popularity) } \seealso{ Other ggraph-facets: \code{\link{facet_edges}()}, \code{\link{facet_graph}()} } \concept{ggraph-facets} ggraph/man/get_nodes.Rd0000644000176200001440000000203713617226162014543 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/nodes.R \name{get_nodes} \alias{get_nodes} \title{Create a node extractor function} \usage{ get_nodes(...) } \arguments{ \item{...}{Additional data that should be cbind'ed together with the node data.} } \value{ A data.frame with the node data as well of any additional data supplied through \code{...} } \description{ This function returns another function that can extract nodes from a ggraph_layout object. As a ggraph_layout object is essentially a data.frame of nodes it might seem useless to provide this function, but since the node data is not necessarily available until after the \code{ggraph()} call it can be beneficial to be able to add information to the node data on a per-layer basis. Unlike \code{\link[=get_edges]{get_edges()}} the use of \code{get_nodes} is not mandatory and is only required if additional data should be added to selected node layers. } \seealso{ Other extractors: \code{\link{get_con}()}, \code{\link{get_edges}()} } \concept{extractors} ggraph/man/facet_graph.Rd0000644000176200001440000001034113617226162015034 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/facet_graph.R \name{facet_graph} \alias{facet_graph} \title{Create a grid of small multiples by node and/or edge attributes} \usage{ facet_graph( facets, row_type = "edge", col_type = "node", margins = FALSE, scales = "fixed", space = "fixed", shrink = TRUE, labeller = "label_value", as.table = TRUE, switch = NULL, drop = TRUE ) } \arguments{ \item{facets}{This argument is soft-deprecated, please use \code{rows} and \code{cols} instead.} \item{row_type, col_type}{Either \code{'node'} or \code{'edge'}. Which data type is being facetted in the rows and columns. Default is to facet on nodes column wise and on edges row wise.} \item{margins}{Either a logical value or a character vector. Margins are additional facets which contain all the data for each of the possible values of the faceting variables. If \code{FALSE}, no additional facets are included (the default). If \code{TRUE}, margins are included for all faceting variables. If specified as a character vector, it is the names of variables for which margins are to be created.} \item{scales}{Are scales shared across all facets (the default, \code{"fixed"}), or do they vary across rows (\code{"free_x"}), columns (\code{"free_y"}), or both rows and columns (\code{"free"})?} \item{space}{If \code{"fixed"}, the default, all panels have the same size. If \code{"free_y"} their height will be proportional to the length of the y scale; if \code{"free_x"} their width will be proportional to the length of the x scale; or if \code{"free"} both height and width will vary. This setting has no effect unless the appropriate scales also vary.} \item{shrink}{If \code{TRUE}, will shrink scales to fit output of statistics, not raw data. If \code{FALSE}, will be range of raw data before statistical summary.} \item{labeller}{A function that takes one data frame of labels and returns a list or data frame of character vectors. Each input column corresponds to one factor. Thus there will be more than one with formulae of the type \code{~cyl + am}. Each output column gets displayed as one separate line in the strip label. This function should inherit from the "labeller" S3 class for compatibility with \code{\link[ggplot2:labeller]{labeller()}}. See \code{\link[ggplot2:label_value]{label_value()}} for more details and pointers to other options.} \item{as.table}{If \code{TRUE}, the default, the facets are laid out like a table with highest values at the bottom-right. If \code{FALSE}, the facets are laid out like a plot with the highest value at the top-right.} \item{switch}{By default, the labels are displayed on the top and right of the plot. If \code{"x"}, the top labels will be displayed to the bottom. If \code{"y"}, the right-hand side labels will be displayed to the left. Can also be set to \code{"both"}.} \item{drop}{If \code{TRUE}, the default, all factor levels not used in the data will automatically be dropped. If \code{FALSE}, all factor levels will be shown, regardless of whether or not they appear in the data.} } \description{ This function is equivalent to \code{\link[ggplot2:facet_grid]{ggplot2::facet_grid()}} in that it allows for building a grid of small multiples where rows and columns correspond to a specific data value. While \code{\link[ggplot2:facet_grid]{ggplot2::facet_grid()}} could be used it would lead to unexpected results as it is not possible to specify whether you are referring to a node or an edge attribute. Furthermore \code{\link[ggplot2:facet_grid]{ggplot2::facet_grid()}} will draw edges in panels even though the panel does not contain both terminal nodes. \code{facet_graph} takes care of all of these issues, allowing you to define which data type the rows and columns are referencing as well as filtering the edges based on the nodes in each panel (even when nodes are not drawn). } \examples{ library(tidygraph) gr <- as_tbl_graph(highschool) \%>\% mutate(popularity = as.character(cut(centrality_degree(mode = 'in'), breaks = 3, labels = c('low', 'medium', 'high') ))) ggraph(gr) + geom_edge_link() + geom_node_point() + facet_graph(year ~ popularity) } \seealso{ Other ggraph-facets: \code{\link{facet_edges}()}, \code{\link{facet_nodes}()} } \concept{ggraph-facets} ggraph/man/ggraph.Rd0000644000176200001440000001507613524253234014050 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/ggraph.R, R/layout.R, R/tbl_graph.R \name{ggraph} \alias{ggraph} \alias{create_layout} \alias{layout_ggraph} \alias{create_layout.default} \alias{create_layout.layout_ggraph} \alias{create_layout.tbl_graph} \alias{layout_tbl_graph} \title{Create a ggraph plot} \usage{ ggraph(graph, layout = "auto", ...) create_layout(graph, layout, circular, ...) \method{create_layout}{default}(graph, layout, ...) \method{create_layout}{layout_ggraph}(graph, ...) \method{create_layout}{tbl_graph}(graph, layout, circular = FALSE, ...) } \arguments{ \item{graph}{The object containing the graph. See \emph{Details} for a list of supported classes. Or a \code{layout_ggraph} object as returned from \code{create_layout} in which case all subsequent arguments is ignored.} \item{layout}{The type of layout to create. Either a valid string, a function, a matrix, or a data.frame (see Details)} \item{...}{Arguments passed on to the layout function.} \item{circular}{Should the layout be transformed into a radial representation. Only possible for some layouts. Defaults to \code{FALSE}} } \value{ For \code{ggraph()} an object of class gg onto which layers, scales, etc. can be added. For \code{create_layout()} an object inheriting from \code{layout_ggraph}. \code{layout_ggraph} itself inherits from \code{data.frame} and can be considered as such. The data.frame contains the node positions in the \code{x} and \code{y} column along with additional columns generated by the specific layout, as well as node parameters inherited from the graph. Additional information is stored as attributes to the data.frame. The original graph object is stored in the \code{graph} attribute and the \code{circular} attribute contains a logical indicating whether the layout has been transformed to a circular representation. } \description{ This function is the equivalent of \code{\link[ggplot2:ggplot]{ggplot2::ggplot()}} in ggplot2. It takes care of setting up the plot object along with creating the layout for the plot based on the graph and the specification passed in. Alternatively a layout can be prepared in advance using \code{create_layout} and passed as the data argument. See \emph{Details} for a description of all available layouts. } \details{ Following is a short description of the different layout types available in ggraph. Each layout is further described in its own help pages. Any type of regular graph/network data can be represented as a tbl_graph object. Because of this the different layouts that can be applied to tbl_graph objects are quite diverse, but not all layouts makes sense to all types of graphs. It is up to the user to understand their data and choose an appropriate layout. For standard node-edge diagrams igraph defines a long range of different layout functions that are all available through the \code{igraph} layout where the specific layout is specified using the \code{algorithm} argument. In order to minimize typing all igraph algorithms can also be passed directly into the \code{layout} argument. Any object that has an appropriate \code{as_tbl_graph} method can be passed into \code{ggraph()} and will automatically be converted underneath. \describe{ \item{\code{auto}}{The default layout. See \code{\link[=layout_tbl_graph_auto]{layout_tbl_graph_auto()}} for further details} \item{\code{igraph}}{Use one of the internal igraph layout algorithms. The algorithm is specified using the \code{algorithm} argument. All strings accepted by the \code{algorithm} argument can also be supplied directly into \code{layout}. See \code{\link[=layout_tbl_graph_igraph]{layout_tbl_graph_igraph()}} for further details} \item{\code{dendrogram}}{Lays out the nodes in a tree-like graph as a dendrogram with leaves set at 0 and parents 1 unit above its tallest child. See \code{\link[=layout_tbl_graph_dendrogram]{layout_tbl_graph_dendrogram()}} for further details} \item{\code{manual}}{Lets the user manually specify the location of each node. See \code{\link[=layout_tbl_graph_manual]{layout_tbl_graph_manual()}} for further details} \item{\code{linear}}{Arranges the nodes linearly or circularly in order to make an arc diagram. See \code{\link[=layout_tbl_graph_linear]{layout_tbl_graph_linear()}} for further details} \item{\code{matrix}}{Arranges nodes on a diagonal thus preparing it for use with \code{\link[=geom_edge_point]{geom_edge_point()}} to make a matrix plot. See \code{\link[=layout_tbl_graph_matrix]{layout_tbl_graph_matrix()}} for further details} \item{\code{treemap}}{Creates a treemap from the graph, that is, a space-filing subdivision of rectangles showing a weighted hierarchy. See \code{\link[=layout_tbl_graph_treemap]{layout_tbl_graph_treemap()}} for further details} \item{\code{circlepack}}{Creates a layout showing a hierarchy as circles within circles. Conceptually equal to treemaps. See \code{\link[=layout_tbl_graph_circlepack]{layout_tbl_graph_circlepack()}} for further details} \item{\code{partition}}{Create icicle or sunburst charts, where each layer subdivides the division given by the preceding layer. See \code{\link[=layout_tbl_graph_partition]{layout_tbl_graph_partition()}} for further details} \item{\code{hive}}{Positions nodes on axes spreading out from the center based on node attributes. See \code{\link[=layout_tbl_graph_hive]{layout_tbl_graph_hive()}} for further details} } Alternatively a matrix or a data.frame can be provided to the \code{layout} argument. In the former case the first column will be used as x coordinates and the second column will by used as y coordinates, further columns are dropped. In the latter case the data.frame is used as the layout table and must thus contain a numeric x and y column. Lastly a function can be provided to the \code{layout} argument. It will be called with the graph object as its first argument and any additional argument passed into \code{ggraph()}/\code{create_layout()}. The function must return either a data.frame or an object coercible to one and have an \code{x} and \code{y} column, or an object coercible to a \code{tbl_graph}. In the latter case the node data is extracted and used as layout (and must thus contain an \code{x} and \code{y} column) and the graph will be added as the \code{graph} attribute. } \examples{ require(tidygraph) gr <- create_notable('bull') layout <- create_layout(gr, layout = 'igraph', algorithm = 'kk') } \seealso{ \code{\link[=get_edges]{get_edges()}} for extracting edge information from the layout and \code{\link[=get_con]{get_con()}} for extracting path information. } \keyword{graph} \keyword{hierarchy} \keyword{layout} \keyword{network} \keyword{visualisation} ggraph/man/flare.Rd0000644000176200001440000000274013276561316013672 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/data_flare.R \docType{data} \name{flare} \alias{flare} \title{The class hierarchy of the flare visualization library} \format{A list of three data.frames describing the software structure of flare: \describe{ \item{edges}{This data.frame maps the hierarchical structure of the class hierarchy as an edgelist, with the class in \code{from} being the superclass of the class in \code{to}.} \item{vertices}{This data.frame gives additional information on the classes. It contains the full name, size and short name of each class.} \item{imports}{This data.frame contains the class imports for each class implementation. The \code{from} column gives the importing class and the \code{to} column gives the import.} }} \source{ The data have been adapted from the JSON downloaded from \url{https://gist.github.com/mbostock/1044242#file-readme-flare-imports-json} courtesy of Mike Bostock. The Flare framework is the work of the \href{http://vis.berkeley.edu/}{UC Berkeley Visualization Lab}. } \usage{ flare } \description{ This dataset contains the graph that describes the class hierarchy for the \href{http://flare.prefuse.org}{Flare} ActionScript visualization library. It contains both the class hierarchy as well as the import connections between classes. This dataset has been used extensively in the D3.js documentation and examples and are included here to make it easy to redo the examples in ggraph. } \keyword{datasets} ggraph/man/layout_tbl_graph_fabric.Rd0000644000176200001440000000542713617226162017447 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/layout_fabric.R \name{layout_tbl_graph_fabric} \alias{layout_tbl_graph_fabric} \alias{node_rank_fabric} \title{Create a fabric layout} \usage{ layout_tbl_graph_fabric( graph, circular = FALSE, sort.by = NULL, shadow.edges = FALSE ) node_rank_fabric() } \arguments{ \item{graph}{An \code{tbl_graph} object} \item{circular}{Ignored} \item{sort.by}{An expression providing the sorting of the nodes. If \code{NULL} the nodes will be ordered by their index in the graph.} \item{shadow.edges}{Should shadow edges be shown.} } \value{ A data.frame with the columns \code{x}, \code{xmin}, \code{xmax}, \code{y}, \code{circular} as well as any information stored as node variables in the tbl_graph object. Further, the edges of the graph will gain a \code{edge_x} variable giving the horizontal position of the edge as well as a \code{shadow_edge} variable denoting whether the edge is a shadow edge added by the layout. } \description{ This layout is a bit unusual in that it shows nodes as horizontal line ranges end edges as evenly spaced vertical spans connecting the nodes. As with the matrix layout the strength comes from better scalability but its use require some experience recognising the patterns that different connectivity features gives rise to. As with matrix layouts the ordering of nodes have huge power over the look of the plot. The \code{node_rank_fabric()} mimics the default ordering from the original BioFabric implementation, but other ranking algorithms from tidygraph can be used with the \code{sort.by} argument as well. Fabric layouts tend to become quite wide as the graph grows which is something that should be handled with care - e.g. by only zooming in on a specific region. } \references{ BioFabric website: \url{http://www.biofabric.org} Longabaugh, William J.R. (2012). \emph{Combing the hairball with BioFabric: a new approach for visualization of large networks}. BMC Bioinformatics, 13: 275. \url{https://doi.org/10.1186/1471-2105-13-275} } \seealso{ Other layout_tbl_graph_*: \code{\link{layout_tbl_graph_auto}()}, \code{\link{layout_tbl_graph_backbone}()}, \code{\link{layout_tbl_graph_centrality}()}, \code{\link{layout_tbl_graph_circlepack}()}, \code{\link{layout_tbl_graph_dendrogram}()}, \code{\link{layout_tbl_graph_eigen}()}, \code{\link{layout_tbl_graph_focus}()}, \code{\link{layout_tbl_graph_hive}()}, \code{\link{layout_tbl_graph_igraph}()}, \code{\link{layout_tbl_graph_linear}()}, \code{\link{layout_tbl_graph_manual}()}, \code{\link{layout_tbl_graph_matrix}()}, \code{\link{layout_tbl_graph_partition}()}, \code{\link{layout_tbl_graph_pmds}()}, \code{\link{layout_tbl_graph_stress}()}, \code{\link{layout_tbl_graph_treemap}()}, \code{\link{layout_tbl_graph_unrooted}()} } \concept{layout_tbl_graph_*} ggraph/man/layout_tbl_graph_auto.Rd0000644000176200001440000000405013617226162017160 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/layout_auto.R \name{layout_tbl_graph_auto} \alias{layout_tbl_graph_auto} \title{Automatically pick a layout based on graph type} \usage{ layout_tbl_graph_auto(graph, circular, ...) } \arguments{ \item{graph}{A tbl_graph object} \item{circular}{Logical. Should the layout be transformed to a circular representation. Defaults to \code{FALSE}. Only applicable if the graph is a tree structure} \item{...}{Arguments passed on to the chosen layout} } \value{ A data.frame with the columns \code{x}, \code{y}, \code{circular} as well as any information stored as node variables in the tbl_graph object. } \description{ This function infers the layout from the graph structure and is the default when calling \code{\link[=ggraph]{ggraph()}}. If an \code{x} and \code{y} argument is passed along, the manual layout is chosen. Otherwise if the graph is either a rooted tree or a rooted forest the layout will be \code{dendrogram} if the nodes contains a height variable or \code{tree} if not. If the tree is unrooted the \code{unrooted} layout will be used. If the tree is a DAG the \code{sygiyama} layout will be used. Otherwise the \code{stress} layout will be used (or \code{sparse_tree} if the graph contains more than 2000 nodes). } \seealso{ Other layout_tbl_graph_*: \code{\link{layout_tbl_graph_backbone}()}, \code{\link{layout_tbl_graph_centrality}()}, \code{\link{layout_tbl_graph_circlepack}()}, \code{\link{layout_tbl_graph_dendrogram}()}, \code{\link{layout_tbl_graph_eigen}()}, \code{\link{layout_tbl_graph_fabric}()}, \code{\link{layout_tbl_graph_focus}()}, \code{\link{layout_tbl_graph_hive}()}, \code{\link{layout_tbl_graph_igraph}()}, \code{\link{layout_tbl_graph_linear}()}, \code{\link{layout_tbl_graph_manual}()}, \code{\link{layout_tbl_graph_matrix}()}, \code{\link{layout_tbl_graph_partition}()}, \code{\link{layout_tbl_graph_pmds}()}, \code{\link{layout_tbl_graph_stress}()}, \code{\link{layout_tbl_graph_treemap}()}, \code{\link{layout_tbl_graph_unrooted}()} } \concept{layout_tbl_graph_*} ggraph/man/guide-helpers.Rd0000644000176200001440000000135413276373323015335 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/edge_colourbar.R, R/edge_direction.R \name{guide_train.edge_colourbar} \alias{guide_train.edge_colourbar} \alias{guide_train.edge_direction} \alias{guide_merge.edge_direction} \alias{guide_geom.edge_direction} \alias{guide_gengrob.edge_direction} \title{Helper methods for guides} \usage{ \method{guide_train}{edge_colourbar}(guide, scale, aesthetic = NULL) \method{guide_train}{edge_direction}(guide, scale, aesthetic = NULL) \method{guide_merge}{edge_direction}(guide, new_guide) \method{guide_geom}{edge_direction}(guide, layers, default_mapping) \method{guide_gengrob}{edge_direction}(guide, theme) } \description{ Helper methods for guides } \keyword{internal} ggraph/man/geom_edge_elbow.Rd0000644000176200001440000002237013617226162015701 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geom_edge_elbow.R \name{geom_edge_elbow} \alias{geom_edge_elbow} \alias{geom_edge_elbow2} \alias{geom_edge_elbow0} \title{Draw edges as elbows} \usage{ geom_edge_elbow( mapping = NULL, data = get_edges(), position = "identity", arrow = NULL, strength = 1, flipped = FALSE, n = 100, lineend = "butt", linejoin = "round", linemitre = 1, label_colour = "black", label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = "rot", force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ... ) geom_edge_elbow2( mapping = NULL, data = get_edges("long"), position = "identity", arrow = NULL, strength = 1, flipped = FALSE, n = 100, lineend = "butt", linejoin = "round", linemitre = 1, label_colour = "black", label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = "rot", force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ... ) geom_edge_elbow0( mapping = NULL, data = get_edges(), position = "identity", arrow = NULL, flipped = FALSE, lineend = "butt", show.legend = NA, ... ) } \arguments{ \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2:aes]{ggplot2::aes()}} or \code{\link[ggplot2:aes_]{ggplot2::aes_()}}. By default x, y, xend, yend, group and circular are mapped to x, y, xend, yend, edge.id and circular in the edge data.} \item{data}{The return of a call to \code{get_edges()} or a data.frame giving edges in correct format (see details for for guidance on the format). See \code{\link[=get_edges]{get_edges()}} for more details on edge extraction.} \item{position}{Position adjustment, either as a string, or the result of a call to a position adjustment function.} \item{arrow}{Arrow specification, as created by \code{\link[grid:arrow]{grid::arrow()}}.} \item{strength}{How bend the elbow should be. 1 will give a right angle, while \code{0} will give a straight line. Ignored for circular layouts} \item{flipped}{Logical, Has the layout been flipped by reassigning the mapping of x, y etc?} \item{n}{The number of points to create along the path.} \item{lineend}{Line end style (round, butt, square).} \item{linejoin}{Line join style (round, mitre, bevel).} \item{linemitre}{Line mitre limit (number greater than 1).} \item{label_colour}{The colour of the edge label. If \code{NA} it will use the colour of the edge.} \item{label_alpha}{The opacity of the edge label. If \code{NA} it will use the opacity of the edge.} \item{label_parse}{If \code{TRUE}, the labels will be parsed into expressions and displayed as described in \code{\link[grDevices:plotmath]{grDevices::plotmath()}}.} \item{check_overlap}{If \code{TRUE}, text that overlaps previous text in the same layer will not be plotted.} \item{angle_calc}{Either 'none', 'along', or 'across'. If 'none' the label will use the angle aesthetic of the geom. If 'along' The label will be written along the edge direction. If 'across' the label will be written across the edge direction.} \item{force_flip}{Logical. If \code{angle_calc} is either 'along' or 'across' should the label be flipped if it is on it's head. Default to \code{TRUE}.} \item{label_dodge}{A \code{\link[grid:unit]{grid::unit()}} giving a fixed vertical shift to add to the label in case of \code{angle_calc} is either 'along' or 'across'} \item{label_push}{A \code{\link[grid:unit]{grid::unit()}} giving a fixed horizontal shift to add to the label in case of \code{angle_calc} is either 'along' or 'across'} \item{show.legend}{logical. Should this layer be included in the legends? \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE} never includes, and \code{TRUE} always includes. It can also be a named logical vector to finely select the aesthetics to display.} \item{...}{Other arguments passed on to \code{\link[ggplot2:layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like \code{colour = "red"} or \code{size = 3}. They may also be parameters to the paired geom/stat.} } \description{ This geom draws edges as an angle in the same manner as known from classic dendrogram plots of hierarchical clustering results. In case a circular transformation has been applied the first line segment will be drawn as an arc as expected. This geom is only applicable to layouts that return a direction for the edges (currently \code{\link[=layout_tbl_graph_dendrogram]{layout_tbl_graph_dendrogram()}}, \code{\link[=layout_tbl_graph_partition]{layout_tbl_graph_partition()}} and \code{\link[=layout_tbl_graph_igraph]{layout_tbl_graph_igraph()}} with the \code{"tree"} algorithm). } \section{Aesthetics}{ \code{geom_edge_elbow} and \code{geom_edge_elbow0} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{y} \item \strong{xend} \item \strong{yend} \item \strong{circular} \item \strong{direction} \item edge_colour \item edge_width \item edge_linetype \item edge_alpha \item filter } \code{geom_edge_elbow2} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{y} \item \strong{group} \item \strong{circular} \item \strong{direction} \item edge_colour \item edge_width \item edge_linetype \item edge_alpha \item filter } \code{geom_edge_elbow} and \code{geom_edge_elbow2} furthermore takes the following aesthetics. \itemize{ \item start_cap \item end_cap \item label \item label_pos \item label_size \item angle \item hjust \item vjust \item family \item fontface \item lineheight } } \section{Computed variables}{ \describe{ \item{index}{The position along the path (not computed for the *0 version)} } } \section{Edge variants}{ Many geom_edge_* layers comes in 3 flavors depending on the level of control needed over the drawing. The default (no numeric postfix) generate a number of points (\code{n}) along the edge and draws it as a path. Each point along the line has a numeric value associated with it giving the position along the path, and it is therefore possible to show the direction of the edge by mapping to this e.g. \code{colour = stat(index)}. The version postfixed with a "2" uses the "long" edge format (see \code{\link[=get_edges]{get_edges()}}) and makes it possible to interpolate node parameter between the start and end node along the edge. It is considerable less performant so should only be used if this is needed. The version postfixed with a "0" draws the edge in the most performant way, often directly using an appropriate grob from the grid package, but does not allow for gradients along the edge. Often it is beneficial to stop the drawing of the edge before it reaches the node, for instance in cases where an arrow should be drawn and the arrowhead shouldn't lay on top or below the node point. geom_edge_* and geom_edge_*2 supports this through the start_cap and end_cap aesthetics that takes a \code{\link[=geometry]{geometry()}} specification and dynamically caps the termini of the edges based on the given specifications. This means that if \code{end_cap = circle(1, 'cm')} the edges will end at a distance of 1cm even during resizing of the plot window. All \verb{geom_edge_*} and \code{geom_edge_*2} have the ability to draw a label along the edge. The reason this is not a separate geom is that in order for the label to know the location of the edge it needs to know the edge type etc. Labels are drawn by providing a label aesthetic. The label_pos can be used to specify where along the edge it should be drawn by supplying a number between 0 and 1. The label_size aesthetic can be used to control the size of the label. Often it is needed to have the label written along the direction of the edge, but since the actual angle is dependent on the plot dimensions this cannot be calculated beforehand. Using the angle_calc argument allows you to specify whether to use the supplied angle aesthetic or whether to draw the label along or across the edge. } \section{Edge aesthetic name expansion}{ In order to avoid excessive typing edge aesthetic names are automatically expanded. Because of this it is not necessary to write \code{edge_colour} within the \code{aes()} call as \code{colour} will automatically be renamed appropriately. } \examples{ require(tidygraph) irisDen <- hclust(dist(iris[1:4], method = 'euclidean'), method = 'ward.D2') \%>\% as_tbl_graph() \%>\% mutate(class = sample(letters[1:3], n(), TRUE)) \%>\% activate(edges) \%>\% mutate(class = sample(letters[1:3], n(), TRUE)) ggraph(irisDen, 'dendrogram', circular = TRUE) + geom_edge_elbow(aes(alpha = stat(index))) ggraph(irisDen, 'dendrogram') + geom_edge_elbow2(aes(colour = node.class)) ggraph(irisDen, 'dendrogram', height = height) + geom_edge_elbow0(aes(colour = class)) } \seealso{ Other geom_edge_*: \code{\link{geom_edge_arc}()}, \code{\link{geom_edge_bend}()}, \code{\link{geom_edge_density}()}, \code{\link{geom_edge_diagonal}()}, \code{\link{geom_edge_fan}()}, \code{\link{geom_edge_hive}()}, \code{\link{geom_edge_link}()}, \code{\link{geom_edge_loop}()}, \code{\link{geom_edge_parallel}()}, \code{\link{geom_edge_point}()}, \code{\link{geom_edge_span}()}, \code{\link{geom_edge_tile}()} } \author{ Thomas Lin Pedersen } \concept{geom_edge_*} ggraph/man/scale_edge_linetype.Rd0000644000176200001440000000703013617226162016556 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/scale_edge_linetype.R \name{scale_edge_linetype} \alias{scale_edge_linetype} \alias{scale_edge_linetype_continuous} \alias{scale_edge_linetype_discrete} \alias{scale_edge_linetype_manual} \alias{scale_edge_linetype_identity} \title{Edge linetype scales} \usage{ scale_edge_linetype(..., na.value = "blank") scale_edge_linetype_continuous(...) scale_edge_linetype_discrete(..., na.value = "blank") scale_edge_linetype_manual(..., values) scale_edge_linetype_identity(..., guide = "none") } \arguments{ \item{...}{Arguments passed on to \code{discrete_scale} \describe{ \item{palette}{A palette function that when called with a single integer argument (the number of levels in the scale) returns the values that they should take.} \item{breaks}{One of: \itemize{ \item \code{NULL} for no breaks \item \code{waiver()} for the default breaks computed by the transformation object \item A character vector of breaks \item A function that takes the limits as input and returns breaks as output }} \item{limits}{A character vector that defines possible values of the scale and their order.} \item{drop}{Should unused factor levels be omitted from the scale? The default, \code{TRUE}, uses the levels that appear in the data; \code{FALSE} uses all the levels in the factor.} \item{na.translate}{Unlike continuous scales, discrete scales can easily show missing values, and do so by default. If you want to remove missing values from a discrete scale, specify \code{na.translate = FALSE}.} \item{aesthetics}{The names of the aesthetics that this scale works with} \item{scale_name}{The name of the scale} \item{name}{The name of the scale. Used as the axis or legend title. If \code{waiver()}, the default, the name of the scale is taken from the first mapping used for that aesthetic. If \code{NULL}, the legend title will be omitted.} \item{labels}{One of: \itemize{ \item \code{NULL} for no labels \item \code{waiver()} for the default labels computed by the transformation object \item A character vector giving labels (must be same length as \code{breaks}) \item A function that takes the breaks as input and returns labels as output }} \item{guide}{A function used to create a guide or its name. See \code{\link[ggplot2:guides]{guides()}} for more info.} \item{super}{The super class to use for the constructed scale} }} \item{na.value}{The linetype to use for \code{NA} values.} \item{values}{a set of aesthetic values to map data values to. If this is a named vector, then the values will be matched based on the names. If unnamed, values will be matched in order (usually alphabetical) with the limits of the scale. Any data values that don't match will be given \code{na.value}.} \item{guide}{Guide to use for this scale. Defaults to \code{"none"}.} } \value{ A ggproto object inheriting from \code{Scale} } \description{ This set of scales defines new linetype scales for edge geoms equivalent to the ones already defined by ggplot2. See \code{\link[ggplot2:scale_linetype]{ggplot2::scale_linetype()}} for more information. The different geoms will know whether to use edge scales or the standard scales so it is not necessary to write \code{edge_linetype} in the call to the geom - just use \code{linetype}. } \seealso{ Other scale_edge_*: \code{\link{scale_edge_alpha}()}, \code{\link{scale_edge_colour}}, \code{\link{scale_edge_fill}}, \code{\link{scale_edge_shape}()}, \code{\link{scale_edge_size}()}, \code{\link{scale_edge_width}()}, \code{\link{scale_label_size}()} } \concept{scale_edge_*} ggraph/man/geom_edge_tile.Rd0000644000176200001440000000653513617226162015533 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geom_edge_tile.R \name{geom_edge_tile} \alias{geom_edge_tile} \title{Draw edges as glyphs} \usage{ geom_edge_tile( mapping = NULL, data = get_edges(), position = "identity", mirror = FALSE, show.legend = NA, ... ) } \arguments{ \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2:aes]{ggplot2::aes()}} or \code{\link[ggplot2:aes_]{ggplot2::aes_()}}. By default x, y, xend, yend, group and circular are mapped to x, y, xend, yend, edge.id and circular in the edge data.} \item{data}{The return of a call to \code{get_edges()} or a data.frame giving edges in correct format (see details for for guidance on the format). See \code{\link[=get_edges]{get_edges()}} for more details on edge extraction.} \item{position}{Position adjustment, either as a string, or the result of a call to a position adjustment function.} \item{mirror}{Logical. Should edge points be duplicated on both sides of the diagonal. Intended for undirected graphs. Default to \code{FALSE}} \item{show.legend}{logical. Should this layer be included in the legends? \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE} never includes, and \code{TRUE} always includes. It can also be a named logical vector to finely select the aesthetics to display.} \item{...}{Other arguments passed on to \code{\link[ggplot2:layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like \code{colour = "red"} or \code{size = 3}. They may also be parameters to the paired geom/stat.} } \description{ This geom draws edges as tiles with their x-position defined by the x-position of the start node, and the y-position defined by the y-position of the end node. As such it will result in a matrix layout when used in conjunction with \code{\link[=layout_tbl_graph_matrix]{layout_tbl_graph_matrix()}} } \section{Aesthetics}{ \code{geom_edge_tile} understands the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{y} \item edge_fill \item edge_colour \item edge_size \item edge_alpha \item filter } } \section{Edge aesthetic name expansion}{ In order to avoid excessive typing edge aesthetic names are automatically expanded. Because of this it is not necessary to write \code{edge_colour} within the \code{aes()} call as \code{colour} will automatically be renamed appropriately. } \examples{ require(tidygraph) gr <- create_notable('zachary') \%>\% mutate(group = group_infomap()) \%>\% morph(to_split, group) \%>\% activate(edges) \%>\% mutate(edge_group = as.character(.N()$group[1])) \%>\% unmorph() ggraph(gr, 'matrix', sort.by = node_rank_hclust()) + geom_edge_tile(aes(fill = edge_group), mirror = TRUE) + scale_y_reverse() + coord_fixed() + labs(edge_colour = 'Infomap Cluster') + ggtitle("Zachary' Karate Club") } \seealso{ Other geom_edge_*: \code{\link{geom_edge_arc}()}, \code{\link{geom_edge_bend}()}, \code{\link{geom_edge_density}()}, \code{\link{geom_edge_diagonal}()}, \code{\link{geom_edge_elbow}()}, \code{\link{geom_edge_fan}()}, \code{\link{geom_edge_hive}()}, \code{\link{geom_edge_link}()}, \code{\link{geom_edge_loop}()}, \code{\link{geom_edge_parallel}()}, \code{\link{geom_edge_point}()}, \code{\link{geom_edge_span}()} } \author{ Thomas Lin Pedersen } \concept{geom_edge_*} ggraph/man/geom_axis_hive.Rd0000644000176200001440000000606313617226162015565 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geom_axis_hive.R \name{geom_axis_hive} \alias{geom_axis_hive} \title{Draw rectangular bars and labels on hive axes} \usage{ geom_axis_hive( mapping = NULL, data = NULL, position = "identity", label = TRUE, axis = TRUE, show.legend = NA, ... ) } \arguments{ \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2:aes]{aes()}} or \code{\link[ggplot2:aes_]{aes_()}}. If specified and \code{inherit.aes = TRUE} (the default), it is combined with the default mapping at the top level of the plot. You must supply \code{mapping} if there is no plot mapping.} \item{data}{The data to be displayed in this layer. There are three options: If \code{NULL}, the default, the data is inherited from the plot data as specified in the call to \code{\link[ggplot2:ggplot]{ggplot()}}. A \code{data.frame}, or other object, will override the plot data. All objects will be fortified to produce a data frame. See \code{\link[ggplot2:fortify]{fortify()}} for which variables will be created. A \code{function} will be called with a single argument, the plot data. The return value must be a \code{data.frame}, and will be used as the layer data. A \code{function} can be created from a \code{formula} (e.g. \code{~ head(.x, 10)}).} \item{position}{Position adjustment, either as a string, or the result of a call to a position adjustment function.} \item{label}{Should the axes be labelled. Defaults to \code{TRUE}} \item{axis}{Should a rectangle be drawn along the axis. Defaults to \code{TRUE}} \item{show.legend}{logical. Should this layer be included in the legends? \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE} never includes, and \code{TRUE} always includes. It can also be a named logical vector to finely select the aesthetics to display.} \item{...}{Other arguments passed on to \code{\link[ggplot2:layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like \code{colour = "red"} or \code{size = 3}. They may also be parameters to the paired geom/stat.} } \description{ This function lets you annotate the axes in a hive plot with labels and color coded bars. } \section{Aesthetics}{ geom_axis_hive understand the following aesthetics. \itemize{ \item{alpha} \item{colour} \item{fill} \item{size} \item{linetype} \item{label_size} \item{family} \item{fontface} \item{lineheight} } } \examples{ # Plot the flare import graph as a hive plot library(tidygraph) flareGr <- as_tbl_graph(flare$imports) \%>\% mutate( type = dplyr::case_when( centrality_degree(mode = 'in') == 0 ~ 'Source', centrality_degree(mode = 'out') == 0 ~ 'Sink', TRUE ~ 'Both' ) ) \%>\% activate(edges) \%>\% mutate( type = dplyr::case_when( grepl('flare.analytics', paste(.N()$name[from], .N()$name[to])) ~ 'Analytics', TRUE ~ 'Other' ) ) ggraph(flareGr, 'hive', axis = type) + geom_edge_hive(aes(colour = type), edge_alpha = 0.1) + geom_axis_hive(aes(colour = type)) + coord_fixed() } \author{ Thomas Lin Pedersen } ggraph/man/geom_edge_fan.Rd0000644000176200001440000002210613617226162015332 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geom_edge_fan.R \name{geom_edge_fan} \alias{geom_edge_fan} \alias{geom_edge_fan2} \alias{geom_edge_fan0} \title{Draw edges as curves of different curvature} \usage{ geom_edge_fan( mapping = NULL, data = get_edges(), position = "identity", arrow = NULL, strength = 1, n = 100, lineend = "butt", linejoin = "round", linemitre = 1, label_colour = "black", label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = "rot", force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ..., spread ) geom_edge_fan2( mapping = NULL, data = get_edges("long"), position = "identity", arrow = NULL, strength = 1, n = 100, lineend = "butt", linejoin = "round", linemitre = 1, label_colour = "black", label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = "rot", force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ..., spread ) geom_edge_fan0( mapping = NULL, data = get_edges(), position = "identity", arrow = NULL, strength = 1, lineend = "butt", show.legend = NA, ..., spread ) } \arguments{ \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2:aes]{ggplot2::aes()}} or \code{\link[ggplot2:aes_]{ggplot2::aes_()}}. By default x, y, xend, yend, group and circular are mapped to x, y, xend, yend, edge.id and circular in the edge data.} \item{data}{The return of a call to \code{get_edges()} or a data.frame giving edges in correct format (see details for for guidance on the format). See \code{\link[=get_edges]{get_edges()}} for more details on edge extraction.} \item{position}{Position adjustment, either as a string, or the result of a call to a position adjustment function.} \item{arrow}{Arrow specification, as created by \code{\link[grid:arrow]{grid::arrow()}}.} \item{strength}{Modify the width of the fans \code{strength > 1} will create wider fans while the reverse will make them more narrow.} \item{n}{The number of points to create along the path.} \item{lineend}{Line end style (round, butt, square).} \item{linejoin}{Line join style (round, mitre, bevel).} \item{linemitre}{Line mitre limit (number greater than 1).} \item{label_colour}{The colour of the edge label. If \code{NA} it will use the colour of the edge.} \item{label_alpha}{The opacity of the edge label. If \code{NA} it will use the opacity of the edge.} \item{label_parse}{If \code{TRUE}, the labels will be parsed into expressions and displayed as described in \code{\link[grDevices:plotmath]{grDevices::plotmath()}}.} \item{check_overlap}{If \code{TRUE}, text that overlaps previous text in the same layer will not be plotted.} \item{angle_calc}{Either 'none', 'along', or 'across'. If 'none' the label will use the angle aesthetic of the geom. If 'along' The label will be written along the edge direction. If 'across' the label will be written across the edge direction.} \item{force_flip}{Logical. If \code{angle_calc} is either 'along' or 'across' should the label be flipped if it is on it's head. Default to \code{TRUE}.} \item{label_dodge}{A \code{\link[grid:unit]{grid::unit()}} giving a fixed vertical shift to add to the label in case of \code{angle_calc} is either 'along' or 'across'} \item{label_push}{A \code{\link[grid:unit]{grid::unit()}} giving a fixed horizontal shift to add to the label in case of \code{angle_calc} is either 'along' or 'across'} \item{show.legend}{logical. Should this layer be included in the legends? \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE} never includes, and \code{TRUE} always includes. It can also be a named logical vector to finely select the aesthetics to display.} \item{...}{Other arguments passed on to \code{\link[ggplot2:layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like \code{colour = "red"} or \code{size = 3}. They may also be parameters to the paired geom/stat.} \item{spread}{Deprecated. Use \code{strength} instead.} } \description{ This geom draws edges as cubic beziers with the control point positioned half-way between the nodes and at an angle dependent on the presence of parallel edges. This results in parallel edges being drawn in a non-overlapping fashion resembling the standard approach used in \code{\link[igraph:plot.igraph]{igraph::plot.igraph()}}. Before calculating the curvature the edges are sorted by direction so that edges going the same way will be adjacent. This geom is currently the only choice for non-simple graphs if edges should not be overplotted. } \section{Aesthetics}{ \code{geom_edge_fan} and \code{geom_edge_fan0} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{y} \item \strong{xend} \item \strong{yend} \item \strong{from} \item \strong{to} \item edge_colour \item edge_width \item edge_linetype \item edge_alpha \item filter } \code{geom_edge_fan2} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{y} \item \strong{group} \item \strong{from} \item \strong{to} \item edge_colour \item edge_width \item edge_linetype \item edge_alpha \item filter } \code{geom_edge_fan} and \code{geom_edge_fan2} furthermore takes the following aesthetics. \itemize{ \item start_cap \item end_cap \item label \item label_pos \item label_size \item angle \item hjust \item vjust \item family \item fontface \item lineheight } } \section{Computed variables}{ \describe{ \item{index}{The position along the path (not computed for the *0 version)} } } \section{Edge variants}{ Many geom_edge_* layers comes in 3 flavors depending on the level of control needed over the drawing. The default (no numeric postfix) generate a number of points (\code{n}) along the edge and draws it as a path. Each point along the line has a numeric value associated with it giving the position along the path, and it is therefore possible to show the direction of the edge by mapping to this e.g. \code{colour = stat(index)}. The version postfixed with a "2" uses the "long" edge format (see \code{\link[=get_edges]{get_edges()}}) and makes it possible to interpolate node parameter between the start and end node along the edge. It is considerable less performant so should only be used if this is needed. The version postfixed with a "0" draws the edge in the most performant way, often directly using an appropriate grob from the grid package, but does not allow for gradients along the edge. Often it is beneficial to stop the drawing of the edge before it reaches the node, for instance in cases where an arrow should be drawn and the arrowhead shouldn't lay on top or below the node point. geom_edge_* and geom_edge_*2 supports this through the start_cap and end_cap aesthetics that takes a \code{\link[=geometry]{geometry()}} specification and dynamically caps the termini of the edges based on the given specifications. This means that if \code{end_cap = circle(1, 'cm')} the edges will end at a distance of 1cm even during resizing of the plot window. All \verb{geom_edge_*} and \code{geom_edge_*2} have the ability to draw a label along the edge. The reason this is not a separate geom is that in order for the label to know the location of the edge it needs to know the edge type etc. Labels are drawn by providing a label aesthetic. The label_pos can be used to specify where along the edge it should be drawn by supplying a number between 0 and 1. The label_size aesthetic can be used to control the size of the label. Often it is needed to have the label written along the direction of the edge, but since the actual angle is dependent on the plot dimensions this cannot be calculated beforehand. Using the angle_calc argument allows you to specify whether to use the supplied angle aesthetic or whether to draw the label along or across the edge. } \section{Edge aesthetic name expansion}{ In order to avoid excessive typing edge aesthetic names are automatically expanded. Because of this it is not necessary to write \code{edge_colour} within the \code{aes()} call as \code{colour} will automatically be renamed appropriately. } \examples{ require(tidygraph) gr <- create_notable('bull') \%>\% convert(to_directed) \%>\% bind_edges(data.frame(from = c(1, 2, 2, 3), to = c(2, 1, 3, 2))) \%E>\% mutate(class = sample(letters[1:3], 9, TRUE)) \%N>\% mutate(class = sample(c('x', 'y'), 5, TRUE)) ggraph(gr, 'stress') + geom_edge_fan(aes(alpha = stat(index))) ggraph(gr, 'stress') + geom_edge_fan2(aes(colour = node.class)) ggraph(gr, 'stress') + geom_edge_fan0(aes(colour = class)) } \seealso{ Other geom_edge_*: \code{\link{geom_edge_arc}()}, \code{\link{geom_edge_bend}()}, \code{\link{geom_edge_density}()}, \code{\link{geom_edge_diagonal}()}, \code{\link{geom_edge_elbow}()}, \code{\link{geom_edge_hive}()}, \code{\link{geom_edge_link}()}, \code{\link{geom_edge_loop}()}, \code{\link{geom_edge_parallel}()}, \code{\link{geom_edge_point}()}, \code{\link{geom_edge_span}()}, \code{\link{geom_edge_tile}()} } \author{ Thomas Lin Pedersen } \concept{geom_edge_*} ggraph/man/scale_edge_colour.Rd0000644000176200001440000002147413617226162016240 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/scale_edge_colour.R \name{scale_edge_colour} \alias{scale_edge_colour} \alias{scale_edge_colour_hue} \alias{scale_edge_colour_brewer} \alias{scale_edge_colour_distiller} \alias{scale_edge_colour_gradient} \alias{scale_edge_colour_gradient2} \alias{scale_edge_colour_gradientn} \alias{scale_edge_colour_grey} \alias{scale_edge_colour_identity} \alias{scale_edge_colour_manual} \alias{scale_edge_colour_viridis} \alias{scale_edge_colour_continuous} \alias{scale_edge_colour_discrete} \alias{scale_edge_color_hue} \alias{scale_edge_color_brewer} \alias{scale_edge_color_distiller} \alias{scale_edge_color_gradient} \alias{scale_edge_color_gradient2} \alias{scale_edge_color_gradientn} \alias{scale_edge_color_grey} \alias{scale_edge_color_identity} \alias{scale_edge_color_manual} \alias{scale_edge_color_continuous} \alias{scale_edge_color_discrete} \alias{scale_edge_color_viridis} \title{Edge colour scales} \usage{ scale_edge_colour_hue( ..., h = c(0, 360) + 15, c = 100, l = 65, h.start = 0, direction = 1, na.value = "grey50" ) scale_edge_colour_brewer(..., type = "seq", palette = 1, direction = 1) scale_edge_colour_distiller( ..., type = "seq", palette = 1, direction = -1, values = NULL, space = "Lab", na.value = "grey50", guide = "edge_colourbar" ) scale_edge_colour_gradient( ..., low = "#132B43", high = "#56B1F7", space = "Lab", na.value = "grey50", guide = "edge_colourbar" ) scale_edge_colour_gradient2( ..., low = muted("red"), mid = "white", high = muted("blue"), midpoint = 0, space = "Lab", na.value = "grey50", guide = "edge_colourbar" ) scale_edge_colour_gradientn( ..., colours, values = NULL, space = "Lab", na.value = "grey50", guide = "edge_colourbar", colors ) scale_edge_colour_grey(..., start = 0.2, end = 0.8, na.value = "red") scale_edge_colour_identity(..., guide = "none") scale_edge_colour_manual(..., values) scale_edge_colour_viridis( ..., alpha = 1, begin = 0, end = 1, discrete = FALSE, option = "D", direction = 1 ) scale_edge_colour_continuous( ..., low = "#132B43", high = "#56B1F7", space = "Lab", na.value = "grey50", guide = "edge_colourbar" ) scale_edge_colour_discrete( ..., h = c(0, 360) + 15, c = 100, l = 65, h.start = 0, direction = 1, na.value = "grey50" ) scale_edge_color_hue( ..., h = c(0, 360) + 15, c = 100, l = 65, h.start = 0, direction = 1, na.value = "grey50" ) scale_edge_color_brewer(..., type = "seq", palette = 1, direction = 1) scale_edge_color_distiller( ..., type = "seq", palette = 1, direction = -1, values = NULL, space = "Lab", na.value = "grey50", guide = "edge_colourbar" ) scale_edge_color_gradient( ..., low = "#132B43", high = "#56B1F7", space = "Lab", na.value = "grey50", guide = "edge_colourbar" ) scale_edge_color_gradient2( ..., low = muted("red"), mid = "white", high = muted("blue"), midpoint = 0, space = "Lab", na.value = "grey50", guide = "edge_colourbar" ) scale_edge_color_gradientn( ..., colours, values = NULL, space = "Lab", na.value = "grey50", guide = "edge_colourbar", colors ) scale_edge_color_grey(..., start = 0.2, end = 0.8, na.value = "red") scale_edge_color_identity(..., guide = "none") scale_edge_color_manual(..., values) scale_edge_color_continuous( ..., low = "#132B43", high = "#56B1F7", space = "Lab", na.value = "grey50", guide = "edge_colourbar" ) scale_edge_color_discrete( ..., h = c(0, 360) + 15, c = 100, l = 65, h.start = 0, direction = 1, na.value = "grey50" ) scale_edge_color_viridis( ..., alpha = 1, begin = 0, end = 1, discrete = FALSE, option = "D", direction = 1 ) } \arguments{ \item{...}{Arguments passed on to \code{discrete_scale} \describe{ \item{palette}{A palette function that when called with a single integer argument (the number of levels in the scale) returns the values that they should take.} \item{breaks}{One of: \itemize{ \item \code{NULL} for no breaks \item \code{waiver()} for the default breaks computed by the transformation object \item A character vector of breaks \item A function that takes the limits as input and returns breaks as output }} \item{limits}{A character vector that defines possible values of the scale and their order.} \item{drop}{Should unused factor levels be omitted from the scale? The default, \code{TRUE}, uses the levels that appear in the data; \code{FALSE} uses all the levels in the factor.} \item{na.translate}{Unlike continuous scales, discrete scales can easily show missing values, and do so by default. If you want to remove missing values from a discrete scale, specify \code{na.translate = FALSE}.} \item{na.value}{If \code{na.translate = TRUE}, what value aesthetic value should missing be displayed as? Does not apply to position scales where \code{NA} is always placed at the far right.} \item{scale_name}{The name of the scale} \item{name}{The name of the scale. Used as the axis or legend title. If \code{waiver()}, the default, the name of the scale is taken from the first mapping used for that aesthetic. If \code{NULL}, the legend title will be omitted.} \item{labels}{One of: \itemize{ \item \code{NULL} for no labels \item \code{waiver()} for the default labels computed by the transformation object \item A character vector giving labels (must be same length as \code{breaks}) \item A function that takes the breaks as input and returns labels as output }} \item{expand}{Vector of range expansion constants used to add some padding around the data, to ensure that they are placed some distance away from the axes. Use the convenience function \code{\link[ggplot2:expand_scale]{expand_scale()}} to generate the values for the \code{expand} argument. The defaults are to expand the scale by 5\% on each side for continuous variables, and by 0.6 units on each side for discrete variables.} \item{guide}{A function used to create a guide or its name. See \code{\link[ggplot2:guides]{guides()}} for more info.} \item{position}{The position of the axis. "left" or "right" for vertical scales, "top" or "bottom" for horizontal scales} \item{super}{The super class to use for the constructed scale} }} \item{h}{range of hues to use, in [0, 360]} \item{c}{chroma (intensity of colour), maximum value varies depending on combination of hue and luminance.} \item{l}{luminance (lightness), in [0, 100]} \item{h.start}{hue to start at} \item{direction}{direction to travel around the colour wheel, 1 = clockwise, -1 = counter-clockwise} \item{na.value}{Colour to use for missing values} \item{type}{One of seq (sequential), div (diverging) or qual (qualitative)} \item{palette}{If a string, will use that named palette. If a number, will index into the list of palettes of appropriate \code{type}} \item{values}{if colours should not be evenly positioned along the gradient this vector gives the position (between 0 and 1) for each colour in the \code{colours} vector. See \code{\link[=rescale]{rescale()}} for a convenience function to map an arbitrary range to between 0 and 1.} \item{space}{colour space in which to calculate gradient. Must be "Lab" - other values are deprecated.} \item{guide}{Type of legend. Use \code{"colourbar"} for continuous colour bar, or \code{"legend"} for discrete colour legend.} \item{low, high}{Colours for low and high ends of the gradient.} \item{mid}{colour for mid point} \item{midpoint}{The midpoint (in data value) of the diverging scale. Defaults to 0.} \item{colours, colors}{Vector of colours to use for n-colour gradient.} \item{start}{grey value at low end of palette} \item{end}{grey value at high end of palette} \item{alpha}{pass through parameter to \code{viridis}} \item{begin}{The (corrected) hue in [0,1] at which the viridis colormap begins.} \item{discrete}{generate a discrete palette? (default: \code{FALSE} - generate continuous palette)} \item{option}{A character string indicating the colormap option to use. Four options are available: "magma" (or "A"), "inferno" (or "B"), "plasma" (or "C"), "viridis" (or "D", the default option) and "cividis" (or "E").} } \value{ A ggproto object inheriting from \code{Scale} } \description{ This set of scales defines new colour scales for edge geoms equivalent to the ones already defined by ggplot2. The parameters are equivalent to the ones from ggplot2 so there is nothing new under the sun. The different geoms will know whether to use edge scales or the standard scales so it is not necessary to write \code{edge_colour} in the call to the geom - just use \code{colour}. } \seealso{ Other scale_edge_*: \code{\link{scale_edge_alpha}()}, \code{\link{scale_edge_fill}}, \code{\link{scale_edge_linetype}()}, \code{\link{scale_edge_shape}()}, \code{\link{scale_edge_size}()}, \code{\link{scale_edge_width}()}, \code{\link{scale_label_size}()} } \concept{scale_edge_*} ggraph/man/geom_node_range.Rd0000644000176200001440000000526413617226162015711 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/geom_node_range.R \name{geom_node_range} \alias{geom_node_range} \title{Show nodes as a line spanning a horizontal range} \usage{ geom_node_range( mapping = NULL, data = NULL, position = "identity", show.legend = NA, ... ) } \arguments{ \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2:aes]{ggplot2::aes()}} or \code{\link[ggplot2:aes_]{ggplot2::aes_()}}. By default x is mapped to xmin, xend is mapped to xmax and y and yend are mapped to y in the node data.} \item{data}{The data to be displayed in this layer. There are three options: If \code{NULL}, the default, the data is inherited from the plot data as specified in the call to \code{\link[ggplot2:ggplot]{ggplot()}}. A \code{data.frame}, or other object, will override the plot data. All objects will be fortified to produce a data frame. See \code{\link[ggplot2:fortify]{fortify()}} for which variables will be created. A \code{function} will be called with a single argument, the plot data. The return value must be a \code{data.frame}, and will be used as the layer data. A \code{function} can be created from a \code{formula} (e.g. \code{~ head(.x, 10)}).} \item{position}{Position adjustment, either as a string, or the result of a call to a position adjustment function.} \item{show.legend}{logical. Should this layer be included in the legends? \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE} never includes, and \code{TRUE} always includes. It can also be a named logical vector to finely select the aesthetics to display.} \item{...}{Other arguments passed on to \code{\link[ggplot2:layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like \code{colour = "red"} or \code{size = 3}. They may also be parameters to the paired geom/stat.} } \description{ This geom is most useful together with the \link[=layout_tbl_graph_fabric]{fabric} layout for showing the horizontal span of each node. } \section{Aesthetics}{ \code{geom_node_point} understand the following aesthetics. Bold aesthetics are automatically set, but can be overridden. \itemize{ \item \strong{x} \item \strong{xend} \item \strong{y} \item \strong{yend} \item alpha \item colour \item linetype \item size \item filter } } \examples{ require(tidygraph) gr <- as_tbl_graph(highschool) ggraph(gr, layout = 'fabric') + geom_node_range() } \seealso{ Other geom_node_*: \code{\link{geom_node_arc_bar}()}, \code{\link{geom_node_circle}()}, \code{\link{geom_node_point}()}, \code{\link{geom_node_text}()}, \code{\link{geom_node_tile}()}, \code{\link{geom_node_voronoi}()} } \author{ Thomas Lin Pedersen } \concept{geom_node_*} ggraph/man/scale_edge_alpha.Rd0000644000176200001440000000366613617226162016025 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/scale_edge_alpha.R \name{scale_edge_alpha} \alias{scale_edge_alpha} \alias{scale_edge_alpha_continuous} \alias{scale_edge_alpha_discrete} \alias{scale_edge_alpha_manual} \alias{scale_edge_alpha_identity} \title{Edge alpha scales} \usage{ scale_edge_alpha(..., range = c(0.1, 1)) scale_edge_alpha_continuous(..., range = c(0.1, 1)) scale_edge_alpha_discrete(..., range = c(0.1, 1)) scale_edge_alpha_manual(..., values) scale_edge_alpha_identity(..., guide = "none") } \arguments{ \item{...}{Other arguments passed on to \code{\link[ggplot2:continuous_scale]{continuous_scale()}} or \code{\link[ggplot2:discrete_scale]{discrete_scale()}} as appropriate, to control name, limits, breaks, labels and so forth.} \item{range}{Output range of alpha values. Must lie between 0 and 1.} \item{values}{a set of aesthetic values to map data values to. If this is a named vector, then the values will be matched based on the names. If unnamed, values will be matched in order (usually alphabetical) with the limits of the scale. Any data values that don't match will be given \code{na.value}.} \item{guide}{Guide to use for this scale. Defaults to \code{"none"}.} } \value{ A ggproto object inheriting from \code{Scale} } \description{ This set of scales defines new alpha scales for edge geoms equivalent to the ones already defined by ggplot2. See \code{\link[ggplot2:scale_alpha]{ggplot2::scale_alpha()}} for more information. The different geoms will know whether to use edge scales or the standard scales so it is not necessary to write \code{edge_alpha} in the call to the geom - just use \code{alpha}. } \seealso{ Other scale_edge_*: \code{\link{scale_edge_colour}}, \code{\link{scale_edge_fill}}, \code{\link{scale_edge_linetype}()}, \code{\link{scale_edge_shape}()}, \code{\link{scale_edge_size}()}, \code{\link{scale_edge_width}()}, \code{\link{scale_label_size}()} } \concept{scale_edge_*} ggraph/DESCRIPTION0000644000176200001440000000305613617251652013244 0ustar liggesusersPackage: ggraph Type: Package Title: An Implementation of Grammar of Graphics for Graphs and Networks Version: 2.0.1 Authors@R: c(person(given = "Thomas Lin", family = "Pedersen", role = c("cre", "aut"), email = "thomasp85@gmail.com", comment = c(ORCID = "0000-0002-5147-4711")), person(given = "RStudio", role = "cph")) Maintainer: Thomas Lin Pedersen Description: The grammar of graphics as implemented in ggplot2 is a poor fit for graph and network visualizations due to its reliance on tabular data input. ggraph is an extension of the ggplot2 API tailored to graph visualizations and provides the same flexible approach to building up plots layer by layer. License: MIT + file LICENSE Encoding: UTF-8 LazyData: TRUE Imports: Rcpp (>= 0.12.2), dplyr, ggforce (>= 0.3.1), grid, igraph (>= 1.0.0), scales, MASS, digest, gtable, ggrepel, utils, stats, viridis, rlang, tidygraph, graphlayouts (>= 0.5.0) Suggests: network, knitr, rmarkdown, purrr, tibble, seriation, deldir, gganimate LinkingTo: Rcpp RoxygenNote: 7.0.2 Depends: R (>= 2.10), ggplot2 (>= 3.0.0) VignetteBuilder: knitr URL: https://ggraph.data-imaginist.com, https://github.com/thomasp85/ggraph BugReports: https://github.com/thomasp85/ggraph/issues NeedsCompilation: yes Packaged: 2020-02-07 10:43:31 UTC; thomas Author: Thomas Lin Pedersen [cre, aut] (), RStudio [cph] Repository: CRAN Date/Publication: 2020-02-07 12:10:18 UTC ggraph/build/0000755000176200001440000000000013617237522012631 5ustar liggesusersggraph/build/vignette.rds0000644000176200001440000000040613617237522015170 0ustar liggesusersRK@ ?'&zN)17bZ:}kfuчh@j!`3xMx,8echËMEgBQl+"QM+!X*OK̮*gWPmM#S48J"qgv!thA aAU酑Cj`R^d"hõ,ni}7.;,ߎN |״v:jggraph/src/0000755000176200001440000000000013617237522012321 5ustar liggesusersggraph/src/pathAttr.cpp0000644000176200001440000000256213524234420014610 0ustar liggesusers#include using namespace Rcpp; //[[Rcpp::export]] DataFrame pathAttr(DataFrame paths, int ngroups) { LogicalVector solid(ngroups, true); LogicalVector constant(ngroups, true); int currentGroup, currentIndex, i; IntegerVector group = paths["group"]; NumericVector alpha = paths["edge_alpha"]; LogicalVector alpha_na = is_na(alpha); NumericVector width = paths["edge_width"]; LogicalVector width_na = is_na(width); CharacterVector lty = paths["edge_linetype"]; LogicalVector lty_na = is_na(lty); CharacterVector colour = paths["edge_colour"]; LogicalVector colour_na = is_na(colour); currentGroup = group[0]; currentIndex = 0; for (i = 1; i < group.size(); ++i) { if (group[i] == currentGroup) { if (solid[currentIndex]) { solid[currentIndex] = lty[i] == "solid" && lty[i] == lty[i-1]; } if (constant[currentIndex]) { constant[currentIndex] = ((alpha[i] == alpha[i-1]) || (alpha_na[i] && alpha_na[i-1])) && ((width[i] == width[i-1]) || (width_na[i] && width_na[i-1])) && ((lty[i] == lty[i-1]) || (lty_na[i] && lty_na[i-1])) && ((colour[i] == colour[i-1]) || (colour_na[i] && colour_na[i-1])); } } else { currentGroup = group[i]; ++currentIndex; } } return DataFrame::create( Named("solid") = solid, Named("constant") = constant ); } ggraph/src/nodes.h0000644000176200001440000001075613527301120013575 0ustar liggesusers#include using namespace Rcpp; struct Rectangle { double x; double y; double width; double height; }; class Node { std::vector children; std::vector< std::vector > allLeafs; Node* parent; bool hasParent; double Weight; double Height; int Id; int Order; static bool comparePtrToNode(Node* a, Node* b) { return (a->order() < b->order()); } public: Node() { hasParent = false; Weight = 0.0; Height = 0.0; Id = 0; Order = 0; }; Node(int id, int order, double weight) { hasParent = false; Id = id; Order = order; Weight = weight; }; Node(int id, int order, double weight, double height) { hasParent = false; Id = id; Order = order; Weight = weight; Height = height; }; void rotate(double angle, double x, double y) { double s = sin(angle); double c = cos(angle); // translate point back to origin: double new_x = bounds.x - x; double new_y = bounds.y - y; // rotate point bounds.x = new_x * c - new_y * s + x; bounds.y = new_x * s + new_y * c + y; for (unsigned int i = 0; i < children.size(); ++i) { children[i]->rotate(angle, x, y); } }; std::vector getChildren() { std::vector childVec; for (unsigned int i = 0; i < children.size(); ++i) { childVec.push_back(children[i]); } return childVec; }; Node* getParent() { return parent; }; std::vector getLeafs() { std::vector leafVec; collectLeafs(leafVec); return leafVec; }; void collectLeafs(std::vector &leafVec) { if (leaf()) { leafVec.push_back(this); } else { for (unsigned int i = 0; i < children.size(); ++i) { children[i]->collectLeafs(leafVec); } } }; std::vector getParentLeafs() { std::vector leafVec; collectParentLeafs(leafVec); return leafVec; }; void collectParentLeafs(std::vector &leafVec) { if (orphan()) return; std::vector siblings = parent->children; for (unsigned int i = 0; i < siblings.size(); ++i) { if (siblings[i] == this) continue; siblings[i]->collectLeafs(leafVec); } parent->collectParentLeafs(leafVec); }; void collectAllLeafs() { if (orphan()) return; for (unsigned int i = 0; i < children.size(); ++i) { allLeafs.push_back(children[i]->getLeafs()); } allLeafs.push_back(getParentLeafs()); }; std::vector< std::vector > getAllLeafs() { if (allLeafs.empty()) { collectAllLeafs(); } std::vector< std::vector > leafs; for (unsigned int i = 0; i < allLeafs.size(); ++i) { std::vector leafVec; for (unsigned int j = 0; j < allLeafs[i].size(); ++j) { leafVec.push_back(allLeafs[i][j]); } leafs.push_back(leafVec); } return leafs; } unsigned int nChildren() { return children.size(); }; unsigned int nOffspring() { unsigned int ret = nChildren(); for (unsigned int i = 0; i < ret; ++i) { ret += children[i]->nOffspring(); } return ret; }; unsigned int nLeafs() { if (leaf()) return 1; unsigned int ret = 0; for (unsigned int i = 0; i < nChildren(); ++i) { if (children[i]->leaf()) { ret++; } else { ret += children[i]->nLeafs(); } } return ret; } double weight() { return Weight; }; double height() { return Height; }; void addNode(Node* n) { double w = n->weight(); Weight += w; if (hasParent) { parent->addWeight(w); } n->setParent(this); children.push_back(n); }; void addWeight(double w) { if (hasParent) { parent->addWeight(w); } Weight += w; }; void setParent(Node* n) { hasParent = true; parent = n; }; bool orphan() { return !hasParent; }; bool leaf() { return nChildren() == 0; } int order() { return Order; }; void sortChildren() { std::sort(children.begin(), children.end(), comparePtrToNode); }; Node* getRoot() { if (orphan()) { return this; } else { return parent->getRoot(); } } Rectangle bounds; }; std::vector createHierarchy(std::vector parent, std::vector order, std::vector weight); std::vector createHierarchy(std::vector parent, std::vector order, std::vector weight, std::vector height); std::vector createUnrooted(std::vector parent, std::vector order, std::vector length); ggraph/src/circlePack.cpp0000644000176200001440000003212313527300762015063 0ustar liggesusers#include #include using namespace Rcpp; inline int randWrapper(const int n) { return std::floor(float(unif_rand()*n)); } struct Circle { double x; double y; double r; int i; Circle* next; Circle* prev; }; class FrontChain { double center_x; double center_y; double total_weight; Circle enclosure; Circle* next_circle; void place(Circle* c, Circle* c1, Circle* c2) { double r0 = c1->r + c->r; double r1 = c2->r + c->r; double dx = c2->x - c1->x; double dy = c2->y - c1->y; double d = std::sqrt(float(dx*dx + dy*dy)); double a = (r0*r0 - r1*r1 + d*d) / (2*d); double h = std::sqrt(float(r0*r0 - a*a)); c->x = c1->x + a*(c2->x - c1->x)/d - h*(c2->y - c1->y)/d; c->y = c1->y + a*(c2->y - c1->y)/d + h*(c2->x - c1->x)/d; }; double dist_to_center(Circle* c) { double dx = c->x - center_x / total_weight; double dy = c->y - center_y / total_weight; return c->r + std::sqrt(float(dx*dx + dy*dy)); } bool circles_intersect(Circle* c1, Circle* c2) { double dx = c2->x - c1->x; double dy = c2->y - c1->y; double dr = c2->r + c1-> r; return dr*dr > dx*dx + dy*dy; }; void update_closest_circle() { Circle* circle_it = next_circle; Circle* current_close = circle_it; double dist = dist_to_center(circle_it); circle_it = circle_it->next; while(circle_it != next_circle) { double temp_dist = dist_to_center(circle_it); if (temp_dist < dist) { dist = temp_dist; current_close = circle_it; } circle_it = circle_it->next; } next_circle = current_close; }; void update_chain(Circle* from, Circle* to) { from->next = to; to->prev = from; }; bool encloses(Circle enc, Circle* c) { double dx = c->x - enc.x; double dy = c->y - enc.y; double dr = c->r - enc.r; return dr * dr + 1e-6 > dx * dx + dy * dy; }; Circle enclose1(Circle* c1) { Circle enc = {c1->x, c1->y, c1->r}; if (enc.r > 1e10) { stop("enc1 error"); } return enc; }; Circle enclose2(Circle* c1, Circle* c2) { double dx = c2->x - c1->x; double dy = c2->y - c1->y; double dr = c2->r - c1->r; double l = std::sqrt(float(dx*dx + dy*dy)); Circle enc = { (c1->x + c2->x + dx / l * dr) / 2, (c1->y + c2->y + dy / l * dr) / 2, (l + c1->r + c2->r) / 2 }; if (enc.r > 1e10) { stop("enc2 error"); } return enc; }; Circle enclose3(Circle* c1, Circle* c2, Circle* c3) { double A2 = 2 * (c1->x - c2->x); double B2 = 2 * (c1->y - c2->y); double C2 = 2 * (c2->r - c1->r); double D2 = c1->x * c1->x + c1->y * c1->y - c1->r * c1->r - c2->x * c2->x - c2->y * c2->y + c2->r * c2->r; double A3 = 2 * (c1->x - c3->x); double B3 = 2 * (c1->y - c3->y); double C3 = 2 * (c3->r - c1->r); double D3 = c1->x * c1->x + c1->y * c1->y - c1->r * c1->r - c3->x * c3->x - c3->y * c3->y + c3->r * c3->r; double AB = A3 * B2 - A2 * B3; double XA = (B2 * D3 - B3 * D2) / AB - c1->x; double XB = (B3 * C2 - B2 * C3) / AB; double YA = (A3 * D2 - A2 * D3) / AB - c1->y; double YB = (A2 * C3 - A3 * C2) / AB; double A = XB * XB + YB * YB - 1; double B = 2 * (XA * XB + YA * YB + c1->r); double C = XA * XA + YA * YA - c1->r * c1->r; double r = (-B - std::sqrt(float(B * B - 4 * A * C))) / (2 * A); Circle enc = { XA + XB * r + c1->x, YA + YB * r + c1->y, r }; if (enc.r > 1e10) { stop("enc3 error"); } return enc; }; Circle encloseN(std::deque::iterator begin_S, std::deque::iterator end_S, std::deque Q) { Circle enc = {0, 0, 0}; bool has_enc = false; switch (Q.size()) { case 1: enc = enclose1(Q[0]); has_enc = true; break; case 2: enc = enclose2(Q[0], Q[1]); has_enc = true; break; case 3: enc = enclose3(Q[0], Q[1], Q[2]); has_enc = true; break; } std::deque::iterator S_iter = begin_S; while (S_iter != end_S) { if (!has_enc || !encloses(enc, *S_iter)) { Q.push_back(*S_iter); enc = encloseN(begin_S, S_iter, Q); has_enc = true; Q.pop_back(); } S_iter++; } return enc; }; public: FrontChain(Circle* c1, Circle* c2, Circle* c3) { // Set location of first circle to coord center and second at random angle c1->x = 0; c1->y = 0; double d12 = c1->r + c2->r; double angle2 = (2 * M_PI) * R::runif(0, 1); c2->x = std::cos(float(angle2)) * d12; c2->y = std::sin(float(angle2)) * d12; // Place third circle according to the first two place(c3, c2, c1); // Set weighted center double a1 = c1->r*c1->r; double a2 = c2->r*c2->r; double a3 = c3->r*c3->r; total_weight = a1 + a2 + a3; center_x = a1*c1->x + a2*c2->x + a3*c3->x; center_y = a1*c1->y + a2*c2->y + a3*c3->y; // Inititalize the front chain c1->next = c2; c1->prev = c3; c2->next = c3; c2->prev = c1; c3->next = c1; c3->prev = c2; next_circle = c1; }; FrontChain(Circle* c1, Circle* c2) { // Set location of first circle to coord center and second at random angle c1->x = 0; c1->y = 0; double d12 = c1->r + c2->r; double angle2 = (2 * M_PI) * R::runif(0, 1); c2->x = std::cos(float(angle2)) * d12; c2->y = std::sin(float(angle2)) * d12; // Inititalize the front chain c1->next = c2; c1->prev = c2; c2->next = c1; c2->prev = c1; next_circle = c1; }; FrontChain(Circle* c1) { // Set location of first circle to coord center and second at random angle c1->x = 0; c1->y = 0; // Inititalize the front chain c1->next = c1; c1->prev = c1; next_circle = c1; }; void add(Circle* c) { Circle* n = next_circle; Circle* m = next_circle->next; place(c, n, m); bool at_m = false; bool at_n = false; while(true) { n = n->prev; if (n == m) break; if (circles_intersect(c, n)) { at_n = true; break; } m = m->next; if (n == m) break; if (circles_intersect(c, m)) { at_m = true; break; } } if (at_m) { update_chain(next_circle, m); add(c); } else if (at_n) { update_chain(n, next_circle->next); next_circle = n; add(c); } else { c->next = next_circle->next; c->prev = next_circle; next_circle->next->prev = c; next_circle->next = c; double a = c->r*c->r; total_weight += a; center_x += a*c->x; center_y += a*c->y; update_closest_circle(); } }; void center(std::deque::iterator first, std::deque::iterator last) { while (first != last) { first->x -= enclosure.x; first->y -= enclosure.y; first++; } enclosure.x = 0; enclosure.y = 0; }; double enclose_radius() { return enclosure.r; }; std::deque chain_ind() { std::deque index; Circle* circle_it = next_circle; index.push_back(circle_it->i); circle_it = circle_it->next; while(circle_it != next_circle) { index.push_back(circle_it->i); circle_it = circle_it->next; } return index; }; void enclose() { std::deque fc; Circle* circle_it = next_circle; fc.push_back(circle_it); circle_it = circle_it->next; while(circle_it != next_circle) { fc.push_back(circle_it); circle_it = circle_it->next; } if (fc.size() == 1) { enclosure = enclose1(fc[0]); } else if (fc.size() == 2) { enclosure = enclose2(fc[0], fc[1]); } else { std::random_shuffle(fc.begin(), fc.end(), randWrapper); std::deque Q; enclosure = encloseN(fc.begin(), fc.end(), Q); } } }; FrontChain pack_circles(std::deque &circles) { if (circles.size() == 0) { stop("Cannot pack an empty set of circles"); } std::deque::iterator itc; FrontChain fc(&circles[0]); if (circles.size() == 2) { fc = FrontChain(&circles[0], &circles[1]); } else if (circles.size() > 2){ fc = FrontChain(&circles[0], &circles[1], &circles[2]); for (itc = circles.begin() + 3; itc != circles.end(); itc++) { fc.add(&(*itc)); } } fc.enclose(); fc.center(circles.begin(), circles.end()); return fc; } class NodePack { std::vector children; NodePack* parent; bool hasParent; public: int Id; double x; double y; double r; NodePack() { hasParent = false; }; NodePack(int id, double weight) { hasParent = false; Id = id; x = 0.0; y = 0.0; r = std::sqrt(float( weight / M_PI)); }; int nChildren() { return children.size(); }; void addNode(NodePack* n) { n->setParent(this); children.push_back(n); }; void setParent(NodePack* n) { hasParent = true; parent = n; }; bool orphan() { return !hasParent; }; void packChildren() { if (nChildren() != 0) { std::deque circles; int i; double max_r = 0; int max_r_i = 0; for (i = 0; i != nChildren(); i++) { children[i]->packChildren(); if (children[i]->r > max_r) { max_r = children[i]->r; max_r_i = i; } Circle circ = {0, 0, children[i]->r, children[i]->Id}; circles.push_back(circ); } std::swap(children[0], children[max_r_i]); std::swap(circles[0], circles[max_r_i]); FrontChain fc = pack_circles(circles); for (i = 0; i != nChildren(); i++) { children[i]->x = circles[i].x; children[i]->y = circles[i].y; } r = fc.enclose_radius(); } }; void placeChildren(double center_x, double center_y) { x += center_x; y += center_y; if (nChildren() != 0) { int i; for (i = 0; i != nChildren(); i++) { children[i]->placeChildren(x, y); } } } }; std::vector createHierarchy(std::vector parent, std::vector weight) { std::vector nodes; unsigned int i; for (i = 0; i < parent.size(); ++i) { NodePack* node = new NodePack(i + 1, weight[i]); nodes.push_back(node); } for (i = 0; i < parent.size(); ++i) { if (parent[i] >= 0) { nodes[parent[i]]->addNode(nodes[i]); } } return nodes; } int findTopNode(std::vector& nodes) { unsigned int i; bool found = false; for (i = 0; i < nodes.size(); ++i) { if (nodes[i]->orphan()) { found = true; break; } } if (!found) { stop("No top node. Is this a tree structure?"); } return (int) i; } //' Pack circles together //' //' This function is a direct interface to the circle packing algorithm used by //' \code{\link{layout_tbl_graph_circlepack}}. It takes a vector of sizes and //' returns the x and y position of each circle as a two-column matrix. //' //' @param areas A vector of circle areas //' //' @return A matrix with two columns and the same number of rows as the length //' of the "areas" vector. The matrix has the following attributes added: //' "enclosing_radius" giving the radius of the smallest enclosing circle, and //' "front_chain" giving the terminating members of the front chain (see //' Wang \emph{et al}. 2006). //' //' @references //' Wang, W., Wang, H. H., Dai, G., & Wang, H. (2006). \emph{Visualization of //' large hierarchical data by circle packing}. Chi, 517-520. //' //' @export //' //' @examples //' library(ggforce) //' sizes <- sample(10, 100, TRUE) //' //' position <- pack_circles(sizes) //' data <- data.frame(x = position[,1], y = position[,2], r = sqrt(sizes/pi)) //' //' ggplot() + //' geom_circle(aes(x0 = x, y0 = y, r = r), data = data, fill = 'steelblue') + //' geom_circle(aes(x0 = 0, y0 = 0, r = attr(position, 'enclosing_radius'))) + //' geom_polygon(aes(x = x, y = y), //' data = data[attr(position, 'front_chain'), ], //' fill = NA, //' colour = 'black') //' //[[Rcpp::export(name = "pack_circles")]] NumericMatrix pack(NumericVector areas) { NumericVector::iterator itr; std::deque circles; NumericMatrix res(areas.size(), 2); for (itr = areas.begin(); itr != areas.end(); itr++) { Circle c = {0, 0, std::sqrt(float(*itr / M_PI)), static_cast(circles.size()) + 1}; circles.push_back(c); } if (circles.size() == 0) { res.attr("enclosing_radius") = 0; res.attr("front_chain") = IntegerVector(0); } else { FrontChain fc = pack_circles(circles); for (int i = 0; i < areas.size(); i++) { res(i, 0) = circles[i].x; res(i, 1) = circles[i].y; } res.attr("enclosing_radius") = fc.enclose_radius(); res.attr("front_chain") = wrap(fc.chain_ind()); } return res; } //[[Rcpp::export]] NumericMatrix circlePackLayout(IntegerVector parent, NumericVector weight) { NumericMatrix res(parent.size(), 3); unsigned int i; std::vector nodes = createHierarchy(as< std::vector >(parent), as< std::vector >(weight)); int startNode = findTopNode(nodes); nodes[startNode]->packChildren(); nodes[startNode]->placeChildren(0.0, 0.0); for (i = 0; i < nodes.size(); ++i) { res(i, 0) = nodes[i]->x; res(i, 1) = nodes[i]->y; res(i, 2) = nodes[i]->r; delete nodes[i]; } return res; } ggraph/src/unrooted.cpp0000644000176200001440000001045513527511216014664 0ustar liggesusers#include #include "nodes.h" using namespace Rcpp; const double PI2 = 6.28318; void equalAngle(Node* node, double start, double angle) { if (node->leaf()) return; double x = node->bounds.x; double y = node->bounds.y; std::vector children = node->getChildren(); for (unsigned int i = 0; i < children.size(); ++i) { double span = children[i]->nLeafs() * angle; double mid = start + span / 2; children[i]->bounds.x = x + cos(mid) * children[i]->height(); children[i]->bounds.y = y + sin(mid) * children[i]->height(); equalAngle(children[i], start, angle); start += span; } } double equalDaylight(Node* node, double rotation_mod) { if (node->orphan() || node->getParent()->orphan()) return 0; if (node->nChildren() < 2) return 0; std::vector< std::vector > leafs = node->getAllLeafs(); std::vector children = node->getChildren(); std::vector bounds_lower, bounds_upper; double x0 = node->bounds.x; double y0 = node->bounds.y; for (unsigned int i = 0; i < leafs.size(); ++i) { double x1, y1; if (i < children.size()) { x1 = children[i]->bounds.x - x0; y1 = children[i]->bounds.y - y0; } else { x1 = node->getParent()->bounds.x - x0; y1 = node->getParent()->bounds.y - y0; } int lower = 0; int upper = 0; double lower_angle = PI2; double upper_angle = -PI2; for (unsigned int j = 0; j < leafs[i].size(); ++j) { double x2 = leafs[i][j]->bounds.x - x0; double y2 = leafs[i][j]->bounds.y - y0; double dot = x1 * x2 + y1 * y2; double det = x1 * y2 - y1 * x2; double angle = atan2(det, dot); if (angle < lower_angle) { lower_angle = angle; lower = j; } if (angle > upper_angle) { upper_angle = angle; upper = j; } } bounds_lower.push_back(leafs[i][lower]); bounds_upper.push_back(leafs[i][upper]); } std::vector daylight; double daylight_total = 0; for (unsigned int i = 0; i < leafs.size(); ++i) { int prev_i = i - 1; if (prev_i < 0) prev_i = leafs.size() - 1; double x1 = bounds_upper[prev_i]->bounds.x - x0; double y1 = bounds_upper[prev_i]->bounds.y - y0; double x2 = bounds_lower[i]->bounds.x - x0; double y2 = bounds_lower[i]->bounds.y - y0; double dot = x1 * x2 + y1 * y2; double det = x1 * y2 - y1 * x2; double d = atan2(det, dot); daylight.push_back(d); daylight_total += d; } double daylight_ave = daylight_total / leafs.size(); double daylight_acc = 0; double daylight_change = 0; for (unsigned int i = 0; i < leafs.size() - 1; ++i) { daylight_acc = daylight_ave - daylight[i] + daylight_acc; children[i]->rotate(daylight_acc * rotation_mod, x0, y0); daylight_change += fabs(daylight_acc); } return daylight_change / leafs.size(); } //[[Rcpp::export]] NumericMatrix unrooted(IntegerVector parent, IntegerVector order, NumericVector length, bool daylight, double tol, double rotation_mod, int maxiter) { NumericMatrix rect(parent.size(), 2); std::vector nodes = createUnrooted(as< std::vector >(parent), as< std::vector >(order), as< std::vector >(length)); Node* startNode = nodes[0]->getRoot(); std::vector children = startNode->getChildren(); for (unsigned int i = 0; i < children.size(); ++i) { int n_leafs = children[i]->nLeafs(); double split_angle = PI2 / n_leafs; children[i]->bounds.x = 0; children[i]->bounds.y = 0; equalAngle(children[i], 0, split_angle); } if (daylight) { double change = 1e6; double last_change = 0; int count = 0; do { for (unsigned int i = 0; i < nodes.size(); ++i) { rect(i, 0) = nodes[i]->bounds.x; rect(i, 1) = nodes[i]->bounds.y; } last_change = change; change = 0; for (unsigned int i = 0; i < nodes.size(); ++i) { double current_change = equalDaylight(nodes[i], rotation_mod); if (current_change > change) change = current_change; } count++; } while (change < last_change && count < maxiter && change > tol); } else { for (unsigned int i = 0; i < nodes.size(); ++i) { rect(i, 0) = nodes[i]->bounds.x; rect(i, 1) = nodes[i]->bounds.y; } } for (unsigned int i = 0; i < nodes.size(); ++i) { delete nodes[i]; } return rect; } ggraph/src/iciclePlot.cpp0000644000176200001440000000226213526743614015121 0ustar liggesusers#include #include "nodes.h" using namespace Rcpp; void icicleLayout(Node* node, double x, double y) { Rectangle r = {x, y, node->weight(), node->height()}; node->bounds = r; std::vector children = node->getChildren(); if (children.size() != 0) { y += node->height(); for (unsigned int i = 0; i < children.size(); i++) { icicleLayout(children[i], x, y); x += children[i]->weight(); } } } //[[Rcpp::export]] NumericMatrix partitionTree(IntegerVector parent, IntegerVector order, NumericVector weight, NumericVector height) { NumericMatrix rect(parent.size(), 4); unsigned int i; std::vector nodes = createHierarchy(as< std::vector >(parent), as< std::vector >(order), as< std::vector >(weight), as< std::vector >(height)); for (i = 0; i < nodes.size(); ++i) { nodes[i]->sortChildren(); } Node* startNode = nodes[0]->getRoot(); icicleLayout(startNode, 0, 0); for (i = 0; i < nodes.size(); ++i) { rect(i, 0) = nodes[i]->bounds.x; rect(i, 1) = nodes[i]->bounds.y; rect(i, 2) = nodes[i]->bounds.width; rect(i, 3) = nodes[i]->bounds.height; delete nodes[i]; } return rect; } ggraph/src/nodes.cpp0000644000176200001440000000244613526744005014141 0ustar liggesusers#include "nodes.h" std::vector createHierarchy(std::vector parent, std::vector order, std::vector weight) { std::vector nodes; unsigned int i; for (i = 0; i < parent.size(); ++i) { Node* node = new Node(i, order[i], weight[i]); nodes.push_back(node); } for (i = 0; i < parent.size(); ++i) { if (parent[i] >= 0) { nodes[parent[i]]->addNode(nodes[i]); } } return nodes; } std::vector createHierarchy(std::vector parent, std::vector order, std::vector weight, std::vector height) { std::vector nodes; unsigned int i; for (i = 0; i < parent.size(); ++i) { Node* node = new Node(i, order[i], weight[i], height[i]); nodes.push_back(node); } for (i = 0; i < parent.size(); ++i) { if (parent[i] >= 0) { nodes[parent[i]]->addNode(nodes[i]); } } return nodes; } std::vector createUnrooted(std::vector parent, std::vector order, std::vector length) { std::vector nodes; unsigned int i; for (i = 0; i < parent.size(); ++i) { Node* node = new Node(i, order[i], 1, length[i]); nodes.push_back(node); } for (i = 0; i < parent.size(); ++i) { if (parent[i] >= 0) { nodes[parent[i]]->addNode(nodes[i]); } } return nodes; } ggraph/src/lineCutter.cpp0000644000176200001440000001643413526715112015146 0ustar liggesusers#include using namespace Rcpp; double copy_sign(double from, double to) { if (from > 0) { return to > 0 ? to : -1 * to; } else if (from < 0) { return to < 0 ? to : -1 * to; } return to; } struct Point { double x; double y; bool REAL; }; Point bad_point() { Point p = {0, 0, false}; return p; } Point point(double x, double y) { Point p = {x, y, true}; return p; } Point segment_intersect(Point p, Point p0, Point k, Point k0) { double pX = p0.x - p.x; double pY = p0.y - p.y; double kX = k0.x - k.x; double kY = k0.y - k.y; double delta = pX*kY - kX*pY; if (delta == 0) return bad_point(); double s = (-pY * (p.x - k.x) + pX * (p.y - k.y)) / (-kX * pY + pX * kY); double t = (kX * (p.y - k.y) - kY * (p.x - k.x)) / (-kX * pY + pX * kY); if (s >= 0 && s <= 1 && t >= 0 && t <= 1) { return point(p.x + t * pX, p.y + t * pY); } else { return bad_point(); } } Point rect_intersection(Point p, Point p0, double width, double height) { double xmin, xmax, ymin, ymax; xmin = p0.x - width; xmax = p0.x + width; ymin = p0.y - height; ymax = p0.y + height; if (p.x < xmin) { if (p.y > ymin && p.y < ymax) { return segment_intersect(p, p0, point(xmin, ymin), point(xmin, ymax)); } else if (p.y < ymin) { Point p_tmp = segment_intersect(p, p0, point(xmin, ymin), point(xmax, ymin)); if (p_tmp.REAL) { return p_tmp; } else { return segment_intersect(p, p0, point(xmin, ymin), point(xmin, ymax)); } } else { Point p_tmp = segment_intersect(p, p0, point(xmin, ymax), point(xmax, ymax)); if (p_tmp.REAL) { return p_tmp; } else { return segment_intersect(p, p0, point(xmin, ymin), point(xmin, ymax)); } } } else if (p.x > xmax) { if (p.y > ymin && p.y < ymax) { return segment_intersect(p, p0, point(xmax, ymin), point(xmax, ymax)); } else if (p.y < ymin) { Point p_tmp = segment_intersect(p, p0, point(xmin, ymin), point(xmax, ymin)); if (p_tmp.REAL) { return p_tmp; } else { return segment_intersect(p, p0, point(xmax, ymin), point(xmax, ymax)); } } else { Point p_tmp = segment_intersect(p, p0, point(xmin, ymax), point(xmax, ymax)); if (p_tmp.REAL) { return p_tmp; } else { return segment_intersect(p, p0, point(xmax, ymin), point(xmax, ymax)); } } } else { if (p.y < ymin) { return segment_intersect(p, p0, point(xmin, ymin), point(xmax, ymin)); } else { return segment_intersect(p, p0, point(xmin, ymax), point(xmax, ymax)); } } } Point ellipsis_intersection(Point p, Point p0, double width, double height) { double pX = p.x - p0.x; double pY = p.y - p0.y; double mod = (width * height) / std::sqrt(float(width*width*pY*pY + height*height*pX*pX)); double x = mod * pX; double y = mod * pY; x = copy_sign(pX, x); y = copy_sign(pY, y); return point(x + p0.x, y + p0.y); } bool inside_ellipsis(Point p, Point p0, double width, double height) { double pX = p.x - p0.x; double pY = p.y - p0.y; return (pX * pX) / (width * width) + (pY * pY) / (height * height) < 1; } void capRectStart(NumericVector &x, NumericVector &y, int from, int to, double width, double height) { int i; Point p; Point p0 = point(x[from], y[from]); width /= 2; height /= 2; for (i = from; i < to - 1; ++i) { p.x = x[i]; p.y = y[i]; if (std::abs(p.x - p0.x) <= width && std::abs(p.y - p0.y) <= height) { x[i] = NA_REAL; y[i] = NA_REAL; } else { Point intersect = rect_intersection(p, p0, width, height); if (intersect.REAL) { x[i-1] = intersect.x; y[i-1] = intersect.y; } break; } } } void capRectEnd(NumericVector &x, NumericVector &y, int from, int to, double width, double height) { int i = to - 1; Point p; Point p0 = point(x[i], y[i]); width /= 2; height /= 2; for (; i >= from; --i) { p.x = x[i]; p.y = y[i]; if (std::abs(p.x - p0.x) <= width && std::abs(p.y - p0.y) <= height) { x[i] = NA_REAL; y[i] = NA_REAL; } else { Point intersect = rect_intersection(p, p0, width, height); if (intersect.REAL) { x[i+1] = intersect.x; y[i+1] = intersect.y; } break; } } } void capEllipStart(NumericVector &x, NumericVector &y, int from, int to, double width, double height) { int i; Point p; Point p0 = point(x[from], y[from]); width /= 2; height /= 2; for (i = from; i < to - 1; ++i) { p.x = x[i]; p.y = y[i]; if (inside_ellipsis(p, p0, width, height)) { x[i] = NA_REAL; y[i] = NA_REAL; } else { Point intersect = ellipsis_intersection(p, p0, width, height); if (intersect.REAL) { x[i-1] = intersect.x; y[i-1] = intersect.y; } break; } } } void capEllipEnd(NumericVector &x, NumericVector &y, int from, int to, double width, double height) { int i = to - 1; Point p; Point p0 = point(x[i], y[i]); width /= 2; height /= 2; for (; i >= from; --i) { p.x = x[i]; p.y = y[i]; if (inside_ellipsis(p, p0, width, height)) { x[i] = NA_REAL; y[i] = NA_REAL; } else { Point intersect = ellipsis_intersection(p, p0, width, height); if (intersect.REAL) { x[i+1] = intersect.x; y[i+1] = intersect.y; } break; } } } //[[Rcpp::export]] List cut_lines(NumericVector x, NumericVector y, IntegerVector id, NumericVector start_width, NumericVector start_height, NumericVector end_width, NumericVector end_height, CharacterVector start_type, CharacterVector end_type) { NumericVector new_x = clone(x); NumericVector new_y = clone(y); int i, j, group, group_ind; group_ind = j = 0; group = id[group_ind]; for (i = 0; i < id.size(); ++i) { if (group != id[i]) { if (start_width[group_ind] != 0 && start_height[group_ind] != 0) { if (start_type[group_ind] == "circle") { capEllipStart(new_x, new_y, j, i, start_width[group_ind], start_height[group_ind]); } else if (start_type[group_ind] == "rect") { capRectStart(new_x, new_y, j, i, start_width[group_ind], start_height[group_ind]); } } if (end_width[group_ind] != 0 && end_height[group_ind] != 0) { if (end_type[group_ind] == "circle") { capEllipEnd(new_x, new_y, j, i, end_width[group_ind], end_height[group_ind]); } else if (end_type[group_ind] == "rect") { capRectEnd(new_x, new_y, j, i, end_width[group_ind], end_height[group_ind]); } } group = id[i]; ++group_ind; j = i; } } if (start_width[group_ind] != 0 && start_height[group_ind] != 0) { if (start_type[group_ind] == "circle") { capEllipStart(new_x, new_y, j, i, start_width[group_ind], start_height[group_ind]); } else if (start_type[group_ind] == "rect") { capRectStart(new_x, new_y, j, i, start_width[group_ind], start_height[group_ind]); } } if (end_width[group_ind] != 0 && end_height[group_ind] != 0) { if (end_type[group_ind] == "circle") { capEllipEnd(new_x, new_y, j, i, end_width[group_ind], end_height[group_ind]); } else if (end_type[group_ind] == "rect") { capRectEnd(new_x, new_y, j, i, end_width[group_ind], end_height[group_ind]); } } return List::create(Named("x") = new_x, Named("y") = new_y); } ggraph/src/RcppExports.cpp0000644000176200001440000001406013527207727015323 0ustar liggesusers// Generated by using Rcpp::compileAttributes() -> do not edit by hand // Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 #include using namespace Rcpp; // pack NumericMatrix pack(NumericVector areas); RcppExport SEXP _ggraph_pack(SEXP areasSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type areas(areasSEXP); rcpp_result_gen = Rcpp::wrap(pack(areas)); return rcpp_result_gen; END_RCPP } // circlePackLayout NumericMatrix circlePackLayout(IntegerVector parent, NumericVector weight); RcppExport SEXP _ggraph_circlePackLayout(SEXP parentSEXP, SEXP weightSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< IntegerVector >::type parent(parentSEXP); Rcpp::traits::input_parameter< NumericVector >::type weight(weightSEXP); rcpp_result_gen = Rcpp::wrap(circlePackLayout(parent, weight)); return rcpp_result_gen; END_RCPP } // partitionTree NumericMatrix partitionTree(IntegerVector parent, IntegerVector order, NumericVector weight, NumericVector height); RcppExport SEXP _ggraph_partitionTree(SEXP parentSEXP, SEXP orderSEXP, SEXP weightSEXP, SEXP heightSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< IntegerVector >::type parent(parentSEXP); Rcpp::traits::input_parameter< IntegerVector >::type order(orderSEXP); Rcpp::traits::input_parameter< NumericVector >::type weight(weightSEXP); Rcpp::traits::input_parameter< NumericVector >::type height(heightSEXP); rcpp_result_gen = Rcpp::wrap(partitionTree(parent, order, weight, height)); return rcpp_result_gen; END_RCPP } // cut_lines List cut_lines(NumericVector x, NumericVector y, IntegerVector id, NumericVector start_width, NumericVector start_height, NumericVector end_width, NumericVector end_height, CharacterVector start_type, CharacterVector end_type); RcppExport SEXP _ggraph_cut_lines(SEXP xSEXP, SEXP ySEXP, SEXP idSEXP, SEXP start_widthSEXP, SEXP start_heightSEXP, SEXP end_widthSEXP, SEXP end_heightSEXP, SEXP start_typeSEXP, SEXP end_typeSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type x(xSEXP); Rcpp::traits::input_parameter< NumericVector >::type y(ySEXP); Rcpp::traits::input_parameter< IntegerVector >::type id(idSEXP); Rcpp::traits::input_parameter< NumericVector >::type start_width(start_widthSEXP); Rcpp::traits::input_parameter< NumericVector >::type start_height(start_heightSEXP); Rcpp::traits::input_parameter< NumericVector >::type end_width(end_widthSEXP); Rcpp::traits::input_parameter< NumericVector >::type end_height(end_heightSEXP); Rcpp::traits::input_parameter< CharacterVector >::type start_type(start_typeSEXP); Rcpp::traits::input_parameter< CharacterVector >::type end_type(end_typeSEXP); rcpp_result_gen = Rcpp::wrap(cut_lines(x, y, id, start_width, start_height, end_width, end_height, start_type, end_type)); return rcpp_result_gen; END_RCPP } // pathAttr DataFrame pathAttr(DataFrame paths, int ngroups); RcppExport SEXP _ggraph_pathAttr(SEXP pathsSEXP, SEXP ngroupsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< DataFrame >::type paths(pathsSEXP); Rcpp::traits::input_parameter< int >::type ngroups(ngroupsSEXP); rcpp_result_gen = Rcpp::wrap(pathAttr(paths, ngroups)); return rcpp_result_gen; END_RCPP } // splitTreemap NumericMatrix splitTreemap(IntegerVector parent, IntegerVector order, NumericVector weight, double width, double height); RcppExport SEXP _ggraph_splitTreemap(SEXP parentSEXP, SEXP orderSEXP, SEXP weightSEXP, SEXP widthSEXP, SEXP heightSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< IntegerVector >::type parent(parentSEXP); Rcpp::traits::input_parameter< IntegerVector >::type order(orderSEXP); Rcpp::traits::input_parameter< NumericVector >::type weight(weightSEXP); Rcpp::traits::input_parameter< double >::type width(widthSEXP); Rcpp::traits::input_parameter< double >::type height(heightSEXP); rcpp_result_gen = Rcpp::wrap(splitTreemap(parent, order, weight, width, height)); return rcpp_result_gen; END_RCPP } // unrooted NumericMatrix unrooted(IntegerVector parent, IntegerVector order, NumericVector length, bool daylight, double tol, double rotation_mod, int maxiter); RcppExport SEXP _ggraph_unrooted(SEXP parentSEXP, SEXP orderSEXP, SEXP lengthSEXP, SEXP daylightSEXP, SEXP tolSEXP, SEXP rotation_modSEXP, SEXP maxiterSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< IntegerVector >::type parent(parentSEXP); Rcpp::traits::input_parameter< IntegerVector >::type order(orderSEXP); Rcpp::traits::input_parameter< NumericVector >::type length(lengthSEXP); Rcpp::traits::input_parameter< bool >::type daylight(daylightSEXP); Rcpp::traits::input_parameter< double >::type tol(tolSEXP); Rcpp::traits::input_parameter< double >::type rotation_mod(rotation_modSEXP); Rcpp::traits::input_parameter< int >::type maxiter(maxiterSEXP); rcpp_result_gen = Rcpp::wrap(unrooted(parent, order, length, daylight, tol, rotation_mod, maxiter)); return rcpp_result_gen; END_RCPP } static const R_CallMethodDef CallEntries[] = { {"_ggraph_pack", (DL_FUNC) &_ggraph_pack, 1}, {"_ggraph_circlePackLayout", (DL_FUNC) &_ggraph_circlePackLayout, 2}, {"_ggraph_partitionTree", (DL_FUNC) &_ggraph_partitionTree, 4}, {"_ggraph_cut_lines", (DL_FUNC) &_ggraph_cut_lines, 9}, {"_ggraph_pathAttr", (DL_FUNC) &_ggraph_pathAttr, 2}, {"_ggraph_splitTreemap", (DL_FUNC) &_ggraph_splitTreemap, 5}, {"_ggraph_unrooted", (DL_FUNC) &_ggraph_unrooted, 7}, {NULL, NULL, 0} }; RcppExport void R_init_ggraph(DllInfo *dll) { R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); R_useDynamicSymbols(dll, FALSE); } ggraph/src/treemap.cpp0000644000176200001440000000515613526715234014471 0ustar liggesusers#include #include "nodes.h" using namespace Rcpp; double w(std::vector& nodes) { double w = 0; for (unsigned int i = 0; i < nodes.size(); ++i) { w += nodes[i]->weight(); } return w; } void splitLayout(std::vector items, Rectangle r) { if (items.size() == 0) { return; } if (items.size() == 1) { items[0]->bounds = r; splitLayout(items[0]->getChildren(), r); // Layout the children within } else { Rectangle r1, r2; std::vector s1, s2; if (items.size() == 2) { s1.push_back(items[0]); s2.push_back(items[1]); } else { double halfSize = w(items) / 2; double wH = 0; double tmp = 0; bool completed = false; // Pick out half the weight into l1, half into l2 for (unsigned int i = 0; i < items.size(); ++i) { if (completed) { s2.push_back(items[i]); } else { tmp = wH + items[i]->weight(); // Test if it got worse by picking another item if (std::abs(halfSize - tmp) > std::abs(halfSize - wH)) { s2.push_back(items[i]); completed = true; } else { s1.push_back(items[i]); wH = tmp; } } } } double w1 = w(s1); double w2 = w(s2); if (r.width > r.height) { r1.x = r.x; r1.y = r.y; r1.width = r.width * w1/(w1 + w2); r1.height = r.height; r2.x = r.x + r1.width; r2.y = r.y; r2.width = r.width - r1.width; r2.height = r.height; } else { r1.x = r.x; r1.y = r.y; r1.width = r.width; r1.height = r.height * w1/(w1 + w2); r2.x = r.x; r2.y = r.y + r1.height; r2.width = r.width; r2.height = r.height - r1.height; } splitLayout(s1, r1); splitLayout(s2, r2); } } //[[Rcpp::export]] NumericMatrix splitTreemap(IntegerVector parent, IntegerVector order, NumericVector weight, double width, double height) { NumericMatrix rect(parent.size(), 4); unsigned int i; std::vector nodes = createHierarchy(as< std::vector >(parent), as< std::vector >(order), as< std::vector >(weight)); for (i = 0; i < nodes.size(); ++i) { nodes[i]->sortChildren(); } Node* startNode = nodes[0]->getRoot(); Rectangle r = { 0, 0, width, height }; startNode->bounds = r; splitLayout(startNode->getChildren(), r); for (i = 0; i < nodes.size(); ++i) { rect(i, 0) = nodes[i]->bounds.x; rect(i, 1) = nodes[i]->bounds.y; rect(i, 2) = nodes[i]->bounds.width; rect(i, 3) = nodes[i]->bounds.height; delete nodes[i]; } return rect; } ggraph/vignettes/0000755000176200001440000000000013617237522013542 5ustar liggesusersggraph/vignettes/edge_meme_wide.jpg0000644000176200001440000064176213226105174017174 0ustar liggesusersJFIFHH http://ns.adobe.com/xap/1.0/ ,Photoshop 3.08BIM%ُ B~dICC_PROFILETlcms0mntrRGB XYZ  /(acspAPPL-lcms desc>cprtHLwtptchad,rXYZbXYZgXYZrTRC gTRC bTRC chrm0$mluc enUS"sRGB IEC61966-2.1mluc enUS0No copyright, use freelyXYZ -sf32 B%nXYZ o8XYZ $XYZ bparaff Y [chrmT{L&f\CCO }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?( ( YP?dx>;'5# 29N'$;W^:quk:؋6?xGډ̱g$s)З<_3Kkj6 I K' $~l/.BLaCG0}9}Vea/6?\޹g扖/$'@6gf{5oNSY5Ꞻ.b?%S=?W<_GLp>YrNJdE,L5cGOj}wOZv:(a?~F|W 7`qrlPb;z]ԧ)x`bMsX[Z8|ҲZyżWᏃ54O~8pwh)a*:SқJ|w>;Ab燕x7Oy+=|-C\-)GC3Hc{ aJ}%w=5{_v3步.aAsfTt9#)>ݻW2/39umeNy2aET5MI%./u+᎓}zdȍѦ:gߧC<70jW$Z[||džp#E5R~^e~V g?jbB[ڧ7?zP_{}~]:qOĞUf_kߑg-,D8'^^Fe™]bJ*jɴ֗_UfOa+AJ$),l Z>X5}\nϙq`K !p7}ÿ!IMyW<Ys sGRn.ؙ ~j^=مxKZ2m'o{UMw6v0'/988oϯa!)e Nxtx_,߳=Jy F&IԂb^kԲ RcWN(muB!L(!JCo>s_[/a}]{8?Sj3|u{5" қF2STcT]vxjP]nrq:Z6>"ʕQ?M qAY>jj[6d\3~bF]Yt2,N2I6R6ۼ_ca/Va=Gue6nVhdbr/2mm zÃ3_|n>rʊ{Z*'Sf[Xuy % Gx}KtN7Üޭm tJ*/#iwCItCjQ$)ƺx^¯=ܗ(%Bd|A_91QV Xz-aY-}^2jR|E5N+ZU}nxď7È4/^źE5 X, { H#e6i|]\=JtkrӔZqrǑYwy7x8XӣKN^ΊVUJ[~퓢d+}&Vc߷e+=YkmB^G=+p%A0GMyU*qBJqZ“ݷmUu>aՎdRNiI7ɿK)~ w.Ka~:3&\zbXqS1\p狣 fV߮ux=E<6"j*zk~Gio»EOQ"=Z5Za9̆!˖$sYĹx=HWIQqwqOg_?Ƽ3aM9B$Ӈ>_NwVW*i!kej#AyÒG)eO1eXznx-S[%}>0NīE$t9/_ t4{Vk4bgeoU;?]ռ?eTרZ咯ʮZyt|H\q0z \U?ӭ (4ԗS4yAʼHס)(bqvee:e<=I*Tn E+QX H΋\wooy^tCIMq4O&0 <6qҿJWulV2 kUN 7{u-|q#d0]HUYVRVpGw>҇kxS.JE0?ed႟H#rfgFssRNV9Z^nW."/G[_]OL'm4hTf3*)^ra8_EJ*J$u$J tS"ZE$%i_ת9T|e"#9=:cֶREdO;7 ,-,7qOEo>ն{6?L}dA/n<zEY*0B~(Iw_so7 ʽ>jk?Be?oAgwrtGyW1 yuҿ8r %:1x&Ik{zu\9c8ozZA,S\W 4$\Ӧ8 w/x^.  ԕEߓ NsRWӧ:VP¬:͵nwM/r;g|R~?o&?t!ܓ[Z^[)dc/ۢ%𓌳z3C/PVoӜ}~k3!㬟._ ,} a8zμ aåCg-FjL A/JO9ݑ<\boFS~ƃ%/U.y/CZ*(PJ*ʭY99[˖TӮxY G]']ʱEe%oX3UB>.-i7mm~ft?*;Jg Ɩ s:qn c?/beN5%w_?NhiSN`׮\ |sw<me->$;1\q"F⬾t=DE.W]}|Ϝ} M^.oqkyZ$KO^O~1_QGx}JqwPD;./xb#VVw%,=n1ڑ ~A NOac}uv{y~fwp:Jد{k]y?h_>@`$p7 ApOF GW(6EysS=W wWKߵo!R<[ 3o_/_O:$I{xok>>.]Zt!_!^݄X,_{UgBV<^aN=g IS^A";y'vZ:~x%r;)q{ 67Bf2gV YBfFOJkkK>E|+˞&}\Y9~^!v\9!Z?[08108yeyMTS"ɵ+|JJsxø褬}kA4:--,2PcX$t]scq>AWu>n^ "֡?d^IY_M`?kĚwttݚUONZpoaq.*rN%QI[oms\cupj2ɷ S]d~]|md2zo4J)@h7ko x oҫK^ZB[zO3ŪMenJZ^#D-VS0Pc;YN:+pxU m)Pa1S^VncNnJ;{^hbtZqnZSk_Tf^5D`*]^+׍tzmY\;]~NxB붟y fm+mM+J_)}<}4;`-,!3o^W־QTj}}b-~ g0N`%NªAnv3qX E'F7Uw, Xɐ]׏^u M5<-_+7Vr)6 B ys9EӘ'6xw_rxb? |~E?ſƊ+/<ܶ}zW?_-/Qp}&!yBG?P@P@P@?O۟JN)~.ޟ/xMϊ_' s_?|ocO!;^+džƍ qU ~8wziy~ʀقžnw()d#E?v>Pi N>]EF;fȝ2 uҹgZgGGw\ίik[;/]}I+H E:E׿ChSK$x,f}ϟaJUJJf7SǬ^Jvwvwo??lڏaX-≴kɨYx/z61ỰmF? x;޳oKK:ޣiajv7Wοx!TsYhvR+ϚѦb9ZMJ=O|h{ rYF?؋ |?𽗉4{=7_$KaKyhZtG<: χCoAҬzX|ʮ5\)IsF5!Q+;[f_X֩*۴mv쯫{X^]šmU>DžWo>υ|Ajvz{^֓@eI,mؖ'm.n[ZM:MWG$նwM56H$` Ou??`|u; 1ԯ3ZwV{K; |3/;L^(4]#ڍ9qp TV* ϑҕ r/p\M87hu+XkP˝X5VQQz(ԓaASx?wˬVյmOWCR5?o<-syqquq<\<I瑙̫sܧJ0"(ƔT=UI46kDzn?6rF#ZʮU>Wz^\[Uk7 u -㿇 <;4C&a~>x L4:l"~8i hRhyZuq rdYt$z5vZ3ş 5ᯈ x?ls~W$\`9kΚ.ex($YkDѕWl&mk]7oroO#LG<{_ÚV>).4]GRӮ%WN n>*਺g:Y֭N:߽Mnӊ>浣NGNr(9ǙWQmujmְD2H<0m$J&YːsǍ4q JjVvkYG|DjZTk/sh ̿$ωG^6|q}V/^=[O[ /-lYp1mNkӌNoyF7o{9s9Qk2gg',nA.!Ɲx R팋UmI5ꮤ/k_Ư*Ϗ9{cOS|m4İO:υ5 X6:)i_XiּKOoM+wzpo9v#k>oOLE'ĩJgMJ>KM;N*Ua|<ۭU3NOrU84Zut~?|5| qCž(,-xg+|8SῈ౺G<'OՒ)`/ cK6PiУմW4̹UC* (;hviIKt?j>6y}X5BqZ]֩mVK}/7+O}n/G ^k^Y' ~4o|>OMxKՒZ p>Y'ti:+:YU_g7yb*SB$ 'n'3l_S W٧xoeQ'{56Ӥܽgno)[žo;yiOO]ʰ_i>kL8W q|Y:x2T+JVY4(̽ N s&eѥR5'._o]Y6cJDǿgSmcp,V4{RuOkhQ75MS]%$5;e#kQ:QMRUJqTRIY{Vn>m*-z%V$ܛՖ9zuz7|/%;W᧊,GѼs |Dndm5kFas[\,2I~dbN9S}vii;kK]_^Dm3| jo›AMSźWIOڿ3(Úl4<5i_vQUJY|.11J:NRʛ~tRRWuIOl¥G5OsrN)іZ[F\ME杪¶MwZ&a6ZK-Y][`h5p%Q.+*Y.+VU] j޾[_z?O~~ aΟ eUr|zëw.LF 8ş_5п Я"f[b~eYm o oO''uV~ʶ3 Z((^yH9/oR_}MΚI8-Q ~'Z6Ē<>gݣ~bIhO=TkLE|w pXZj5VW]SR*yCT咓J*LNIY/iCKNJz%k7?6 7).ڻ?#6_6k-oŽ?J U=!8jJZ+G%x|_t x |Y?bDt?~Sl=\"Gìn6X*xF Jr|]W9%:nU9r|){T3lbRVrbZrp)J[K)S\,~bc[9 =|##Eo`͔́*tRP,ych{ewFtγ~ڣI'ngu~W#?cS]?~ A p{n3/OU)Ek?i֑kg_ %ZT庥[u?'߂ xm_ >K_ hTI>㷸FoJ1_EYp|B|ߜil-RV{2Vi2czبM&I;]>"SmSCH-Ʊ#Þ'4:au0~dYYcd1SyaRjJxFQ:6ԅd\i <GxSFXF{ӒeX7^9qj\ΉjM҅:"OS: ` (0˕*m9ǚs{Y?hn7^}d94RK[ꬓ-4xs_ ~>^St {M|aۛ)ӵZҵ }GJ&մ\[]5)2H`4)ԧ Ы5VQ'uKN/QNtjFM^-vk۵ZG?`#-Z3Q㇄~VGÛ/Z@m+R|'s^? 9M7G'αodgK O$"6rN-rwR-ENQq<\>9ODL&K*|2TݛW&j[~u wy wsB|~|(y 2xMٕ *( aC e[:7Ik%ge%&>$c_|6/VUs {_]U/i6z&suh.usX,Nf5QGp 980x)Q*QFgi墷>ETjoqsn/hעV#_$q~/44c! f?*$煄%G0%N*#MܫdjV\۳'[R[*tb?y++=}N~{kd߂?>|#?ߎ |Qs1mWjz&} E4MBM7v6w<&mVbњVRb+J־}<Ҷđ&j\rtb T Tiɫ^͟B3>)g~?O kR4?KNRRHn.ncKͳxƕ%N9G1jiFikJ* 4މYdf/(bqjb*`Ա Wv>XφegYdٛJ>i׶v3[~ S<__]/|FS?zw~a`uOQ ڤK;c*E nLV뼼9*N8g~o8oIWIw#<]GնszF9nYZֽw~2OO ~ʼ+xD7&?xor!&ToBC(EZ4cs̫wuBL<)k'#\{g_ | |A--~.&ЏÞ?Ѽ_ J yuoj&3~˳HQ^UJ\_kvvvMUYR41SThIG'VduWIY]8O'?*^1c_7 |Lk_EhRHNRhO4t_Cé^jpq$H[N8Ze8ZP:2nx|^] ;*ε(Qk̏?7xC]oa<".]Ҕ2֍]]hJe_M?ƾgO x߲O ~hzޅᏉ5^bV5;O4{. [9ຂ)GFgG1qKKPx\*ʅ(#{-ce{F%eRζ&#>yQWI[*uFZ>6 uOأ{(j _Gi,gυkei+:!_k ]\ZZX~$w%il֊Ɵ)f0NigRRJѴQVV.cVWZkܔj7̒Zto(ko/߀>x3>>xO:o f]?dd>)\x&k^xOIXZi=Akh!¼=q< ,.rqRrRj ̴Wg3 &&t*z5ʥȟ5ս"iadq~?%-ً{NR7HCuqu=x\lF$M.؜ǯVYS+RiSm*qrrqRInM90<g:j'ΥK(\[m?z:&?_WOdž<-g?~4|G?_ t|?ּ[x#GAԺev3ܤ!g΂0+ҏ*Jن=U<#Zo܌u R*b^Ii:R}g/l'mi]ثM! c~&>\ݏls_別'.|bFeWV7;1?{FL?o_6?xN"'k~;ק|Pm ]\cv1J߹.Emxպc5apַ_ubwoǡ13LssinܴzypO߃K9WI;TD\^oBxi?c{|NWFk5s ĩY fq}X_m5oֵG_9iHΩklk+\ϔ>:Xgqui_Sb4$Cxv?_[]Gv;Y]|)J)[go?g[_1>~;R8g9\ʿO` ( ( !7uLdx~;c?-ܛ⼾Oa?eԾ)CnR> `7񟗠ϱ==/nBd99iw_hAxɌ N?Zg+-n^5Uflt=G$t-/~9Jcl/>ja3?ؿiO a 6yN~K n*ܜM}srhq9[ *wRQt+TIKtNB 4Vm_I)SKOS{;h`׈mmZHec{+K8+1kq%,ut㊫ɇQrI_ݏ#OU<5X?ynV?#|A<~q=(e. `h=$z JSqc/_>})3~%q`?d xx7o;/x3\I5OIk/XH/Fio-&_3|uS1 Zxxີkk*+ǒe]GhHY0l?yW?'~ NJȿ?'=@OO'+k6|KNW!G{tK丵0*tcpP!⿀:W- MB/%{]|=5C tI[Hn%8SNqͱT(?{T#Re+a(47dxl&* bgC1V_hN-7Tתѻh g?kF`rF>tVxs_k9<;LaKxgY VxQu~&Y? ܯZ7QnOmISPyRG)<6& J:SIB*RD%ޮWi:ܾ|ƾ ӼIle^_MYDO?M.H!$ *08VU`)O^RA*q0+f&+V[Xdc%!K2SͤKQWݥv/_O\??~_~#hZ?ſud~(:~O/|Huio4/GYx{vIpl4=7G,r_츚8<5 Y?zIxr;c˲LfęW9i[T# *qmG>Yr =.~[uॿk߲n~|G犼n? KҵO :N rxKRvejbOV7qF*Uuquf*|%VZ9arsM $_g>^aԣ{ThJ 擔/KS^?Pv_ 0HW@V$d.hp<2O\Jk#|T}{X|h?#}t0"Rꖉy#ȕ|] ]kDrѻzi?s>*~?58S˸.dyn{*O `%K=Y{{>?kOڿj|M;= Ni[h^a-r56k2j7(\Ʊ*y>[ ? c.lN6I՝I|v[#󪎶OMVO SPr~ j .x"2x?Z,zT&ᧅfX4>ߡkZp&m0~wRsz0JmX3rMyjP XvRQN#I**>,<$׳⚒5ƭwn4yW89XqwSjs`ghSJ2n>6ba'G?ٯeԪB•z΍OcRQz>j);hHq+mmʔ9VS${H쁌1Momf޵42/Uw]߭[tĕT0SPmIm/r\? ?XF1Lj )_>#yy*uqxsT2(> "gӔ~^okj5ov?9W8|k!7- sofV儷^[%lV\隭 I`-\8Mf|W_> W[r¬nDӽֺT8ai[&Qk> ʵE{):LkmUQ#TEP"*P6(G=Ssf[rsNmɶۖI$޷~lx~ɲjViSZ]4ml-,|gɟ~mWsW}7 ?s?3QU<%xD^yp,?n&D6gƺrlGs{H<]Ǟ}ĚτUG/nsA집%miGL<5<ׅ*8,>m*I)G YI5/z3q]%--shr:9j?sʌ3)Ù9]ZMhח 𶣩}̇˝.K`XNnF9X V>oJ8 2 \4FyIO{޺>*m)kR,"wO-]Y~ +N_qO9+)w)qOH8GdB+~ͳy\$]OZPwv?@உ-ࡐcM dI ~m뼺sq XѫmBaBzя<ӣ^y4rG^籔֫4)ңUE-Z 7{h~o5s|C}ss||Muk4u*!w(cl  YGW=RU)Nia:3Ǝ"K9;X/U/ ࡚JN56PV,Y(xuмmR|ڿgKۿ!F17ڛo4>U". <&ebi^9MnZF"95ƥfM[vfQg`5|&uK4eBMk-uw{+Y/g_&'rKQޟ<8oMe}N,{%߄<)6PBљOB\g&CJppT,dgtJr0tە9 )xCpJ42~m󦔡N '5^TۋQ]:|4~hةmb XL XĘ9sEon$1juh`< 9NxIyImw-C*;WkcK8= W_ߖ3X۫Bev]TWq?-+szN8I_J1VϬ˨)M7wWգJXKnM?*,8ﺯ6~[2N.\ß|1_xgT[O)O[]8ׯFUm7kc/ ewc3x;۹f_j(钴lӵk}a9ʵY##.n8_n?qU#( ( (?/9N?߅#CcǓ{?0x֟Ӏ}?/sS%3(qO,hl8Q>@&MͼֲzDf2J"m OsNZۧ__7[/E1I z}zuW<~d#Tvgy#_u\sn۸wϑ%l\SPtvU6v2s?"2ԇz0Rg%_s7lc c#m߱ŦzK&0}+'ɔgTGI?~ZY)BK_7WO~ye7ժcUGɾkRKX_/|q7Oc_i/u'G.IOyKw & _ٸ~_|#֯{7~<)lFs̘~և5)ԶjYVv|}Q"{_ƿ&#,s >2? Eif &|$ i |O/gƸ;?ox+ÙͿ޿+I1s $اk9q}5߂K+O0.*OC fG:Nm/!erz?d ~$C-W+d|M'_J yˈmo7'59Ab<T3r1?=Q |z~ x{ 8:*̒˼EM^&z&~gDx_?CN%MgClG^7?599n I?Em-?Nߛ[[sLWha\]GMקBV8yRoNf՛a',%FDRtޖI?k -d|Rqן;<-+R3+}gvG+;&׽k^ӣ+c Y*9~> W2׆Idʾ iÊdiokže8ӝo$n?;Z}#<3P) 7:1J U:0OLM,$^֭>~joMxmumwtXzumhx@Ou(=K1\rbm s2"*N ,~heuf̣7]MҍnW).V~ߢ~ |' 6<?.ĞփZŽ5摪Cqnzm[_B3U4ɬmmfS[딩FUnU*Q%ȓQIݹK/WwFujF.R4ju'5gtN6?ϧ k\|1)?ƿ4c<>":4sq6_Z>?_[79x39/!U:+4XUR7QJje608ac[cg'9JQIjrv+Tp-i*MC<&: Fwr O";0x:wMcM5mi?4ᨨyx/S7>w?%Ksݒd/O7xW=WU޹lZυ{xNOe4kŶ +NFGsas%՝&Ky>.>eR8֦Ko;=g-Z>kK]z?-և} T|C .WubI4m'IQMJHl+MkaI"-Ն:<7ֳ:n9mN2rw'to~wnhi*q^9PV U4.uo# o?.Wǿs<> uiL$-zlxO&8Ux`$&">2>% ũtN*T+Fj2j2Vh5Twng'C4*t>G(#M2W?)^A!?Sx^CS5(¥||'NBFnΤi0vvO\7J5qZuR3V۾LNi=tQwi7 /ngg3-[I>ufض%hҟ6彶b.w7]x+MRמÇ*g58N\ɧ Ӝi(snQ\x{K:RXê_갔o{Vť3E|w}&%Ht:Q`*nV|w&L+֓*ԄnP}zx 8:pErQjI(Bmy]Q<o=1 <8:nc}a\$z4c2I74?$7(tk!Wl˰g|C۸@˞\zeBQ۫/}|M5Rv[N33' t%LSt_ Yl_~'Vj;wk2rϽoWh|v0؋_uE_<WWcګNU^r,.)Ƭ!iK}љɵO}bIVOKe#N!+)Xg`|@y|Dk --w.-՝"Eob~>5?4Y"gp9POrG_jU*.OJQv8bs.Px峚j}YK\靣ǭyٚ6}J'Lmv_fRot\p1{B[Rjoi_'? l'x{GL|gmCOx@ռ}sQRSӭX}g˺W)s74XJN>Ώ9TNRI%{nz?cL'bNG|aHyAF,:b(MUgJ4%jk='u<-H&o?3rj8߉ SR*e<;_C^RW%~w,51w-85/-geLq5H=AaҼ,چh`ʝJuRKgf 9Ja%X|L{Dݶ]*̿'+*K36I}4Sm쓍TkcVRm9by7nT4r[jAIvx9|?m-׿r#kx_5j6GĘƫZ*M 5ܣ$:Q{2;˷kW׭]Oٟ>009 1xOV^Čh?hc'+V꓌+ӜKtg^O/idT_4s%efI+\nT7_g1(^YTZ|?k"bm3VqJj/>G_U{~r7q4~HVST __oAv2#7[&M_ME֚oRchk\,.GƵc kUxVȷwqw?O'8FHŸv8_Ms_㋪Os,0\tzoLO_|Sq пiD ~"xgZ^-uf𞱥[-1ܵX|v gjOTͤ yp|,:Mh޻T0xju kKV,,QMof~; goُ !ʰ#S,㏌?k^5^е[~ևh~ ,I,JXl?5ū7\ pU&'QR?+V)[wD򊑕Y׎xgaҥͪٽB/?dQo`+#_~ g<'/Iz&Wk_IM{ai^ o4sھu{z8`9j: dd+i{:˰70teuSMnNӗRJ;{x I3L|PM . eGG{K-&viIJΗZ{X;H}LM9PY}V׽zTO9:JֺF=?;2z;8i2IU֚X~)6lh?ë];PcK+m_954GQw{kbO1 lToޮ1eU(3΢۶? ԡ=[p~:EP@,mYB/relksΎG]2Wzhߘ"WmN8}a}u򷖟j&.e'IB?z]WE[|S Xrsv}:hO[K2mp7{뭥vs?*ďuq5/ƾ\/ui̯%g Bec)SvյI[C?bg1=V?\Ͼ]lneFϞ8NKob/lSiv|t'?| X~ĞQ w?h$nt ª_bTC6ʹt)Nߗ!x}Sck+4Z kj6Ih춶f9?ilـ~_D89 #=(#5 ēU_[uۡaȳT򝯾~<l8ͷsN?i# ;gn"˼>j;[Ӏ9gbn\l]0n]vom /%Ct|]? ^?>>W_xCTмKa7Ʉz^O;۽X.gAx!U\SΣp?;i^g a4Oh,mPB*jZmm*k Fy.Yg 3*JqM+7x;zէ cY{JT\qќ.-eeGsoE|^x+_8.~V~0moF pxƅZYڃ#0B⩸F RG,Z~Y9B? _yp첓M<_ wNxJڇ^0qi:V6bRhf _KTCc+6 ˁPZE-^O^Qq ˥8~b06VutMr<-5ݺ~GM/Oofx™ h`>eS8_gU-]jMݥv~.J}?~ e,#WqG÷&._CR1o[ierԟ QtRB0K{.]gU2.*KK-&~?L$~Gecuc[G<>_/8;Nݧ1\?&m 87{׃f=gG+^!Id| +ľ>/ >3EkR<>+]!%| ㏃^o8**rds2,$1ج]6s['VonUWN|m~o>#|kqso4ٿ wֺoy ?xljܡGXqFД|уQi?Sjr\.e4/QohZi)JK)'_icJM9#O)|X\FTW]ȩ *f~y^2oe3:wqɩ_^_q]& s R8cOBW,pG%=G ?_UlΆ"V^^[oTXy#(hqmtVkKvw?X^x.I{.m|1n+hlp?Qk OkcS!)sV%-JTp}+_-ooWUd]T?>r>JO\K~kO%.x'ٵgwfZYV@a'x}㟹m G-?i2j?cO -Cky p~EN碏ﬕqRoi_.~q ͪ.aݒoCq\ /Gm?XH ( ( (KK w_d~_;~=UeOcԾ)CnR=i.5r7w+Y~XRhhg-5gD6?y:,dĮ=Ԁt1!_S-K**bVMRXGn17PX??_գG~5/_t-C}7zKHNes_ ;L{ԷN)YWr< < ewjӭEwѧδr[R_?𜱏'/-_;6Vt# Cw=/Kxk֬-ﴭsB4nluM/T;))6$ʪCqfR6|{ɴ?h˗Wk XjG~__<'3;B/Ǐ[O!SGUSfLj=VK+k-t]?I6Vaa P ZY^:X*RmeB9E%Vδvri -O6i_h(+8-ui)I]|>ҿfko'?wMWO+76t|2Mmb],t j> h֩[qjռ|G<sQ]rfV,;;*5IEFk箓'B^`SIۖ\fۄO'1~,l~hdp){I xp?x븒*|OͦJIm4V'8/{WO _ݫ|:vm/ztg_ڂ~~zxGZKhC}g<,jcxu<~ywaF9_婹9lc&˰UxSj,i95h}g:͔-FBTKJyuVUf־B]y2YH~A9K-W{I_\ xW_IJ֙mkI4iVE4~1$MkcЕެyF\~cgԖ "Rq oh𜛵RNҏ}~ìF;qUf&Q.1,t(FqM6욹:Z<$C\G)#Hg 8#U8C.UOrΤOlUUh6_ Љ6~(뱓ß_F cSQ{$W_OE0͵>'/ >*j5߉_%O->%3ncxJt;QNN@M_?N0Ui ʜEJqvdx(HIi aݚE~˹⳵x-mm{P&y& !/<ҢF"CLm*5f:vNnIϴ[嚽Ntʴg߶$*RP/3ouJVGM 1oFv׾'Ŀ=#X|bkik toWzޝzÛi=QAgFj1YG𤚗eQR\uS/7||3i|x^0P̲=\?Q ;ON){((Exڮ7M>6qavghz݋$wv2+kwi(o(9qif1`kCLV V&:ќ[Y_wMoL&"X|u % *X5X%$(Ms'{كg=| w6|ǃ+Ү4~[|Cskmڔz߈ _Y5[R.%HmVk [0UIO J^SY+]Fc&y|*6Գ k'ޤ}l鍊k{8h|,> _wvk״LS ikYMm7|;k3 9ou GQL̩>T~jUR%fF-Js9y-|Î#+)C뵨J1zr42WU/$-9iƪX=VH`ؚ9_3sEwm!K*q_oK8o{]k? 5K(`Yu7/Lx5L2NIfeE^gYͼ+rceWQvJFol8QٵuU5qT*XOI;%*Rg =įcw>5M:?$}CvZ.Z1ZI=ʼG z.u?x5?Ay{jƹ\ڝѽef YoN')a0Jю>N1碊Ws9J*1Yҡ(9#wc%s()+E{&lN៊ ;t|eդ} >OhG&6ŦZW<#<JVTtiڥgW.HrNVNox jXrcJ9zI9qNʷ+L3x8x8REدky^^Sii>9? 3LG*o/奿H-;sFy## בڿ^YI҅ym$9Y5̖wW s*l%ު_YūHt$@v[o*W1ֵ #?lߎ%jW|ZFFoPnGԃ!گ)UBR%b;|%xђnRwJB*1wI_+Ź:vNqkVDE:[7罝>)Dgo7HpyƤ@Q) 9}>,V"ZLӖ7F\Jm99Ԍgo?%'Sߊ| K!ēk?QÓ>SI"Uldu4m8 fK=ዔTZk Eh*0:TJST^\g*mn^UJ;-Խ]g'ִo [N_5]#At=]KUm_P4?NQOṯRF.-FT)f7.geyenm.P*v I]^RJ)Y?gn? D?>?|6j<|=++ Uj׾.h/dH?Okæ{M6K5=6UK?x,.*IT, 4JiMaP{nYuINP*T(;BpK-|.i{lceROfOJfܜR?E?Z BG}$kSi)SS_ihq4;4)*~s/>98֤ƝRV9vi̼ff%ΏbrZkl }#NU(&JTs]Nonxz7T8gRzSm{T^kMrMҼZhQM?w:0|ppHP: ;ڶkޑ2]'g*Mgp?gdS+sկ+h2Wd XKi[Lƃo@ƒG UV*OUfk޿~:kSY8KIQgC}Ș1܌ JlΕJ&gW/:[bqw)[=>c9ʶ%J挬vW?ϊ~?OSয়øQ^_~.sqƗ^DuH#{7&[yvX>2ʫJ1*JSwV-5w~T*yҎ*cUjz5|2m_{:svJH?~&~6 ë+9eX~Hг$8zmN=a/}C:jkOE5̭̝׵İp1~YVYN *=+ӯ9KߵZrg :_ᗉDvyh8WwȮ~+9Ku'3njC@! ]!*9<%璬=kʮGԯ Wŭ=;s/5)A\ViH{_٘ei֯ʸµ:Ye9M'$Cy^%O9fIt%7?\>G 4|L? 6OcnzqU\KKh'S&?b6OOnYYnA]?OVuM3DW.3]lsI7FxfqyZJF]Y!=I)Q{'k} ǡRq^JwwCO~R6}v}k&AÜ?-3._-%_ϏJ? ( ( (TM?_ sS*υs߈qo1J{Eg1xoU}?YR̾SzgblDe:O_ߗr0&0氟_:rmeb`o[!rc`={VtIݧZRe_tczH̞@M8N{|SWw1I$yvG?K_w:bv֟K1n~b}qpJ4Evk,\Qt\I5E3fOIa;(m3 w,3PTHb26m:j2O{z|<~ מog1(ܗ) J? , ZB_ 3ƟZmCYŽ(KI#/ݲoR7q]~Go,~*&n&z=ĿS-Ro;9Aug{ux!;&m7 x3 -tɀ)F^NGZP O[5MEfy_|=TWNѷkm{i5ߊ5RCxYS5w_&SԵGR}CP+.څp_>h=&,t=El}ޟFl+d A:x|EtzKB5,vUR[y>d1iqơ\IpI/KC2nJ-\?+rۯ[jpV:O?E@K9= uO Nms^^Icmm-XY V'FqWs+[oG*XJꤜݟY7ܐ*Aۏ*'|olG;Y/H8 ђK?h_ōn7hY_G ) ,uxZΕ}"6fEy ;_5> l$RM٩Y46z;IYc'z3V܏iX`}1y1$JG(or:ƭc96aRsr\ɴڻmjmy `|*bjAw|'| u+KO {](hRiڥ.|BP(”5l{ތ=2 ܱtfܚN-4Tm_2_àx/Ԯmy]%3^$MU;LBh$oqkmOMtEiE%Vo)mn?9ֿÏ({O'wkL4M{kiZvioaiGeZZinB~B0KE.ZvvZ6O3LǬvk_]nO O)o3Cc$>q߈p>֒I8tҫ8^R3iewu+_>6wg3j㏋ZK'NֹilRk;In&7;Q6θUጥ(EBQR(JRҼ9*{I쟀nx%e*K6RmJ+?mo)?A)3 w71h u_ÿjZx_m \4W}b5Ux,FB^›Sc٦|e:[[&{_ƿV. OK4S焾`n %~6'tiQu.`_"Ή@+T*N:z&%$֒R<Byvin4<-yQK{ZLjO9uGƾ!oeu=_\ԧ5-SUզY|^ijfrQ55$k{?e A[mA|ˮTg_47y73Ikcy 7hf9{K7!OɸUe}Ϫ?`&m/ө%ŏ*,ZoQ:xhhwgnͱlpT4,o6UayTeJm-t ?p|cNo['2w㏆Qpk¯'ʼnjůO⏌]׉uZ',|]_ۦ _P=~oϚב^rv}n7s1~ a}~VkY]M5>|C_n UuJZ&]|co%]kIbu[-ݽ?j61׷hjwwwO<Xtk-?hdhvz. 3[tBJ5}2Do|=[Ykzfmoqg=峼~Ks%x𹂭dfMoM]9e嶫cV෿|9o~.;4Z^GQĒQ}㏌{F8Ρj ^jHc95KBO7r^mn7 eZғU$K]5kS ~߷kįvWzZԶ֚爾0мi֍kzy][.wf>ѷC:Qz5ofze,Ek=7Wӿ]z3-_ ڭ9,RsC(m1PI刼R$򘄱cfۨrm}vz^,4U)9}{徧|8k<93ߴ=o]V>*|)K 7N oZMyi:FsZIiFe-XZEa8xZJ7-}V 3 ֭z|(|In ~pkߋ>*Z /?G +Rtּ)oj&izi+oZj+\O=)J)5.H.ui)'Җwnlʪ))nw-ne{~9k^[ c!BF Hs)#Ua+i9[W?IW,TiSTm}Qe/lYf31/X;uoD3'8c../WiB:[v;c,"eQ{?ʟ[}oe- ^PwƱ|1ŶQ--/|9.ivz6/Rβ&3N_HSnJ뛚QmA6_v8 o vGkF^._R/xLi|#{f.G$,oF.tУewv ܿI 6L,#ԇnRmzGڵZCZ4䥪n*2[N'7<=xž?ω?;~_?5m>/_-'GmCD5}_ZbҵK }7Wӕ/uBI<^&fw{ms ZmdO|SDg~bsh5H*D&,PۋoiAHΎVٟL¥y%+Ż-Nw[jY}ZZjm-8: xtyՒĚ|[M-i]xCG%¼T[V+4vM]ϴ| Zf "[!Ղ"X`4 4" x1֜S (B #ݱZO{W?ٛ?f[ό/xNͯ'!e7c~:';gjz7tXb5(5[ ,.k1PV/>˦]L'0jտ%wVeS vUM['|?_#F\1aSi\B-, ʻz_J|DiFקioU𒊜7GgmvًCԾ~3/|_3d*' @t>GқYo5 O\~blm4;M>-#gyj[M_3}uv}W};p%A$]oDIvc<56in#\W"^K݉Dp@9:|yպ+/ȣqBb:9YFu9tStxC͸"<xOʱw;m>,/ӵ[ZJ;Um;(:fYX u.]zYO5>Tތ7q=19qq/]s*.PNGs,v,lQͻ1;1uZ|ϘOc]]Ki2vyx9ߏ{ }7ce^MDqi[_|@4WDGmOSq]>r-fuS?/"/Pdٻ_ woKo~/ɲƿHZӍ2v|̮t~P@P@P@p|./a 7y2Οπ 4?/e^kEFa7λ$(> ֿz~e[ٳ:arޜcڲ1p-F9cZ_:~=I> S\RzYV]ѳO~˿ȯGknL^ϷOBV'_4yc~w9j<\cm+Y>Wojs8fW(j6C(_(>Q {uֽ6WC Wv]?%qI OכeʱRF+cq~fXVQI;}uZZ]lr/V.0z?Coj=Jm?[/,x!3]`@81i6f_5VdayO=Ȱ~>D lz:w< )%˺LsLz%Yqj n?O׶iaa֟o,Q[Ls 9dY?iG3Y. W}dzb Prp9ׁ~&ʿ$/\_a#FX;c8Ƕsrʰi.vMb昷4'ȷ_=vhCGXV$w#8%:vu\K%Vj'}߉Bԩӻ|YHo63׮y⸫XlD(.M_uQ10'$ٞaf #YfFmŁEϹ`ՏߏkZާJƾdXa<UhMD_-̱9!/d[ۢ?~)xz\2GU\OٟՉWLM2mB"T ;>%(Pו^^oV?`7kdv?&i}um=jm >Ms; GaB5.#R$v޾K [ٷemmuwf~z|T]CQ{taQ_6 w8ibcҡ0~9iwmWgZ^7nM>&.+8qgw˷ֽlբSUN.4'׿j¦ci۔b쯺^I}#G} J\uIix#wZM2Qx|4HI1@Y|zc3L4[R:TcMHuh_؞%ԓHVKmng>dwKq7kf}dv^DO߆}| 5z UwOۼ]nG4Şkp K9|&0EH?&%8[OW!~]{,Gj t-sH4=2 #ű+1:"1]6h6t]˵室O7~].QQږ+aΩ%;$h ^ 5{dU)E־'FK#Z{RЕmJk}J[K٣J3_yj;>wohagX suIE{ni~o|Oտri,Aa(]f{UӍzk~O}=?>vYc}0_a(8=QSU Rո$ܖ߹]rv\X_4P+rTz~5У(Ovw<2|G5 MBu8;$s{W*ZT"MӒW* |CeSr];F$qym6xF3?uHb=CŖ"QvW_ΐڸ]JQ<,[ W!Gx<#)J?:rU}UDGI?%]o$>*"L.!(+%E)hO\\WfX*xJ5iE.VvxJOW(/c᛻soCsVS6dIĊ|fbWGs^3]Z~Ӕga57{:n_//;7ծ@ʁ%o)ݾp?J e[ jX=MC# ‰#IH$}e,S2+-o>R Ԛ;CKOPW@J]fPTv qsF.6WKWO帄W?K>|=!A8ϖ^'Qoc/QnPW7=juu/Cxڪe_&y[h@o/#~W*UgFTkJzt`3 hէo{((A-8r'͹}LWY&jRWR]WnzQv\!ZXNǕ3<rߟ=kJU]_k~WN6£'>VFUݥ6e%GMfZ5}]~L}?O:,O= 6敥kw 0]B?l*sXVN)I5];y]'Ϋr'?Zgmm%fWSj8us|MYb6QwmZMRu޻G?B>!feP c!q2Qϸk+S<ۄ߳W歧]?]/4| Uc l=4Nm+o;y_ʬk^K]޺gA#yab#1M Y{RVvqK_u꒺߲/˫Qo8ZSti^Q;]~zM؏|<~]kJsUke._'¤f%Rin;+_?'M yI=\EUj)c~п{|~ypvkfTZoKu]_ LkT@~_|ul󳞸+SSs{kt#Qua,s{Tb9f=qه?Z]Ƣ[hl JYw4ˋ[(ip}q帹/?7v}.xk~㵞%ٍS6?\b,Ҷj/9;mopvt$>~?Q5//͜ގWdg! &?ϥpN&i6m?"K ~?xjt¹S3ѽ߁[WFD ybc+v?c(( ( (?9D߷˜a?< -5T_Oc^%EnP=g`x\9G AV~5(VԷ6oj@y&O9S+<\iBQD{(1O6osv,kGS᮫SE dX|e*ۿ~N޷]RoD{u]L*^yǏB<~ߥ{x\s|ӆqZ[Z?J+A̜2x\9ErV]/ۿ_sN3^Fbߜcמ?>Vޟ} 49C^ovyk9degvGXc/9>ݗ~;.ul}㸟Q8?19_sbi{r:\={hoSvV͝10PΒF2qS\zc_y (Og~WGmu@%Q ;xr?}z7?1[%A~u/]Ql=@c}/O|w/ƭիt9^,}zg֚zկ62A lF}BObYS\Oϸ#~U Uc(c?3zi5V4m޶][Kc 4"MKu>A/,>KSQ>W~g{ӉZJ־QIU]Co[3%9ݺ|n:ϳ>-FwUه{Zl5J[HQNv;|./ו~w s>YW_W ]^FelCkOl,^FLןW%OHjv_^ԩM)){w{#n[8mQ@say<*O?U%*ߗuv'~! r #SɆo'.F+5jOo^tШУQڔ~T~0׈oR{&mC(6wgv#5j._Nɮd^3 2y0mUyL2̬ke4m}Ngƪs>U42gsMIOqo8Cr1 l۳<ƿiJgu5t{#5y%sáocV4\P8 T)) Wb{{km>=}KǍRUqOirl8?Eg*SDT+kkOW~]nmtUӥU7͖F>GW{M.eoiN=~|-6՞M~?h$l֎p7+kg8b1N:yIhֻ]Oct +#n;-`?}S1W_7Q7tG1\g[l-bD1`@8IcI&o[[;l*=z[OzgE5;I%c8a+ =w{)bU1㳴/-z..5 =`yIYߩ^A2A$Cz̽ sQ_G ZnKx>{˷峙fKm#Z䇄0ƺq7V7O[y'CU^֪;Cztx$j/52_|&gIɧnNjVk~}+xVM4Zh"l}3OԌr@Lvϸ$uJOF<|},?T35N _GUݬ}3~yjjB}J083LX\TOI?~ƴ"~ݵ8(v_?N09?RZᭉRHk}U֥OI__}oCcKD/ۻo52zk[o]:WqIOI JbTtUq~`9_fn_ǯQiۡ$FO:#U}o ώI]ok/[kV<#ӯF}#c|̢ j>T%Oj;`I ,>׎ղeys޻vO9T}6RGzm<9z7'O/?4 8돻՜oON2ɨ(U%ª'&/~z?_?]?gVKfmo?_Е ԨY9XEO,9^4Uqm5{ߚikjXȹw?C?>K]J>_Y#"3=+ ~hdga\9L{jVMw{0'3Rw:\)*ϒ|G_a7Gw}zn۷KFE^M*uKsCs/Ub׫&_9Jz_6<_Dyv2)ϖ;I]mgByu~.MMkm׫g_~$y "m}F1GM֔]emս\.8Zm;&|Y'_PTi#GF\:LHeɰswS1(ק3O-veH$*9G~W 7{m6X{GWjsvV[SO3Iāwdoy/=qSHφUiv1܁åi-4bjNO_ןVH\ϯlz~.W_q Ek_+i^ogV8=ʦSQ[ )Snko6I$1^f{{}#]3O<}ѷ:~ڰou_WsW:]RG9lޛ:{1+'W49]vx< Og#{WhuCwpz 9Qb}mȈC/}떤Ҳkv*XU?zEmEPO.5- Tt+?ӹ,*_̒8˴K6v8BUn|߂^ЅAIJ]_>ѡaz $vtqv^alMgDٳV0}O{};W<=HݝM]i/S~ ~Ǻ/[ˈn0^H4EGvdVP+is;??OO}?b;4,媺 H z:I~C6ժ}>*G f涚~]zgS k;T!@l=?/$d}~uW9oҢ'ݶmn/= L6?0z`g^1m=r=ς8N^_l_3t) @xsr1Ӱ.C;U'Ri7}So_ t躆s$s󴻕#{s=%vFkMrKe~\CZN6=`6o>+Tm9'xAwO+yv$OJ]wV1XTMJ3jI&֖kTS>2?K+f2k.u[kN)C̛ڪ=ʵ~ӒfsNϡ&iwZxB9{pQgWg$/i=~MFEʵU;ˮDϫ4[SJ_6|ߣ{WQWx\\-uZ?"I[~߷>eepfZkIwڦB.[w` O+saiIsZl>3Z-6I.Zs}1o'Q%>}sWF[mkOXŸe`HU9~ JF}?gT~I=ge Y#Dib;H$ԀAgyte|UM/ߡAG6_UZQZvMvOoǖ~{QxxS+s5=5ۦ]ZpI?翐~k>3\W+s_[ֿO0goS!=k CMe0_P|nx4gyLqH6%=9-~8[&W4ח|n̻?ўʳڗe\&w־ŭ9?I5\F.sӞh8_0o>`%~_\`8~lv%?1jha s qӧ=kng_++?@|2^xVH,D/sqkggY9"c//,%]V  {T=<=v%Ț;PWi2o˛z'Q iGMS֞L$Bǂ.? 09wrTz*tZoYwNIgVbP2f<}g4mD#FL>b0CK<7\rnRZVeOyp|iw<36?VjܓOɲM^5~_]_ĿprFc&96|9l -t0? xj)__-OV{ө %nXOo=+xHF//Ym?1\]Eyujór$lzq #m>Tͦ}>f$a~gӧkݣS5yw9Fuw4/+y,A8vݜ$/~ƉbھyOoKwuv(1c!'ؒlzԬc}޽|DN?D;qk(x!FV̬W?  zZ7T rFDS%Yd V_GA@P@P@ul࠿-C`i\L?GĜ%Uw)1~%Rk4?[0Qާ-,緥|E<#g{Kc>u. 9H$Y7ո'$yts얟6hlyG=RkN.oKFb;qK$tYX>:Eo@3!_^O?/]P/Zۋ(Ie <^/]___9}2/3q'g 6 '9}8stCo聬8Lq/rݿ穴7~ЛVČn35wݚcJ2 9“"2@07wں)_"tB|k_2[+{tiFo^q9ֹkFLh'I,vB ߽g~QIZ-yHQy䷂,GA<֦RwR~O1eGB#zJOF w}IX#$2\u7{kY}6%ƞ#o_?5:M't_ q__:mMAd_ji顁=Hѐ^#ef'UđӮ_RҴcq-?S}M.;}N , dZr}Џ\kRk/1qp&l.x}}:L>6>߇>mH>|AhYɑBZ͇PGyKW9eRZo'6OJ>k=*rЃ{t?FíAp4[,ޡ67_9\W~StןU mbO]([_ײJ<8uLP!U73_Ҥ~8I=CҴ+#KXa ~!Β3KJUOܯ9wGs80R;5u>bV%06B F6'}>)|T}3j]:rj&__\\b9;u>m0+6)}gvpn[?6"wrS Sm6GҺ)bWi(z-wik$IkZ*_ww}r~x|-l7.##隵 0Ik.m^[}[|x?:qmY]z^isEz螿wǟlWnRO1up xG_^%y%I΄yu^Z?tP/ {>_*_h/7v7}f3Eo*K˩Yoj' t" EODVO4ɿN@'mlkoWl5?DWs|YQ)U?_u?9(xMP'FVAc_Q_dbhFW|.фS/ӋI|W\b8WT;onxH+=ק2zs,=|s!'d둞k[y9vVCʡJ+I^ڭ5?Ee?Fqxw[h-Ct_ąp3܌sQ>u'ɭXn*t[}>4> 5fQieԲ?i&`_G1UNOY%{t}nݻޏ  jǟNKyi~5SL>*hkzZ~kZ@x ד?͞I;h|`<SrǓ![Uw¯v|hmKfe+HDA1^ω#KONU8pHg!e{V|_^ѷ,M.[>-AǞTߗԡ1+Gwhh?Z_w{U(rKKOkha+?Ǟܷ~/;R9Br-mgg|=^'ݤC"fQ@ot;rAY.Zׯ,"IE-l_?~?kYVM$ˌۃzWV +v}zXX6֯}GlGěJ˹.$d`YPn_eˑYt}#bM{z/R'<vy$x+k؇]=9֪+O1Lr@=WaZGuUz- Y]_n v+w:M5M|F* zB$!`t5b_=_֣~g5OP??W$!+=:]2\۟RdYѢƫAx|¦ Њ3&6Sd'K<ɌsO‚ ( ( (% jf]biaoVo[_O /=W*K]3 G̔+RD@8y?v}8;urKT0<ԺI'kSi1LQP;FHlMy?J䪭v=N-/_]>\ {5 FVÏ^A2EK=9ΗƿF}=MžUWK_DrWɴC r2Ϧqk/Co莮=^nnsǷ57j gD6,㜟. e`l\c=qkr-K6/fd}h~$WgT6mm-$v?:Dɭb}&R? N6_ZXH%wpy8^L,+=ws:w bדWkyF:L./~gEDTIn?N5bwi*ۺ\7B2#:RCX7HԻtp8#?|YiimѿЊ}R{uo* , 7|OׯZū;uzY^G1Me?M}fMȫ+td1D%xQn_:ㄽvz~O}v6' H QQM%R_+Y㓁nn$8FLvN;2KW}v~_lc`ݿCmw}?cp[_o'>{zMk ˿՘~Y?mm^S+i?J*oKy܆)}W̸F, g߅5g TZWnRԯ2nX0+Fzw8X{^"^v x}5X6M@w?R?J[}8ٵrS ;Ь>ѣOk:x@XMsE1oVKN ?sRIkߣ /Etv/jPGm406>!?3Ze۪oK.Vuvz~N?!~)%s}Oω$Roy(i"%N?@R]^OW{t_\^vߗC~+ k_ |Tc״q&u3:;K]_kj[(W?/O}sE=ՅBKtcn[=5ʀ꯭|/VQJׂzuuKG+Qz_9z8Ŭ v2\D1IѕU*5X; :QQV Q-=c~z8"v|^BMߋI=?nm'fD6۰(?Wy~.5Z>=SqWσe'"ewFuNGOsΥ(Sz{ƚӳ_2u#*+Jv7צ3G^+O=mo({ٲl Mݴz_C8$n=-4AI`!~'MGyr*Fp~~k[>q~;*m=-3M$R[4&9Ԗ9zW|YuRjTKe׵~aM]nן]/yCR#H5%yc=h8b."s`O;jjK+zrEſ+۟ I/Y,pIn M-ٽHv%lg[4wsg++42a랔=©p6͕w۟N?_إz2w=ag"? :6v>vj{tu]~bw-J F_?qL{ׁ{)ǒn]9Mzfhծ &'P* 2y#OMܥ? n=zoYRsoϯ7YMRJqa=kr2迖V+O&N>p/\"g* WKoV["t[dc`Svi^\D7^ΔmNOFυ#6vڞ. b'_ʍHdG"t秷ޢ)q ]Wקd{k;Za&P|cH_qf޴'5]NwJ&_ρ]pխzyU\K}?VVDNg|Uq>6ǗT=2|7Ag1ծt~PP@P@P@t1n7]O[#?~S)k_K.qU2zUw,In9/\s5o7ѝT~̚en\U$俯胳fVfhĀ( y˓׌Ӥ$c]e{6[:BJ1Ĝ#]08+O빗C+3߁?Z育׿'-Q/|ֱ~Hfdl>Orh^]eߡJMzv;u}:$T4О\1\3>]xڭJ׾+olg '5\/osE˻ZFLzTl%G'b߼OUR}7eQse'Au?տSZ+K^eYwL>L,#m9Ј+r%ч-vzkk+>ڽ#oKNn &|xRw)bǏv*;2I/^ooivz76@g^b>v[TVnWl_q[]۴Inyb#Bp\JW{%MϥIUlAS!6?]?o=֞OhxwZ箫\9Tq2PgXdWZ ˹fMI$M~`G?=ToYpzN/YyhsOE߂$|bn%ԴG?0-W0IfS_pX6.z)ўGs^^]?Oqnj.K:kmF9纄ʲZ< w;<o1>cfdOo!}>}xI9U9MtשUF7wE3 SwMoSҭ44u{0<6"Sϣxc qj>8iѽ 5?L>x]$Csf-6yJűk :xh?/6*ٽ1pQO7"<J/@bˍȓ)F޾+9D0œ,~_mς2> Pxo.-.7E#=5\$vۜs_5ԫu`/o ՜_Vm> BntzfrG0f~\38m -Wqƿya1uT9yw[2{޽}:Tr #bY#(VsHW˳^ZQov y#w?~?z]li-?>>|2sy-8?C[a5S ~9S>nyM,qkǶ3~mHG|2hdh׳ϥ}~aI<`??O?s=߫=#nMq(7ӷN݆ko6znbeKk11#)Wӌ+<㗪?A.d$$_ 6&[_{ӯSO(DhYUgYL?<3z~*_J\|Ͳoh -bM(5XhEm_4^} ^ז8Hc#g= 6:-{=ɰ+;6{oo޷#8,x_5С qA\a*`XZê}Ϗ^i)U'=r=krSQvj»!/?9*|y/څN,-];?s:߯eqO+kTݳ#FP(T >~y-U[i-m6@SE\ u~ P@P@P@]G?/6?c/яng?~_P~[ɱ=kr#qe7@W ~_b'}~[-[b_ _U:<[eyn&;=ޱ%ߗrڼn#ho$ēϦ+ԩjy184zX2=};`{W~=?㔮й=mM6P~q7mWZGv+鞘G}붌n+]F,~$*9UVm?ﮯjn-bK Ӹco*+tmZϠZm܍HՀ$zN[9J.W}U[^ =}7OЯO:F(l{Ƣi~{W3k8qe\Ӷ8U=Ej6[zW#vw Y}?sy~?YTojM(Gl׸Y)7u5k-]E?ɗFU0"=9yFt}4w\0?NJڕ5߮^v)V&~G4Vsl*Mr5 Cߑ)m޷Rqm#|ҸtFkBӆ.ݾRh0KpyQ:ۣB]2簾 }{iveMJWX?M*J=** ~W%jk_m-OS Q[c|^_QaY Տ^_JS)?yRrkۡ/4혹oem\DVxb _>/s_qMVW~8V)/_>Uk24̶{v u}#]B;|ɋlc̛}+/89?_ i5ߢ,*4۾_mOxVKs/ȞW0i`Y{+)[G+Ue^Gej;M53HʿL:nzN??%VjZ+A'}]n}:K5Bԅ SNܜc){V3xW,10ۙÀ2q~e[ϙb?yӍmڶO'w՚L?zX.=:v˿g:]yM-!#eL@O6_֮8^ew.5T{/۷Cegd80Ilg#xgwv˫%ɪֶVoE!f@d۹۾!_( x WRp}d׺_]5>Nukk_=?/0Akua$vۢqlO!.qG۩I*^m `+e |[kun[W᷍5}wx^]CF)\IfY>ow/$59v]q0Ou޿יtYJUQ>>~Ԭ#XsY>ԲE&Tf yC*}UR}wJO9?7p#ENZ/eqfi*ߧNx ~ Z\FAtdsqki#Jx$* q9+ӚJiڥo>LD":x*! CűK-z.<ہ)e2\:|xۊq2ۖu~>jvS= ц5%S}_J i;S9553uThڊ1[r8+Sʝ+ۑ/@)Vͭ3վ^?N5k]܎"nq=~C_eG?g <*II7"J6z\84l vOJ{Wwe[`>|cl8K~lݟ|Q6+<㗪?C|2 (%GX񁑌,Zw%Ku܃Ɏ0W7Zn|g:VU7^HA:ou%ץjFa_(h7|z $\^0i_tbwk7CZm%;Xr֣~ƄKO]O_.5K,lrn3Rxc}>[Z]mwRMrZ6 Yfm+ 8Сm迮q8Q[?,h:<"ٶ["q߻lo޵f<ZӷQ]]?ڟ >[IM1A4Ѷ!1M1_5̥R\ ҳџ 66o,ּU}uwKggR^/zjo)'Dc-|7m"'8?-LTGUu]ڭ?L]o֌%9<#αN}n8cfwwjy a%Rmb'~Զkw_4RWe~v[o=E4Wx!̿ؿ5l{_c]VM]uYWI~%X1]h1sk%Uu8ΤfQ.?)X\gHK#( ( (??(7ŋe?A85[_b^K&ī_+uLX'rҼavC ? ?Ogܺo=+tId۷?7?zQ쟟ʍT֍|YZA .c%b:Q쟟r^+4FqܓNsrjyt~X(us2ڧqdOZf#l'Aׯ8i;Kl xaBXr[h1zKO_b\El" sǿ\Ni_fsMEj!ځO17HRR+-_SnPIf;ɻս UOeh~Ա}}:q- <@k)ˢ^gUk·rK_yxslO9m4_;;莂 cȖ0sU{g Oe K$M`O Roo)Rv;%ZI~c; F}Q?_֏TQ6ױ䴑{5w.X|.Z%qܬ=?+m)_h.~ z|Зn#NroZxj${joUga9rl}_}[-) 0Kb9 $T,? ]_ϭ~*ɥ8Mb 1OdI&LD Vh?&֡_X={qWOJ 5%//rݴ_ՏvL:|=?rS]Ȼb LM_oZTm]:3Gnu^^j:팱IrUg L<4SIr]/KLViY쏽5dxzKK.Wh=ȋQqe־6qN~ϷF.5[)~ Q)9*1}=UF_ yi;%R_߮_t9I54\ } Lt'|t۷]-s4zymq֧mlVd~ #s9t^m%Z'>Po^ts^^n`t'ԴKH=l$yӝ9NvRR'~I5&2[KEZ_r>iz|SQSok_v])^@k_ޥڷV}V>5.C?~$I<3&]if"1O=bQL…0ѵk^OYT|?uxGЯ>c[F`a T׻/Ѳ4*[sQZy!U$w _OKl:6a~>SAc_hFǮUnk󏰒X.gã X }菵m8A(dzB%Z kI+Z˻N_fVZIx v\祕T樵vOdȟN"C,)79)~/y?[_p1 Y+i{|oC]ۋyDӗe V?S_>K1>[ M(̲cc7JcVޗx]{tk7lPc݅css?Lgn<9Kq/E27_ָqqԭiXr Oaɒ$߭~s?It1Ջto-CBto/w6s_7w' ah^Zz[5Xw"yn3оOynJvvl˵Z=QZ:VYH-:+]M_OmOr7%Gm-6z'u^)'cd]Fr̔]+rԚvmV.-㧧ƚֳ%;;d!PIv8}/tf_o4|N?7oM-?}16IUθOzq?DZ*J|2G9߶ >Y[ `g[av\呀:?3Ԍe K:4u$}/5W1G~_jފӊ~gvm07[j^5~gVE 3wKoq朔^_T3T"n:vGЪ߭a7#rͻ'a<oy>2U-=]7N6Kxa% _#y:c_tBIu]SgP.gۅiԯtWu:&v[%OMT{K[kmF~ @"G(>_WB~WCA#2H B+Icf&Lbmc#r0:cYrZr%.gwQwk G Λ~JG@P|ۺm MaEgyr7b2#g]^ZcӢn|; Ӡ=+Rku0G61Vz6ViGQ|6@Mg~*7~~v>Hϙsc9[N~벟1>R+K]UF,Zo]^;}E8wzuz<4?_onvw>V~_տRQ/k.!pYQ=:km\Be^V+|/#tލ}u 8_^K_AxY0ulrZBz)RMmmzv^Rz\q]s)SO.lo"S,ӯZ%Y__WtҋMÚsڟ/I휷߃(>o?/j{|#'\[]_ČϾ{KDO@K?zy)FK_ _:$x2s}Bc?~k5?5#(VcǞ) 27]JH/ٶ_ќniz7rtr=+k=x@56$:{ɲ EE|jrҔV_zY mibx/ཎY$<\CmulIR?{nl "Oo+W]U7Szpn0I-~]xNHﵛk3yeN!(~MJ()Juyjv jb*,iex=pcĚ<|>,{/ުM+=9"khPBq얭ogW518<{KE߶5{{/kڌ>e+ܴ1Syxkl.FStgm#? 8bF5%RS4:W<?W.}quMKgQfrg,(G.iT'ﯻ` mJ֕I7}n^7NK|2*Gn=ȱIZ+_]ajq^Wz;^[魕ScNZjSm[~5v?sk-r 5P`q@єכ, r%^l<[~+qkVsR5,z{y58&U:>FIOn=[=ƑZı1l~ؽ-܅9o*6#Oe%wMt"C6W_U߶ϧ2XzE/|wE,.uRpq?B1_r%7'-_EnokSE{վGV"t%o/K%IAkTvokӣ?&Bn wk#ҵ o;/-0Ϝǻ; E.҃RmV GI.k>k5Y&Q$C c12q;Z1}nQ C Ke{jQ8q fӥYxAC"OpKݽODumi]:Δ7|R7u+ gO2,ϲ+Ȟxiܗkie|~F6o>;+[GT`*rf\KËTeg}?i vOFFmNG7Ҭ>Q;o~PCQ@}F}:~U߯}Z^ ^k~^\7aomoq'ħq:;u^;֦=.lڻG;|_ 1]B0q5nb3[O|,TX}˽>%#h$i}>flwֿIp4A;kX|~e)]Mz̗*Q#(&> s^4bJַ] 7Y_[}CWwW\]Bnȑ.c%y[]{9v Nj{oO<'gXZ PcEđɎ(9<8bRM_s}w>h޻z'!GnsսGkuz+'5./_km=_5pu iip3} >nm~_ՋARv58ެmxOoNr<}PXOpzc>Uµv2*IFN~#6VZ(U\waYͽt4tWlg?ư/_?^iȄi}h>-Z yp!6 5ٌ%vm.׵ }U%x̻=CLU$F/d25SE}%' k%k{z_˷~|>xjuKhp-V.G@s_OUFqw_Utw} t8.e}6{_~,Kg-eEDe,m`U`2Lwr7ؙBwOZZ{/כ*|SR)˫KDnL~H]\;@}Gٽ_Bk,zN..^dvtxsGpU_*}VM|V2!SP!Yw jtzgOz4t4JMMWwNw?@k.ҭYIWGSFL;\0Z7m޽c y;s]vnn4mkGlnyw]yZ֦ ´?ž_CImUǶwoĺ#Uڭw-HOuH=䯬⠣>4z^;v9_?֯/I hv ւ?ؒO__ Liڿo_u>Cލvπ6~ljt9=R+k6]o6HwkīƕGӫ=8e>Ƽ,M<=.5! Ӵ`n3C< TO *gŐ5Js%-ݸsԝq{9iigkߚ6O}䚥,4thMF2:OQڧfn}[CL?7ut_5o|FaMh TA3sqҔ* t{J=W⎏ljv/V[io#N\\^K^ݼ}~ UxdDQYuyO,@]xAbKcSk{DU˦῏wgASKOmOhP]9k\ i -};3z_eyl-|?хV%R01)LeSm)߾ߏWjO=^ܣ}dynOZ}H{}Za;'v6|>**q$^ -?~Gݷ|/?i"LX*;G+2/%x;&8_3E'uq-}e/]إz}lBIQY3Ȕu39 {=_1w: _T3-,IEy/0W5g2@[3p=lꋨ?spZjTxYwH|nȓVZ}^=OJB-}nռӮ?>&$6[XJbOhJ{`prv}jv?t>WfnLrt=Ts~U>g2Ujђw%js{|U=č7ȢR?wrCaַK3ū^* i$O\c1(k yi[R(Oeg~|'Kx䉼dt߹6A-w~)/5i禝Ob"W[~~[_'8`1t{4mR;b:!M]~+T&Z=,WשF@yp0H!-Kc R3Q{ C[*3 TOSj:\e.L{לp z+hpR;w??=¦Ui y#U)˴'w=$^{_˧_(yX(彨D?|3U]_}SĮ.<}Sws[jeWm]j[޽?A`^%<u x<?\75%Md~W\@}_gP@P@P@w _į~/O??2Q郧!\[XU}?41wϯǥ|uL_n^^~ΤK_IC_~G+t'4O/66V7W?<~U휟%~քZ@s~ʹgfT5([x[ytTWߗ\~ 8Q95NWm}z##*3oJ眭W}?? K[QUݓNWk8IO"Sr}V1ኾ꾘sܾK ?z:{[7g?`+t}cIO|Ƨ<eیv{V~LRGR}BVo_?-: 5em"LE&p=?jWKwwۮ8=k9Wk[Oi TgaD9*`tP_g#Nbӟ]kN:ov2w7}v7 lU='IQ_֟.*(0~9nPGןuӡFyKsߧo-WRXRӷk:}LO:c"osu3Ms>^{5oﯽkDZ_Fqx8G߿W}Yu>*ih$oncv7s_Gjݛz`gn>Y8۲<0]Ę&蛮3ۏoϩFMhՏʞ~g|a;: FkmrLi : :RQ,[{`Fy6ֻlk~~+X/1^Ϧ { ힾ[׵ŹU:UqTjݵ$VAq*<ߛ~7_=>Oh.͏u/hT"`KӊnOUI}ܽ?vqXU oo/o7hy{u+͵ǖݖGX}]~/jZUݴZefC{lؗtN`Ʌ|m=>wqsbmt{k Z3t)l"F9縓1ZO,ohwkPT]:%o^~~^m4,-"/qqAy`WYxγ*V^W_ez^JUbȥO(-';@r68׫ [%z]<-;FOqN<ޫK{mO˯^ԠH\Im$fBmꬡ+tj\+пX|hZJf`TW>a$+y$KBS-~սt_a`#I'ɦ^w۷c[imq 7'nybVmyhg|8Rzۧ\5 ]Pn=\us?v*JO;*m^nBm3Ư^n |\1SA T< $cX 'ekFK~4Gxs཭q S$*/B$k1#mm{^ۍk)ƣPTwz)o_ci^ja>Qаq\u*;}=~v~<.7gQvOE}];rH8~J;UyNK>r1+&‘ ~e0:qF]ϴO/Io%S*)2Fn].k~?fo;_>z]J)Cp*o9__r:6G?}#!{u_ֽjM=YeYyDİov<5Sފߦ_~cuoKmo˹taiMHMFLL>MyUo}6̰tSw}V {-Yi "YTVGo7^ї]3{ՓЄu^=-eW:ϯ|&e/ kyI_2gw6ڶUz9$n%Zv[yK;:x?qjѽ OKᎿs}#3 QQOo_]>M?Ck{YM¡tx tw~yoO{Mh:EnڼCf9tH1¿e hݽ4m]^gV6O Fn]?zt'?\i(Qр%k}&zSqdg}G_ NQ4~:Ef]J{!a9 Y. S$_&DGDo?b??_YK_Dxv6mnYÑH#Ox wiO"~_tîb(^6˵A?*ҴvrD>mtH!yY-Аr3_%kVs:PohuiO鞾}.|Pqb|yOȕ@7H9XzWNM)6߳<*7MYg>$~ A-?Njɾk٧5!*fN={U:?n?aի4Gٸdt9z{֟4=h{$I*Ĺ@_l>|cUվb$~/>_fֱM(cq#6XUTbHk=kqzQGExz]ke߱T}M][E_j0xG_sc2*uʛz?;mNjiV]@ap1}sWO+hss%fz?s[wnzq󓁌~_ZEyߦ46Uwk-;-m{mCD7Cu>P6{NpMjaC 飵 *8`eepD WTeuu_aS֯⻁<ɏL?m'xy?XT&g{_|'/,]_;znkY{5?\3%Md~W\@}_gP@P@P@go)G8T>HvyGV(|7|Gpbo,e1 2V JU,rI;uw-l}KY)ǒG~S\x+yVKA<Xo: I-,}Y^'I ѰJ?YON־_}eQ.t*=u[5젔;z{r}An$_3[o=}Əm?TEJk}ݾXE#c?޺jP?^(b=y`` :s_^T$\iϞ7~ ^d[v?DZKE!y%bdfO^ ]nJ$Ѡ8ݽLf! ~㎿9Jz)i)rZ3T'|?J4%{"}c:I׾}tpv>}6׷g6]ʮly=}e>h[ VYt$S7~rʧN/?÷){Ͻ+iupهt۴ǟָkb7Xh]FvQ&8VIj_1-62Z)g$s<\dolrsI/]M_^&m֏m4DT'͸5 q%o~^z_wԢV_߽)o{wMo$m𒡒58/i|{oko˟%E_-}Iƭx3Zck-]Qڿ,Kꘗ<ڧ%m;^>(UR>jߍ/~|z? nc5=/kgkkR~qGY a5dQ%өGQ[emiO?69b]JrW_ן䏶F>衃XӴ-B1:b L; ^]^2oߧb*uQdwgiII1G R-AAw+{=:ոvӯ: [Se s>pVS=1 zznhߥݾW nW9ޯסM?Vq:=tI+wܭ?v+̭+7_eg.^i-S[ DyiK,$qqx T]]OޑqJ{ee *J"1ccj㊷$PSSWW[Tx܉IIj`ZXߦ/OM,|w{ϩ4;w) 8-W}_SN!<3Q}#A,̟S{.i]g6]-SL3N+{wדZ^!H ' XO~:sW5o-aK-T-p Vk}kDw}kz[vVn^-Ev&qjD]?^Jb^mo5-|h8%/o8ݞ\5Hk%o?i#⯳YGͼۜ98A Ws($}~*j{-?MgMm/aq1ћ/,ڸ9x/%ӾtIH&X_l{_d# pM~[fs.~3:eαF SGjڿ>S} k b{fV|#Dp})AYק(=}Vӧsȥ/YѬh4Ɩ[)vC=x@ϖwXU~:!C]u-Řϔ[w04KI={<|DwVoG:#g"{8QxL ! 0K},I5f|&5})/Go̶67#;-^>tk:}k0pI]k}vk]\駣z\ď^蚏O ŕtmĞt;=Ut@k0Ҏ"O&*z}>/kAI+{kյ F6hwx%[rDPlُ}_ 4NQĩ5m_>543i7 %-aHg~ I/<8Go_Wewgi9l "r~OO|?yz?A+Xg<_vӭ͊$l^`}8=ɩ?ќz[\)sF1|x9;ӷXlti ŏc+j߇ZM~1N_ni^yscrmY]$griY;#88_Vy-u[_n6Qq;zyrgG8V==2eyeـ|oӷyNz'٫i b=zhi++ .#w6b rjd}ײChv~_6zǭ*TݏBL6 PQ?H5S/F\ݮkݮvaE||ܶhk5VC7-#]ñ> -y_[^5l.X729 Jl|cR8TZ=˱Lj˩R+J.)m奭Ykҩz"+,_!w.s?Pp(i.?#߃u >Я( ( (??͊#93ğW?[~k2k4 NF(CU_F"nxN_"}cen;W(??>k)YSK^: mӧgeR$hQ@{9 Mj4sW?+?=~u~{)l??/*BM=}<sgX˒GёiƧ$os{L[PѪzװ,F"z~Xa/"HFЇm6׈7o#էH%yD#.ە?rcZ_Y)ĞvNj*6׿8*?h컀ۓKۏznw]Β ~iZuXUV ˵r=uE(&GIʴV˙~v;xwT4-$7U#ǯlc\17z|Ѫ^.f_VkT<ؖ!V^G74-}ITlRwoCȦ]{u~Guυoڏ=,a_3" _%ot%լ̺bM=Gſζ8{߿͕~'2RB8Zx[gy"1ս&c53Ə&ү2|ۢl[NAs*^9u?]ϵϒt3+x$kkۦ~=e4)Ɵ&v~JTz?SB[w2k)YՓ`HLrdFwܭQmz|^^ʤg%mcsҼ5⻋WIô8ʁu'_COJMEjѾb" @'G{={~?863)$'z>g?-\ ހ \ ߻n秽iDm^u#ӆcGE_FߌwgP۳Co'{cnG?.Εnu#dq|FJ=|YUޮzV3:]qk ?],1/98Udg4w/[ekmQ]<-R[녑f%V7y;`9SW<9F3Wn.'I/C=FԞrwt;vJ a-_~SYʽI6?K *l"geq_Kro_:32(e>FI"e%D|8ǸвT-2sZa8ڿo?PxWвJmy9D\>\M4_s Q^ۣ=?®t|Laq8~ooA_5 ڲtsVOߧGt5ψ%pYzF}k -̽Vu/BZۚ<\DU.y{kx;v)_יuoSH/̟`wyA^?@1rbEϽcUNM6|h#MmgA+؀DcmB=_tRY%xR0>w5K k˻@ԩ_ؽ.Qo^0T^ӨJʦU N.]ٷyO6&lWwqa%jjl'ϝjzyojHvQ,ᄑ_Ov#KSP\ʹO߻\"ٞDa5Ok}=|.:sG;KKճ?Q"|y~zG߯I|9!ߍ)} PKmm 3*?€ ( ( (*r?:~Of4*k+z8'EXUl!g9sG~6W{qj?6GPkN*wg#6T{FܸGq_lWi0=k9U~l{Vm*'ˌ*0=zx7WI)5˷g=^I=]tz+6OU"F$o5,i޿?$r֫M{oz M$؞sOR= yfVxi-d9wo;c>Oa_ [_=kƚqu|ӧf][ b9~![|wgA&i$AQJsAۖN"XȦ[l}ŗRE"ޟiU^vˆ6u*"\"ӥ;XIߟKkp,SOnhpNGVR?pWU_Gvk_e7㿕-X? {04%NZ=__>lva&c^&gj(|- Q/+cp0[OQ_Erﯢ?lh#Go?.OέSMwqu46i<3GXx2E},J (J+OWs`l_NxznF`[ºTIms~(O5G*F|o\{\\>Ĩ-ᏹ+a~O7kvpTFtG=b9+lJ2kMOS? IQc?]{ۤZwK]5}z\FJkmҧ¿6%٭-EV!? g֔D[=;~{#lJmn&}<`, 4rixg\*~?:=S_s|~*2Tq|w~_KV :UFޜW%lBU_Wdxek?zUxo[_<2d|D#J? :{so?D};M>ҭ[d/Eai8Y.r~kݎOKo8IZyixhupsd~5/G~G|#[s 7[Q"E/1oǸlqj2ѵ?LbtލY7eok7ZBd([?F?w򸮈eМom5´o{חGvy_f||I!ԦDk]Ͱ+(O<ѓI]WsǞ&}}5oxOZ#.4>O߈6W,$j_g6//7rғDWyę--mp@(IL==Δm}oo>nINr"$RgM9RCvߔ08̯^}l4N>}C(J~[ioC*Kgh_s&h?/N>_oQצ8xә>rnSOw]^1:ף5Z~  ]bAPգU+W6){H[m[|m[ϕ*|`F TIev?!PN?_~SleTҿ|ۿI4tлXP|NSԫ65龽Ԕ\W^T[ims°Ϟ?-cs^}D񗌴1Ŷڻ$Z+kǑ=rʗZq]4p>s6vkn9L^ У%A?kn>ѱLfEdUSW_z;Y>[(k]W?x3ƶ?^Z%=7L|7*\0hѿ_|e5k7:\]E[v*P}~]gk>~f$#̾0_iWi{hK n?c&I鰏aofc\ڢ_|={u?Uo'e;l|q15T[Sq^3ɋ.c}{cį]Bi[짭kYNzgQ=15UE<1u?F{"X@A!A̟9~A{7S6йl. Q׿~?_p5W Iox}Oָ|_dvSmOo^{qӼ`$7 fgmsBw-kŮOC/:W|ꉷ'+q_kp^_OxM`qQWW 1S߯o/|/dGM矼C(IgM[NS?0Y93qЯNm3˕rǙ}5}_i% anuM}VÕ}{}|Ogzw-k9@G1r|nxyxgkk.yZ߭U~w"j܏ x9Ǿ,ɿοImqNz~}_ҧP@P@P@?> D6؛r~;~ yP\bo. 'hl!ĵAlpW'+}#VwbTF?L1:?_颓[3V=.ɑ2?_||/&q3{sAڳ'^uv{fw^;'QHRn6Lgsǥsժ%fjJұ:)~=>z! xn?Tʏi.>O foN_g $ gГӿ9 I&䩛P]_1.. ."@ݻۯn?ZӂI+[WCg+/gPɯ+>\$|oLd*t]Liߗϧg-6䡶uXmB I_߿kڜɽ:i.|uݱ(g'?קO& qΦ֗?!$rsӧ'񪩗0ʒW\ێ&%Usk+;^>ي_Siᝢ>=:`W,a_$6C'&+F 3MbZq],lM<>)i2rO߸>e,_u^ݵ8٥ZEm-Z_;WLc>b5d-{uMt;($.ζ/_] e =9\y;-tw16O,1ݛmn꣉J/> XoCƇd|9Q-͊>s߯n7ײ)=z+/[v>(>2uݤe~_,|U/BY4 LwLggЊΣx5,dc9GAF50oki>/Sk=HfֽEgNI+{&{\ b"Ҷ޼CxO ;ZoXح@ zU5n}-);=mOþxgŋ|l|QaMK6bh@׫IA4w[~7|#lkzim 1G.̲2B鎣ְ=}uweロyX$F\YO^:w5كm;~GeYit׹bҚM>Ԡ(~*rN9}*k[5wGJ߂3压-WS-xwK{f1RJ^ =>V_Ŝ=.~¸6>]-kl~u~< F0ֳndAB@QYj܊i^'̨rվtcYaF>^g=gSe0x[J1ZAWeZ .~$tk<VqjQZkg]>]BMO >h$kʏ3#؞bxr9}ђMʢ}\u>*jBN1zSkn$3A()6H{ pOJ3FI۫ӽ_S؂8]^{?^SxkRA}%FO4}р<3}9|bR[קMLUgB$ۼ=M5-/Tv֪oIcsמN[ʙ耏pO s[4j0Knտ5KdsRc Z/o}+1r)yi&%7+n.K/ʏS Wvq0bQpsg+x@leg\[Y$] aF+Q^FO$h^OM[͞6c_'tY=7Qg5KG˯~{7-!¿1kan .3tNJX)I_I0^RZ_m~XWw ݹ<+hn"gbB:~hvj\SX^}>)/GCmUꢗ۷ÃAϊv3Bl4]~99̳˟a]ZM]>ou[1cWhCepd^o\m %zQڕv˽ 4J9ˣj7ZqFZl]$`dPp~]8ڶO$m?pKE7_4|Mj&=Rńq@3؎1s_μPSRɶdUO-~K_J?B>ieq􍎫ΈGi³Kg`zߟ?FM/q2 ɵq_SGp`Ucn=,S/H3kLͿL}Lw_ӊ|9=YVH\[z{M>T~0#*|<|@=c=0~_ uVM!~񬎊X~x>{[̽4gT rr19 חo dҤ%߇Yc'կJioQ4-Y./dڱ,ҿ\j]_Y"B9'[f3__C0|}||<}o_")X$3ǐynjU֦~9dXu}^}sl-2$?ڴD>ߑZ?o,Rpm7e `f3O簠 ( ( <;4I> 4>/KЊ׌e?Pb?aWQ1go \Lןa־_xxNͷRN1?J[v:uvvc:}Z=6#c?QG?0^}B<Voyubߢʑ@K;\'WTwO7?O^O()}z>KR/fPӺWnkv<[akżqnӯk䳞v]:_ȵ72[+,wc~ ðq橣絶i/A]8_NiokQs*;@g$ʰ?'WSfzZo6x.xB}@玸>ԎP٥=6ոFMJM9N_V WeYJWfQV^JO#(IS׶Wz#iu$zլO,䎬ۏ=ׯzx)M=mSƝՇĂ oֹTuҚݟ6},>!ZDvd䍻{g ?JkeU[i/sӥZ[ߡu鎾<{^ owwz;]k_uS䦩Mk{T{/aᛝCv.+ +EDy|M:(?T %wOVE[~en#0Jn]Su?t~֣ܥ&wwGGVgG |1;Uɝ@&hRK/$3RQ╽e%M uE׭e9:[G'0? ]Ғ4ѭ-KYħ=;5KHVX" LL~rGj䋣(KyMU7aQJ>m~> :k~5ZG,x!TC+xNy 7nU{-Wn> է8Vۯ}.P~0i/4GNUIm#ACb?Wֿ*rp6iN ~WB".WhR<쏠nmՖbVU;,06ˁߙ_))o{_Վm-lW>pc׼ShV7<ˀkG_k&~n,Dp2II^__ZU ;7=<.^[ʖ+5a/̃=cuuΤZs2&w趻ON4kh %e$kx9`ó?%ͩĦ҄>f<Jx֛Pq\ٴn&볻}?᫨u\6$WpC+F^kgMgn3 Pi]Emn]~q|v|@5bػy\s~}??LgZ JTI iFRVt)5*jnܪG%4rJ;w,Jb6r{WbeeoW̽!7Ïi崽JՏ(^R0^)?J8 uNKo50QQ򏝏t> 6O.J'f&ˡlRxCOÏX"b߽_}9)Vo?j0jV:j|PP۹+QK{eO8Reu|03g駩ښ_2=ɗ39"Ua-2 =w~vS˝?kyvc埌ddg~Cbh(׵(2|O_H?:dS?~$Mֵ_c jg}:]|˦x+Wl|9^\4W4W~JUo}|}\ޗ~Z~qף_E<ݺB ~k ϸ+<[a?VHi>k?şڅ_kEQcvMhtq!eҽ~.Xm]VhޯڿI66qx_I:x_pϦzp%,vGFk VqpX{.i=WO~}OcW 7v h5O[,mL:4s_P$ּ$u:IMGϗvkͳiZ 6u?Dm|U¾5k_Iq8HVU/k Im_c({Hrj.e|VM%7?Kap# NJ$OVstz&cas,.**kw>\EI|'͌Gכ n.\ZCZei ޘ^Dj89m]cJ+lی#ߜ ?m/fbjwUhdz8\UpJ;kR]EgdgpX_Gӵ}O>eYAxy *?g_o*Fzɶw֓y%#/I!1mٷ6geT"4V2[+8V?~F=EV6տ[.ޝ~Fuphmv8;[rz!WSG/<:s'{.oK){DQc*Ƕ?Jૉ9iesNn}_s8Da;v~I8=?\s{mϡ26K%oV2!3IdW2]iz?L*XT>^84EM;Q5 b}:pzҤ-mϏc;~NKMYz`O=ɬI_}??4ϊٷg9%|>?c$uz}pa8{Y*S|+3( ( (?[ķ)]˗?Üzc!9xFX me>O~8@aHt_ [}euu`j#u qz.ZƝk.;'.彴yuՄcǷҴMc~vy@~E _NG6+ԖW_9k{$A2PW<}s?JLkK_fho<,Fe(^KtxƹOU9AξRN7:~^~v:7eI9Vk-ěO=0EwT5G{-?;D+`CrӆٿQK~^W^wZ*2b.^'뀿J駅5,ʌ&Z>o˧BHYHw`N1g#=keIvWnOKۨ(+%_IKl}Jd۷4k :Zꉡmk1 9F+x9#N3:xxyJ7Nv?\; :cxNW.wN_^$t-]Y%4h_ ׭_oş mGv$=SLԒkѢ.8S߻T"~XǍu ݴ `)2_^(/3OnH]R(hx(YO *jj롲<^_BB7RJ;.h6%+7m|}|, ~G7[4oV#Le`?^æ/[Ҡׯkv.:~]F;{=M^K7b! " yiI ֣4}/׌}>ͭ\ӛn"lXIZ2dy_Gᯅ瞶 w+by>UvrcsU/hO?-2hg!>;DgX!SI^P縦}x'['tT2:w,j> w1t,pi?i+t}Co{3/ú΍j7"X--K3-_y{_0'3o+( Xx#"&g\yIC_YJPWJ)|s/C\h˙ <3e}Y?z)j7Ԩџm˿t_׼Ov\]?yY9Tsq ј[Mxnhʆ!ٻ_[V=|g O£M:ϋU]}r#R||*7WK)ծ(I)wRoEfݯes0AΜQ|_W5o?-5xt'Xӭd(Y$ Xd?6LF?E 2_6;&%T/s \`S .eU9C>>I%W#2!yGݟ}@'k}6O?Y~ɦ[hdex_xP#[zWf䮖{4z}}ij[VL?,>W<n]~҂}9|]:Nݤh'F.ǒK ?1I='~ |W4F]G?}+Jy'f6WK[t]_zC%=%FG'՟v>^9r<+;Hc;6fߑ^YAJ˧}]zxfM0!II SgyaWT`%EQAӷ}×>:ɺ;k+$vE%#+鰙d]_וڽ_>Kռg%ӷ$7ݷ8ӊ(ђMz~7"U!'wI}?-u]y;,%J19\z״ XSs XGAneWNz˺ \Iy; F/Ó>n=WOъ<?ǽ?MşH-%p?? ( ( ((e]qu&2gQzl~CF5V-Zf^3E~_>4.O?.*\\O9NI=vvY8zl.9&yUll^Gxj DGnl~<=+0GFɿ%˯MO'AQ]u<鄷!'WzЇ*Q_ϐۺ姟[7VԀ?ҝL?eѷ0qg>=sN?ʶ?pro2bW-&O;q]*2v/3Ξ*S׻nQG23~:z~ב:5m4Yp%No\K_henwgB t'ڼwiKuTgmWSńB2 n'$cOj池%ͺ{zkÔV"mj-͏WFWOױWhH$~IU>7~ ]Ŷi}"H?~cƹyK\wr̷%~ u!(&m/Mg붇)x7š?5x öQ[hӭ-mʕJ޿*}Vu?wxhS{.k:[C#JHHCmpxǗQ~Rrqz~Z3YcN*D9o5* ?3N xV7w@C5,+FbiaA w`,%x^sI5_uSMﱅyge_HA$/@#8=:Ovݬi^=(VBk[iio˽mmNSP𮙩I>wtNy`~r1^}\% _7?KmסfmN+Mݾz>tFxz]>II%żI݈BFj?-ګ~hfYE{ںv!.b/6ykT\ۄtoe8 uegӣ;㼝PJ~I3>(|>еZ F\FkYX"R,|l;WuVwV*0ķBsۧe}~r a1n㱸<i-0:ܫ!;\%*t!+}ziqվF֯𖭥jR%Cw[_4F3"Wq M.eyAyZ}sxNtjig[L,gKK{.->U3 =Wԟ<Iuz+uONJOY_M[ſt H>2#>L'\]y 9M>U9_7k(=ո^M/:ƛ5F#]I1URK,7oz+_?2E5&`~:|\:{giG'݊+Qj8(ˡ|J|ь&ߟ3k|C,+'箖8.\|ea<iJu}rz<}닲E<ʬ]9%O7dpu,~WX~q|f|FBҤĶI;Ow*B k$>b?v-gy<ҒM~W%O 1frMEk'sٶ"WxE=?Nֵe@}rǻ؈(*'N.V*HŽ{iժ|]jo$yïG^+CH6kVh6_(zqyZ<<դ[4ז?=x)JVOMO<3`|.kcecqk`<JU$W9ʮsZtߦwmב siX]7s?Y~m.C+nÌ\+?J~^gh2x >`Yub@dd[ebQyVR0%{SZ;.T|ҋzY;+U^,wkEuSO3?\j+Sς;[K6/౗@Pa3X:Z߇O%Yߟi7S+W;{5]Ȍs^'ۮ帎%]Kh_3upʜoQ;/kgd/STj]~/Sxh6q1Jo]Oޯs"Jk/ҡ>'-z>|~MabV\CsmE{O[F.v__GÙ_m#\;o&4XO-99|aaJn-m>h{kc."8/.QɛAl ǵCy60]lHcXGUAk?αuj+{Ǚ7mwe劜\ՒZkeO3| #9u X!d[!B  τ3)ɺ*J1mI~x nW+Pխ:O5:.-S:#|%$n\e2w [#e5&ox<ʎ:jk_58i,9J廟b+^u%mUʜ)%(m{EN~Οj|Awc C"R1WN?̯n`|֞!Ҽ/`t9>H+Fr=|MuR[5} ,$-UE->;`Y۬;< ;F]um#o˿G'_L,gH~azf|=q^ܩJ$uW)z-)9nW(4+I.lvrdqrGҾ:ɹhu^o VM}{]t>"aׅ܀Fp_?Vo㯓u}W]zʄaiW;5ƻyXշ&mR?%} Zjy]8A?n5IIG`yÜp׭Ƚ`gQ]d`}~z_%ω63LbBenV+ã?ˡ_}_jy =.E#8[ܑC{y}K}CŖ ̛|_x޹qRknDv;pj +|OdKٛrF.)oR<"+$mev?Towl%_Պ? ( ( (ҿ[I|8Pi~i8mak/؜S~u,06zt?c$ss_\/ 5oLcoocKTT!rA{tPGM/x1ʢkDz?lx^\,YQ^3}p+p(Q|U]ϗcegz~h焄q۝~GڽR;IQVۧ~ҲW\BA;wW'/Eo/שz_[H[=sǾkX(=疱}wjߩb[ Y1}k ;mC{9YץjήL@Oqٛ/mu=|>I+]nggt!c,?k:U;~# iۧߢeǁq?\zો]k ޲V^̽MQ,>ӵoS'?OX맗գ_^}aH3? yqNXG&o_4#Zoe]?=; yxO,@ZsG*}[zmy_h]r[+zZ<0ܟCc_5 iU.RMydѣF1NR杺:TRS/oEi牘=ͳ׭Z.77;*,4[t_ݞyz}F_N~$ۿ##߽|1Oq6]ׯ쾫gkK$}iKB+̩YCsP;r ɯe`qx{i4.=T#u1#SW,`"a 8[ Frm&5cQqJO'J7V}MQnClO195l&#}z3ryv.Ibݫ-o[OTs?g,ʧI'T0Q~9>gIh]y϶HՅ\I)Cd꫹Y_9[⧊.4 te$Ԭ~i1(-dڠ2 /=\nzŻm?⇇Rj|R;m!sbh'콜og~spIe5KڮMr6m_hZ?'u5ϙ ,掠k31Pt˷k|I/koÑa]b .w6d:H9xvL2tifN=8yM DVX-wSwOθ-yi4 ,WW;-% %ԯ0z ]IEE&w\+Wޒrn?3̲d춿O7o뮮ǂ|[>'i$~ےUU=Ҿ&øМtIY=lk_3:\Ϛ]v9?Ykmʊ?Pkkua]%ݨIZ٢1}N}X? B񰦣u) is2*o*׮ڵ>_كB`}FkJ1-%x+wGӾlCu&ĻT QWfaKؿک- VpꞾg5+]Vݭ>^;t\_'-Ht]{c-ɼo&YqNډϽ|5leN?,iOV۝i->q}_< j~mowIu׿oGedќ;3z0\b$չ\tM-]ڲ[ U+:벿O#?~:?;#wbQ<S$Jsq59r63^[ko3oEFO}>M5Hn$&Sqӎf\RkO=ɰ(}֏}7߯E8 cW%IM 8R X뜵Wm[K Tot{|-7[\LPd۶hBd?ķ'ZkϩFuo&G1-_Z}9mE^ygIyi>[kr0I$ ,2ԑ+,cM?o߁''9;n>ѿXZI6BWލ0OPqֿM2sKnw.Yk[~vhtE\ERSn+:n⮾z˱W]}Ů.S5G-j n*1\u6*={$m.q}v˫7g5c=ZVX ǦГQ屡(ںJ_<~ ܮAMnC%$]dHA1P? |2Uꖺ^}[zw:LdnMόԖkDFXF18,eUHGz^ϖ:lꝟ|&\4V8e8cS6&C+^㢷/y{Kw_Fz'5{6^ fG.OG+LEmV ?f[ixfxf[1*c'kIkӿC󈴤{6[_?eu)+ɓyH$~_-)։O~/_ f/d4y<@v'BlifBk>F6U^ֶ߯v繹:o5m+MQ-Ǖi3`pҡF9|:Qqz>Eut$uy~K ”t禫iݹClrm( '}UD~őqz4y.z-zko3o |fc?=RR-[,PH36m…_q?**TƣNh9-M?OC㨹K(]eN+ɟR#jOkκuǚ%Bmzk +Pr3V6Vkӡ=|UORm,t\|8 8aprtߴv=L6i |*N;J._gg˛"eS-e$(@ <9p5οn8K_7sOFO۬,ę3|c?}n I5/]=O]ƶ_-7g+ X]1ŠpPnZw`ISK__GxWjfi\F~ާn=دd2UEmZWɿgNK sJڇUͿ,!϶=0JP^~VGbs3wk8ؒ(#z0{J3\TKts{YNWKQ5ߜZ=˼ Ǧyϧ?yXoOK}>=ODKH"!08_Gj&+W/[χA#y=?;pIoAltquq'vۜ( zdOח|w}[~u<QO?#?̾axUBcqY_^?]p҂EyZؤ(.wO~~*YI}5Aϲ.hCHO|vMrT+|x{//M P)?zV1no_SZ C烟tzLWe5e_#6Pԡk!:}Z/Z|JeX𜍪Ml $:yQC'ʑvԊ ⷱվF[v>pVyݯ ~ )`U0ks}둪4d/  Q̩֕X'v?C/;'-| gZm2C#z[N=n=#O^kGrj7rէʓ_|>1Yx^{BRdM>q\&WYFS;.&5]=zO#4 iV u7 !٥Nv?ydʸ+2QM}.``SMoy[^gA֋yw/83%WV3wBFmko;8}.x[gfr֧ıK%M00'ܝ9fmӥ/w_X.Du^yO5k|Cg-.LѼ@00sU+BJP~]}K3w[-.~V]:d?u=Wq$K.M]9UV*MˡlU &~V>BLPxԏiڅ<ki5<MieRQOHF>i?~/;"M+Y`_hi-ٜ cvҺ3lyGFZk{ݚ{=9Zg3#$dž|Or`%i&m4?O}}k~0F VzR䭽״IC8!E+ ;%>iC鿊W|kaA׫: m+Nٷw[쿯:>#tdu+I|>yB{ҿJa~TU̝{W>Jd{p_@yl 8;?mmyձ]{}NÞ#| -*u9|ӷxqiJFtn=>KqS{u{ GDY o#ԊEr\kTM}^G5lo9jZ ^^6"-2h,V04jR+ʹ֋*nIz-ikF夡'l񁩥ٚa0 uĜ+3}^;W_=5yrWknd* gbqѴ/3h(~[_o7 ;;[Y|.*8$%i/>$lx.8)&]W__n x.?i E0In$g^=U+o/M}\uC`kKJGmO"'yeM{gs3OzSէ -?F.FM;IM6`8<j1OjePr<^kɻevٜ͞g7n;~nێNs]:-Sb=on??#kDڅֈ4Y63ysbZOwV^O?KZ?Gx3kIxqIn_{b_VI=kٟQv~:~ O]|ЭVq.jQnPY?6`밌y5FҲO{U}QQ7~m$}Ŀ l4]qy>cfUUEf>^]n:5r,kK-]O ٿL,7rbqӐyN: _<8FXpjO׿C\7bRi5S~0g/Mfjz}D.n B(6yX<{i[3JJ^̿_]<~dxƺs-ʹ% 1l~A>T8ғyi[N2[I?[M?] uHSx)zdg[ W)˪ ץjpEmr~dxcWc}2OrdžjJN/M?_^FthB}۴V/h4˹ G :|JyL񵚵yld+KuEY/NfU<`'~דӽ^{(ՔfD>{1R\6\Lt6v5_ ~Ozмf9 -%yoc=$ЙV2* _ ζ"mJܭ~{x7RlMfAeep50=1J׽hF?^}ZE1$p>Nyz{5ߖi5鷙eTN;rۮ1?F~S_791_Kw%|~P@P@P@GI-\;~_ 'h_ڑ<\8jUc&W뼾]Oc-7?^?D!XqЌ}N7՜dҊniri=wK__=?#3qdʟK/<4}ҷ;~;[`׫l[;핾\4xS~~z~gi- DJQa#1UmWu{kPho;[s ‚Q2Z=w\g1|\\7}˾F]m85Eߵor3tq^6a^Uj +&z<=-K5PyFDG?2k­_= 8h--t7纅scp;zgnOznoO_+wsaϧkXh>WŖ_#}zz .['L;괶}sLӞpx"Os֩覆4mfGu;ttV5͉wTߝd_ÏZ2j!avvO<θ*y}ysj+|խc/^i[OM=Osz%xjѧ<)MI[YYGh}+;3s oa'mbp146V*Wu|ijZE^Gn!ip+~a qڿ4qww>FޛokWnjڛQ>cAv-iψLpd^_nQ8ʤn[[NYbT{TUMe*MC^}R\,|X+,Ř彁Ȳ8XJ4iTwU޷wm굳G)Ԕc9-Zw|%DkeG . ~`2wcHyz*nlM[KZ*K>6\CY~[._+ռ XGsXjyW%=|6sv%ɨSwmiJ1||[u+y>mU 2H&Aρҕ8r&N&ҏ9;q4,KW^V~ldnvǭ>8x[I}hxN4ۥi e ?.rG8~Tv4rz[}?T} g8&uO_O/ߴ|m[{UضY FWc鍠⡐}W V5Su%%wf5tj:67%gWܮmLk3c/_8,Îq cF)4to+l}>1 v'LomHGVX۴v3mlIJ*kVu_~9(wZ84{AM8[M/ne~˖f@IoJf87<7_eeVInh7Ě=֩-f5s|idpk*=YC%?0T~WO4k;:sM͓Q}*ki*ΏR}#ҵ_בON3y>}??ө;lUtQ7~׎Q\`\ ψm}Jj?͟yE}!//lkrӽ!'Vu~1܂ *xVv~ҍyO׿fq7侸RyfIdh]BOo,ֲs[7U6쭥y+Zq,mg]m,,U-k3X_LjWnLKt-wެA'̽=vn?bzG*[k*rkhOMg'W.9$*"?];^S^}}:MD]w*CAopSU{ݪ:yUWy.5HX`hxV_[ S=Cz}y婯3][aEWW'q OX+W{YEUmk"J f_\en u p7~K9j_?+>(|EUxHɶig}2DɤgmTmc6(KUwoU&+o)N{?0 _<0I-U?~s$n幗^Nb\jX>.=W^"QQ}G+Qt.s_ͶN#6zG n29fW`ఒu]yu_~O}k -D_:!xҪ]>o_z[gLRwM<^o4,[I]O??_a^+Mo-֜oktehٚ;6yb=}^L+|Ltwkѻ]]ֿZԴ MWao 0'0R9wϯ*SԉX%y8[skJSSqWhZ>&r}m}m6ȈvKdVI>{97ޞI.bwB1!ps٘dZn.W͖'UצO} 6 XPha~c퍠+_%[[`*~m|5mΑ,zYX%lg`b7WxƜ~Ҧ/_#52vTL5+Q<9m wwme#?,l_=*8Z;;iP[V^ϞtxS/݀O3;W:k=kSu#%x/!w+SKvo}GZC$jXn=zW6)}aA6{4(ECrb5 "lc?ӊ륁ʣn=:% W9&쵿K^!Y1*ȗc!z>KI_TWnB2BUSp}zyiyVC*U we&ՅטaY(#hC~@1h4I]m秂*Bk_{_}%_[ΈWQ17E' n{A>_%[Ye]O=?=<ُ^|IecsMMu1F{B+*vfsU&}SٹK_?Gj}eFz_~x'Vl6\H|~Bf%k|n2wjg]Ca VWo$s_v[dA /l8-|9߯]+ӧݦ9vko-FE"R9w,%w^3wjv}N߭ϷNd"1c@~xֿяj9_Zf/0|9zğI/2$pՊ?B ( ( (R(`OB?hڜ^u8ԌWXv{yu%,5JiG6!鶱Q}@Ǐ+z0u|?뾆xDi_tgoϷ]jzO_r){xӴּ-`xL\9o .{ۯkiƱD> Uߚ^Oqm~6XI(1 h\5b5_}翇V_/K4hvaH5䓳jXH¿ͷO׮8?ioVӣ9{p=cGF^G/gi(u_֧_xzE!IncӒ{u5R]쌕W}VmV( 2νcү"IV_ߡf<Re>)?gU_ѫ_;,zzFm+?̋ =V5vk?gvo#I]*K[/O9~66?~_ϊ~&^|jlL:mG%` F;1~ VmMaS[JRK[o˭Y'SQJG|:׿ODl&ic6,e`G,o%߃uٲsBVK2v<$y*K_r)Yϙo7Qmt\laɲixo԰p)K~韢PvW# Tu(oQ61(̇z6FFEIY7xKU?8Obd6 KM?Õ8'q*fqi/?*e;4Zr(fqa#+yfTmuׯ~~";=&ֿ'HyHdC(_@Ϩ̧SOcݧx| (C?3jSiF^2Nܕ 㲃_pq^MC5/sk8.ZAѻ$z =_V7[v ]j~S⪽mgosYêI*m!1$^^#k+-:v F{.=?WwZ\Q*#O컚3|VvOy4];Gey}Vޗ^>_yC,- 11e;?֗=ӡ64g1)6m_oOBI~5ΫMEoϧҿxW[;hkQ6>vnmfy|BK:I^??MRj߷S4}ݿ ?u xN5MS7]Y($ Ѿ~2 >#s3]B. O,떥* {]Ϊtԭ/_x8nҒE3r^#WwLe ^}j.X;^g[]OWMBo/KukgCiТ5I6=#y-rKSJ Rzz^:W[<{ SGzׁ{%z^(Glb"i?3OƼG*_|qOrO'FZ}_<]N5.d`rs.==kl3^*.v.}O2Tm]_~>Gi>#)H"x>QI/=#{):u5EJ-4mu^?ߓúo>U+y$ [9/l}k UПťX.+[o:4Px'#ך>23~ma3rW\u鶺c2VԛLJL" ` '}_t'fw;Hei4Yؿ?ʹY󨫤;M+b}oeMq>T 1 9#q^g_2GrJJvГZzXKAmj"歪q[z/.@dG`O\{tjo)6ۢ;DI͚RݣIa?3W3MYu6kμ%R-]#oixz8k`sOrSa!W+1׹X\4w]=> mD.V~%Q9Q 31ϾM|?.kϥВ%zzE~gx=e[+L ^ުѴ>k r}{]Sg$EmOhxn]GLJ WJ!*GROO_Uj*T>9}ڿSޣ4v?=DX.&jM) -I}>sڽ/=^Ǜmnٴg4G/7 }{A-9;oxӬ+eo\ӕߋvk40X׌+.#}W4.cmw- &WVA zݏϓ8Sѩ$z0a?wt=~O^][A9m}c谹KRV~5> ^_Iwb Z닄`[6cp\3gx%:r?^M>8,„) 6n^MKcpnf(@yp-"0{W_<7:2&<_8'^9gM.=_P@P@P@qP~Es~7L?_ҹPU*)_sopuZO)_M]TZe1Nn]^n-4?~;T烒+u{X:*.0kI;ݗߣT-#mnUn־Wnm_˧iϧǑ;/ߎ]g"f;v;zq,{Ϟ:v}e0bvl,fG1\27 %j>^]:J^]ONni)&eݘ6Zdڄ茹BĈ?/otkiweB]=FIWj.9;u-|w ǻ9=~6;2>Æ6iF yy9A31|' ZEQw #otZҴ/Kwm#WxL:+'{u>սm4e4q9X[<"kҥiVu$~mv kirW__<-um Q-vy3ҽ,R.S|}gRK~0|`!ԭ\]%%ͺ-}CL_pbvK{/rN>?p7Q(3ۏm*^fܷ@3 :?40񤧦K 9uqO1 ;Dj?RзzԜ9޴i~:B4欮7Ӵt]NSVgEό>LY Ze΢C,;:Z6) Ig`\{{O 86Kגm~eB?poï!rg~_mM ?Ӓ%w}ֳC#A.bUo,QA?7ƅ NI_ֺO|q5]lmE My-}m -4L7l@2G٦6j=2^ά_/?3'k᭬Pkϙt5ԗ2c_:vb͑?*8qѭ:~\WS~*~кfehlu3}rS@+7TiTƺi&?*Q]p?e9m֋iU+"# _Ԣ'7kWzv?9j{?ׂuuM&i"GXˍs7Ub6U#Nn%m&Z׿SihĨ~mMuw}yR1vqy9gf]mwJ ko6_ù^/|k[[tfbp=CRѥX3%>ssP4(fbu^y~;=RӒz].߶dYӇ]}:~GiiFO- W!fXmpnkj-hJJ X|ڣiUdSD;32~9?JUuy`nV?/z{xo%-㷐%O_kuE3,V-V}魻;}oLq [2V..;53մ? }y-ox6jw:Ǩam%#VY~+#cZcISR7%~}xlnmRFRnw8%P¬_\ü5N߾Ӿ[Ox?z_Z~kauwnV_`YOҿuC~ՋQw=Gos ߈+N3ݭo_xDW3ZA!e-|78jR:Ti[^mgxq䜛MkO;CM5;_) S &~!pqOy-;my[c r޷o_=KLQ83|aYןϪTn[BϚ"ӎ|v:RUU? >zJOϪ(MvZ'vקSnm!Yszsk|j*(=<=Å(98-U ~e"5 KtYLXVR~`GbL.7Kht5yo!*G>:tm[v,Iͯ=^fyZg"292IμԌUJ&5+G(FmN~>|>j׾$D)=wRq%&-^!c6b,._ތ@ӮC[J˨MqX!gXrǣy.\W~nK>/Emoݾ y~YGoƷֳCGAwokTz]O]vϳTaB~3xV8z iql nTmfɯSc=_/>>xyMC>/-8<(S~o~[Nb#}۞6ğ@+_kisemzw2uv[ H UcN~|߇]kyƔؠ"ON g[S׵/c%I?Fy=I 8?wӚvosΏo` sLvzSE&}X y@Opy5%ECe~;Ӌ]tw#y|,^z$iUR4Jn;EDtWZ8)m>=xmMC_"*9-wxImbx vf4ψW\[ OSdFQF.%OnGJ\WM?gemݻ]zuSDS\}|7|yX5VX{]]65Á#yE}8٫F=4[:'#J"L7?Mˑylbtw&[>+iIS7i FSD[-xhws?uT|>%h[_qjF*bXgNSOտ?:| 8i:WN<yyeKvBCfBI}@`P4۽ZwQH߶]v&8vUzSϧC Jjzմ|;~Op#g`q_/;x''蠯gq Joo4_[r|8KD;2"뵥/33}ܾ %|W=Ĉ#H9m qךS1wʜWTYUkUZKl>z~#PߩUX)FKc=WE<:JkVK_ekpȭ-(pl眞M~| 6|gzWG5uckJ}>Fg GN/bJK +RZۯ^UR-YxcMl\\`yQ?[sG:Uu8y]y_L;֛=wݯбBGa$E?#tX6k:XӝI'^:;lz>_>\xwuWEt<2·2y =k2 -iw;|{IRPA-wEk~~u|p(|..C|"%E!KI,`D?ҿN˸ Z,JMK' _/+NiqL`bwJ!#:J7 ]=kg|ؚu}$]v=ZuީDkiaft.B3>x8Zm$wեd} `tG?? ~ChgOI5z?2YʱֿψMJXߧRӶnm=+[7;is$NkK7kxxrJqb$շ}|M| pO_>IgԤs6lq,m̛bQ`gFU|!^U^]5);t)*(MߚkH<{|2ޡeiypuL Y7?)HָG,X6ds>ows4=/4_Y{!eXi6y9?!K Vv_]מc|[jKN~~=[Jt]q5vHB`Im8ke}Wz_֫F/|=$ BO_@xgx#Ч=Eh2]MmtVrg,RZ_x{JVw7G U6#salmtxl|UK]t]ޝOZj?Ck`@R9h{^ut= K|zG?7,~x@Kw#W0nJuV1 6wֳ$;3*bR}}+ӂ+PzOgո[eI78vvt3ѺΏi-ضVyC! O@;z5p+Ug+]v>Ęj~=:Zr+{gԣ eO\=Ky>C=*G}=%.]|MVw&ٷF}r znUÕח<|_(6tVyOjkգmWQX"Mچ g=|Bq}O.qtuUܯiz4|7N~Fr]۾~"]?q"Mf֧WJ%d5~l.zOEfӽV]WsB55^=|-{u wEym鑁^?֑~EZi+5ʺ_vsU7m1i-*π zdc?k1jk?|4t3;0 wNz/ƾ}f 0oC *t)9mmKn ^dbʭۆcs+Z~gyt%.Wg==ws/![H#x r>Lyϥ|'|{K?@F NMd屡j4]Xi~{6\:Ľ{Xw\][8n߯e|'RD_?h_-KJmxXGzyf~C*OfRi]%JbݵV~9Ĝ[MT_iԪ{%~ $MSCZѵ[9A AF)#!R3$ ?,)<&>VqդOoj`YծS|wߧ ~'Fw5nJO5)dG^2yCm''~lHG&.i 6v}0o0*ٳ鶺\sx] &$iGLH!+ltzu N<.t`𲜓wk~w/ Pψ?5KI (2t̾W3㏽#%%}__L JtiQ[oLbH*2rgpIi?qiW4,}NX%Z+窢őW+^.+L4}lR__|u/|2fm~qyhb+ s[/O/&.}m:]ߍu;]ٌ=ԋDy򱯴¶)mi[h*|_vlEgXhʩO>}vIOO~p0d ރ׵tEYkDouŴavmHmnx8˽QUW kbLSu4$~S$r:s"fyLǶzwQ?s7sپWt}E%:H*7I5K׽:k^Ȼoz~]}nZΆ/?Zy]Oyl`Gyo7;pGzU/cxoʯD]LA56:2O߳k ]S,'_Z|P@P@P@񴟀V]")7Hŕ{_$zDH$I6}}k${m~geT~@*AڧaM|e =AWvIpy`=k'wo|&uVqKn ?qT+V՞Hǝ˿->pN GR\}YAnHFFW*_['ocOQ|o;~p|ߵ6zSVyjw.ڣOi[]u{*>u':m;CW\7{7~D⛽rkRaVhIXn9 gte4Z6}J^ˣ_4M I5hZxl{1=kjT_ދmRZ|]s঑M?]1Ey~mq\Ykȕ\H_7ήvW\B['}^ֳ?U,4cV:/d>~ZG%&#~Z.g [x ^cf|@ˉRi?2s_? GZ->UvW9}әemQFhݟ&|f<3I ]XyRE =A#k˾_~T_do&f?"V}JRC Er "YG^}Gj3qY IVڒOE~Om|{iY̶L;o2Kl'p޿GK%]+R]xÐOy1P^5^<`xr֯(QM[Wo$VJy%[y@6WmTeďբUa ^N;*qn_FiU+$_=zl|&?5[ EZN02bNUҧ94ԞOQI\VKnN{>ܹ-{Aij|;ԭٍ'-.0_wW.4I|VѽogY>JT![}_{_S׎7˽3Je"[dʇD!Ѕ5n[^# .r-{I~U ,RXޟ>?˭\xTv3 su|\>b~NEvދ{뾾]8|&ktV=?#~ +; K q,vmP ns ʮe7ZR~Z;KZ%N ?3Z&oowN6pZΙo:yؖ96b狁-|[M__?30خxu}ψhM{¾ѯ5Y-O0M!,u bUݑ$o⯩XWwB93ZRNN2oyYO[{oqko,cXW/"N|kە]o.ECsQDN.jsKb1sK-no,QIbH9_PN:[goUDқoo`[ɨ+0*w0}Y{co]ez-ZiV~V8m&-ZX.&Yd3Y N<˔$s'SoMmkqҭ%=4k};뺜r[\ _?,mNT ~lv)᢯(5h~=6sMMKh߫=Nᶃl.9UnnOs0TJ뻵 6Zu4KMt7(g|Y,>Qǩ+壀(.^nG_0jU%S{:Rm;LDq0],{q\ R~.>]zYM/ۣgMKKuב]ӷ2Y"n"fQQuf!>U%{*/c/~9x#X"p4Wz?fqUIk~WZo@RCޝc~k J0}R oc#Mڤmߙ=/q񾫤jp],ҬL7V`]'-N+rig=fxzOmX59cɸC"; Mx{k"|MWۯ۹ś ,9KFwKHzw?4W_rj>ǽdepP(#9]qSe׽ =Rk6"mbk wnn[_G~^ٗS}/׎uoq{/Bkce.:Ũuc$qdF"轢U]eݭeY1R<Ӕeo h~K}g/x8FvBAGS n>LcɦZ}ާ.B7=[wmtxDՖxm/mD{{*Eß#HChVtڌkw{S H˻5I6E\5J: TL{mUmlyl;VVs_C,/ wd2dbB Z>տ)>N^;$cj{HF1imx-Gߟ j*f'ƽkmkcN֌E,[gG{___=^L"?cwG3ϼ|W^m _-vԷj JZRJF>q+Uʷ-i鱬#Z]* $:~JܾqO3X~m~n M=dRgJSg?5%UIn֘yl?׃KsK} eHsL1#L!9$~Dd~8׻ؚ3W+qՈq۩u|Zyj; 29l/[cy5P#9Jާ+K5rorF>v18oOηqSzY_S\;nZ})xjc׌gJj/os5~qy_2G?R(~:~Տ;RM?\t!24`s{ֽ74oO[?F>C e#+kQ|hzC4љ+/fP8B>#?aGA@֟6P@P@P@'*w#w`hڢڨAǻq`)9JN߄m<]85gQ%?]>VOӦ9Ux~Վ:儗X̩A>9צk ~hl+0s{z]~r"ju4J_29ݟ]kVG]>/Xbx\|~UN^|"M3k3oG!#wdҺ)Gm<%"?[.÷?C׭m)rg}k='rC/=L1U-U]fqa8/Η:˿%ĿGk-#lR)yKtetZE=_s8>At~%Ӵixs3B<Y099q Vw"ˣו|~o$G?GtKh*vhwN+ˎ.j٧$~C mry#Ua~R ՟ngq]w~.`c;sr=oMZ5_r?Lȫ'ʿO3l􉯡@7v*ܹ /.2*k (?ݱYF_2#J]8uɲ%#4e%(a޿MT^y%̫q7/k4l#`R.өBGb zyRoo~'[=JQ慷n._U-Mh,?Ɓqֿ=ͱ:#I7}/]Oѿxzr0\ӟBW)J99RtKqnWwV뭏YMQnij@*qA1%G;)}U͡Ikk]̫0Co$s+[HYdt'w 2KYou,iSd fl7'6ҟȸǙAN1[_Ӊf{ѻz.]Y:[KFoX >:uqGRC.黻_[nOԥi+tbVa_s9I-ӯ~cͶ+oͨvQYou yy<Fzoٻ| +Wc*fjnkW]zyjOU7QϞIݓa*RNWyinoxy|G 0 X8ڴͤQZsQ_ZĪt}4; b޸H |z"WvO< J{ݿ]/.m/ $UbfQczrWFoѮ~&?T FFNdz{H[哙)8K2G$c,ݤ$Z{m5r>.woeG.V|Ku|ɷy'uvNK{}w͋I][s4;M#ZlWj|I=ՀU6]t<'$H݉7q AZh{]hNwJ6ռ;&.Iն .#(F=_%Ό鴽u&oZjvo a14hBM}ak}5i?@~ _n5f>{a=b?UUoGPkn"Nuݾ{w_zTiF-u{_Z^vVaq$fO&o8H~oLpw:u,4_ʔwUeU :߾ޖ6کtr._r-&մhPwle\7JdOfogzbUp~B_4L*~չI;˖WVRmY'7QI;sTOwiunHY?C2\F+FIY}uViO[?kiCAV)wuk,:{eH6#\\^۹4*T}^v=,|[㿱h,!Km~7v0,6T%g^ҕcvUC޾&+;5h=:^GFW[j޽>g֞7BόDﭶ]Gd=!܂)`s\LT^i-7xYUuuwjI7ޞ:sbC^]2u8m<_!1?,~Σ,*.t{k{Y?/岍ihu>=G?m-˰:y{{:mǙkI5/އeSr mN*ihSi?;?셯3<3'ͻ׮2t]7vvx_|yImuOPUX޼ǎ۽|^>gYv{Bo}Ox4]P ۯđ.[/l u(I{uɉj[OeSXGt5:g^ߏ2dk22omk۱([o%1m@} 3WӃOS;=?o[hF2In2yڼS^q_gȇX%Ǘo'ǧCsֹ(K髿$wم^TI[1wj_[3O#J͙Y"Rbߟ鎞]g.UTa`Q[?Z.J9OӜ[mN'<~,q*)ks{OURoIo{~o%i|>׷FuW}7soy݄Vqӟc{?_<5~w64I&&sl\W<6>U6]?C?{hV6gY?mr~o߳B\mwQW[0`t ꏛ ( ( ($ G;a Ol?M~5cj8?X/*WqiMW?ťi RHCװT?UV_+*QTݿ^ͩ%\?swm~ΔRONϮ&h#=T?\Y/P ؼw>¹].z _~_6>?nS_vӾر+:o\mKyH'?SJ:j?Du20xÒp8zVXZWNU뼌(&IjS"YTmܽ )]תL;i7&ίuo+_SVv51[墢\sMH-j}BZ][뾫8'~nmA_߱q>CSJ-XT1ڿ '*PI6)'IZZM?$Tzo~&{;*֗m}MKQ8 bxλtNZO?m )~,-s+8[AۏϹ.߷$m187Sm?)N14r[[[p_m+&կ/O^dž[[F6ٶ,?b4ov]6OSUe-+~HB}ի-F/EOQ~{>7Lڃ$vT<)w9LObc޼׹z^<y(?P|%맻asi呈!9;~m ^]w>AB|^.H~9܌N6 sТ?{Icg絜ڊVi)YtE Tc/F6:sr?V~٫ut}=W_O9`%Vm/)X{=k-%ۮ_eӔ󶽽ωT-4+& yG,B#veWj=߯+d|&i}1~0vmռ/I\2J?h=>V\{̰/o`Jv:v/GXj[M?zŎ$7ev'~4q8)+֖v<,Ψ98&m%}+y&=KZi< ϱbq5,>7YM[M^91 4]U外|և˺Ļ=\hU/?7n }9p9mh5I>e}߫ɴm`ukhc^fTkwᎵ,jV[%uomkqLn^~}~_#ļMi{푂/НMzL+heNݾ1'r~x[DK{ۗ#![sG|~%ө4%_$Bo1!YU=ȧķ$_W4Ūm^Z~ס? _|HmA NPyӧNٛGae7/V=-UqnU\~xkHXDT 8n³dቚ#̲tc!0I\vis5ɶ.#eoJPnߏݲJu4Vy7 [ ׭?MeNqH|X(RAHa+꣜Svkj]tigz[~~_<3^{mBß}C6#5c :wGby4/W%lbfN*0MmFRNN*_EW}T*{;gFW^q>nM/᮫q}jMȖ%#qg_ML>g+^)>\N/O螏?+>D𽧌êf A/>+b\';hm~ۮt%VMr/=>tg֥u+E2;I.vD_ѺX֩i{~ݗ䐝D[o=?77~xW<_=M.lOY_O3Ʊ8p1qj9^D'ofeS2> ๾)-_oxMׂ9of1ݦ=&2ON2\ܭ;}lu'`5wu_}(75Aq2soXWSG)?joޝN,'KnJ3qiwI;i/맦ݤ7_. G7dsnzU&z;n/?=Rc̻~7t tA*; |l״ZB77oOsيO_xX_MѤG `مzRrnTԵ}<~(E(×Emb﵊&?5/-&?Cr\){Zb~h| ODm2,亻iJ!GuI dLWUUO>taNi-/<. R{= [5n-7s{;lHQm^6 w\|(Q}>žb)R[6ޞv]O%Vd&)%U ;Gq_gƔRK7ky'u~o~\]!1MT~|WV{ikt<6ӵַ}e~˧I=}x//*_='Dzۈ&'8ד~=}?9tM21׫(sÛ?l~HV ! rP˦s=߫<קQIU>#$k h;j%#YZܽhv}_a9eGo?7^OoƭG _w˲*y]q?d̷ ʡmd8^axjryq㕠w9obnsfvS8Ok"mjKiI~wOhn,o_DSֿ,6_?ϼIi_EW<'e "&1l}+|i?1i+ߦ_יZg'/=y<^ݫ)ɸ%}־1Viׯ{b$X8 s{z}+*UIsi]|Ƨ}_/C?ߌ _os_T]^=P.qG(?=?j€ ( ( (37Dr ^ Qf~ޜz{(Q~ՏU45?{u$ +Þ/՟aICzcY^]4U?Ҧf|o4w󑻌OjH^[Ij_4Wa?ȑqM4N Pޝx1M9]}*0U?>58ZjH^_տnXK|~^:q9+;.{~ ӹ_]'}>vhiAon\8 k2_:-"TFw98ϯ?e;o z57~7s'oֆ5}޵Ss.J_~T)]k:_ռ?w~!`J|V_N; Óֿ (V{gXj+Kۻ5,}[_xe+~s$lf+iVWy:Uym3[~_''O.Y1׃}W[OG`vO?B{}(b`u~J^օ;|_?>#VM>+})?*1#rw'W k_uKx5M`SS{g49<`<בTW.)kg^szJy 'Vxq׍>pkC*oQ_mpl*Ӌg- ύ͸W]e%us+_jҬ""1T5콜v߮v>q*%R=zc=ͅqt g5'pհE?s_my)ɼGTu%%.MNyT:C6o:F!P;aNNIyԨ6w{vv|\[_GncIXZ;1l ۆ:ϩ1yvת#/i8SڔhoM乍mْO01v\/?s˥/.EϜ"p1As_1>z_\Z1wO7}>#T-d;dR 9]d}nAbbGuk, oEqmW٫_-xd?PG;i#M=Co'|X0^d -W9[Sy?~cFWZv]?!S8Fv^Rϕj/_|cæhmCJ_3 ȹvI}Ndy=GV3v٫_sۘp_CJ8A+JMɦڳkRKmMoZ]HG}kM 2<1ژ]&̅X`pۿۡh>ɦS.4Kmhqm p= sڽjXk_ߣs̞/՘:cI],BUW!~X_R[ӟKOzHm$x7O[LA+>Tm?EfF*D؏o4~'אjzS_j . v_~f8#bU$jYc\=KW~c ~uSޟ¨#d%x#~kMUs#<{dar|ܸ号4պ~_"ISiu`.S{?+u,38+a/r񘨹Too"ݭxB"+߮2p}zS{B?Џ J庸:}@"I/>&VO)A6W:P@P@P@5>oa?Gxg_9GNU>_[*9Sܡ.HY{V}L6'oÿYiy~bP3+嗧~ǔ~ {&e]SNKکjזa( ~nNp}(]hkq7?=\ժ%5kw~E*^-5$Q7Ё=Oӥi _.3Oy_FWQtR].ﱋgrOC{jיx$t"˷OLtYw __CEo NӥD>a`Ù_?(/UcZ4ad+݇"U5n{MTܿQ]xr4?e?m|5FПn(OOhwslwXxw/#Jqk猖kYF{jz*:bM-Vnv?~`F%IF@<خdѶ{ ][rl*X}ܴwA U'Izʞ}^GVө;Yүs'MNA'wGNZj޺C,9h~󽬓Iyk+/"ıbI@ov۵E=\#nߦqW?'6}ti1)i [odF:?u/lZ=ؒ+]uF[86{.i' r}}OR]0\cFn2tc*{~U=ڱSq:|ϐQ+y[{oj% pJb8d"yPgip7&w #o9{~?x IO_[K-/PQ?$6׊yB#"J;ϞC(輼 5SkE>O'RpeNK梶~|]}NAvA 1btV]n>85 i!dعEuk.jkѴ}^X~jj7O=Jr_ݻ~M{ƾ(B,q3~5#%dZ[OcߊF!:wkhǑ6Rۨ=q߿= *RJ<8~E_9Vw[St>mar3zכZ6֗=\%oy/nx,-eE@<>ȦE֚yF?R]L *$7^{Wd>OӷO8(߷m>2ZE e-]8,y۞:Kg -!$Ve9fR{M{4֝X?_P~Ѿ뿥#XGJʦ~Βo iPm{]&=oY[L=Ͻyzu~y 33ŗl.`o‹حo hEMNu%ZgTo?[ˣNM_h~ExRt_>?}ԁ[F[Sb$6:yJHq_a2xj۪JTkӞ&Qm//Bݯ |9I yuoa:Aqǽ8pS`6>j:OHE}K %Qwj}UROz=㶷kpiR}ԪǠ+bTjҾ˧SY¯+{>4_ SsCRos~4N.?%1⯛E֌}ϭ5:ݛ_u;?jΩ?_=Ƴ]xe<4vS-o.6>jKﱓ!p}TW+ū?ֿq N?Oltؒs'AN2rr=}NG'G"ʇe=ƷIWO:s 7$LjjWz&SuhL@oPH?{Q'jOMN=7o;egӣVC)0mW]}oͻfpm-?|0\-\tP"1~{3Vox3}ʀ͗inCԓ噊Y.ޏ| ;7H#+[?=c '}՗ܿ#++w/q4,6͏QW^gJ ^m> F>ǽU7tܐTo׻?+ F+co [Q?g ȶOQVhA8W2P@P@P@?SA_ i 5}(VT)rC`J$g媙Y#yXz-z1 j:$Vl| aFiwxQeL;<1xռ3FN>YD״}WJP״,[K[wݜW+*ԥu|>2]ӅZpUM5(yq<Ɯsnhdg?-uGc(W~:~~,+hR*tشjޝ-2i˪]\0|APG8C4gE,F#tԏ 9JَU)4UL߿٨r[iZGH.QsUxL5xߒNnv{[7Ÿ>!|A/|Q⯂SNWl4{g-T/%I{+y\"ì熳Z/%Jopq/ss!̶y/-IUy`/[zYs[OSfBY|KBRz[Xg.m{_"yaCrV0y{$TJGҜiT"ڔ N'$s8w,({Ilw>^GxPjwg7?hM:dWr[ BI.=ʻD!b,򠯰>RRn⥙qҫJ+=J~_CRêJ?`K݄9[r[fݺUϚ"@>,Rе}2RӵmTԴO&UѵK3P/[MCO-si{0q_\-.Kfj,S9A|K✪8T*Vqn[ukר|RѼ 9(oωSľ!7-Vz%Ɨie LIDoo!΢k:9<-L Y/iNug'wVɸ>gKZ۴h}~^#F^)񟈴 HM O,}KTP0G}{+| Ü8dyV LyVN)rVRZٶկmmg?1fCfX܆t}S{9IR|)]Z~?>+* x3O5|?t~MBC}P_wO؇kx¿ |/sh7^>/ ۥn;:n붂&SB9L0e͝f+֟]=)..2ɾ?ake5i;-s每~xo #E[ɬ4_ h}αjS-s?`/('h]GEdooi Oi;?C@MSTg֊iO^[pj*Є%NR,├Zagϙr~S\q}CT(Sg8˞>ֵ7)8T^ 4ߴbNi#rOC@3ف8\$pujjjV蒽-]]~yV+38ꨮgk[o|F~Es 3_`ǁ`սIo >eӯ/aAm=F)$YUu+&x9t|! -l/!ѵqkc/Sz {rh9%.Xl۶.T},=_}w-+RM-I+_F⟼|n_7ZߎdًgOx[ij3*m~;W.^RQ56) ؊W:iVmp\8y᩷jCU,Z;RZ OYv}#E@FxH >]I@k֥=ܕ֫eZTT߈_W}~lVnдq5L<1 } ^䤣/-U֍SQZ^ɫۺW[[MQ"CAK#$XN̍v?+ *'(U;SN䯣8\xy]+[y?+So',wQh:S|\ɆDm-\ ɲTjQ^ 5I˛[ucX%N5+?T{|}Lc:E|dd >xrD5xGJ/|IYz5вﯔjY[I/HۛKY83,㲉վ[$Ֆ0oDɵWӧ}÷kNn^dm윣=?L+Fڝƥiz^_Ghnl.gx5!{mB;)DwA/SeXRx(ǪtkI6ծٽG2iaimIZZE64zf S*ӿe/> xZxk7|5-Zz5eU¾4%nz|D/x&|<;ye~}z8ysit:T!enhշ?R^_ğK㏅|/s_x#M5=N8-<mV|A?[V hy a5?2Mv-۵I^9|W FQ¼F%6u[JTn SItJ++mᴿNk+{\ln$J6cvmG<1Jc-kaahN/Ky~gf_ xjt3+ߪ-nIx¾!t_n|YV4ˈ[_4FQ~3hV{hX|?hⱔjic#tzw- B=l8ZN.na޶[k  ֜_ a{Ye̳&hy ycy,`$$JE|CJp՟,Gԣ%%W뾷}F1:xŇKFenexs?Gqo Ix1ѴiW>#|;>2jGXiMb7j7vNdS3PQ櫍żQ4iד(Rh}kYHb1\0xGOrU'ks$j } }.PŝB+a^.#q![r`*Y,E5 yâj4n!#X8sPU*`7{<]/gROwKnx^Jxv5WF`|TTJ2} w#USPk_M*JhJv$v+p;:Jmc8Uk6H=R۪eۛDM7̺Mۥ̳WYZD[jr—Wm8Сכpҧm'yίaۍ(IvxFkqU%ūiy%7|/ivw.vus1]gɖXቧ4a*e8(#y-tj74qe5|[\},촻9?_gᆓY~.mwj?֑iqkvik-lnMԢh̆Ft/J VkMMJ '|4Z7TM*NZMF2~v_]kٛƟ#PߴO]_oxoI}¶}֧]u/xf +ykP5dfYJ_"h)7-ߴ݃:VQX{:8դj7ӎiZh߰YKiRRch{`VH VI$4"=a:GYF7)7һF8qafQri${r^VI=o?~?i+p5ݿ%5g]4.#mhZTڜj7qفrEXRu2]EO R~=W/k[6A ξ-ﴼtmzZ yhw]hfo_zC$vZVsy5ı[y2,ϳ_iSee.z$Y{nk+tzjySc^2iE{:TIQף睞}~&g|7/|%.xD- J1iލ@$O2UӬoUYdou0&_6핵R\}EJ2\m)FQ~Z_k}r#N[{cᏊ?>+__?>,|AԬMƕHFJ妪r¤'kN-8ዜd0LTF:]ilϕ%_-nn> |K4GWx3\ĩU6iikjCCE#t9bӍƛ{/. fؚsFR˰pz>Upq5=|ˉXJ9IWZROw^,n5m*[?`_[w:oٗÚv"ׇ5Z/뢬dTwo2i?uacҌUOKjWgСBt׭-}S>W#k}#6j }xr ϯ_ZIuΝ&$46%_"k xOGi9rm%8rںaU[F;| / aώ^8yÚ?|DқKuX [ŽYjtRKnQ`FgJQN M}Qjj8\*j;N?*X?bG"ɛrL $R5iETb|W UtcgUٽzmwc*\iy;JSvۮ\[2Mɤ>Bu#v;Umhsx]悺Mofm2׶\[g>cSOQa:ԫ٩ʾ.[)+S;dg;ZҦ?ciAyUM/!~ޟ^sFk|l>"^7<SMӴ?u#za-t5;ex$V#uj;թ{OK4䥇R٭:K,?xosx{NԟᗈpTi_> |G񤶖N^KӠ4٬^;NS7Z%UU}\!o%^I*.e{J0m~K$J[isF6Űs̐ei^F&Nʤ\d'ٟE,Zb}^ N"O# ߧO|Zu8KUSɯDݏ DV_'ƽOg!+^r펨ό3~wŰyM|Jӝ}I@P@P@kxb7A#}k泺>ֽ'ڔWT~+[õ9(]I:}?T,Hߖ?J×)6 Uh9;j'ƟZkΜp/_͗Ӟ10Mo)֞`8 N|[گ᭟텣ij?~ӺVt5H~:j1jO6{[i7z5g]{egsTOzºpu_xSN4x់x,<<4࿇3ꘊ+iG4_ m֥k8,5l6QROYI5)V7ƭXN6rkxJqy[&!_t(ׯʗzxjtMӄ_O ݼ~Uc3TH?;cl%(EptgV4[9nJpYTս1ЂUJhiIuvGӿjhCaKk3`7x=[LB|MWš|z&cLZwf{n"Y_* ˙jRu)-:2RJ<E_ [ Ӛa'cڿLIJ)(JRKa |@? Yi;|ZI%{­O z¥ySTp|ђQL4'Mk*H>{coheӴ+oڰYmL$J v3{xk[2b'kJYSjjI&9lqVE A a(SwNќ՝rn~%ȿ ,?mM#K}G?<׊! ϊ ߈YclM ^ f9K Xb3lm\1 8!ʴ.f8aZv*PZVm4>džss3NBOwK [}ed4[Io%nVF g\HWAmU"4`ۨm[X1Zql/>'s uj***?^PMkG$ڄQ_SIæ+OvXIZKgNυ/+g⏕ Zx7C?ɔ:ƃ0+֒X^$9epeR҉q !'dtd.^9&wM:u՝eD]~6iRtgvRhV'v]餹n x<Qy=xƆZ_n@ȼywtyx:fq'<3,'RSFZuZihv:Ja8<ξKZ_uB?fM&i_ς tq'OW?ZZyR:?"MK} ˴Ċ~?^y+U¤%R.GUd[<\3 41ZXUc8s;۽O?l;? 3Մjynj Gᖩ[^}S^m* bDRM7ž4|kc֡umOM^񤵶?/,~^|nT_F {Пz%,ߵſp.ɠxgÿ~$,Ӝ{>!mb,8 U7 pGE]Qc-*+=svAx#|}0t(Sԯj_s܏#_2—%^*UH?iIoBFk_L }9^G^G&B\%k+]sB7>uuUhʕ5'k?zK.M7}t_5 ĚmBIB|*"2HگƷopE}Բ3 {rJK7FgĸHԍa11Oe/l֚K{ިL F?Omj.Yg˓K^F-Yajq5%WkF7ګ_(5}6Z/4-5->!qosi}nwV۶Vh.`Le"\, k` ?NGu%n[9Rp&f3S^i3me8ݦdK6~߳]q 'I$. ǀ.|o { K0Srzr_℞?[K%~ CJs{/xw:6qhExGHEQ,=7I7+[RN]k6I.EnZj8 #YkNw rēm5qs&~0|VC K߆> ]|VΣuLBj0m3RXat~iҩ쮚U{|qfW/gSnʭGmtVnE}?h}̏s$SZE*>ZIBm)V\)GЌ=(.Oq5i*9T1Oz3%b՟bOW?_Nf>~->(V=+Íuizַ6k_ZɬZakv4ĽԖ㾭_+µg1E]RpW[ uq{L.7^CVZ]=cFw~|2>x'ŏ i?]GZ|]=zϧkZhv\ZD]ij:f ]ZVַg>;:<]ywiz*ɧ+4f9fK+RIڔfNFۅD[5 FqoTo[ ,mo ^Yo|$|coq>‘ e6B_}\܋o<[F`*#_𨥌`cvN]>9^ A(s;WoKeuKc_&֍_7K߅zO\|[|9=SUm2|7fg26݉$[ pŹg|SI{э8F-z&݆MXBk3JIw99W-'o}??~)xWŸwX???#H[U-,d~G}=fUӣF$I[0\ 6t;MI'k]_⽔pJXjMR{[kM?sol,ogamqyq*,-"YdXYB4grJ4+ɯQqYu}_g55{k~[$k?jkvmVk4~:~['WDSwQ;Ǟ% ?|t?&nK>84=Md{??^_cq|tp5 ejeB_+ 7ux8O=GQqSI(roTnJgh;as 7GSSѴ.$hw52΋#&4*.Q_4`@<29PvcSi6´VZ{e&U<%Nt N"%:mZ )ky%~6hvl 0>H`!8@UG(cjJ}k*Ud_}o]Oȩԭ8?4ieXJɦJwz';6VEK??(Gs7쇧5ImƟt]L-_JZ]?ѬSs9Kzq3 u4{,Ctn IO&X~׍qq)5j:$zW$f It]|X<|gn^I~_'K}i<Sv_p=?څ>6~ߊx3 xψ+FwY[i=UӳQkޱѕjK¢z,$$Z~E4b(Dp V ~eB7C WEcB{3twPU&t,6WI_V29]TRsMN2m;kiꖯR๿L dπW?`6w>8TIy?C/[F fl>xѫf<3#ļ{W*1I9o)w xpR Zu^RqMea,#Y$bem__5QwjGFCz(>^#0ˣy94y;'vm;ď :~iv~+ -ÿ-'h~mFֻyeh5>e5 تp~215N$ZrRv2Qmx5)$|px[M7$6>|$ɼ36`X8'Z MC fog5:ؼ~jw[GG䴲im{5Y|*Pw^]c{M%]?_[iriKj]ޓ]'-'⍇oiQmVz08>JWWNN*1%lU(ŧwprJNu.W9k5W),-ҍ޸6N.nRq !|3x\+u-K4ZtIx_-na8#oh8=sj>7jRQnT[8&2?>$p%b<<*|^ކ346l5_ WK g'_~|?=N]c<Ὲ^ 4CJS4=?^%ҵ{[SLk"?R {{Eᧂ9]nXⰘFvW+mu}/0xgMAKSZ+}ZJN/NxΥ~z/-~/?0?ik__|>NFwy%m'fK9ʖeE%M>žhƒ'׵ks^:ǑR,|RƧ)+;5̜#+^ܯrNl]ZτA\-[ú<3"2kiRR,LDp J3ʳ<>Hvrٙdu<3)6RUdj\ܭn1ݻk6g7ߖo /n$E9Ǐ>-xƖȑaKl+Ψ#`}R'aաSZ8xF2̛ՓwvmʯcZyT+I 4Rr'-RnV| Kr[L]A<ussI3K3 *^?:Ηi^7b8O3jI*>kMs[ U594TٶvSʔl[5xꊬVIЪӏ^߮?X>Ͽ+W04A'[|if8[1%Uo:̩11mZ8l+LKq2f%<.ǢZxE"<'{EVEBY'hVC*QN^U!3JIM{:\Rf9|k2Sۼj Wճo5~ԟ7칤xO]5E|Sih+VmF/ I>%漪f s: . '^2ZԆmy:-ITJYg8L-gaØ|MYө/z^WˤoYh]+ww/ cX<9h???w5 >8.x/^7ULj4oiZ%HtZ_Eig)үzmJN Iǝq:()INT}<S2ksno฿|)''0 Xb(`j~ԉ8|XYmSJ[wCȪ-=5F9|_^թk ӟƋ |lI߆:6cco ~:I4/cԴۥ/b>tQwGXMUʰJnөM{ZvNxZ8p`k}W1>|BXMfQA].i'ȒF3 /Yex/~0KDw'ⷃU\j|CeS_[,D2L]Dr,]Ei(UJM'K)4乮zZ<]~Ώ]uN54n~ʿ?XgY4[?ZΣekw}oKm!=p6O]|C햽q᧶VyV96 Ü'W+ө1ɤV|elNB ^iIʝ7yξq8Z|"ݿ4}]W@a[c!~ZcǍCþ'EeO~4/NgicOCuxt,9`&xގ#y6|ǛC$ptq,iΥ9׿6塞e_|_Ꮚh0) 675}y- τ}w*;^7"1yv?1PU28ZotjU2\y&ӥ)|>%O (/ Vn=ի k*j<6:N2ϣ7 ,ݟAr~x'V9"^՝L*qjUVWGuoZ5T+ iҌS_w߆ߵo_!-:$˚G ΝAWDx]VRX.? _-7VwֈpmեǍbg敲iJK'գ%V-J Z6z6S]B#S bgYV6Zo Ɵ]wj<=J1 <-NGey|^E{.ި<8 oe-Y|8t ?|8&e~7l&nj;k->(-u]7ux{#Nk3)sO G qp{ǚQ䦡N*0#׸yx|YF"wJZjy();^RNRsm_ZIo r#GфU/?=:SNOyIݥ/'>Nό[[x?wC'Ó+>|/c06gAbƾ@ ( ( 5OƣsVlgb8 3`J42l?%i:U3(շ.-ϜR-)qxIJή2PƠ},ƛ?+ 'a}sI{>_x~ ѼU_~ Qs4|1axGGԭ<2+Mr}>q؉)RGOIJ=/\>4|>+6hch/WYH)azFVpJogO#br$EPuMwv:?<^3#էb#xYWԬr[F֍l/$_PI#Ə^4r+ ~=M&)d_ˣ6d$l-a/yz^I.ix{U;\6'AJ.iRB\OɾUp4FWrJ$5I2ںni)6{xa7o U4|=(#XxU9iEy0I m uz*xS{.zu{fu9%xpL.UKkfB?O|·/0;o6 C 7Haqqap̪F˳s*-7}ֶvi}%/akT9VIy?ޮs$iemn( (TF3:D k-ExÚBeC/0lC0,7+@}H8'Ud؜<,bNSi]-oswe7hg&Pq_%(MUV'OGؿH\&W>)7wV>$Fɲ ~קV 9TL6I@Si\xޘx[NZz=MvNӭ:Jtb(ޥ⠺̕Ij'ľ/?|MF{~<7]mY|ȶC9z<;D ha?jLag5LUû97n%]]b+5ʭ'4d?gD+TjZkHץI6}BG_Peٍ`Hvi>&|Xwo0O<`vo2XNF5wq?%EN{ʮ"KJOZ#21߿{C|_"|E_˕cn]x/P_?fdron_5x^K]$򼮒Z(^Rl{ۆkux~_g0M.0}Of<}׮b1︨ ciPz4/{]g/0jEw#Dݼ:5_ZF ZW}W^<?6> V|-)Q>ʽCԛmCʓ=i~_$~?t8" ݜs_'qtWۧټEd߶gU3xz~'־Zot6VռP/|Y޼BϨx{SM)rX8\2]P̠s.SW]To}5l^30烴 y'{֕N)18g5 9*j.<+6Z4??~2K;+7'^F<9 5gBjJ#F5 4~ ⵱OxjTwQ8a5ZFQž 0q~7N0M&i'L½Xui`)TN'=uZDS ~>I3< 6!L >xryE] !2e[q0 ;[2Jÿy'l:8.ei*^1MowkvxJQkʟ>?_Q4XL#nj` m aT>̟&VYڷ\U~+"VEX岩u2zz;;-['fޚgl[w jkΤޚW}V[r/|Wjc7ି7Zw6~%ҧY6-.uc?8/\ytgSXzNp42j &^㦼ٝ~y,Tg:189ҍdyN>B}bpy{oI=v񵃶СqhfYuhcSMy%.'Qe҆_GQI6vɽz;ݟį~_ >)~%W1>_?"i7&tt/śm/xK,hԲzSbT6ե+(.[{C72,Z*f裉T'>xvrj)Ė gTFCvۛaږ2)-_>o%8Mg*'%i$⓾e,Z>.~K/oڋĿNO |EoMO +M1xW߃S֯?~-9ŸQ,}?z4j,=,.&6gPfxZ*3e3TJ6Is7${']f9;N<ာh`P(pr320K\WMip ަQQmzjMWxK֞.yoC [Y<ڟiNx71ϪMrƣzrwr.iϝ9+4餻ﭾ-PB<>|uEᢔDZ`fh~4~ܭ_Yhn.FMk?LѾ"Q {1 +w+sWWO"S1KI*i/[8zzed$gm*ky?`_؈/Eg {S jxZ'_?8aG/M##%΃eB۞=xI\]hR u8.mHBr# `>'և)C7c_U\:R{fxV һo v)hY[zT jV~)o|A%4jT`KMoMPtXdY#$Ve(GKg85^Z$c:m${=>#OE,JMJ3n+mfw;IsWt!Tē)47ڧ.jMokG/֊k8mJz|ֺ??;OI(y-SMJ|=u+-=>%%9y,_='R72pbt(ۻ= &\:z?b\d+Gndq_} pRޗsDTW[% )SUqs.]^ɟSnsLO\'dדm*oyq9.y\ _j 4~eomn?إ qo|cw>$ 3 v,_VdKOR'ջma$`w;b-|njtk S]rZyA[Umls~*Hޟ_x-^ϫx0QR\uU5GNnqKuc޽\;Ob{TdžgƿDT<.G! (ٓ~?<]_ ;pX2'?h\xv8]`PSU576٧>> 9_V?1_K[S? z f*Sƍ)&V:@./|5:v6YkA׶Ǐ*92vne9Ta7}=92M-nQ9MGCΥ^==Z?o~$HgߋA xcH⿄x>=Բh3|q5Ύ𖠆[HK1~|sOe\vcG9'1S]fJ2挓mF=E:Y[)(heh)}bJ[J\>ir)EY6w>o/ )5x'3X4 x,4zP>Uq:^ )US)v)t?yI%eGAY\RK*EE+Y}6mi''+y1 a`|Ƭj¡ ~ ڱsjnQ}/i)?I(mM~ev:O8<?R{[bqg_V|hP@P@P@5* @?ჾ4CAW/2>"oMtTݦ]ENWWwziZ v+V _O?h_GsEjgQAc?i̗M-=Ū%*HWW> *μLb}goXi=[DDKZ꿴3p=<<򜧆ʧҖgoxI~_yd3i?0K~)3vp?Uc;C^G1$:rK^jx*TZWmlTJYSLjwkgnOp_^ S$վ|=MfPмkxWM ;{YXj6-ookW q/)K+EAUٽlME2֝nu11o\o sJ g.UdΏ߷֖njjunN=ǿεF-լ)]Hl\'q&30 Tի5Ӛ/=TS*NU. Se,<*iJj&{(+|+`_g> mA׼IQI4[xo>|wu][Ս= Ȱ \;Jur\lY;]RVNJͫsݩ:QTcrRS*OwJ[ݾ H4mYL.XaDh`4,fiׇԒ\%9ӊMF5)ѨW733nOs)OQ7N]fvOcdl|k-P?jP ɤE7wSE zʪü\LMu{u3*Y p~*Tk2*y&7KbqSF<Ҕ%\w*ecPmfؕtӍz0BP{$iGOMIحIfMqp"BS(~Ig8uU{;+tI7W YۛVT/Ӗr-{Z?ߵ,ܥ{O`|,|&'TG6S+e"]/|#0&+XζMf[w ;-uWG$r"Fk{/d_ %C>w̱xsڎs&RMc+nz-ԍxy6<1ޣGW{Νo]䬵ٻ[s/*YEYEru(ImxqM7uM_C=']5^{jڅܱD'ڍ˙s*D;1 }86l"ז& K*`oEЕ}$ۻq.&x.lE?KxY'dTՕjL}?WDK |5֏;"2O{gqqinOyY(C0,2J֣F" am|*+lQXl%dem)5vܯQr6Moվ':_xēѿ'h'o=<[ /מ|\_f~NJyi}v ~h\8ܺ9CYfkAboJqFcw3Oq`o@ # O>|3/!? C5M<>{2Gn ȱH}Mve{8ض{<&4\nkrO.ގڥgwgZ\ukմ[ē<2, M Y$XDv::EN#!%(ʕUMWRNqkxMtw=Y-ctkft?/O$$Ѐ;a,Rmp<sT<6|vl?Y~<| ~.8ycxvOutM]a{"1_+˭?ԴkxZHV(Y4{˝~Vǹi8^[ `qխ.V4k~ڿQ;lύw5/Znx  ⋍*[᷂mM\hYy;MN#1>9~6;aBK֣ 6e.hIٷc^ϤTKG(Fk^ԖUL@eY^8dVcϋg JWG$-*B_G,n,[ʽOCI:\JVS.ψ߶:_{񿀼GxK^m> D֕iڽͰХqycr%9աx~ኍb B<֜O[.i5[j*8lhTi[T齛iԏ _j>é^hE֥)Ɵoq{wD1G*s_kPUx* F,l3jsAU%*WUEJ-)CUmO2׃,Mh̜T}/:M^?^| ?x~lt|r~΍[uWNO&Ȁ0+$ٯgN{RMڍ:3RFzF)SISo2tlpVQ*I/m^u*լ6^ugx|+SM,N3z[ExQrb W(.l.WBX,c~%|Be /~|Qpؓ˟1 pD|c(^o`kWh3Z|쯣hNOfxO yVJ:.jZNSm]hQH2P$brȤFG9.~vT(Q^g:Js4^5و\0TvK淌{'S^0 v] 猵<]F~Ϛ9oe( yv~_m'KTF?LU'-Ubq]z\|>Οin`0TKk99l\]dGm!FA0`&021o L5)I-WVW6WvxU.jo7LJMb#\$LHGxrRmMqxeV9_g +׾ jw0g}vN9l,fkn'S{5ƅ1Fڦqc6'6?/P#gŻ9|U8ǂj=]<ڤco-+g7.g +? `o؋i?{~޸OO{;|,#Ai ,mFԾLd-3aWſu L}*ygx-̱8Y]ʝlmjHmJq$LD|#\烰sW(eSIUQRM{=cxlƭ=] & &q"K$nZY7k,WV\ٳs)A[ n0 H֫Jt1X%6MM8˚wOV,66XnRWNNwuUŸF/Ii~: {7->,Ώ-O%nˣ1iS TITnӣ"M%wwKk?Ȥ*R*wmޝY+YNVO].j밧E+| F^ u"򫟑0W\IB2ܪg.=Gۼen8oaQ 4+8s1^6;|uӏN O qxφe?/_ƴ7oY#|(q3ɿj_USG[W|OXpy~*?_y_l&acұ\>2X%~xKB񿋧.Q'K[k>/xׅhF`OxDrvvuxk zH_wFM_f՗ٞl"!o4pW?sO~{+:^a2~BqVUZq2IR^Dc`}_t_FAf_"M -Is##V ;ZL'jZOX:3ݖzh|ER2dI$+9.~Vn]%_`?G|sџ>uh>om;?W߃5On8pǥK"֛ zN-|Lbha*xnUgzJSJRX9FqU`vNx?5I>i%9lwRM9+>^~7P?Kx_Lg4?xGo.o=s_7ռs?KxF~)n5Iն=|?K,4KofoI̗Z<U@sS҉fLЍOY|?<{B2xc';OO$4n8%xLDX7NrMֺM&kY$WMVg3*PRI$!YJ\OѯzUi=>g:mٟm^anhՒm*v{|[Lڽ7O@J_C1C?\ZGR^` ( ( <9UEg[=kUܟ?aih|]i|eu~&MUY?yǎ<xww#|Z*xƟ |oO|k ]G~x'/_]Czm&r#fX,E-zhtkb+brQձ8Y1 Fn to0Y|x+5y)QGN0RvkwY? %>\F#y"Q*8f߷9S5[Ӝ'UNkbpO[3,ag7RgRVMrjQϫ?a*7e؏9"~6ֿnuOڎRp3/:o+M2![:)b/+S<,`Jmb֡B iїoZ*7JQj-?E2TsjU*֫R/5j1 ̽Г5 (K^7|[xz|2Y!= 2_| ok5=^JK8*>Zq7fe_fǚP!7(%ǟT=lҮpa_>` Vs(8sIO(򻅦+! ~+/435}zi>o|O}-7uxMԭf++{$QC2s%R88N4j6mJ2l JX px.&-jk$՗$Zm>af5hÿQٟVGi)F5\wZCݭ|EqOcOh7ͯ-P8b]7zqøsޥW9*wk/I1QRxޚ҇4 (-׵7}ߊ-; G40tTI [KɮX< >5GsTtqLJ.\\ѭiSSpr& 2Y*sx? TZ\vm]4x)GI.U+{K_V/&EIa7Cot/zՖqsceWomm,2wR䊊+F1IVݦu]|٦Q'J+{Nފɽ|?)_&z.j'ğ??ƫK- φ MxX~/ZG/}⯈*SUmY^wm{cy<2UCq6eˊc&d߰tZveٖ79bTj'%<ae.rT~N৿O:?n? |%c?]*|w/ڃW|_?ψ<74O^5ޥN-.+c6~~ K◆|a/n"]IYլWR#J /S/>+K$e9e{VN[5[ͥ+٤Ǜ]_-fY](%A>a]o j#$~ؿ<|W' U%J)Ji"z)YI;5{]hZimo)V۳'ww x*7~!/w- }xUƭiΕhIYwYK7NSŨRQNdUpmBiZmy%2Mߗ8ik_o_ Y7! n?F)?Ei @7~-[Z姇Ok.WZ6=ڋ`<5*0:޽o}89`"IU$8FX<#V~&z\[9r!  ߰. *ow*90s0 {*FOIEОI4UJ^w[v{ ?wwu%ҟ?c;hfڗ>.P6Co?n<Z^k6>/.t+w{#z> <ƕ_σ(Q3l}YAܮqZ2q=%og“M_>OWiw\˺o9񯏿h? ~x&OSↃ2H O l|?o?.wWO_QI}?E˦1;X PޕJV֌dk7ϘJ2 ZZfҼҽծ?_I/h> ^KOyu"KlM6{+UƛiZZJ-+ KLWH% ,T0񨓡F'JbJmk]5iwFu~:]jM{;j/_Y$zC>7Ɠ1K;ifk?x'ᯈ|Ujw)3Fnh-Ҩ3ӟSr;(UIl<%c^4:od׺*wW$~ટ|;%D_٭Q?  ^7ᯊ|!Om|S]NDm7 Ώw:62[\lʩG NjU;Zڵ-IPoixJ23WQG!`)6$59ywj_7[]_K2`g ۷0 dBQl,3e*I]G4+ovd'(ӚoM{{o.@i_ٻ_t?@x?:/~]=i,%޹7I.|nr률xH+o,pTjf50ќ}2=:y:e(G(IEHOp8|| ')]*Sr\ڸͥIT]s] G<.m +a~-,-ģ!ˆ%iK#*2`]%mI[KM9`'Ɍ>OUz} CGWV xHa'K xXť28b6Dͼżp.8>6N#t76IrV'-;^)Gѳ)QM9/~Q^rNSZIݟ}X?Oca- TiƷB''jrg5ގJ[Qj鶜}L pGUm+:n>iO]dિKjI?`pFѴ؟Bn uɞⱥ^vTEI.KjWIvzhgS..fgzgfչ\ֲLn>='ƿ|J ]b__xntڣ7޷ ڧt5jvI-Xo+&= D^#V-ԗSza08qQJСIg(˙]'?*$R_9UOxWVʰo\p G~]9K8fwZ_s+/Қ-+w}uW4_{ O{#P/{ڼfXwh߅>!ֵ?i;6Zx-LtNʵAQ[UNp4cIJM6g*j'a8`hJWeCyIc t" M|T#~ؿ*CC0,1|QU}qV\΂Eye9S˰pu?ݒw{]nݝ%}(?PO/sw)D?˜|5o__xZGM xW׵K[Oof/7ڻ6 ѾxSLִ]WDjnkqciז_X#ZLxo|U>"M~hz1whڤ6h2is-m5qK HjЌ,M9't|ej=^ַwDžXbgKZ# 4֧q Wß'> Vyx]ů/ï_+xDtQO j OHNUե=f&3iU| m`aJQVTX y%Jb+E#餶nq'W2Ϳg⟏gIG/_^h h|7ƞu9-f+Kj-$cgnS,>n7}U._V4ӓi4>JiqYRzCҴ^rq^Up\ {t3wſ>ǞU3Þ6ghw%Nj4kV- Z5;Q- F0' BxڕRV꺱V{r[Em̧ X;>l B~z)&VNmmY#K~߲e|?0ګ@oto|UO {/ŭ7CXӼ_i|ljtxoNk3>ӿ)҆2Kޕ,_?]թ$R}x^beB+7¬#AuyjKDƴc5-Rk27 G3? `ړⷅ~tK?*x K ƺwǾᾡA}ss Q? xK.|Q?4/v&i|"Ot}Dծ-4i R]ZI8~t9Itd[_s`R7awW&kInI?3 !)il?dm/%qOE?h-R׆)-W^/t]Ďll.>e}q]_ϧF'^x_Ҵk_ Z/x~-Z^*HӴ_!b(<e*Hi%Hr(9¤r 6/0pQX=ZT[:J*\{)M.{79¤Tv~*?OOMo_/}?<=xOcĚWW ࠟN7\Mͅߊ/-]i]ψu_ZngisjRkamkel|niΥ89Ekz"X+-v phώ?M٣~:¿(C|}rO<3M~Mvx+)q|@|ExÞ <;?]Z Fit8 #Pu!TrըAOV9R` #+u\R\wѻ{4|*,>Y[Z\_(_?ۣ3 Ŀ _>>x#!.x:Ծ6~zZZ85Y:$w[,A^_`|F>GI[]ŏk$7637#_S|•HJ8ڮTRp*Rfes¢SPIF(~('VqOYY?់>x~kG?|iY~ʟg6-G㷇xUyu_Q<(uգi txKetV3|rb(ʤ+NU:\(5Q&+g ͸C6KK0RQ䧄¢~Wӵxbye8XIa0ٝ<-*5pFU9B*tݹ%(Ӄ1>~ 2#׼W#äxc❬߳vkᇊ|E/vKĞ+,x~0ֵ/-KZ o ZG;xXaدeBUZ:mK$oe$&camlEoC+99s~cUu8ЍXBTԳ_+oK|H>)Zx+xz?ߴV D9K+7ÚL#?7c֮QCNU*Vo:zAx঱Q47}C#.^"۟JfXdtr+S)Ik8Yޔ]jSҩ.:i+lջZ=O ׎< .MԼ1xڟ&/$躄?i'+a9&c+9Nm<կx{B1zpW\/M_&|W~ƺcZd^Xx~aR^\D 0  'NiFknpx7'ӡJ*‡;e*﷧V|A1%ͽ\ I4 ݅HФzsZ] 6ݓIhmc­p)ᰱ' 'K٫S,] h7[ ( ϞBb<}̞l%wBv~ahfXl.SymMo(TQU5gcÚeo*-*ʻD~dK788{<,D)X_WXX9 91]TMJK]o> 8JX8FЗo?ε[If,t,hX61>gǯCMbrV:1Z)ESau{EjZrGNƷ2Ċ69CeL*ctkF';rJSWz^Y/ {.ЊUpY(zIr-U'k=ͯ(GEi_<#ҧѯ_š;4&(FhѢbJs`>"|:]Nx{M+ݴ?w*φ7edΉVU3[9jHEy3IUVTY:i:iEkRMN')c)ЇՓ][kxr+y"m.oVp[|28v9WWU',5܌Rk!\Ҋu.{wu?kt5(a"0fn"Pa+k;>~KmǟK?ajDqo;Hgt!mZu +Iw<9\80'J%՛KWo7OS*\ ([.H->[\toÑima-W8U12y]ݟgoY aӧocϧzз54S-o4cm$=r)jaԥV V쒲~f-Nvދ~rEF_A55^U$偨AMiM, GQ"-ܶ~^_INzz`zt+ԡLO./+Eo"K'Ne2$hXsFa0RUӶ"~<2os纋&ԓ +{ܭԏ$5*͹]Y_M<Zi࿈cUӢɐ0D7`|þG.k,>r:O]._WQ zJM[8 ZagB.nggoikO~&ɂ ~8_jq4N(ԫ(1rrr.fU?B:R^;(SM).zrw[XZo-Em= Iܹ=U@'r+6"X6w߯z:՞;yjxZMF$Y<'׽}VZT&utv҇ß1W:KSץݽ=co ΗmcN_Nzzyye^Nu}u٧CJcf^zupw"M ?LӏOq3lN6sYt4/*)ՄrQZ۫j U%ӊ1NH)l Dx ~+ɖy\=Zj%)vv=d8 Ҍ{X;hN߇s5O᱾찅19$xWdx2}rryմe3QRWO.0Xln) ykMVk}?O|c8Ps#:Y|E|N"R<귭de4bpmc迭v9.a*o(p ^IkRN RHڲ/{.b1>%*+MſVgmd/<2]IU8DZl~Ss~?T0&rEߦ֋=iv0Hش{p0\ȯ>|IQSŪoͫJs~힗FN."6\ȿiwz \17*U%]7a*4`Mk}:YTbUL5JVVKGE>O ^n4m/@N,e=OβZ2V\XOu^qO]thʖ:Q[*4pnֵt],-l>!IN5xťORR5ڽO?h_ǣXeز'^+1S_2wMcjKFʺϝ4;$4cc,$HeU,?+Iޣ/'*o~DI/?H>!CqKy|L~eX¯aż@Ŕ+IP@ggraph/vignettes/Edges.Rmd0000644000176200001440000004072513617226142015241 0ustar liggesusers--- title: "Edges" author: "Thomas Lin Pedersen" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Edges} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- If the natural `ggplot2` equivalent to nodes is `geom_point()`, then surely the equivalent to edges must be `geom_segment()`? Well, sort of, but there's a bit more to it than that. ![One does not simply draw a line between two nodes](edge_meme_wide.jpg) While nodes are the sensible, mature, and predictably geoms, edges are the edgy (sorry), younger cousins that pushes the boundaries. To put it bluntly: > On the ggraph savannah you definitely want to be an edge! ## Meet the `geom_edge_*()` family While the introduction might feel a bit over-the-top it is entirely true. An edge is an abstract concept denoting a relationship between two entities. A straight line is simply just one of many ways this relationship can be visualised. As we saw when [discussing nodes](Nodes.html) sometimes it is not drawn at all but impied using containment or position (treemap, circle packing, and partition layouts), but more often it is shown using a line of some sort. This use-case is handled by the large family of edge geoms provided in `ggraph`. Some of the edges are general while others are dedicated to specific layouts. Let's creates some graphs for illustrative purposes first: ```{r, message=FALSE} library(ggraph) library(tidygraph) library(purrr) library(rlang) set_graph_style(plot_margin = margin(1,1,1,1)) hierarchy <- as_tbl_graph(hclust(dist(iris[, 1:4]))) %>% mutate(Class = map_bfs_back_chr(node_is_root(), .f = function(node, path, ...) { if (leaf[node]) { as.character(iris$Species[as.integer(label[node])]) } else { species <- unique(unlist(path$result)) if (length(species) == 1) { species } else { NA_character_ } } })) hairball <- as_tbl_graph(highschool) %>% mutate( year_pop = map_local(mode = 'in', .f = function(neighborhood, ...) { neighborhood %E>% pull(year) %>% table() %>% sort(decreasing = TRUE) }), pop_devel = map_chr(year_pop, function(pop) { if (length(pop) == 0 || length(unique(pop)) == 1) return('unchanged') switch(names(pop)[which.max(pop)], '1957' = 'decreased', '1958' = 'increased') }), popularity = map_dbl(year_pop, ~ .[1]) %|% 0 ) %>% activate(edges) %>% mutate(year = as.character(year)) ``` ### Link While you don't have to use a straight line for edges it is certainly possible and `geom_edge_link()` is here to serve your needs: ```{r} ggraph(hairball, layout = 'stress') + geom_edge_link(aes(colour = year)) ``` There's really not much more to it --- every edge is simply a straight line between the terminal nodes. Moving on... ### Fan Sometimes the graph is not simple, i.e. it has multiple edges between the same nodes. Using links is a bad choice here because edges will overlap and the viewer will be unable to discover parallel edges. `geom_edge_fan()` got you covered here. If there are no parallel edges it behaves like `geom_edge_link()` and draws a straight line, but if parallel edges exists it will spread them out as arcs with different curvature. Parallel edges will be sorted by directionality prior to plotting so edges flowing in the same direction will be plotted together: ```{r} ggraph(hairball, layout = 'stress') + geom_edge_fan(aes(colour = year)) ``` ### Parallel An alternative to `geom_edge_fan()` is `geom_edge_parallel()`. It will draw edges as straight lines but in the case of multi-edges it will offset each edge a bit so they run parallel to each other. As with `geom_edge_fan()` the edges will be sorted by direction first. The offset is done at draw time and will thus remain constant even during resizing: ```{r} ggraph(hairball, layout = 'stress') + geom_edge_parallel(aes(colour = year)) ``` ### Loops Loops cannot be shown with regular edges as they have no length. A dedicated `geom_edge_loop()` exists for these cases: ```{r} # let's make some of the student love themselves loopy_hairball <- hairball %>% bind_edges(tibble::tibble(from = 1:5, to = 1:5, year = rep('1957', 5))) ggraph(loopy_hairball, layout = 'stress') + geom_edge_link(aes(colour = year), alpha = 0.25) + geom_edge_loop(aes(colour = year)) ``` The direction, span, and strength of the loop can all be controlled, but in general loops will add a lot of visual clutter to your plot unless the graph is very simple. ### Density This one is definitely strange, and I'm unsure of it's usefulness, but it is here and it deserves an introduction. Consider the case where it is of interest to see which types of edges dominates certain areas of the graph. You can colour the edges, but edges can tend to get overplotted, thus reducing readability. `geom_edge_density()` lets you add a shading to your plot based on the density of edges in a certain area: ```{r} ggraph(hairball, layout = 'stress') + geom_edge_density(aes(fill = year)) + geom_edge_link(alpha = 0.25) ``` ### Arcs While some insists that curved edges should be used in standard *"hairball"* graph visualisations it really is a poor choice, as it increases overplotting and decreases interpretability for virtually no gain (unless complexity is your thing). That doesn't mean arcs have no use in graph visualizations. Linear and circular layouts can benefit greatly from them and `geom_edge_arc()` is provided precisely for this scenario: ```{r} ggraph(hairball, layout = 'linear') + geom_edge_arc(aes(colour = year)) ``` Arcs behave differently in circular layouts as they will always bend towards the center no matter the direction of the edge (the same thing can be achieved in a linear layout by setting `fold = TRUE`). ```{r} ggraph(hairball, layout = 'linear', circular = TRUE) + geom_edge_arc(aes(colour = year)) + coord_fixed() ``` ### Elbow Aah... The classic dendrogram with its right angle bends. Of course such visualizations are also supported with the `geom_edge_elbow()`. It goes without saying that this type of edge requires a layout that flows in a defined direction, such as a tree: ```{r} ggraph(hierarchy, layout = 'dendrogram', height = height) + geom_edge_elbow() ``` ### Diagonals If right angles aren't really your thing `ggraph` provides a smoother version in the form of `geom_edge_diagonal()`. This edge is a quadratic bezier with control points positioned at the same x-value as the terminal nodes and halfway in-between the nodes on the y-axis. The result is more organic than the elbows: ```{r} ggraph(hierarchy, layout = 'dendrogram', height = height) + geom_edge_diagonal() ``` It tends to look a bit weird with hugely unbalanced trees so use with care... ### Bends An alternative to diagonals are bend edges which are elbow edges with a smoothed corner. It is implemented as a quadratic bezier with control points at the location of the expected elbow corner: ```{r} ggraph(hierarchy, layout = 'dendrogram', height = height) + geom_edge_bend() ``` ### Hive This is certainly a very specific type of edge, intended only for use with hive plots. It draws edges as quadratic beziers with control point positioned perpendicular to the axes of the hive layout: ```{r} ggraph(hairball, layout = 'hive', axis = pop_devel, sort.by = popularity) + geom_edge_hive(aes(colour = year)) + geom_axis_hive(label = FALSE) + coord_fixed() ``` ### Span As with the hive edge the `geom_edge_span()` is made in particular for a specific layout - the fabric layout. It draws the edge as a vertical line connecting the horizontal node lines of the layout, potentially with a terminal shape. ```{r} ggraph(hairball, layout = 'fabric', sort.by = node_rank_fabric()) + geom_node_range(colour = 'grey') + geom_edge_span(end_shape = 'circle') + coord_fixed() ``` ### Point and tile It may seem weird to have edge geoms that doesn't have any span, but the matrix layout calls for exactly that. The terminal nodes of the edge are determined by the vertical and horizontal position of the mark, and for that reason the geom doesn't need any extend. The point and tile geoms serve the same purpose but are simply different geometry types: ```{r} ggraph(hairball, layout = 'matrix', sort.by = bfs_rank()) + geom_edge_point() + coord_fixed() ``` ```{r} ggraph(hairball, layout = 'matrix', sort.by = bfs_rank()) + geom_edge_tile() + coord_fixed() ``` ## The three types of edge geoms Almost all edge geoms comes in three variants. The basic variant (no suffix) as well as the variant suffixed with 2 (e.g. `geom_edge_link2()`) calculates a number (`n`) of points along the edge and draws it as a path. The variant suffixed with 0 (e.g. `geom_edge_diagonal0()`) uses the build in grid grobs to draw the edges directly (in case of a diagonal it uses `bezierGrob()`). It might seem strange to have so many different implementations of the same geoms but there's a reason to the insanity... ### Base variant The basic edge geom is drawn by calculating a number of points along the edge path and draw a line between these. This means that you're in control of the detail level of curved edges and that all complex calculations happens up front. Generally you will see better performance using the base variant rather than the 0-variant that uses grid grobs, unless you set the number of points to calculate to something huge (50--100 is usually sufficient for a smooth look). Apart from better performance you also get a nice bonus (you actually get several, but only one is discussed here): The possibility of drawing a gradient along the edge. Each calculated point gets an index value between 0 and 1 that specifies how far along the edge it is positioned and this value can be used to e.g. map to an alpha level to show the direction of the edge: ```{r} ggraph(hairball, layout = 'linear') + geom_edge_arc(aes(colour = year, alpha = stat(index))) + scale_edge_alpha('Edge direction', guide = 'edge_direction') ``` ### 2-variant Like the base variant the 2-variant calculates points along the edge and draws a path along them. The difference here is that in this variant you can map node attributes to the edge and the aesthetics are then interpolated along the edge. This is easier to show than to explain: ```{r} ggraph(hierarchy, layout = 'dendrogram', height = height) + geom_edge_elbow2(aes(colour = node.Class)) ``` There are considerably more computation going on than in the base variant so unless you need to interpolate values between the terminal nodes you should go with the base variant. ### 0-variant This is, sadly, the boring one at the end. You don't get the luxury of smooth gradients over the edge and often you have a considerably worse performance. What you gain though is tack sharp resolution in the curves so if this is of utmost importance you are covered by this variant. ## Edge strength Many of the edge geoms takes a strength argument that denotes their deviation from a straight line. Setting `strength = 0` will always result in a straight line, while `strength = 1` is the default look. Anything in between can be used to modify the look of the edge, while values outside that range will probably result in some weird looks. Some examples are shown below: ```{r} small_tree <- create_tree(5, 2) ggraph(small_tree, 'dendrogram') + geom_edge_elbow(strength = 0.75) ``` ```{r} ggraph(small_tree, 'dendrogram') + geom_edge_diagonal(strength = 0.5) ``` ## Decorating edges An edge is so much more than a line... Well at least it is also potentially an arrow and a label. This section will go into how these can be added. To clearly see the effect here we will use a slightly simpler graph ```{r} # Random names - I swear simple <- create_notable('bull') %>% mutate(name = c('Thomas', 'Bob', 'Hadley', 'Winston', 'Baptiste')) %>% activate(edges) %>% mutate(type = sample(c('friend', 'foe'), 5, TRUE)) ``` ### Arrows While we saw above that direction can be encoded as a gradient, the good old arrow is still available. As with the standard `ggplot2` geoms an arrow can be added using the arrow argument: ```{r} ggraph(simple, layout = 'graphopt') + geom_edge_link(arrow = arrow(length = unit(4, 'mm'))) + geom_node_point(size = 5) ``` I hope you think *Ugh* at the sight of this. The edges naturally extend to the node center and nodes are thus drawn on top of the arrow heads. There's a solution to this in the form of the `start_cap` and `end_cap` aesthetics in the base and 2-variant edge geoms (sorry 0-variant). This can be used to start and stop the edge drawing at an absolute distance from the terminal nodes. Watch this: ```{r} ggraph(simple, layout = 'graphopt') + geom_edge_link(arrow = arrow(length = unit(4, 'mm')), end_cap = circle(3, 'mm')) + geom_node_point(size = 5) ``` Using the `circle()`, `square()`, `ellipsis()`, and `rectangle()` helpers it is possible to get a lot of control over how edges are capped at either end. This works for any edge, curved or not: ```{r} ggraph(simple, layout = 'linear', circular = TRUE) + geom_edge_arc(arrow = arrow(length = unit(4, 'mm')), start_cap = circle(3, 'mm'), end_cap = circle(3, 'mm')) + geom_node_point(size = 5) + coord_fixed() ``` When plotting node labels you often want to avoid that incoming and outgoing edges overlaps with the labels. `ggraph` provides a helper that calculates the bounding rectangle of the labels and cap edges based on that: ```{r} ggraph(simple, layout = 'graphopt') + geom_edge_link(aes(start_cap = label_rect(node1.name), end_cap = label_rect(node2.name)), arrow = arrow(length = unit(4, 'mm'))) + geom_node_text(aes(label = name)) ``` The capping of edges is dynamic and responds to resizing of the plot so the absolute size of the cap areas are maintained at all time. #### A quick note on directionality In `ggraph` there is no such thing as an undirected graph. Every edge has a start and an end node. For undirected graphs the start and end of edges is arbitrary but still exists and it is thus possible to add arrowheads to undirected graphs as well. This should not be done of course, but this is the responsibility of the user as `ggraph` does not make any checks during rendering. ### Labels You would expect that edge labels would be their own geom(s), but `ggraph` departs from the stringent grammar interpretation here. This is because the label placement is dependent on the choice of edge. Because of this edge labeling is bundled with each edge geom (but not the 0-variant) through the label aesthetic ```{r} ggraph(simple, layout = 'graphopt') + geom_edge_link(aes(label = type), arrow = arrow(length = unit(4, 'mm')), end_cap = circle(3, 'mm')) + geom_node_point(size = 5) ``` Usually you would like the labels to run along the edges, but providing a fixed angle will only work at a very specific aspect ratio. Instead `ggraph` offers to calculate the correct angle dynamically so the labels always runs along the edge. Furthermore it can offset the label by an absolute length: ```{r} ggraph(simple, layout = 'graphopt') + geom_edge_link(aes(label = type), angle_calc = 'along', label_dodge = unit(2.5, 'mm'), arrow = arrow(length = unit(4, 'mm')), end_cap = circle(3, 'mm')) + geom_node_point(size = 5) ``` `ggraph` offers a lot of additional customization of the edge labels but this shows the main features. As with arrowheads labels can severely clutter your visualization so it is only advisable on very simple graphs. ## Connections The estranged cousin of edges are connections. While edges show the relational nature of the nodes in the graph structure, connections connect nodes that are not connected in the graph. This is done by finding the shortest path between the two nodes. Currently the only connection geom available is `geom_conn_bundle()` that implements the hierarchical edge bundling technique: ```{r} flaregraph <- tbl_graph(flare$vertices, flare$edges) from <- match(flare$imports$from, flare$vertices$name) to <- match(flare$imports$to, flare$vertices$name) ggraph(flaregraph, layout = 'dendrogram', circular = TRUE) + geom_conn_bundle(data = get_con(from = from, to = to), alpha = 0.1) + coord_fixed() ``` The connection concept is underutilized at the moment but I expect to add more support for this in coming releases. ## Want more? Check out the other vignettes for more information on how to specify [layouts](Layouts.html) and draw [nodes](Nodes.html)... ggraph/vignettes/Layouts.Rmd0000644000176200001440000003235713617226142015654 0ustar liggesusers--- title: "Layouts" author: "Thomas Lin Pedersen" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Layouts} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- In very short terms, a layout is the vertical and horizontal placement of nodes when plotting a particular graph structure. Conversely, a layout algorithm is an algorithm that takes in a graph structure (and potentially some additional parameters) and return the vertical and horizontal position of the nodes. Often, when people think of network visualizations, they think of node-edge diagrams where strongly connected nodes are attempted to be plotted in close proximity. Layouts can be a lot of other things too though --- e.g. hive plots and treemaps. One of the driving factors behind `ggraph` has been to develop an API where any type of visual representation of graph structures is supported. In order to achieve this we first need a flexible way of defining the layout... ## The ggraph() and create_layout() functions As the layout is a global specification of the spatial position of the nodes it spans all layers in the plot and should thus be defined outside of calls to geoms or stats. In `ggraph` it is often done as part of the plot initialization using `ggraph()` --- a function equivalent in intent to `ggplot()`. As a minimum `ggraph()` must be passed a graph object supported by `ggraph`: ```{r, message=FALSE} library(ggraph) library(tidygraph) set_graph_style(plot_margin = margin(1,1,1,1)) graph <- as_tbl_graph(highschool) # Not specifying the layout - defaults to "auto" ggraph(graph) + geom_edge_link(aes(colour = factor(year))) + geom_node_point() ``` Not specifying a layout will make `ggraph` pick one for you. This is only intended to get quickly up and running. The choice of layout should be deliberate on the part of the user as it will have a great effect on what the end result will communicate. From now on all calls to `ggraph()` will contain a specification of the layout: ```{r} ggraph(graph, layout = 'kk') + geom_edge_link(aes(colour = factor(year))) + geom_node_point() ``` If the layout algorithm accepts additional parameters (most do), they can be supplied in the call to `ggraph()` as well: ```{r} ggraph(graph, layout = 'kk', maxiter = 100) + geom_edge_link(aes(colour = factor(year))) + geom_node_point() ``` If any layout parameters refers to node or edge variables they must be supplied as unquoted expression (like inside `aes()` and `tidyverse` verbs) In addition to specifying the layout during plot creation it can also happen separately using `create_layout()`. This function takes the same arguments as `ggraph()` but returns a `layout_ggraph` object that can later be used in place of a graph structure in ggraph call: ```{r} layout <- create_layout(graph, layout = 'eigen') ggraph(layout) + geom_edge_link(aes(colour = factor(year))) + geom_node_point() ``` Examining the return of `create_layout()` we see that it is really just a `data.frame` of node positions and (possible) attributes. Furthermore the original graph object along with other relevant information is passed along as attributes: ```{r} head(layout) ``` ```{r} attributes(layout) ``` As it is just a `data.frame` it means that any standard `ggplot2` call will work by addressing the nodes. Still, use of the `geom_node_*()` family provided by `ggraph` is encouraged as it makes it explicit which part of the data structure is being worked with. ## Adding support for new data sources Out of the box `ggraph` supports `tbl_graph` objects from tidygraph natively. Any other type of object will be attempted to be coerced to a `tbl_graph` object automatically. Tidygraph provide conversions for most known graph structure in R so almost any data type is supported by ggraph by extension. If there is wish for support for additional classes this can be achieved by providing a `as_tbl_graph()` method for the class. If you do this, consider submitting the method to tidygraph so others can benefit from your work. ## Layouts abound There's a lot of different layouts in `ggraph` --- All layouts from the graphlayouts and igraph packages are available, an ggraph itself also provide some of the more specialised layouts itself. All in all ggraph provides well above 20 different layouts to choose from, far more than we can cover in this text. I urge you to explore the different layout types. Blindly running along with the default layouts is a sad but common mistake in network visualisation that can cloud or distort the insight the network might hold. If ggraph lacks the needed layout it is always possible to supply your own layout function that takes a tbl_graph object and returns a data.frame of node positions, or supply the positions directly by passing a matrix or data.frame to the layout argument. ### A note on circularity Some layouts can be shown effectively both in a standard Cartesian projection as well as in a polar projection. The standard approach in `ggplot2` has been to change the coordinate system with the addition of e.g. `coord_polar()`. This approach --- while consistent with the grammar --- is not optimal for `ggraph` as it does not allow layers to decide how to respond to circularity. The prime example of this is trying to draw straight lines in a plot using `coord_polar()`. Instead circularity is part of the layout specification and gets communicated to the layers with the `circular` column in the data, allowing each layer to respond appropriately. Sometimes standard and circular representations of the same layout get used so often that they get different names. In `ggraph` they'll have the same name and only differ in whether or not `circular` is set to `TRUE`: ```{r} # An arc diagram ggraph(graph, layout = 'linear') + geom_edge_arc(aes(colour = factor(year))) ``` ```{r} # A coord diagram ggraph(graph, layout = 'linear', circular = TRUE) + geom_edge_arc(aes(colour = factor(year))) + coord_fixed() ``` ```{r} graph <- tbl_graph(flare$vertices, flare$edges) # An icicle plot ggraph(graph, 'partition') + geom_node_tile(aes(fill = depth), size = 0.25) ``` ```{r} # A sunburst plot ggraph(graph, 'partition', circular = TRUE) + geom_node_arc_bar(aes(fill = depth), size = 0.25) + coord_fixed() ``` Not every layout has a meaningful circular representation in which cases the `circular` argument will be ignored. ### Node-edge diagram layouts Both `graphlayout` and `igraph` provides a range of different layout algorithms for classic node-edge diagrams (colloquially referred to as hairballs). Some of these are incredibly simple such as *randomly*, *grid*, *circle*, and *star*, while others tries to optimize the position of nodes based on different characteristics of the graph. There is no such thing as "the best layout algorithm" as algorithms have been optimized for different scenarios. Experiment with the choices at hand and remember to take the end result with a grain of salt, as it is just one of a range of possible "optimal node position" results. Below is a sample of some of the layouts available through `igraph` applied to the highschool graph. ```{r, fig.show='hold', results='hide'} graph <- as_tbl_graph(highschool) %>% mutate(degree = centrality_degree()) lapply(c('stress', 'fr', 'lgl', 'graphopt'), function(layout) { ggraph(graph, layout = layout) + geom_edge_link(aes(colour = factor(year)), show.legend = FALSE) + geom_node_point() + labs(caption = paste0('Layout: ', layout)) }) ``` ### Hive plots A hive plot, while still technically a node-edge diagram, is a bit different from the rest as it uses information pertaining to the nodes, rather than the connection information in the graph. This means that hive plots, to a certain extent are more interpretable as well as less vulnerable to small changes in the graph structure. They are less common though, so use will often require some additional explanation. ```{r} graph <- graph %>% mutate(friends = ifelse( centrality_degree(mode = 'in') < 5, 'few', ifelse(centrality_degree(mode = 'in') >= 15, 'many', 'medium') )) ggraph(graph, 'hive', axis = friends, sort.by = degree) + geom_edge_hive(aes(colour = factor(year))) + geom_axis_hive(aes(colour = friends), size = 2, label = FALSE) + coord_fixed() ``` ### Focal layouts Some layouts can put focus on a single node or a group of nodes by defining all other positions relative to that. An example of this is the `focus` layout, but the `centrality` layout is very akin to it: ```{r} ggraph(graph, 'focus', focus = node_is_center()) + ggforce::geom_circle(aes(x0 = 0, y0 = 0, r = r), data.frame(r = 1:5), colour = 'grey') + geom_edge_link() + geom_node_point() + coord_fixed() ``` ### Hierarchical layouts Trees and hierarchies are an important subset of graph structures, and `ggraph` provides a range of layouts optimized for their visual representation. Some of these use enclosure and position rather than edges to communicate relations (e.g. treemaps and circle packing). Still, these layouts can just as well be used for drawing edges if you wish to: ```{r} graph <- tbl_graph(flare$vertices, flare$edges) set.seed(1) ggraph(graph, 'circlepack', weight = size) + geom_node_circle(aes(fill = depth), size = 0.25, n = 50) + coord_fixed() ``` ```{r} set.seed(1) ggraph(graph, 'circlepack', weight = size) + geom_edge_link() + geom_node_point(aes(colour = depth)) + coord_fixed() ``` ```{r} ggraph(graph, 'treemap', weight = size) + geom_node_tile(aes(fill = depth), size = 0.25) ``` ```{r} ggraph(graph, 'treemap', weight = size) + geom_edge_link() + geom_node_point(aes(colour = depth)) ``` The most recognized tree plot is probably dendrograms though. If nothing else is stated the height of each node is calculated based on the distance to its farthest sibling (the tree layout, on the other hand, puts all nodes at a certain depth at the same level): ```{r} ggraph(graph, 'tree') + geom_edge_diagonal() ``` The height of each branch point can be set to a variable --- e.g. the height provided by hclust and dendrogram objects: ```{r} dendrogram <- hclust(dist(iris[, 1:4])) ggraph(dendrogram, 'dendrogram', height = height) + geom_edge_elbow() ``` Dendrograms are one of the layouts that are amenable for circular transformations, which can be effective in giving more space at the leafs of the tree at the expense of the space given to the root: ```{r} ggraph(dendrogram, 'dendrogram', circular = TRUE) + geom_edge_elbow() + coord_fixed() ``` A type of trees known especially in phylogeny is unrooted trees, where no node is considered the root. Often a dendrogram layout will not be faithful as it implicitly position a node at the root. To avoid that you can use the `unrooted` layout instead. ```{r} ggraph(dendrogram, 'unrooted') + geom_edge_link() ``` Often unrooted trees have a branch length attached - this can be passed to both the `dendrogram` and `unrooted` layout to determine the length of each edge. ### Matrix layouts Many node-edge diagram layouts suffer from poor scalability, where edges will eventually begin to overlap to the extend that the plot becomes unintellible. One way to combat this is by only plotting subsets of the larger plot, but another approach is to choose a layout that avoids overlapping edges altogether. The matrix layout places each node on a diagonal and draws edges by drawing points or tiles at the intersection of the vertical and horizontal position of its terminal nodes. Using matrix layouts efficiently requires that you begin to recognise the specific patterns that different network topologies gives rise to. Further, it is important to recognise the large effect that the node order has on the look of the matrix layout: ```{r} graph <- create_notable('zachary') ggraph(graph, 'matrix', sort.by = node_rank_leafsort()) + geom_edge_point(mirror = TRUE) + coord_fixed() ``` ```{r} ggraph(graph, 'matrix', sort.by = node_rank_spectral()) + geom_edge_point(mirror = TRUE) + coord_fixed() ``` ### Fabric layouts Another special layout that promises scalability is the biofabric layout (here named `fabric` to avoid it being ignored for non-biological networks). The fabric layout is special in that it positions nodes evenly spaced on the y-axis and then draws edges as vertical (and by extension, parallel) lines evenly separated as well, connecting the nodes. Nodes are drawn as horizontal lines spanning the extent of the edges that departs from it. As with matrix layouts the node ordering have a huge impact on the final look and interpreting the plot may take some getting used to. ```{r} ggraph(graph, 'fabric', sort.by = node_rank_fabric()) + geom_node_range(colour = 'grey') + geom_edge_span(end_shape = 'square') + coord_fixed() ``` Fabric layouts allow something called shadow edges where all edges are duplicated to make it easier to follow all edges originating from each node. ```{r} ggraph(graph, 'fabric', sort.by = node_rank_fabric(), shadow.edges =TRUE) + geom_node_range(colour = 'grey') + geom_edge_span(aes(filter = shadow_edge), colour ='lightblue' , end_shape = 'square') + geom_edge_span(aes(filter = !shadow_edge), end_shape = 'square') + coord_fixed() ``` ## Want more? Check out the other vignettes for more information on how to draw [nodes](Nodes.html) and [edges](Edges.html)... ggraph/vignettes/tidygraph.Rmd0000644000176200001440000001331313527251470016200 0ustar liggesusers--- title: "tidygraph and ggraph" author: "Thomas Lin Pedersen" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{tidygraph and ggraph} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include=FALSE} library(ggraph) set_graph_style(family = 'Arial', size = 7, foreground = 'lightgrey', plot_margin = margin(0, 0, 0, 0)) ``` Following `ggraph` v2.0 the `tidygraph` package has been used as the central data structure. The integration goes beyond using it as a simple background engine and has deep implications for what you can do and how you can do it when plotting with `ggraph`. This vignette will go into the details of the `ggraph`/`tidygraph` relationship — buckle up... ## Supported data structures Prior to v2 `ggraph` had two main supported data structures, namely `dendrogram` and `igraph`. In addition `hclust` and `network` were supported by automatic conversion to `dendrogram` and `igraph` respectively. Each of the two data structures had their own layouts and under the hood two different set of functionality had to be maintained to extract nodes and edges etc. In v2 and going forward this has been simplified and `ggraph` now uses only `tbl_graph` as a graph representation. This does not mean that you're out of luck if you're not buying into the whole `tidygraph` idea. Every object supported by `tidygraph` is supported directly in `ggraph` by automatic conversion to `tbl_graph`. This means that `igraph`, `dendrogram`, `hclust`, and `network` is still supported in addition to `data.tree`, `phylo`, and `graph` as well as a number of `data.frame`, `matrix`, and `list` representations. The change has reduced internal code complexity quite a bit which will make it easier to provide new features in future. From a user point of view it has the benefit of simplifying the API in that `ggraph` doesn't really care what type of network object you pass in - every layout and geom just works with every data structure. Further, it simplifies how to add `ggraph` support to additional data structures: just write an `as_tbl_graph()` method for the class!. Due to the large support of classes and data structures in `tidygraph` this should relatively straightforward. If you're developer of a package that defines a custom network class simply export an `as_tbl_graph()` method for the class to gain native `ggraph` (and `tidygraph`) support, or add it directly to `tidygraph` through a [PR](https://github.com/thomasp85/tidygraph/pulls). This simplification for both me and the users have really been the motivation for the integration of `tidygraph` but as it were it has also allowed or instigated a number of cool new features that will be explored below. ## NSE in layout specifications In `ggraph` the initiation will need to specify a layout to use for the subsequent node and edge geoms. Many of these layouts use different node and edge variables in their calculations e.g. a node size or an edge weight. Prior to v2 these arguments would simply take a string naming the respective variable to use, but following the v2 update these arguments implement Non-Standard Evaluation (NSE) in a manner known from both `dplyr` and `ggplot2` where it is used inside `aes()` calls. Depending on whether the argument refers to a node or edge value the provided expression will be evaluated in the context of nodes or edges respectively. The bottomline is that given a network such as this: ```{r, message=FALSE} library(tidygraph) graph <- as_tbl_graph( data.frame( from = sample(5, 20, TRUE), to = sample(5, 20, TRUE), weight = runif(20) ) ) graph ``` Then, instead of writing: ```{r, eval=FALSE} ggraph(graph, layout = 'fr', weights = "weight") + geom_edge_link() + geom_node_point() ``` You would simply write: ```{r} ggraph(graph, layout = 'fr', weights = weight) + geom_edge_link() + geom_node_point() ``` This change means that it is much easier to experiment with modifications to node and edge parameters affecting layouts as it is not necessary to modify the underlying graph but only the plotting code, e.g.: ```{r} ggraph(graph, layout = 'fr', weights = log(weight)) + geom_edge_link() + geom_node_point() ``` ## Access to tidygraph algorithms in ggraph code The most important improvement resulting from the integration of `tidygraph` and `ggraph` is that `tidygraph` algorithms are now directly usable within `ggraph` calls. This means that it is no longer necessary to precompute and store derived node and edge variables on the graph in order to use them in a plot: ```{r} graph <- create_notable('zachary') ggraph(graph, layout = 'fr') + geom_edge_link() + geom_node_point(aes(size = centrality_pagerank())) + theme(legend.position = 'bottom') ``` here it is not necessary to first compute the pagerank centrality and store it as a node variable in order to plot it, and if you're interested in looking at one of the myriad of other centrality measures you simply change the plotting code. This feature makes it much easier and painfree to investigate the effect of different graph measures on your plots and is a huge benefit when iterating on your visualisation. Access to `tidygraph` is available within `ggraph()` and `aes()` calls, and within `facet` formulas. It is thus possible to use algorithms when specifying layouts, adding aesthetics to geoms and splitting into subplots - all areas were ease of iteration is vital: ```{r, message=FALSE} ggraph(graph, 'matrix', sort.by = node_rank_leafsort()) + geom_edge_point(aes(colour = centrality_edge_betweenness()), mirror = TRUE) + theme(legend.position = 'bottom') ``` ```{r} ggraph(graph, 'fr') + geom_edge_link() + geom_node_point() + facet_nodes(~ group_infomap()) ``` ggraph/vignettes/Nodes.Rmd0000644000176200001440000001313213527251314015251 0ustar liggesusers--- title: "Nodes" author: "Thomas Lin Pedersen" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Nodes} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- Nodes in a network are the entities that are connected. Sometimes these are also referred to as vertices, but `ggraph` has opted for this nomenclature and uses it consistently. While the nodes in a graph are the abstract concepts of entities, and the layout is their physical placement, the node geoms are the visual manifestation of the entities. Conceptually one can simply think of it in terms of a scatter plot --- the layout provides the x and y coordinates, and these can be used to draw nodes in different ways in the plotting window. Actually, due to the design of `ggraph` the standard *scatterplot-like* geoms from `ggplot2` can be used directly for plotting nodes: ```{r, message=FALSE} library(ggraph) library(tidygraph) set_graph_style(plot_margin = margin(1,1,1,1)) gr <- as_tbl_graph(highschool) ggraph(gr, layout = 'kk') + geom_point(aes(x = x, y = y)) ``` The reason this works is that, as discussed in the Layout vignette, layouts return a `data.frame` of node positions and metadata and this is used as the default plot data: ```{r} head(create_layout(gr, layout = 'kk')) ``` ## `geom_node_*()` While usage of the default `ggplot2` is absolutely allowed, `ggraph` comes with its own set of node geoms. Many of these are direct translations of `ggplot2` own geoms like `geom_point()` so one could wonder why bother to use them. The first reason is to provide clear code. It is not apparent anywhere that the standard geoms are addressing the nodes and using `geom_node_*()` makes it clear that this layer will draw nodes. The second reason is that it will save typing. Since `ggraph` is in control of the shape of the input data through the layout calculations, it knows that *x* and *y* position is encoded in an `x` and `y` column. This means that `geom_node_*` can default the x and y aesthetics so there's no need to type them: ```{r} ggraph(gr, layout = 'kk') + geom_node_point() ``` sometimes there is a need for addressing the x and y aesthetics, which is still possible, for instance if a partition layout should be inverted: ```{r} gr <- tbl_graph(flare$vertices, flare$edges) ggraph(gr, layout = 'partition') + geom_node_tile(aes(y = -y, fill = depth)) ``` of course this could also be accomplished by reversing the y-axis using `scale_y_reverse()` so this is just to illustrate that the defaults are easily overwritten if needed. The third reason is for the added functionality. All `ggraph` geoms get a `filter` aesthetic that allows you to quickly filter the input data. The use of this can be illustrated when plotting a tree: ```{r} ggraph(gr, layout = 'dendrogram', circular = TRUE) + geom_edge_diagonal() + geom_node_point(aes(filter = leaf)) + coord_fixed() ``` In the above plot only the terminal nodes are drawn by filtering on the logical leaf column provided by the dendrogram layout. ## The different node geoms The usual suspects are of course provided in the form of `geom_node_point()` (showcased above), `geom_node_text()`, and `geom_node_label()`. These work as expected, taking in the usual aesthetics (plus *filter*). Only x and y are defaulted so everything else must be provided e.g. label which does not default to the `name` column like is done in `igraph`. One feature sets `geom_node_text()` and `geom_node_label()` apart from their `ggplot2` counterparts: both have a `repel` argument that, when set to `TRUE`, will use the repel functionality provided by the [ggrepel](https://CRAN.R-project.org/package=ggrepel) package to avoid overlapping text. There is also `geom_node_voronoi()` that plots nodes as cells from a voronoi tesselation. This is useful for e.g. showing dominance of certain node types in an area as overlapping is avoided: ```{r} graph <- create_notable('meredith') %>% mutate(group = sample(c('A', 'B'), n(), TRUE)) ggraph(graph, 'stress') + geom_node_voronoi(aes(fill = group), max.radius = 1) + geom_node_point() + geom_edge_link() + coord_fixed() ``` Apart from these geoms there's a set of geoms mainly useful for spatial node layouts such as treemaps, partition, circle packing, and fabric. `geom_node_tile()` and `geom_node_range()` are the `ggraph` counterpart to `ggplot2`s `geom_tile()` and `geom_linerange()` while `geom_node_circle()` and `geom_node_arc_bar()` maps to `ggforce`s `geom_circle()` and `geom_arc_bar()`. Collective for these is that the spatial dimensions of the geoms (e.g. radius, width, and height) are precalculated by their intended layouts and defaulted by the geoms: ```{r} ggraph(gr, layout = 'treemap', weight = size) + geom_node_tile(aes(fill = depth)) ``` All spatial node geoms will be center-based, meaning that the x and y value of the layout will refer to the center of the layout and not e.g. the bottom-left corner. This makes it easier to add labels to spatial layouts as well as using spatial layouts in a non-spatial way: ```{r} l <- ggraph(gr, layout = 'partition', circular = TRUE) l + geom_node_arc_bar(aes(fill = depth)) + coord_fixed() ``` ```{r} l + geom_edge_diagonal() + geom_node_point(aes(colour = depth)) + coord_fixed() ``` More node geoms are sure to appear in `ggraph` with time but they will generally be quite easily comprehensible due to their strong resemblance to the standard `ggplot2` geoms. After all it is just points on a plane... ## Want more? Check out the other vignettes for more information on how to specify [layouts](Layouts.html) and draw [edges](Edges.html)... ggraph/R/0000755000176200001440000000000013617226232011727 5ustar liggesusersggraph/R/geom_axis_hive.R0000644000176200001440000001263213525464065015052 0ustar liggesusers#' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom dplyr %>% group_by mutate slice ungroup #' @export StatAxisHive <- ggproto('StatAxisHive', StatFilter, setup_data = function(data, params) { data <- data %>% group_by(.data$angle, .data$section, .data$PANEL) %>% mutate( x = min(.data$r) * cos(.data$angle[1]) * 1.1, y = min(.data$r) * sin(.data$angle[1]) * 1.1, xend = max(.data$r) * cos(.data$angle[1]) * 1.1, yend = max(.data$r) * sin(.data$angle[1]) * 1.1, max_r = max(.data$r), min_r = min(.data$r) ) %>% slice(1) %>% ungroup() as.data.frame(data, stringsAsFactors = FALSE) }, required_aes = c('r', 'angle', 'center_size', 'axis', 'section'), extra_params = c('na.rm', 'n', 'curvature') ) #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom grid textGrob nullGrob #' @importFrom dplyr %>% group_by summarise #' @export GeomAxisHive <- ggproto('GeomAxisHive', GeomSegment, draw_panel = function(data, panel_scales, coord, label = TRUE, axis = TRUE, label_colour = 'black') { data$x <- data$x / 1.1 data$y <- data$y / 1.1 data$xend <- data$xend / 1.1 data$yend <- data$yend / 1.1 data <- coord$transform(data, panel_scales) label_data <- data %>% group_by(.data$axis) %>% summarise( x = max(.data$max_r) * cos(mean(.data$angle)), y = max(.data$max_r) * sin(mean(.data$angle)), label = .data$axis[1], angle = mean(.data$angle) / (2 * pi) * 360 - 90, colour = .data$colour[1], label_size = .data$label_size[1], family = .data$family[1], fontface = .data$fontface[1], lineheight = .data$lineheight[1] ) label_data <- as.data.frame(label_data, stringsAsFactors = FALSE) lab_dist <- sqrt(label_data$x^2 + label_data$y^2) dist_dodge <- max(lab_dist) * 1.05 - max(lab_dist) label_data$x <- label_data$x * (dist_dodge + lab_dist) / lab_dist label_data$y <- label_data$y * (dist_dodge + lab_dist) / lab_dist label_data$angle <- label_data$angle + ifelse(label_data$angle < 0, 360, 0) label_data$angle <- label_data$angle - ifelse(label_data$angle > 360, 360, 0) upside_label <- label_data$angle > 90 & label_data$angle < 270 label_data$angle[upside_label] <- label_data$angle[upside_label] + 180 label_data <- coord$transform(label_data, panel_scales) label_data$label_colour <- if (is.na(label_colour)) { label_data$colour } else { label_colour } label_grob <- if (label) { textGrob(label_data$label, label_data$x, label_data$y, default.units = 'native', rot = label_data$angle, gp = gpar( col = label_data$label_colour, fontsize = label_data$label_size * .pt, fontfamily = label_data$family, fontface = label_data$fontface, lineheight = label_data$lineheight ) ) } else { nullGrob() } axis_grob <- if (axis) { segmentsGrob(data$x, data$y, data$xend, data$yend, default.units = 'native', gp = gpar( col = alpha(data$colour, data$alpha), fill = alpha(data$colour, data$alpha), lwd = data$size * .pt, lty = data$linetype, lineend = 'square' ) ) } else { nullGrob() } gList(axis_grob, label_grob) }, default_aes = aes( colour = 'black', size = 0.5, linetype = 1, alpha = NA, label_size = 3.88, family = '', fontface = 1, lineheight = 1.2 ) ) #' Draw rectangular bars and labels on hive axes #' #' This function lets you annotate the axes in a hive plot with labels and #' color coded bars. #' #' @inheritParams ggplot2::geom_point #' #' @param label Should the axes be labelled. Defaults to `TRUE` #' #' @param axis Should a rectangle be drawn along the axis. Defaults to `TRUE` #' #' @section Aesthetics: #' geom_axis_hive understand the following aesthetics. #' \itemize{ #' \item{alpha} #' \item{colour} #' \item{fill} #' \item{size} #' \item{linetype} #' \item{label_size} #' \item{family} #' \item{fontface} #' \item{lineheight} #' } #' #' @author Thomas Lin Pedersen #' #' @export #' #' @examples #' # Plot the flare import graph as a hive plot #' library(tidygraph) #' flareGr <- as_tbl_graph(flare$imports) %>% #' mutate( #' type = dplyr::case_when( #' centrality_degree(mode = 'in') == 0 ~ 'Source', #' centrality_degree(mode = 'out') == 0 ~ 'Sink', #' TRUE ~ 'Both' #' ) #' ) %>% #' activate(edges) %>% #' mutate( #' type = dplyr::case_when( #' grepl('flare.analytics', paste(.N()$name[from], .N()$name[to])) ~ 'Analytics', #' TRUE ~ 'Other' #' ) #' ) #' ggraph(flareGr, 'hive', axis = type) + #' geom_edge_hive(aes(colour = type), edge_alpha = 0.1) + #' geom_axis_hive(aes(colour = type)) + #' coord_fixed() geom_axis_hive <- function(mapping = NULL, data = NULL, position = 'identity', label = TRUE, axis = TRUE, show.legend = NA, ...) { mapping <- aes_intersect(mapping, aes(r = r, angle = angle, center_size = center_size, axis = axis, section = section)) layer( data = data, mapping = mapping, stat = StatAxisHive, geom = GeomAxisHive, position = position, show.legend = show.legend, inherit.aes = FALSE, params = list(na.rm = FALSE, label = label, axis = axis, ...) ) } ggraph/R/layout_pmds.R0000644000176200001440000000274313526506125014421 0ustar liggesusers#' Place nodes based on a multidimensional scaling of a set of pivot nodes #' #' This layout is similar to the 'mds' layout but uses only a subset of pivot #' nodes for the mds calculation, making it considerably faster and thus suited #' for large graphs #' #' @param graph A tbl_graph object #' @param pivots The number of pivot nodes #' @param weights An expression evaluated on the edge data to provide edge #' weights for the layout. Currently ignored for the sparse version #' @param circular ignored #' #' @return A data.frame with the columns `x`, `y`, `circular` as #' well as any information stored as node variables in the tbl_graph object. #' #' @references #' Brandes, U. and Pich, C. (2006). *Eigensolver Methods for Progressive #' Multidimensional Scaling of Large Data.* In International Symposium on Graph #' Drawing (pp. 42-53). Springer #' #' @family layout_tbl_graph_* #' #' @author The underlying algorithm is implemented in the graphlayouts package #' by David Schoch #' #' @importFrom graphlayouts layout_with_pmds layout_tbl_graph_pmds <- function(graph, pivots, weights = NULL, circular = FALSE) { weights <- eval_tidy(enquo(weights), .E()) if (is.null(weights)) weights <- NA xy <- layout_with_pmds(graph, pivots = pivots, weights = weights) nodes <- new_data_frame(list(x = xy[,1], y = xy[,2])) nodes$circular <- FALSE extra_data <- as_tibble(graph, active = 'nodes') nodes <- cbind(nodes, extra_data[, !names(extra_data) %in% names(nodes), drop = FALSE]) nodes } ggraph/R/geom_edge_parallel.R0000644000176200001440000002074713526604265015660 0ustar liggesusers#' Draw multi edges as parallel lines #' #' This geom draws multi edges as parallel lines. The edges are first sorted by #' direction and then shifted a fixed amount so that all edges are visible. #' #' @inheritSection geom_edge_link Edge variants #' @inheritSection geom_edge_link Edge aesthetic name expansion #' #' @section Aesthetics: #' `geom_edge_parallel` and `geom_edge_parallel0` understand the following #' aesthetics. Bold aesthetics are automatically set, but can be overridden. #' #' - **x** #' - **y** #' - **xend** #' - **yend** #' - **from** #' - **to** #' - edge_colour #' - edge_width #' - edge_linetype #' - edge_alpha #' - filter #' #' `geom_edge_parallel2` understand the following aesthetics. Bold aesthetics are #' automatically set, but can be overridden. #' #' - **x** #' - **y** #' - **group** #' - **from** #' - **to** #' - edge_colour #' - edge_width #' - edge_linetype #' - edge_alpha #' - filter #' #' `geom_edge_parallel` and `geom_edge_parallel2` furthermore takes the following #' aesthetics. #' #' - start_cap #' - end_cap #' - label #' - label_pos #' - label_size #' - angle #' - hjust #' - vjust #' - family #' - fontface #' - lineheight #' #' @section Computed variables: #' #' \describe{ #' \item{index}{The position along the path (not computed for the *0 version)} #' } #' #' @inheritParams geom_edge_link #' @inheritParams ggplot2::geom_path #' #' @param sep The separation between parallel edges, given as a [grid::unit()] #' #' @author David Schoch and Thomas Lin Pedersen #' #' @family geom_edge_* #' #' @examples #' require(tidygraph) #' gr <- create_notable('bull') %>% #' convert(to_directed) %>% #' bind_edges(data.frame(from = c(1, 2, 2, 3), to = c(2, 1, 3, 2))) %E>% #' mutate(class = sample(letters[1:3], 9, TRUE)) %N>% #' mutate(class = sample(c('x', 'y'), 5, TRUE)) #' #' ggraph(gr, 'stress') + #' geom_edge_parallel(aes(alpha = stat(index))) #' #' ggraph(gr, 'stress') + #' geom_edge_parallel2(aes(colour = node.class)) #' #' ggraph(gr, 'stress') + #' geom_edge_parallel0(aes(colour = class)) #' #' # Use capping and sep to fine tune the look #' ggraph(gr, 'stress') + #' geom_edge_parallel(start_cap = circle(1), end_cap = circle(1), #' arrow = arrow(length = unit(2, 'mm')), sep = unit(4, 'mm')) + #' geom_node_point(size = 12) #' #' @rdname geom_edge_parallel #' @name geom_edge_parallel #' NULL #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatLink #' @export StatEdgeParallel <- ggproto('StatEdgeParallel', StatLink, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } data <- remove_loop(data) if (nrow(data) == 0) return(NULL) data2 <- data data2$x <- data2$xend data2$y <- data2$yend data$.position <- edge_positions(data, data2, params) StatLink$setup_data(data, params) }, required_aes = c('x', 'y', 'xend', 'yend', 'from', 'to'), default_aes = aes(filter = TRUE), extra_params = c("na.rm", "n") ) #' @rdname geom_edge_parallel #' #' @importFrom ggforce StatLink #' @export geom_edge_parallel <- function(mapping = NULL, data = get_edges(), position = "identity", arrow = NULL, sep = unit(2, 'mm'), n = 100, lineend = "butt", linejoin = "round", linemitre = 1, label_colour = 'black', label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = 'rot', force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes(x = x, y = y, xend = xend, yend = yend, from = from, to = to, group = edge.id)) layer(data = data, mapping = mapping, stat = StatEdgeParallel, geom = GeomEdgeParallelPath, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list(arrow = arrow, lineend = lineend, linejoin = linejoin, linemitre = linemitre, na.rm = FALSE, sep = sep, n = n, interpolate = FALSE, label_colour = label_colour, label_alpha = label_alpha, label_parse = label_parse, check_overlap = check_overlap, angle_calc = angle_calc, force_flip = force_flip, label_dodge = label_dodge, label_push = label_push, ...) ) ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatBezier2 #' @export StatEdgeParallel2 <- ggproto('StatEdgeParallel2', StatLink2, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } data <- remove_loop2(data) if (nrow(data) == 0) return(NULL) data <- data[order(data$group), ] data2 <- data[c(FALSE, TRUE), ] data1 <- data[c(TRUE, FALSE), ] data$.position <- rep(edge_positions(data1, data2), each = 2) StatLink2$setup_data(data, params) }, required_aes = c('x', 'y', 'group', 'from', 'to'), default_aes = aes(filter = TRUE), extra_params = c('na.rm', 'n') ) #' @rdname geom_edge_parallel #' #' @export geom_edge_parallel2 <- function(mapping = NULL, data = get_edges('long'), position = 'identity', arrow = NULL, sep = unit(2, 'mm'), n = 100, lineend = 'butt', linejoin = 'round', linemitre = 1, label_colour = 'black', label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = 'rot', force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes( x = x, y = y, group = edge.id, from = from, to = to )) layer( data = data, mapping = mapping, stat = StatEdgeParallel2, geom = GeomEdgeParallelPath, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list(arrow = arrow, lineend = lineend, linejoin = linejoin, linemitre = linemitre, na.rm = FALSE, sep = sep, n = n, interpolate = TRUE, label_colour = label_colour, label_alpha = label_alpha, label_parse = label_parse, check_overlap = check_overlap, angle_calc = angle_calc, force_flip = force_flip, label_dodge = label_dodge, label_push = label_push, ...) ) ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatBezier0 #' @export StatEdgeParallel0 <- ggproto('StatEdgeFan0', StatIdentity, setup_data = function(data, params) { StatEdgeParallel$setup_data(data, params) }, required_aes = c('x', 'y', 'xend', 'yend', 'from', 'to'), default_aes = aes(filter = TRUE), extra_params = c('na.rm') ) #' @rdname geom_edge_parallel #' #' @export geom_edge_parallel0 <- function(mapping = NULL, data = get_edges(), position = 'identity', arrow = NULL, sep = unit(2, 'mm'), lineend = 'butt', show.legend = NA, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes( x = x, y = y, xend = xend, yend = yend, from = from, to = to )) layer( data = data, mapping = mapping, stat = StatEdgeParallel0, geom = GeomEdgeParallelSegment, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, na.rm = FALSE, sep = sep, ... ) ) ) } #' @importFrom dplyr %>% group_by arrange summarise n ungroup pull edge_positions <- function(from, to, params) { from$.id <- paste(pmin(from$from, to$to), pmax(from$from, to$to), sep = '-') from$.orig_ind <- seq_len(nrow(from)) from %>% group_by(.data$PANEL, .data$.id) %>% arrange(.data$from) %>% mutate(position = seq_len(n()) - 0.5 - n() / 2) %>% mutate(position = .data$position * ifelse(.data$from < .data$to, 1, -1)) %>% ungroup() %>% arrange(.data$.orig_ind) %>% pull(.data$position) } ggraph/R/edge_colourbar.R0000644000176200001440000000630313525467211015032 0ustar liggesusers#' Colourbar legend for edges #' #' This function is equivalent to [ggplot2::guide_colourbar()] but #' works for edge aesthetics. #' #' @inheritParams ggplot2::guide_colourbar #' #' @return A guide object #' #' @importFrom grid is.unit unit #' @importFrom digest digest #' @export guide_edge_colourbar <- function(title = waiver(), title.position = NULL, title.theme = NULL, title.hjust = NULL, title.vjust = NULL, label = TRUE, label.position = NULL, label.theme = NULL, label.hjust = NULL, label.vjust = NULL, barwidth = NULL, barheight = NULL, nbin = 20, raster = TRUE, ticks = TRUE, draw.ulim = TRUE, draw.llim = TRUE, direction = NULL, default.unit = 'line', reverse = FALSE, order = 0, ...) { if (!is.null(barwidth) && !is.unit(barwidth)) { barwidth <- unit(barwidth, default.unit) } if (!is.null(barheight) && !is.unit(barheight)) { barheight <- unit(barheight, default.unit) } guide <- list( title = title, title.position = title.position, title.theme = title.theme, title.hjust = title.hjust, title.vjust = title.vjust, label = label, label.position = label.position, label.theme = label.theme, label.hjust = label.hjust, label.vjust = label.vjust, barwidth = barwidth, barheight = barheight, nbin = nbin, raster = raster, ticks = ticks, draw.ulim = draw.ulim, draw.llim = draw.llim, direction = direction, default.unit = default.unit, reverse = reverse, order = order, available_aes = c('edge_colour', 'edge_fill'), ..., name = 'edge_colourbar' ) class(guide) <- c('guide', 'edge_colourbar', 'colorbar') guide } #' @rdname guide_edge_colourbar #' @export guide_edge_colorbar <- guide_edge_colourbar #' @importFrom scales discard #' @importFrom stats setNames #' @rdname guide-helpers #' @export guide_train.edge_colourbar <- function(guide, scale, aesthetic = NULL) { if (length(intersect(scale$aesthetics, c( 'edge_colour', 'edge_fill' ))) == 0) { warning('edge_colourbar guide needs edge_colour or edge_fill scales.') return(NULL) } if (scale$is_discrete()) { warning('edge_colourbar guide needs continuous scales.') return(NULL) } breaks <- scale$get_breaks() if (length(breaks) == 0 || all(is.na(breaks))) { return() } ticks <- as.data.frame(setNames( list(scale$map(breaks)), aesthetic %||% scale$aesthetics[1] ), stringsAsFactors = FALSE) ticks$.value <- breaks ticks$.label <- scale$get_labels(breaks) guide$key <- ticks .limits <- scale$get_limits() .bar <- discard(pretty(.limits, n = guide$nbin), scale$get_limits()) if (length(.bar) == 0) { .bar <- unique(.limits) } guide$bar <- new_data_frame(list( colour = scale$map(.bar), value = .bar, stringsAsFactors = FALSE )) if (guide$reverse) { guide$key <- guide$key[nrow(guide$key):1, ] guide$bar <- guide$bar[nrow(guide$bar):1, ] } guide$hash <- with(guide, digest::digest(list( title, key$.label, bar, name ))) guide } ggraph/R/geom_edge_arc.R0000644000176200001440000002466313527431516014630 0ustar liggesusers#' Draw edges as Arcs #' #' This geom is mainly intended for arc linear and circular diagrams (i.e. used #' together with [layout_tbl_graph_linear()]), though it can be used #' elsewhere. It draws edges as arcs with a height proportional to the distance #' between the nodes. Arcs are calculated as beziers. For linear layout the #' placement of control points are related to the `curvature` argument and #' the distance between the two nodes. For circular layout the control points #' are placed on the same angle as the start and end node at a distance related #' to the distance between the nodes. #' #' @inheritSection geom_edge_link Edge variants #' @inheritSection geom_edge_link Edge aesthetic name expansion #' #' @section Aesthetics: #' `geom_edge_arc` and `geom_edge_arc0` understand the following #' aesthetics. Bold aesthetics are automatically set, but can be overridden. #' #' - **x** #' - **y** #' - **xend** #' - **yend** #' - **circular** #' - edge_colour #' - edge_width #' - edge_linetype #' - edge_alpha #' - filter #' #' `geom_edge_arc2` understand the following aesthetics. Bold aesthetics are #' automatically set, but can be overridden. #' #' - **x** #' - **y** #' - **group** #' - **circular** #' - edge_colour #' - edge_width #' - edge_linetype #' - edge_alpha #' - filter #' #' `geom_edge_arc` and `geom_edge_arc2` furthermore takes the following #' aesthetics. #' #' - start_cap #' - end_cap #' - label #' - label_pos #' - label_size #' - angle #' - hjust #' - vjust #' - family #' - fontface #' - lineheight #' #' #' @section Computed variables: #' #' \describe{ #' \item{index}{The position along the path (not computed for the *0 version)} #' } #' #' @inheritParams geom_edge_link #' @inheritParams ggplot2::geom_path #' #' @param strength The bend of the curve. 1 approximates a halfcircle while 0 #' will give a straight line. Negative number will change the direction of the #' curve. Only used if `circular = FALSE`. #' #' @param fold Logical. Should arcs appear on the same side of the nodes despite #' different directions. Default to `FALSE`. #' #' @param curvature Deprecated. Use `strength` instead. #' #' @author Thomas Lin Pedersen #' #' @family geom_edge_* #' #' @examples #' require(tidygraph) #' # Make a graph with different directions of edges #' gr <- create_notable('Meredith') %>% #' convert(to_directed) %>% #' mutate(class = sample(letters[1:3], n(), replace = TRUE)) %>% #' activate(edges) %>% #' mutate( #' class = sample(letters[1:3], n(), replace = TRUE), #' switch = sample(c(TRUE, FALSE), n(), replace = TRUE) #' ) %>% #' reroute(from = to, to = from, subset = switch) #' #' ggraph(gr, 'linear') + #' geom_edge_arc(aes(alpha = stat(index))) #' #' ggraph(gr, 'linear') + #' geom_edge_arc2(aes(colour = node.class), strength = 0.6) #' #' ggraph(gr, 'linear', circular = TRUE) + #' geom_edge_arc0(aes(colour = class)) #' @rdname geom_edge_arc #' @name geom_edge_arc #' NULL #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatBezier #' @export StatEdgeArc <- ggproto('StatEdgeArc', StatBezier, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } data <- remove_loop(data) if (nrow(data) == 0) return(NULL) data$group <- make_unique(data$group) data2 <- data data2$x <- data2$xend data2$y <- data2$yend create_arc(data, data2, params) }, required_aes = c('x', 'y', 'xend', 'yend', 'circular'), default_aes = aes(filter = TRUE), extra_params = c('na.rm', 'n', 'strength', 'fold', 'curvature') ) #' @rdname geom_edge_arc #' #' @export geom_edge_arc <- function(mapping = NULL, data = get_edges(), position = 'identity', arrow = NULL, strength = 1, n = 100, fold = FALSE, lineend = 'butt', linejoin = 'round', linemitre = 1, label_colour = 'black', label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = 'rot', force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ..., curvature) { if (!missing(curvature)) { .Deprecated(msg = 'The curvature argument has been deprecated in favour of strength') strength <- curvature } mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes( x = x, y = y, xend = xend, yend = yend, circular = circular, group = edge.id )) layer( data = data, mapping = mapping, stat = StatEdgeArc, geom = GeomEdgePath, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, linejoin = linejoin, linemitre = linemitre, na.rm = FALSE, n = n, interpolate = FALSE, strength = strength, fold = fold, label_colour = label_colour, label_alpha = label_alpha, label_parse = label_parse, check_overlap = check_overlap, angle_calc = angle_calc, force_flip = force_flip, label_dodge = label_dodge, label_push = label_push, ... ) ) ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatBezier2 #' @export StatEdgeArc2 <- ggproto('StatEdgeArc2', StatBezier2, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } data <- remove_loop2(data) if (nrow(data) == 0) return(NULL) data <- data[order(data$group), ] data2 <- data[c(FALSE, TRUE), ] data <- data[c(TRUE, FALSE), ] create_arc(data, data2, params) }, required_aes = c('x', 'y', 'group', 'circular'), default_aes = aes(filter = TRUE), extra_params = c('na.rm', 'n', 'strength', 'fold', 'curvature') ) #' @rdname geom_edge_arc #' #' @export geom_edge_arc2 <- function(mapping = NULL, data = get_edges('long'), position = 'identity', arrow = NULL, strength = 1, n = 100, fold = FALSE, lineend = 'butt', linejoin = 'round', linemitre = 1, label_colour = 'black', label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = 'rot', force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ..., curvature) { if (!missing(curvature)) { .Deprecated(msg = 'The curvature argument has been deprecated in favour of strength') strength <- curvature } mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes( x = x, y = y, group = edge.id, circular = circular )) layer( data = data, mapping = mapping, stat = StatEdgeArc2, geom = GeomEdgePath, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, linejoin = linejoin, linemitre = linemitre, na.rm = FALSE, n = n, interpolate = TRUE, strength = strength, fold = fold, label_colour = label_colour, label_alpha = label_alpha, label_parse = label_parse, check_overlap = check_overlap, angle_calc = angle_calc, force_flip = force_flip, label_dodge = label_dodge, label_push = label_push, ... ) ) ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatBezier0 #' @export StatEdgeArc0 <- ggproto('StatEdgeArc0', StatBezier0, setup_data = function(data, params) { StatEdgeArc$setup_data(data, params) }, required_aes = c('x', 'y', 'xend', 'yend', 'circular'), default_aes = aes(filter = TRUE), extra_params = c('na.rm', 'strength', 'fold', 'curvature') ) #' @rdname geom_edge_arc #' #' @export geom_edge_arc0 <- function(mapping = NULL, data = get_edges(), position = 'identity', arrow = NULL, strength = 1, lineend = 'butt', show.legend = NA, fold = fold, ..., curvature) { if (!missing(curvature)) { .Deprecated(msg = 'The curvature argument has been deprecated in favour of strength') strength <- curvature } mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes( x = x, y = y, xend = xend, yend = yend, circular = circular )) layer( data = data, mapping = mapping, stat = StatEdgeArc0, geom = GeomEdgeBezier, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, na.rm = FALSE, strength = strength, fold = FALSE, ... ) ) ) } create_arc <- function(from, to, params) { bezier_start <- seq(1, by = 4, length.out = nrow(from)) from$index <- bezier_start to$index <- bezier_start + 3 data2 <- from data3 <- to data2$index <- bezier_start + 1 data3$index <- bezier_start + 2 node_dist <- sqrt((to$x - from$x)^2 + (to$y - from$y)^2) / 2 circ <- from$circular if (any(circ)) { r0 <- sqrt(to$x[circ]^2 + to$y[circ]^2) r1 <- sqrt(to$x[circ]^2 + to$y[circ]^2) data2$x[circ] <- from$x[circ] * (1 - (node_dist[circ] / r0)) data2$y[circ] <- from$y[circ] * (1 - (node_dist[circ] / r0)) data3$x[circ] <- to$x[circ] * (1 - (node_dist[circ] / r1)) data3$y[circ] <- to$y[circ] * (1 - (node_dist[circ] / r1)) } if (any(!circ)) { strength <- pi / 2 * -params$strength edge_angle <- atan2( to$y[!circ] - from$y[!circ], to$x[!circ] - from$x[!circ] ) start_angle <- edge_angle - strength end_angle <- edge_angle - pi + strength data2$x[!circ] <- data2$x[!circ] + cos(start_angle) * node_dist[!circ] data2$y[!circ] <- data2$y[!circ] + sin(start_angle) * node_dist[!circ] data3$x[!circ] <- data3$x[!circ] + cos(end_angle) * node_dist[!circ] data3$y[!circ] <- data3$y[!circ] + sin(end_angle) * node_dist[!circ] if (params$fold) { # data2$x[!circ] <- abs(data2$x[!circ]) * sign(params$strength) data2$y[!circ] <- abs(data2$y[!circ]) * sign(params$strength) # data3$x[!circ] <- abs(data3$x[!circ]) * sign(params$strength) data3$y[!circ] <- abs(data3$y[!circ]) * sign(params$strength) } } data <- rbind_dfs(list(from, data2, data3, to)) data[order(data$index), names(data) != 'index'] } ggraph/R/layout_matrix.R0000644000176200001440000000336713525463372014772 0ustar liggesusers#' Place nodes on a diagonal #' #' This layout puts all nodes on a diagonal, thus preparing the layout for use #' with [geom_edge_point()] resulting in a matrix layout. While matrix #' layouts excel in scalability, the interpretation of the visual is very #' dependent on the sorting of the nodes. Different sorting algorithms have been #' implemented in `tidygraph` and these can be used directly. Behrisch #' *et al.* (2016) have provided a nice overview of some of the different #' sorting algorithms and what insight they might bring, along with a rundown of #' different patterns to look out for. #' #' @param graph An `tbl_graph` object #' #' @param circular Ignored #' #' @param sort.by An expression providing the sorting of the nodes. If `NULL` #' the nodes will be ordered by their index in the graph. #' #' @return A data.frame with the columns `x`, `y`, `circular` as #' well as any information stored as node variables in the tbl_graph object. #' #' @family layout_tbl_graph_* #' #' @importFrom igraph gorder #' @importFrom rlang enquo eval_tidy #' #' @references #' Behrisch, M., Bach, B., Riche, N. H., Schreck, T., Fekete, J.-D. (2016). #' *Matrix Reordering Methods for Table and Network Visualization*. #' Computer Graphics Forum, 35: 693–716. #' layout_tbl_graph_matrix <- function(graph, circular = FALSE, sort.by = NULL) { sort.by <- enquo(sort.by) sort.by <- eval_tidy(sort.by, .N()) if (!is.null(sort.by)) { pos <- order(order(sort.by)) } else { pos <- seq_len(gorder(graph)) } nodes <- new_data_frame(list(x = pos, y = pos)) extra_data <- as_tibble(graph, active = 'nodes') nodes <- cbind(nodes, extra_data[, !names(extra_data) %in% names(nodes), drop = FALSE]) nodes$circular <- FALSE nodes } ggraph/R/parallelPath.R0000644000176200001440000000316013617015024014456 0ustar liggesusers#' @export #' @keywords internal makeContent.parallelPath <- function(x) { will_cap <- inherits(x, 'cappedpathgrob') if (!is.null(x$id)) { sep <- x$sep[!duplicated(x$id)] } x <- NextMethod() if (will_cap) { x$children[[1]]$sep <- sep[x$children[[1]]$id] if (inherits(x$children[[1]], 'segments')) { x$children[[1]] <- shift_segments(x$children[[1]]) } else { x$children[[1]] <- shift_paths(x$children[[1]]) } x } else if (inherits(x, 'segments')) { shift_segments(x) } else { shift_paths(x) } } shift_segments <- function(x) { x0_new <- convertX(x$x0, 'mm', TRUE) x1_new <- convertX(x$x1, 'mm', TRUE) y0_new <- convertY(x$y0, 'mm', TRUE) y1_new <- convertY(x$y1, 'mm', TRUE) sep <- convertWidth(x$sep, 'mm', TRUE) xdiff <- x1_new - x0_new ydiff <- y1_new - y0_new lengths <- sqrt(xdiff * xdiff + ydiff * ydiff) xshift <- -ydiff / lengths * sep yshift <- xdiff / lengths * sep x$x0 <- unit(x0_new + xshift, 'mm') x$x1 <- unit(x1_new + xshift, 'mm') x$y0 <- unit(y0_new + yshift, 'mm') x$y1 <- unit(y1_new + yshift, 'mm') x } shift_paths <- function(x) { first <- which(!duplicated(x$id)) last <- which(!duplicated(x$id, fromLast = TRUE)) sep <- convertWidth(x$sep[first], 'mm', TRUE) x_new <- convertX(x$x, 'mm', TRUE) y_new <- convertY(x$y, 'mm', TRUE) xdiff <- x_new[last] - x_new[first] ydiff <- y_new[last] - y_new[first] lengths <- sqrt(xdiff * xdiff + ydiff * ydiff) xshift <- -ydiff / lengths * sep yshift <- xdiff / lengths * sep x$x <- unit(x_new + xshift[x$id], 'mm') x$y <- unit(y_new + yshift[x$id], 'mm') x } ggraph/R/scale_label_size.R0000644000176200001440000000271013524253142015327 0ustar liggesusers#' Edge label size scales #' #' This set of scales defines new size scales for edge labels in order to allow #' for separate sizing of edges and their labels. #' #' @return A ggproto object inheriting from `Scale` #' #' @family scale_edge_* #' #' @name scale_label_size #' @rdname scale_label_size #' NULL #' @rdname scale_label_size #' #' @inheritParams ggplot2::scale_size_continuous #' #' @importFrom scales area_pal #' @export scale_label_size_continuous <- function(..., range = c(1, 6)) { continuous_scale('label_size', 'area', area_pal(range), ...) } #' @rdname scale_label_size #' #' @export scale_label_size <- scale_label_size_continuous #' @rdname scale_label_size #' #' @inheritParams ggplot2::scale_size_discrete #' #' @importFrom scales rescale_pal #' @export scale_label_size_discrete <- function(..., range = c(2, 6)) { discrete_scale('label_size', 'size_d', function(n) { area <- seq(range[1]^2, range[2]^2, length.out = n) sqrt(area) }, ...) } #' @rdname scale_label_size #' #' @inheritParams ggplot2::scale_size_manual #' #' @export scale_label_size_manual <- function(..., values) { manual_scale('label_size', values, ...) } #' @rdname scale_label_size #' #' @inheritParams ggplot2::scale_size_identity #' #' @importFrom scales identity_pal #' @export scale_label_size_identity <- function(..., guide = 'none') { sc <- discrete_scale('label_size', 'identity', identity_pal(), ..., guide = guide, super = ScaleDiscreteIdentity ) sc } ggraph/R/layout_manual.R0000644000176200001440000000175313525462036014734 0ustar liggesusers#' Manually specify a layout for layout_tbl_graph #' #' This layout function lets you pass the node positions in manually. The #' supplied positions must match the order of the nodes in the tbl_graph #' #' @param graph An `tbl_graph` object #' #' @param x,y Expressions with the x and y positions of the nodes #' #' @param circular Ignored #' #' @return A data.frame with the columns `x`, `y`, `circular` as #' well as any information stored as node variables in the tbl_graph. #' #' @family layout_tbl_graph_* #' #' @importFrom rlang enquo eval_tidy #' layout_tbl_graph_manual <- function(graph, x, y, circular) { if (circular) { warning('circular argument ignored for manual layout') } x <- enquo(x) y <- enquo(y) layout <- new_data_frame(list( x = eval_tidy(x, .N()), y = eval_tidy(y, .N()) )) extra_data <- as_tibble(graph, active = 'nodes') layout <- cbind(layout, extra_data[, !names(extra_data) %in% names(layout), drop = FALSE]) layout$circular <- FALSE layout } ggraph/R/utils.R0000644000176200001440000002323113525471116013214 0ustar liggesusers#' Get the angle of nodes and edges #' #' These helper functions makes it easy to calculate the angle associated with #' nodes and edges. For nodes the angle is defined as the angle of the vector #' pointing towards the node position, and is thus mainly suited for circular #' layouts where it can be used to calculate the angle of labels. For edges it #' is simply the angle of the vector describing the edge. #' #' @param x,y A vector of positions #' #' @param xend,yend The end position of the edge #' #' @param degrees Logical. Should the angle be returned in degree (`TRUE`) #' or radians (`FALSE`). Defaults to `TRUE`. #' #' @return A vector with the angle of each node/edge #' #' @examples #' require(tidygraph) #' flareGraph <- tbl_graph(flare$vertices, flare$edges) #' #' ggraph(flareGraph, 'dendrogram', circular = TRUE) + #' geom_edge_diagonal0() + #' geom_node_text(aes(filter = leaf, angle = node_angle(x, y), label = shortName), #' hjust = 'outward', size = 2 #' ) + #' expand_limits(x = c(-1.3, 1.3), y = c(-1.3, 1.3)) #' @export #' node_angle <- function(x, y, degrees = TRUE) { angles <- atan2(y, x) angles[angles < 0] <- angles[angles < 0] + 2 * pi if (degrees) { angles * 360 / (2 * pi) } else { angles } } #' @rdname node_angle #' #' @export edge_angle <- function(x, y, xend, yend, degrees = TRUE) { x <- xend - x y <- yend - y node_angle(x, y, degrees) } ### COPY FROM GGPLOT2 NON-EXPORTS #' @importFrom scales rescale_mid mid_rescaler <- function(mid) { function(x, to = c(0, 1), from = range(x, na.rm = TRUE)) { rescale_mid(x, to, from, mid) } } manual_scale <- function(aesthetic, values, ...) { pal <- function(n) { if (n > length(values)) { stop('Insufficient values in manual scale. ', n, ' needed but only ', length(values), ' provided.', call. = FALSE ) } values } discrete_scale(aesthetic, 'manual', pal, ...) } #' @importFrom scales zero_range resolution <- function(x, zero = TRUE) { if (is.integer(x) || zero_range(range(x, na.rm = TRUE))) { return(1) } x <- unique(as.numeric(x)) if (zero) { x <- unique(c(0, x)) } min(diff(sort(x))) } '%||%' <- function(a, b) { if (!is.null(a)) { a } else { b } } #' @importFrom grid grobName ggname <- function(prefix, grob) { grob$name <- grobName(grob, prefix) grob } element_render <- function(theme, element, ..., name = NULL) { el <- calc_element(element, theme) if (is.null(el)) { message('Theme element ', element, ' missing') return(zeroGrob()) } ggname(paste(element, name, sep = '.'), element_grob(el, ...)) } .all_aesthetics <- c( 'adj', 'alpha', 'angle', 'bg', 'cex', 'col', 'color', 'colour', 'fg', 'fill', 'group', 'hjust', 'label', 'linetype', 'lower', 'lty', 'lwd', 'max', 'middle', 'min', 'pch', 'radius', 'sample', 'shape', 'size', 'srt', 'upper', 'vjust', 'weight', 'width', 'x', 'xend', 'xmax', 'xmin', 'xintercept', 'y', 'yend', 'ymax', 'ymin', 'yintercept', 'z' ) .base_to_ggplot <- structure( c( 'colour', 'colour', 'shape', 'size', 'linetype', 'size', 'angle', 'hjust', 'fill', 'colour', 'ymin', 'ymax' ), .Names = c( 'col', 'color', 'pch', 'cex', 'lty', 'lwd', 'srt', 'adj', 'bg', 'fg', 'min', 'max' ) ) rename_aes <- function(x) { # Convert prefixes to full names full <- match(names(x), .all_aesthetics) names(x)[!is.na(full)] <- .all_aesthetics[full[!is.na(full)]] old_names <- match(names(x), names(.base_to_ggplot)) names(x)[!is.na(old_names)] <- .base_to_ggplot[old_names[!is.na(old_names)]] x } pch_lookup <- c( "square open" = 0, "circle open" = 1, "triangle open" = 2, "plus" = 3, "cross" = 4, "diamond open" = 5, "triangle down open" = 6, "square cross" = 7, "asterisk" = 8, "diamond plus" = 9, "circle plus" = 10, "star" = 11, "square plus" = 12, "circle cross" = 13, "square triangle" = 14, "triangle square" = 14, "square" = 15, "circle small" = 16, "triangle" = 17, "diamond" = 18, "circle" = 19, "bullet" = 20, "circle filled" = 21, "square filled" = 22, "diamond filled" = 23, "triangle filled" = 24, "triangle down filled" = 25 ) translate_pch <- function(pch) { if (is.numeric(pch)) return(pch) if (!is.character(pch)) stop('Unknown shape format', call. = FALSE) if (nchar(pch[1]) <= 1) return(pch) pch <- charmatch(pch, names(pch_lookup)) if (any(is.na(pch))) stop('Unknown shape name', call. = FALSE) if (any(pch == 0)) stop('Ambiguous shape name', call. = FALSE) pch_lookup[pch] } #' @importFrom viridis scale_color_viridis #' @export viridis::scale_color_viridis #' @importFrom viridis scale_fill_viridis #' @export viridis::scale_fill_viridis new_data_frame <- function(x = list(), n = NULL) { if (length(x) != 0 && is.null(names(x))) stop('Elements must be named', call. = FALSE) lengths <- vapply(x, length, integer(1)) if (is.null(n)) { n <- if (length(x) == 0) 0 else max(lengths) } for (i in seq_along(x)) { if (lengths[i] == n) next if (lengths[i] != 1) stop('Elements must equal the number of rows or 1', call. = FALSE) x[[i]] <- rep(x[[i]], n) } class(x) <- 'data.frame' attr(x, 'row.names') <- .set_row_names(n) x } df_rows <- function(x, i) { new_data_frame(lapply(x, `[`, i = i)) } split_matrix <- function(x, col_names = colnames(x)) { force(col_names) x <- lapply(seq_len(ncol(x)), function(i) x[, i]) if (!is.null(col_names)) names(x) <- col_names x } # More performant modifyList without recursion modify_list <- function(old, new) { for (i in names(new)) old[[i]] <- new[[i]] old } empty <- function(df) { is.null(df) || nrow(df) == 0 || ncol(df) == 0 } split_indices <- function(group) { split(seq_along(group), group) } # Adapted from plyr:::id_vars # Create a unique id for elements in a single vector id_var <- function(x, drop = FALSE) { if (length(x) == 0) { id <- integer() n <- 0L } else if (!is.null(attr(x, 'n')) && !drop) { return(x) } else if (is.factor(x) && !drop) { x <- addNA(x, ifany = TRUE) id <- as.integer(x) n <- length(levels(x)) } else { levels <- sort(unique(x), na.last = TRUE) id <- match(x, levels) n <- max(id) } attr(id, 'n') <- n id } #' Create an unique integer id for each unique row in a data.frame #' #' Properties: #' - `order(id)` is equivalent to `do.call(order, df)` #' - rows containing the same data have the same value #' - if `drop = FALSE` then room for all possibilites #' #' @param .variables list of variables #' @param drop Should unused factor levels be dropped? #' #' @return An integer vector with attribute `n` giving the total number of #' possible unique rows #' #' @keywords internal #' @noRd #' id <- function(.variables, drop = FALSE) { nrows <- NULL if (is.data.frame(.variables)) { nrows <- nrow(.variables) .variables <- unclass(.variables) } lengths <- vapply(.variables, length, integer(1)) .variables <- .variables[lengths != 0] if (length(.variables) == 0) { n <- nrows %||% 0L id <- seq_len(n) attr(id, 'n') <- n return(id) } if (length(.variables) == 1) { return(id_var(.variables[[1]], drop = drop)) } ids <- rev(lapply(.variables, id_var, drop = drop)) p <- length(ids) ndistinct <- vapply(ids, attr, 'n', FUN.VALUE = numeric(1), USE.NAMES = FALSE) n <- prod(ndistinct) if (n > 2^31) { char_id <- do.call('paste', c(ids, sep = '\r')) res <- match(char_id, unique(char_id)) } else { combs <- c(1, cumprod(ndistinct[-p])) mat <- do.call('cbind', ids) res <- c((mat - 1L) %*% combs + 1L) } if (drop) { id_var(res, drop = TRUE) } else { res <- as.integer(res) attr(res, 'n') <- n res } } #' Bind data frames together by common column names #' #' This function is akin to `plyr::rbind.fill`, `dplyr::bind_rows`, and #' `data.table::rbindlist`. It takes data frames in a list and stacks them on #' top of each other, filling out values with `NA` if the column is missing from #' a data.frame #' #' @param dfs A list of data frames #' #' @return A data.frame with the union of all columns from the data frames given #' in `dfs` #' #' @keywords internal #' @noRd #' rbind_dfs <- function(dfs) { out <- list() columns <- unique(unlist(lapply(dfs, names))) nrows <- vapply(dfs, .row_names_info, integer(1), type = 2L) total <- sum(nrows) if (length(columns) == 0) return(new_data_frame(list(), total)) allocated <- rep(FALSE, length(columns)) names(allocated) <- columns col_levels <- list() for (df in dfs) { new_columns <- intersect(names(df), columns[!allocated]) for (col in new_columns) { if (is.factor(df[[col]])) { all_factors <- all(vapply(dfs, function(df) { val <- .subset2(df, col) is.null(val) || is.factor(val) }, logical(1))) if (all_factors) { col_levels[[col]] <- unique( unlist(lapply(dfs, function(df) levels(.subset2(df, col)))) ) } out[[col]] <- rep(NA_character_, total) } else { out[[col]] <- rep(.subset2(df, col)[1][NA], total) } } allocated[new_columns] <- TRUE if (all(allocated)) break } pos <- c(cumsum(nrows) - nrows + 1) for (i in seq_along(dfs)) { df <- dfs[[i]] rng <- seq(pos[i], length.out = nrows[i]) for (col in names(df)) { if (inherits(df[[col]], 'factor')) { out[[col]][rng] <- as.character(df[[col]]) } else { out[[col]][rng] <- df[[col]] } } } for (col in names(col_levels)) { out[[col]] <- factor(out[[col]], levels = col_levels[[col]]) } attributes(out) <- list( class = 'data.frame', names = names(out), row.names = .set_row_names(total) ) out } ggraph/R/zzz.R0000644000176200001440000000143713526711451012715 0ustar liggesusers.onLoad <- function(...) { register_s3_method("gganimate", "layer_type", "GeomEdgePath") invisible() } register_s3_method <- function(pkg, generic, class, fun = NULL) { stopifnot(is.character(pkg), length(pkg) == 1) stopifnot(is.character(generic), length(generic) == 1) stopifnot(is.character(class), length(class) == 1) if (is.null(fun)) { fun <- get(paste0(generic, ".", class), envir = parent.frame()) } else { stopifnot(is.function(fun)) } if (pkg %in% loadedNamespaces()) { registerS3method(generic, class, fun, envir = asNamespace(pkg)) } # Always register hook in case package is later unloaded & reloaded setHook( packageEvent(pkg, "onLoad"), function(...) { registerS3method(generic, class, fun, envir = asNamespace(pkg)) } ) } ggraph/R/layout_partition.R0000644000176200001440000001053413530724042015457 0ustar liggesusers#' Calculate nodes as areas dividing their parent #' #' The partition layout is a way to show hierarchical data in the same way as #' [layout_tbl_graph_treemap()]. Instead of subdividing the parent area #' the partition layout shows the division of a nodes children next to the area #' of the node itself. As such the node positions will be very reminiscent of #' a reingold-tilford tree layout but by plotting nodes as areas it better #' communicate the total weight of a node by summing up all its children. #' Often partition layouts are called icicle plots or sunburst diagrams (in case #' a radial transform is applied). #' #' @note #' partition is a layout intended for trees, that is, graphs where nodes #' only have one parent and zero or more children. If the provided graph does #' not fit this format an attempt to convert it to such a format will be made. #' #' @param graph An `tbl_graph` object #' #' @param weight An optional node variable to use as weight. Will only affect #' the weight of leaf nodes as the weight of non-leaf nodes are derived from #' their children. #' #' @param circular Logical. Should the layout be transformed to a circular #' representation. If `TRUE` the resulting layout will be a sunburst #' diagram. #' #' @param height An optional node variable to use as height. If `NULL` #' all nodes will be given a height of 1. #' #' @param sort.by The name of a node variable to sort the nodes by. #' #' @param direction The direction of the tree in the graph. `'out'` (default) #' means that parents point towards their children, while `'in'` means that #' children point towards their parent. #' #' @param const.area Logical. Should 'height' be scaled for area proportionality #' when using `circular = TRUE`. Defaults to `TRUE`. #' #' @param offset If `circular = TRUE`, where should it begin. Defaults to #' `pi/2` which is equivalent to 12 o'clock. #' #' @return If `circular = FALSE` A data.frame with the columns `x`, #' `y`, `width`, `height`, `leaf`, #' `depth`, `circular` as well as any information stored as node #' variables in the tbl_graph object. #' If `circular = TRUE` A data.frame with the columns `x`, `y`, #' `r0`, `r`, `start`, `end`, `leaf`, #' `depth`, `circular` as well as any information stored as node #' variables in the tbl_graph object. #' #' @references #' Kruskal, J. B., Landwehr, J. M. (1983). *Icicle Plots: Better Displays #' for Hierarchical Clustering*. American Statistician Vol 37(2), 162-168. #' https://doi.org/10.2307/2685881 #' #' @family layout_tbl_graph_* #' #' @importFrom ggforce radial_trans #' layout_tbl_graph_partition <- function(graph, weight = NULL, circular = FALSE, height = NULL, sort.by = NULL, direction = 'out', offset = pi / 2, const.area = TRUE) { weight <- enquo(weight) weight <- eval_tidy(weight, .N()) height <- enquo(height) height <- eval_tidy(height, .N()) sort.by <- enquo(sort.by) sort.by <- eval_tidy(sort.by, .N()) hierarchy <- tree_to_hierarchy(graph, direction, sort.by, weight, height) layout <- partitionTree(hierarchy$parent, hierarchy$order, hierarchy$weight, hierarchy$height)[-1, ] if (circular) { if (const.area) { y0 <- sqrt(layout[, 2]) y1 <- sqrt(layout[, 2] + layout[, 4]) layout[, 2] <- y0 layout[, 4] <- y1 - y0 } width_range <- c(0, max(rowSums(layout[, c(1, 3)]))) radial <- radial_trans( r.range = c(0, 1), a.range = width_range, offset = offset, pad = 0 ) coords <- radial$transform( layout[, 2] + layout[, 4] / 2, layout[, 1] + layout[, 3] / 2 ) layout <- new_data_frame(list( x = coords$x, y = coords$y, r0 = layout[, 2], r = layout[, 2] + layout[, 4], start = 2 * pi * layout[, 1] / width_range[2], end = 2 * pi * (layout[, 1] + layout[, 3]) / width_range[2], circular = TRUE )) layout$x[1] <- 0 layout$y[1] <- 0 } else { layout <- new_data_frame(list( x = layout[, 1] + layout[, 3] / 2, y = layout[, 2] + layout[, 4] / 2, width = layout[, 3], height = layout[, 4], circular = FALSE )) } layout$leaf <- degree(graph, mode = direction) == 0 layout$depth <- node_depth(graph, mode = direction) extra_data <- as_tibble(graph, active = 'nodes') layout <- cbind(layout, extra_data[, !names(extra_data) %in% names(layout), drop = FALSE]) attr(layout, 'graph') <- add_direction(graph, layout) layout } ggraph/R/layout_igraph.R0000644000176200001440000001346113524253120014717 0ustar liggesusers#' Use igraph layout algorithms for layout_tbl_graph #' #' This layout function makes it easy to apply one of the layout algorithms #' supplied in igraph when plotting with ggraph. Layout names are auto completed #' so there is no need to write `layout_with_graphopt` or #' `layout_as_tree`, just `graphopt` and `tree` (though the #' former will also work if you want to be super explicit). Circular layout is #' only supported for tree-like layout (`tree` and `sugiyama`) and #' will throw an error when applied to other layouts. #' #' @details #' igraph provides a huge amount of possible layouts. They are all briefly #' described below: #' #' \strong{Hierarchical layouts} #' #' \describe{ #' \item{`tree`}{Uses the *Reingold-Tilford* algorithm to place the #' nodes below their parent with the parent centered above its children. See #' [igraph::as_tree()]} #' \item{`sugiyama`}{Designed for directed acyclic graphs (that is, #' hierarchies where multiple parents are allowed) it minimizes the number of #' crossing edges. See [igraph::with_sugiyama()]} #' } #' #' \strong{Standard layouts} #' #' \describe{ #' \item{`bipartite`}{Minimize edge-crossings in a simple two-row (or #' column) layout for bipartite graphs. See [igraph::as_bipartite()]} #' \item{`star`}{Place one node in the center and the rest equidistantly #' around it. See [igraph::as_star()]} #' \item{`circle`}{Place nodes in a circle in the order of their index. #' Consider using [layout_tbl_graph_linear()] with `circular=TRUE` #' for more control. See [igraph::in_circle()]} #' \item{`nicely`}{Tries to pick an appropriate layout. See #' [igraph::nicely()] for a description of the simple decision tree #' it uses} #' \item{`dh`}{Uses *Davidson and Harels* simulated annealing #' algorithm to place nodes. See [igraph::with_dh()]} #' \item{`gem`}{Place nodes on the plane using the GEM force-directed #' layout algorithm. See [igraph::with_gem()]} #' \item{`graphopt`}{Uses the Graphopt algorithm based on alternating #' attraction and repulsion to place nodes. See #' [igraph::with_graphopt()]} #' \item{`grid`}{Place nodes on a rectangular grid. See #' [igraph::on_grid()]} #' \item{`mds`}{Perform a multidimensional scaling of nodes using either #' the shortest path or a user supplied distance. See #' [igraph::with_mds()]} #' \item{`sphere`}{Place nodes uniformly on a sphere - less relevant for #' 2D visualizations of networks. See [igraph::on_sphere()]} #' \item{`randomly`}{Places nodes uniformly random. See #' [igraph::randomly()]} #' \item{`fr`}{Places nodes according to the force-directed algorithm of #' Fruchterman and Reingold. See [igraph::with_fr()]} #' \item{`kk`}{Uses the spring-based algorithm by Kamada and Kawai to #' place nodes. See [igraph::with_kk()]} #' \item{`drl`}{Uses the force directed algorithm from the DrL toolbox to #' place nodes. See [igraph::with_drl()]} #' \item{`lgl`}{Uses the algorithm from Large Graph Layout to place #' nodes. See [igraph::with_lgl()]} #' } #' #' @note This function is not intended to be used directly but by setting #' `layout = 'igraph'` in [create_layout()] #' #' @param graph A `tbl_graph` object. #' #' @param algorithm The type of layout algorithm to apply. See *Details* or #' [igraph::layout_()] for links to the layouts supplied by igraph. #' #' @param circular Logical. Should the layout be transformed to a circular #' representation. Defaults to `FALSE`. Only applicable to #' `algorithm = 'tree'` and `algorithm = 'sugiyama'`. #' #' @param offset If `circular = TRUE`, where should it begin. Defaults to #' `pi/2` which is equivalent to 12 o'clock. #' #' @param use.dummy Logical. In the case of `algorithm = 'sugiyama'` should the #' dummy-infused graph be used rather than the original. Defaults to #' `FALSE`. #' #' @param ... Arguments passed on to the respective layout functions #' #' @return A data.frame with the columns `x`, `y`, `circular` as #' well as any information stored as node variables in the tbl_graph object. #' #' @family layout_tbl_graph_* #' #' @importFrom igraph graph_attr components layout_as_bipartite layout_as_star layout_as_tree layout_in_circle layout_nicely layout_with_dh layout_with_drl layout_with_gem layout_with_graphopt layout_on_grid layout_with_mds layout_with_sugiyama layout_on_sphere layout_randomly layout_with_fr layout_with_kk layout_with_lgl #' @importFrom rlang quos eval_tidy #' @importFrom stats setNames #' layout_tbl_graph_igraph <- function(graph, algorithm, circular, offset = pi / 2, use.dummy = FALSE, ...) { algorithm <- as.igraphlayout(algorithm) dots <- quos(...) dots <- setNames(lapply(names(dots), function(nq) { if (nq == 'weights') { eval_tidy(dots[[nq]], as_tibble(graph, active = 'edges')) } else { eval_tidy(dots[[nq]]) } }), names(dots)) layout <- do.call(algorithm, c(list(graph), dots)) if (algorithm == 'layout_with_sugiyama') { if (use.dummy) { graph <- as_tbl_graph(layout$extd_graph) layout <- graph_attr(layout$extd_graph, 'layout') } else { layout <- layout$layout } } if (algorithm == 'layout_as_tree') { layout[, 1] <- layout[, 1] + components(graph)$membership - 1 } extra_data <- as_tibble(graph, active = 'nodes') layout <- cbind(x = layout[, 1], y = layout[, 2], extra_data[, !names(extra_data) %in% c('x', 'y'), drop = FALSE]) graph <- add_direction(graph, layout) if (circular) { if (!algorithm %in% c('layout_as_tree', 'layout_with_sugiyama')) { stop('Circular layout only applicable to tree and DAG layout') } radial <- radial_trans( r.range = rev(range(layout$y)), a.range = range(layout$x), offset = offset ) coords <- radial$transform(layout$y, layout$x) layout$x <- coords$x layout$y <- coords$y } layout$circular <- circular attr(layout, 'graph') <- graph layout } ggraph/R/geometry.R0000644000176200001440000001450213525467607013722 0ustar liggesusers#' Define simple shapes for line capping #' #' This set of functions makes it easy to define shapes at the terminal points #' of edges that are used to shorten the edges. The shapes themselves are not #' drawn, but the edges will end at the boundary of the shape rather than at #' the node position. This is especially relevant when drawing arrows at the #' edges as the arrows will be partly obscured by the node unless the edge is #' shortened. Edge shortening is dynamic and will update as the plot is resized, #' making sure that the capping remains at an absolute distance to the end #' point. #' #' @details #' `geometry` is the base constructor, while the rest are helpers to save #' typing. `circle` creates circles width a given radius, `square` #' creates squares at a given side length, `ellipsis` creates ellipses with #' given a and b values (width and height radii), and `rectangle` makes #' rectangles of a given width and height. label_rect is a helper that, given #' a list of strings and potentially formatting options creates a rectangle that #' encloses the string. #' #' @param type The type of geometry to use. Currently `'circle'` and #' `'rect'` is supported. #' #' @param width,height,length,radius,a,b The dimensions of the shape. #' #' @param unit,width_unit,height_unit,a_unit,b_unit The unit for the numbers #' given. #' #' @return A geometry object encoding the specified shape. #' #' @examples #' geometry(c('circle', 'rect', 'rect'), 1:3, 3:1) #' #' circle(1:4, 'mm') #' #' label_rect(c('some', 'different', 'words'), fontsize = 18) #' @export #' geometry <- function(type = 'circle', width = 1, height = width, width_unit = 'cm', height_unit = width_unit) { l <- max(length(type), length(width), length(height)) g <- rep(type, length.out = l) g_na <- is.na(g) width <- rep(width, length.out = l) width[g_na] <- 0 height <- rep(height, length.out = l) height[g_na] <- 0 uwidth <- rep(width_unit, length.out = l) uheight <- rep(height_unit, length.out = l) g[g_na] <- 'circle' attributes(g) <- list( width = width, uwidth = uwidth, height = height, uheight = uheight, class = 'geometry' ) g } #' @rdname geometry #' #' @export circle <- function(radius = 1, unit = 'cm') { geometry('circle', width = radius * 2, width_unit = unit) } #' @rdname geometry #' #' @export square <- function(length = 1, unit = 'cm') { geometry('rect', width = length, width_unit = unit) } #' @rdname geometry #' #' @export ellipsis <- function(a = 1, b = 1, a_unit = 'cm', b_unit = a_unit) { geometry('circle', width = a * 2, height = b * 2, width_unit = a_unit, height_unit = b_unit ) } #' @rdname geometry #' #' @export rectangle <- function(width = 1, height = 1, width_unit = 'cm', height_unit = width_unit) { geometry('rect', width = width, height = height, width_unit = width_unit, height_unit = height_unit ) } #' @rdname geometry #' #' @param label The text to be enclosed #' #' @param padding extra size to be added around the text using the #' [ggplot2::margin()] function #' #' @param ... Passed on to [grid::gpar()] #' #' @export #' @importFrom grid convertWidth convertHeight textGrob grobWidth grobHeight label_rect <- function(label, padding = margin(1, 1, 1.5, 1, 'mm'), ...) { grobs <- lapply(label, textGrob, gp = gpar(...)) width <- abs_width(grobs) height <- abs_height(grobs) width <- width + sum(convertWidth(padding[c(2, 4)], 'cm', TRUE)) height <- height + sum(convertHeight(padding[c(1, 3)], 'cm', TRUE)) geometry('rect', width = width, height = height) } #' @rdname geometry #' #' @param x An object to test for geometry inheritance #' #' @export is.geometry <- function(x) inherits(x, 'geometry') #' @export length.geometry <- function(x) length(unclass(x)) #' @export `[.geometry` <- function(x, i, ...) { g <- unclass(x)[i] attributes(g) <- list( width = attr(x, 'width')[i], height = attr(x, 'height')[i], uwidth = attr(x, 'uwidth')[i], uheight = attr(x, 'uheight')[i], class = 'geometry' ) g } #' @export `[<-.geometry` <- function(x, ..., value) { if (!is.geometry(value)) stop('Only possible to insert geometries', call. = FALSE) type <- unclass(x) type[...] <- unclass(value) width <- attr(x, 'width') width[...] <- attr(value, 'width') height <- attr(x, 'height') height[...] <- attr(value, 'height') uwidth <- attr(x, 'uwidth') uwidth[...] <- attr(value, 'uwidth') uheight <- attr(x, 'uheight') uheight[...] <- attr(value, 'uheight') attributes(type) <- list( width = width, height = height, uwidth = uwidth, uheight = uheight, class = 'geometry' ) type } #' @export format.geometry <- function(x, ...) { paste0( unclass(x), '(', attr(x, 'width'), attr(x, 'uwidth'), ', ', attr(x, 'height'), attr(x, 'uheight'), ')' ) } #' @export print.geometry <- function(x, ...) { print(format(x, ...)) } #' @export rep.geometry <- function(x, ...) { i <- rep(seq_along(x), ...) x[i] } #' @export as.data.frame.geometry <- function(x, row.names = NULL, optional = FALSE, ...) { new_data_frame(list(x)) } #' @export c.geometry <- function(...) { geometries <- list(...) base <- do.call(c, lapply(geometries, unclass)) g_attr <- do.call(Map, c(list(f = c), lapply(geometries, attributes))) g_attr$class <- 'geometry' attributes(base) <- g_attr base } #' @export is.na.geometry <- function(x) { is.na(unclass(x)) } geo_type <- function(x) { if (!is.geometry(x)) stop('x must be a geometry object', call. = FALSE) unclass(x) } geo_width <- function(x) { if (!is.geometry(x)) stop('x must be a geometry object', call. = FALSE) unit(attr(x, 'width'), attr(x, 'uwidth')) } geo_height <- function(x) { if (!is.geometry(x)) stop('x must be a geometry object', call. = FALSE) unit(attr(x, 'height'), attr(x, 'uheight')) } #' @importFrom grid convertHeight grobHeight abs_height <- function(grobs) { vapply(grobs, function(g) convertHeight(grobHeight(g), 'cm', TRUE), numeric(1)) } #' @importFrom grid convertWidth grobWidth abs_width <- function(grobs) { vapply(grobs, function(g) convertWidth(grobWidth(g), 'cm', TRUE), numeric(1)) } #' Define default scale type for geometry #' #' This function is quite useless as geometry is not meant to be scaled, but it #' is a requirement for ggplot2 to handle it correctly. #' #' @export #' #' @keywords internal scale_type.geometry <- function(x) 'identity' ggraph/R/scale_edge_alpha.R0000644000176200001440000000314513524253130015267 0ustar liggesusers#' Edge alpha scales #' #' This set of scales defines new alpha scales for edge geoms equivalent to the #' ones already defined by ggplot2. See [ggplot2::scale_alpha()] for #' more information. The different geoms will know whether to use edge scales or #' the standard scales so it is not necessary to write `edge_alpha` in #' the call to the geom - just use `alpha`. #' #' @return A ggproto object inheriting from `Scale` #' #' @family scale_edge_* #' #' @name scale_edge_alpha #' @rdname scale_edge_alpha #' NULL #' @rdname scale_edge_alpha #' #' @inheritParams ggplot2::scale_alpha #' #' @importFrom scales rescale_pal #' @export scale_edge_alpha <- function(..., range = c(0.1, 1)) { continuous_scale('edge_alpha', 'alpha_c', rescale_pal(range), ...) } #' @rdname scale_edge_alpha #' #' @export scale_edge_alpha_continuous <- scale_edge_alpha #' @rdname scale_edge_alpha #' #' #' @inheritParams ggplot2::scale_alpha_discrete #' #' @export scale_edge_alpha_discrete <- function(..., range = c(0.1, 1)) { discrete_scale( 'edge_alpha', 'alpha_d', function(n) seq(range[1], range[2], length.out = n), ... ) } #' @rdname scale_edge_alpha #' #' @inheritParams ggplot2::scale_alpha_manual #' #' @export scale_edge_alpha_manual <- function(..., values) { manual_scale('edge_alpha', values, ...) } #' @rdname scale_edge_alpha #' #' @inheritParams ggplot2::scale_alpha_identity #' #' @importFrom scales identity_pal #' @export scale_edge_alpha_identity <- function(..., guide = 'none') { sc <- continuous_scale('edge_alpha', 'identity', identity_pal(), ..., guide = guide, super = ScaleContinuousIdentity ) sc } ggraph/R/layout_backbone.R0000644000176200001440000000354513526505742015227 0ustar liggesusers#' Place node to emphasize group structure #' #' This layout is optimised for drawing small-world types of graphs often found #' in social networks, where distinct groups are still highly connected to the #' remaining graph. Typical layouts struggle with this as they attempt to #' minimise the edge length of all edges equally. The backbone layout is based #' on weighing edges based on how well they hold together communities. The end #' result is that communities tend to stick together despite high #' interconnectivity. #' #' @param graph A tbl_graph object #' @param keep The fraction of edges to use for creating the backbone #' @param circular ignored #' #' @return A data.frame with the columns `x`, `y`, `circular` as #' well as any information stored as node variables in the tbl_graph object. #' Further an edge attribute called `backbone` is added giving whether the edge #' was selected as backbone. #' #' @references #' Nocaj, A., Ortmann, M., & Brandes, U. (2015). *Untangling the hairballs of #' multi-centered, small-world online social media networks.* Journal of Graph #' Algorithms and Applications: JGAA, 19(2), 595-618. #' #' @family layout_tbl_graph_* #' #' @author The underlying algorithm is implemented in the graphlayouts package #' by David Schoch #' #' @importFrom graphlayouts layout_as_backbone #' layout_tbl_graph_backbone <- function(graph, keep = 0.2, circular = FALSE) { layout <- layout_as_backbone(graph, keep = keep, backbone = TRUE) xy <- layout$xy nodes <- new_data_frame(list(x = xy[,1], y = xy[,2])) nodes$circular <- FALSE extra_data <- as_tibble(graph, active = 'nodes') nodes <- cbind(nodes, extra_data[, !names(extra_data) %in% names(nodes), drop = FALSE]) graph <- activate(graph, 'edges') graph <- mutate(graph, backbone = seq_len(graph_size()) %in% layout$backbone) attr(nodes, 'graph') <- activate(graph, 'nodes') nodes } ggraph/R/geom_node_point.R0000644000176200001440000000240213527221743015217 0ustar liggesusers#' Show nodes as points #' #' This geom is equivalent in functionality to [ggplot2::geom_point()] #' and allows for simple plotting of nodes in different shapes, colours and sizes. #' #' @section Aesthetics: #' `geom_node_point` understand the following aesthetics. Bold aesthetics are #' automatically set, but can be overridden. #' #' - **x** #' - **y** #' - alpha #' - colour #' - fill #' - shape #' - size #' - stroke #' - filter #' #' @inheritParams ggplot2::geom_point #' #' @param mapping Set of aesthetic mappings created by [ggplot2::aes()] #' or [ggplot2::aes_()]. By default x and y are mapped to x and y in #' the node data. #' #' @author Thomas Lin Pedersen #' #' @family geom_node_* #' #' @examples #' require(tidygraph) #' gr <- create_notable('bull') %>% #' mutate(class = sample(letters[1:3], n(), replace = TRUE)) #' #' ggraph(gr, 'stress') + geom_node_point() #' @export #' geom_node_point <- function(mapping = NULL, data = NULL, position = 'identity', show.legend = NA, ...) { mapping <- aes_intersect(mapping, aes(x = x, y = y)) layer( data = data, mapping = mapping, stat = StatFilter, geom = GeomPoint, position = position, show.legend = show.legend, inherit.aes = FALSE, params = list(na.rm = FALSE, ...) ) } ggraph/R/scale_edge_colour.R0000644000176200001440000001467013524253133015515 0ustar liggesusers#' Edge colour scales #' #' This set of scales defines new colour scales for edge geoms equivalent to the #' ones already defined by ggplot2. The parameters are equivalent to the ones #' from ggplot2 so there is nothing new under the sun. The different geoms will #' know whether to use edge scales or the standard scales so it is not necessary #' to write `edge_colour` in the call to the geom - just use `colour`. #' #' @return A ggproto object inheriting from `Scale` #' #' @family scale_edge_* #' #' @name scale_edge_colour #' @rdname scale_edge_colour #' NULL #' @rdname scale_edge_colour #' #' @inheritParams ggplot2::scale_colour_hue #' #' @importFrom scales hue_pal #' @export scale_edge_colour_hue <- function(..., h = c(0, 360) + 15, c = 100, l = 65, h.start = 0, direction = 1, na.value = 'grey50') { discrete_scale('edge_colour', 'hue', hue_pal(h, c, l, h.start, direction), na.value = na.value, ... ) } #' @rdname scale_edge_colour #' #' @inheritParams ggplot2::scale_colour_brewer #' #' @importFrom scales brewer_pal #' @export scale_edge_colour_brewer <- function(..., type = 'seq', palette = 1, direction = 1) { discrete_scale('edge_colour', 'brewer', brewer_pal(type, palette, direction), ...) } #' @rdname scale_edge_colour #' #' @inheritParams ggplot2::scale_colour_distiller #' #' @importFrom scales gradient_n_pal brewer_pal #' @export scale_edge_colour_distiller <- function(..., type = 'seq', palette = 1, direction = -1, values = NULL, space = 'Lab', na.value = 'grey50', guide = 'edge_colourbar') { # warn about using a qualitative brewer palette to generate the gradient type <- match.arg(type, c('seq', 'div', 'qual')) if (type == 'qual') { warning('Using a discrete colour palette in a continuous scale.\n Consider using type = "seq" or type = "div" instead', call. = FALSE) } continuous_scale('edge_colour', 'distiller', gradient_n_pal(brewer_pal(type, palette, direction)(6), values, space), na.value = na.value, guide = guide, ... ) # NB: 6 colours per palette gives nice gradients; more results in more saturated colours which do not look as good } #' @rdname scale_edge_colour #' #' @inheritParams ggplot2::scale_colour_gradient #' @param low,high Colours for low and high ends of the gradient. #' #' @importFrom scales seq_gradient_pal #' @export scale_edge_colour_gradient <- function(..., low = '#132B43', high = '#56B1F7', space = 'Lab', na.value = 'grey50', guide = 'edge_colourbar') { continuous_scale('edge_colour', 'gradient', seq_gradient_pal(low, high, space), na.value = na.value, guide = guide, ... ) } #' @rdname scale_edge_colour #' #' @inheritParams ggplot2::scale_colour_gradient2 #' #' @importFrom scales div_gradient_pal muted #' @export scale_edge_colour_gradient2 <- function(..., low = muted('red'), mid = 'white', high = muted('blue'), midpoint = 0, space = 'Lab', na.value = 'grey50', guide = 'edge_colourbar') { continuous_scale('edge_colour', 'gradient2', div_gradient_pal(low, mid, high, space), na.value = na.value, guide = guide, ..., rescaler = mid_rescaler(mid = midpoint) ) } #' @rdname scale_edge_colour #' #' @inheritParams ggplot2::scale_colour_gradientn #' @param colours,colors Vector of colours to use for n-colour gradient. #' #' @importFrom scales gradient_n_pal #' @export scale_edge_colour_gradientn <- function(..., colours, values = NULL, space = 'Lab', na.value = 'grey50', guide = 'edge_colourbar', colors) { colours <- if (missing(colours)) colors else colours continuous_scale('edge_colour', 'gradientn', gradient_n_pal(colours, values, space), na.value = na.value, guide = guide, ... ) } #' @rdname scale_edge_colour #' #' @inheritParams ggplot2::scale_colour_grey #' #' @importFrom scales grey_pal #' @export scale_edge_colour_grey <- function(..., start = 0.2, end = 0.8, na.value = 'red') { discrete_scale('edge_colour', 'grey', grey_pal(start, end), na.value = na.value, ... ) } #' @rdname scale_edge_colour #' #' @inheritParams ggplot2::scale_colour_identity #' #' @importFrom scales identity_pal #' @export scale_edge_colour_identity <- function(..., guide = 'none') { sc <- discrete_scale('edge_colour', 'identity', identity_pal(), ..., guide = guide, super = ScaleDiscreteIdentity ) sc } #' @rdname scale_edge_colour #' #' @inheritParams ggplot2::scale_colour_manual #' #' @export scale_edge_colour_manual <- function(..., values) { manual_scale('edge_colour', values, ...) } #' @rdname scale_edge_colour #' #' @inheritParams viridis::scale_colour_viridis #' #' @importFrom viridis viridis viridis_pal #' @export scale_edge_colour_viridis <- function(..., alpha = 1, begin = 0, end = 1, discrete = FALSE, option = 'D', direction = 1) { if (direction == -1) { tmp <- begin begin <- end end <- tmp } if (discrete) { discrete_scale( 'edge_colour', 'viridis', viridis_pal( alpha = alpha, begin = begin, end = end, option = option ), ... ) } else { scale_edge_colour_gradientn(colours = viridis(256, alpha = alpha, begin = begin, end = end, option = option ), ...) } } #' @rdname scale_edge_colour #' #' @export scale_edge_colour_continuous <- scale_edge_colour_gradient #' @rdname scale_edge_colour #' #' @export scale_edge_colour_discrete <- scale_edge_colour_hue #' @rdname scale_edge_colour #' #' @export scale_edge_color_hue <- scale_edge_colour_hue #' @rdname scale_edge_colour #' #' @export scale_edge_color_brewer <- scale_edge_colour_brewer #' @rdname scale_edge_colour #' #' @export scale_edge_color_distiller <- scale_edge_colour_distiller #' @rdname scale_edge_colour #' #' @export scale_edge_color_gradient <- scale_edge_colour_gradient #' @rdname scale_edge_colour #' #' @export scale_edge_color_gradient2 <- scale_edge_colour_gradient2 #' @rdname scale_edge_colour #' #' @export scale_edge_color_gradientn <- scale_edge_colour_gradientn #' @rdname scale_edge_colour #' #' @export scale_edge_color_grey <- scale_edge_colour_grey #' @rdname scale_edge_colour #' #' @export scale_edge_color_identity <- scale_edge_colour_identity #' @rdname scale_edge_colour #' #' @export scale_edge_color_manual <- scale_edge_colour_manual #' @rdname scale_edge_colour #' #' @export scale_edge_color_continuous <- scale_edge_colour_continuous #' @rdname scale_edge_colour #' #' @export scale_edge_color_discrete <- scale_edge_colour_discrete #' @rdname scale_edge_colour #' #' @export scale_edge_color_viridis <- scale_edge_colour_viridis ggraph/R/ggraph.R0000644000176200001440000001371113531220357013322 0ustar liggesusers#' Create a ggraph plot #' #' This function is the equivalent of [ggplot2::ggplot()] in ggplot2. #' It takes care of setting up the plot object along with creating the layout #' for the plot based on the graph and the specification passed in. #' Alternatively a layout can be prepared in advance using #' `create_layout` and passed as the data argument. See *Details* for #' a description of all available layouts. #' #' @details #' Following is a short description of the different layout types available in #' ggraph. Each layout is further described in its own help pages. Any type of #' regular graph/network data can be represented as a tbl_graph object. Because #' of this the different layouts that can be applied to tbl_graph #' objects are quite diverse, but not all layouts makes sense to all types of #' graphs. It is up to the user to understand their data and choose an #' appropriate layout. For standard node-edge diagrams igraph defines a #' long range of different layout functions that are all available through the #' `igraph` layout where the specific layout is specified using the #' `algorithm` argument. In order to minimize typing all igraph algorithms #' can also be passed directly into the `layout` argument. #' #' Any object that has an appropriate `as_tbl_graph` method can be passed #' into `ggraph()` and will automatically be converted underneath. #' #' \describe{ #' \item{`auto`}{The default layout. See [layout_tbl_graph_auto()] for further #' details} #' \item{`igraph`}{Use one of the internal igraph layout algorithms. #' The algorithm is specified using the `algorithm` argument. All strings #' accepted by the `algorithm` argument can also be supplied directly #' into `layout`. See [layout_tbl_graph_igraph()] for further #' details} #' \item{`dendrogram`}{Lays out the nodes in a tree-like graph as a #' dendrogram with leaves set at 0 and parents 1 unit above its tallest child. #' See [layout_tbl_graph_dendrogram()] for further details} #' \item{`manual`}{Lets the user manually specify the location of each #' node. See [layout_tbl_graph_manual()] for further details} #' \item{`linear`}{Arranges the nodes linearly or circularly in order to #' make an arc diagram. See [layout_tbl_graph_linear()] for further #' details} #' \item{`matrix`}{Arranges nodes on a diagonal thus preparing it for use with #' [geom_edge_point()] to make a matrix plot. See [layout_tbl_graph_matrix()] #' for further details} #' \item{`treemap`}{Creates a treemap from the graph, that is, a #' space-filing subdivision of rectangles showing a weighted hierarchy. See #' [layout_tbl_graph_treemap()] for further details} #' \item{`circlepack`}{Creates a layout showing a hierarchy as circles #' within circles. Conceptually equal to treemaps. See #' [layout_tbl_graph_circlepack()] for further details} #' \item{`partition`}{Create icicle or sunburst charts, where each layer #' subdivides the division given by the preceding layer. See #' [layout_tbl_graph_partition()] for further details} #' \item{`hive`}{Positions nodes on axes spreading out from the center #' based on node attributes. See [layout_tbl_graph_hive()] for further #' details} #' } #' #' Alternatively a matrix or a data.frame can be provided to the `layout` #' argument. In the former case the first column will be used as x coordinates #' and the second column will by used as y coordinates, further columns are #' dropped. In the latter case the data.frame is used as the layout table and #' must thus contain a numeric x and y column. #' #' Lastly a function can be provided to the `layout` argument. It will be called #' with the graph object as its first argument and any additional argument #' passed into `ggraph()`/`create_layout()`. The function must return either a #' data.frame or an object coercible to one and have an `x` and `y` column, or #' an object coercible to a `tbl_graph`. In the latter case the node data is #' extracted and used as layout (and must thus contain an `x` and `y` column) #' and the graph will be added as the `graph` attribute. #' #' @param graph The object containing the graph. See *Details* for a list #' of supported classes. Or a `layout_ggraph` object as returned from #' `create_layout` in which case all subsequent arguments is ignored. #' #' @param layout The type of layout to create. Either a valid string, a #' function, a matrix, or a data.frame (see Details) #' #' @param circular Should the layout be transformed into a radial #' representation. Only possible for some layouts. Defaults to `FALSE` #' #' @param ... Arguments passed on to the layout function. #' #' @return For `ggraph()` an object of class gg onto which layers, scales, #' etc. can be added. For `create_layout()` an object inheriting from #' `layout_ggraph`. `layout_ggraph` itself inherits from #' `data.frame` and can be considered as such. The data.frame contains #' the node positions in the `x` and `y` column along with #' additional columns generated by the specific layout, as well as node #' parameters inherited from the graph. Additional information is stored as #' attributes to the data.frame. The original graph object is stored in the #' `graph` attribute and the `circular` attribute contains a logical #' indicating whether the layout has been transformed to a circular #' representation. #' #' @keywords layout network graph hierarchy visualisation #' #' @seealso [get_edges()] for extracting edge information from the #' layout and [get_con()] for extracting path information. #' #' @examples #' require(tidygraph) #' gr <- create_notable('bull') #' layout <- create_layout(gr, layout = 'igraph', algorithm = 'kk') #' @export #' ggraph <- function(graph, layout = 'auto', ...) { envir <- parent.frame() p <- ggplot(data = create_layout(graph, layout, ...), environment = envir) + th_no_axes() class(p) <- c('ggraph', class(p)) p } #' @export ggplot_build.ggraph <- function(plot) { .register_graph_context(attr(plot$data, 'graph'), free = TRUE) NextMethod() } ggraph/R/geom_edge_span.R0000644000176200001440000001436413617226142015016 0ustar liggesusers#' Draw edges as vertical spans #' #' This edge geom is mainly intended for use with [fabric][layout_tbl_graph_fabric] #' layouts. It draws edges as vertical segments with an optional end shape #' adornment. Due to the special nature of fabric layouts where nodes are not #' a single point in space but a line, this geom doesn't derive the x position #' from the location of the terminal nodes, but defaults to using the `edge_x` #' variable calculated by the fabric layout. If this geom is used with other #' layouts `x`and `xend` must be given explicitly. #' #' @inheritSection geom_edge_link Edge variants #' @inheritSection geom_edge_link Edge aesthetic name expansion #' #' @section Aesthetics: #' `geom_edge_span` and `geom_edge_span0` understand the following #' aesthetics. Bold aesthetics are automatically set, but can be overridden. #' #' - **x** #' - **y** #' - **xend** #' - **yend** #' - edge_colour #' - edge_width #' - edge_linetype #' - edge_alpha #' - filter #' #' `geom_edge_span2` understand the following aesthetics. Bold aesthetics are #' automatically set, but can be overridden. #' #' - **x** #' - **y** #' - **group** #' - edge_colour #' - edge_width #' - edge_linetype #' - edge_alpha #' - filter #' #' `geom_edge_span` and `geom_edge_span2` furthermore takes the following #' aesthetics. #' #' - start_cap #' - end_cap #' - label #' - label_pos #' - label_size #' - angle #' - hjust #' - vjust #' - family #' - fontface #' - lineheight #' #' @section Computed variables: #' #' \describe{ #' \item{index}{The position along the path (not computed for the *0 version)} #' } #' #' @inheritParams geom_edge_link #' @inheritParams ggplot2::geom_path #' #' @param end_shape The adornment to put at the ends of the span. The naming #' follows the conventions of the shape aesthetic in [ggplot2::geom_point()] #' #' @author Thomas Lin Pedersen #' #' @family geom_edge_* #' #' @examples #' require(tidygraph) #' gr <- play_smallworld(n_dim = 3, dim_size = 3, order = 1, p_rewire = 0.6) #' #' # Standard use #' ggraph(gr, 'fabric', sort.by = node_rank_fabric()) + #' geom_node_range(colour = 'grey80') + #' geom_edge_span() #' #' # Add end shapes #' ggraph(gr, 'fabric', sort.by = node_rank_fabric()) + #' geom_node_range(colour = 'grey80') + #' geom_edge_span(end_shape = 'circle') #' #' # If the layout include shadow edges these can be styled differently #' ggraph(gr, 'fabric', sort.by = node_rank_fabric(), shadow.edges = TRUE) + #' geom_node_range(colour = 'grey80') + #' geom_edge_span(aes(colour = shadow_edge), end_shape = 'square') + #' scale_edge_colour_manual(values = c('FALSE' = 'black', 'TRUE' = 'grey')) #' #' @rdname geom_edge_span #' @name geom_edge_span #' NULL #' @rdname geom_edge_span #' #' @importFrom ggforce StatLink #' @export geom_edge_span <- function(mapping = NULL, data = get_edges('short'), position = 'identity', end_shape = NA, arrow = NULL, n = 100, lineend = 'butt', linejoin = 'round', linemitre = 1, label_colour = 'black', label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = 'rot', force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes(x = edge_x, y = y, xend = edge_x, yend = yend, group = edge.id)) layer( data = data, mapping = mapping, stat = StatEdgeLink, geom = GeomEdgeSpanPath, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, linejoin = linejoin, linemitre = linemitre, na.rm = FALSE, n = n, interpolate = FALSE, end_shape = end_shape, label_colour = label_colour, label_alpha = label_alpha, label_parse = label_parse, check_overlap = check_overlap, angle_calc = angle_calc, force_flip = force_flip, label_dodge = label_dodge, label_push = label_push, ... ) ) ) } #' @rdname geom_edge_span #' #' @importFrom ggforce StatLink2 #' @export geom_edge_span2 <- function(mapping = NULL, data = get_edges('long'), position = 'identity', end_shape = NA, arrow = NULL, n = 100, lineend = 'butt', linejoin = 'round', linemitre = 1, label_colour = 'black', label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = 'rot', force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes(x = edge_x, y = y, group = edge.id)) layer( data = data, mapping = mapping, stat = StatEdgeLink2, geom = GeomEdgeSpanPath, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, linejoin = linejoin, linemitre = linemitre, na.rm = FALSE, n = n, interpolate = TRUE, end_shape = end_shape, label_colour = label_colour, label_alpha = label_alpha, label_parse = label_parse, check_overlap = check_overlap, angle_calc = angle_calc, force_flip = force_flip, label_dodge = label_dodge, label_push = label_push, ... ) ) ) } #' @rdname geom_edge_span #' #' @importFrom ggforce StatLink2 #' @export geom_edge_span0 <- function(mapping = NULL, data = get_edges(), position = 'identity', end_shape = NA, arrow = NULL, lineend = 'butt', show.legend = NA, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes(x = edge_x, y = y, xend = edge_x, yend = yend)) layer( data = data, mapping = mapping, stat = StatFilter, geom = GeomEdgeSpanSegment, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list(end_shape = end_shape, arrow = arrow, lineend = lineend, na.rm = FALSE, ...) ) ) } ggraph/R/data_highschool.R0000644000176200001440000000132713523247752015203 0ustar liggesusers#' Friendship among high school boys #' #' This dataset shows the friendship among high school boys as assessed by the #' question: "What fellows here in school do you go around with most often?". #' The question was posed twice, with one year in between (1957 and 1958) and #' shows the evolution in friendship between the two timepoints. #' #' @format #' The graph is stored as an unnamed edgelist with a year attribute. #' \describe{ #' \item{from}{The boy answering the question} #' \item{to}{The boy being the answer to the question} #' \item{year}{The year the friendship was reported} #' } #' #' @source #' Coleman, J. S. *Introduction to Mathematical Sociology*. New York: Free #' Press, pp.450-451. #' 'highschool' ggraph/R/geom_edge_density.R0000644000176200001440000001261113526467426015540 0ustar liggesusers#' Show edges as a density map #' #' This geom makes it possible to add a layer showing edge presence as a density #' map. Each edge is converted to `n` points along the line and a jitter is #' applied. Based on this dataset a two-dimensional kernel density estimation is #' applied and plotted as a raster image. The density is mapped to the alpha #' level, making it possible to map a variable to the fill. #' #' @inheritSection geom_edge_link Edge aesthetic name expansion #' #' @section Aesthetics: #' `geom_edge_density` understand the following aesthetics. Bold aesthetics are #' automatically set, but can be overridden. #' #' **x** #' **y** #' **xend** #' **yend** #' edge_fill #' filter #' #' #' @section Computed variables: #' #' \describe{ #' \item{x, y}{The coordinates for each pixel in the raster} #' \item{density}{The density associated with the pixel} #' } #' #' @inheritParams geom_edge_link #' @inheritParams ggplot2::geom_raster #' #' @param n The number of points to estimate in the x and y direction, i.e. the #' resolution of the raster. #' #' @author Thomas Lin Pedersen #' #' @family geom_edge_* #' #' @examples #' require(tidygraph) #' gr <- create_notable('bull') %>% #' activate(edges) %>% #' mutate(class = sample(letters[1:3], n(), replace = TRUE)) #' #' ggraph(gr, 'stress') + #' geom_edge_density(aes(fill = class)) + #' geom_edge_link() + geom_node_point() #' @rdname geom_edge_density #' @name geom_edge_density #' NULL #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatLink #' @importFrom MASS bandwidth.nrd kde2d #' @export StatEdgeDensity <- ggproto('StatEdgeDensity', Stat, compute_group = function(data, scales, na.rm = FALSE, h = NULL, n = 100, bins = NULL, binwidth = NULL) { group <- data$group[1] x_range <- diff(range(c(data$x, data$xend))) y_range <- diff(range(c(data$y, data$yend))) x_extend <- x_range / 10 y_extend <- y_range / 10 data <- StatLink$compute_panel(data, n = 50) data <- PositionJitter$compute_layer(data, list( width = x_extend, height = y_extend )) if (is.null(h)) { h <- c(MASS::bandwidth.nrd(data$x), MASS::bandwidth.nrd(data$y)) } dens <- MASS::kde2d(data$x, data$y, h = h, n = n, lims = c( scales$x$dimension(), scales$y$dimension() ) + c( -x_extend, x_extend, -y_extend, y_extend ) ) df <- expand.grid(x = dens$x, y = dens$y) df$z <- as.vector(dens$z) df$group <- group names(df) <- c('x', 'y', 'density', 'group') df }, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } data }, default_aes = aes(filter = TRUE), required_aes = c('x', 'y', 'xend', 'yend') ) #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom grid gTree gList grobName rasterGrob rectGrob gpar GeomEdgeDensity <- ggproto('GeomEdgeDensity', GeomRaster, draw_panel = function(self, data, panel_scales, coord, ...) { groups <- split(data, factor(data$group)) max_density <- max(rowSums(do.call( cbind, lapply(groups, `[[`, i = 'density') ))) grobs <- lapply(groups, function(group) { self$draw_group(group, panel_scales, coord, max.alpha = max_density, ... ) }) grobs <- gTree(children = do.call('gList', grobs)) grobs$name <- grobName(grobs, 'geom_edge_density') grobs }, draw_group = function(data, panel_scales, coord, max.alpha) { if (!inherits(coord, 'CoordCartesian')) { stop('geom_raster only works with Cartesian coordinates', call. = FALSE ) } data <- coord$transform(data, panel_scales) x_pos <- as.integer((data$x - min(data$x)) / resolution( data$x, FALSE )) y_pos <- as.integer((data$y - min(data$y)) / resolution( data$y, FALSE )) nrow <- max(y_pos) + 1 ncol <- max(x_pos) + 1 raster <- matrix(NA_character_, nrow = nrow, ncol = ncol) raster[cbind(nrow - y_pos, x_pos + 1)] <- alpha( data$edge_fill, data$density / max.alpha ) x_rng <- c(min(data$xmin, na.rm = TRUE), max(data$xmax, na.rm = TRUE)) y_rng <- c(min(data$ymin, na.rm = TRUE), max(data$ymax, na.rm = TRUE)) rasterGrob(raster, x = mean(x_rng), y = mean(y_rng), width = diff(x_rng), height = diff(y_rng), default.units = 'native', interpolate = TRUE ) }, draw_key = function(data, params, size) { rectGrob(gp = gpar( col = NA, fill = alpha(data), lty = 0 )) }, required_aes = c('x', 'y'), default_aes = aes(edge_fill = 'darkgrey') ) #' @rdname geom_edge_density #' #' @importFrom ggforce StatLink #' @export geom_edge_density <- function(mapping = NULL, data = get_edges('short'), position = 'identity', show.legend = NA, n = 100, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes(x = x, y = y, xend = xend, yend = yend)) layer( data = data, mapping = mapping, stat = StatEdgeDensity, geom = GeomEdgeDensity, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list(na.rm = FALSE, n = n, ...) ) ) } ggraph/R/scale_edge_width.R0000644000176200001440000000354613524253141015330 0ustar liggesusers#' Edge width scales #' #' This set of scales defines width scales for edge geoms. Of all the new edge #' scales defined in ggraph, this is the only one not having an equivalent in #' ggplot2. In essence it mimics the use of size in #' [ggplot2::geom_line()] and related. As almost all edge #' representations are lines of some sort, edge_width will be used much more #' often than edge_size. It is not necessary to spell out that it is an edge #' scale as the geom knows if it is drawing an edge. Just write `width` and #' not `edge_width` in the call to geoms. #' #' @return A ggproto object inheriting from `Scale` #' #' @family scale_edge_* #' #' @name scale_edge_width #' @rdname scale_edge_width #' NULL #' @rdname scale_edge_width #' #' @inheritParams ggplot2::scale_size_continuous #' #' @importFrom scales rescale_pal #' @export scale_edge_width_continuous <- function(..., range = c(1, 6)) { continuous_scale('edge_width', 'width_c', rescale_pal(range), ...) } #' @rdname scale_edge_width #' #' @export scale_edge_width <- scale_edge_width_continuous #' @rdname scale_edge_width #' #' @inheritParams ggplot2::scale_size_discrete #' #' @importFrom scales rescale_pal #' @export scale_edge_width_discrete <- function(..., range = c(2, 6)) { discrete_scale('edge_width', 'width_d', function(n) { area <- seq(range[1]^2, range[2]^2, length.out = n) sqrt(area) }, ...) } #' @rdname scale_edge_width #' #' @inheritParams ggplot2::scale_size_manual #' #' @export scale_edge_width_manual <- function(..., values) { manual_scale('edge_width', values, ...) } #' @rdname scale_edge_width #' #' @inheritParams ggplot2::scale_size_identity #' #' @importFrom scales identity_pal #' @export scale_edge_width_identity <- function(..., guide = 'none') { sc <- discrete_scale('edge_width', 'identity', identity_pal(), ..., guide = guide, super = ScaleDiscreteIdentity ) sc } ggraph/R/tbl_graph.R0000644000176200001440000002054413527511127014021 0ustar liggesusers#' @rdname ggraph #' @aliases layout_tbl_graph #' #' @importFrom igraph gorder #' @export #' create_layout.tbl_graph <- function(graph, layout, circular = FALSE, ...) { graph <- mutate(activate(graph, 'nodes'), .ggraph.orig_index = seq_len(graph_order())) graph <- prepare_graph(graph, layout, ...) .register_graph_context(graph, free = TRUE) if (gorder(graph) == 0) { layout <- new_data_frame(list(x = numeric(), y = numeric(), circular = logical())) layout <- cbind(layout, .N()) } else { layout <- layout_to_table(layout, graph, circular = circular, ...) } layout$.ggraph.index <- seq_len(nrow(layout)) if (is.null(attr(layout, 'graph'))) { attr(layout, 'graph') <- graph } attr(layout, 'circular') <- circular class(layout) <- c( 'layout_tbl_graph', 'layout_ggraph', 'data.frame' ) check_layout(layout) } collect_edges.layout_tbl_graph <- function(layout) { gr <- attr(layout, 'graph') edges <- as_tibble(gr, active = 'edges') edges$circular <- rep(attr(layout, 'circular'), nrow(edges)) as.data.frame(edges, stringsAsFactors = FALSE) } #' @importFrom igraph shortest_paths #' @importFrom rlang enquo eval_tidy collect_connections.layout_tbl_graph <- function(layout, from, to, weight = NULL, mode = 'all') { from <- match(from, layout$.ggraph.orig_index) to <- match(to, layout$.ggraph.orig_index) weight <- eval_tidy(enquo(weight), collect_edges(layout)) if (is.null(weight)) { weight <- NA } graph <- attr(layout, 'graph') to <- split(to, from) connections <- lapply(seq_along(to), function(i) { paths <- shortest_paths(graph, as.integer(names(to)[i]), to[[i]], mode = mode, weights = weight)$vpath lapply(paths, as.numeric) }) unlist(connections, recursive = FALSE) } # HELPERS ----------------------------------------------------------------- is.igraphlayout <- function(type) { if (type %in% igraphlayouts) { TRUE } else if (any(paste0(c('as_', 'in_', 'with_', 'on_'), type) %in% igraphlayouts)) { TRUE } else { FALSE } } as.igraphlayout <- function(type) { if (type %in% igraphlayouts) { layout <- type } else { new_type <- paste0(c('as_', 'in_', 'with_', 'on_'), type) type_ind <- which(new_type %in% igraphlayouts) if (length(type_ind) == 0) { stop('Cannot find igraph layout') } layout <- new_type[type_ind] } paste0('layout_', layout) } #' @importFrom igraph gorder permute prepare_graph <- function(graph, layout, direction = 'out', ...) { if (!is.character(layout)) { return(graph) } is_hierarchy <- layout %in% c( 'dendrogram', 'treemap', 'circlepack', 'partition' ) if (is_hierarchy || (layout == 'auto' && with_graph(graph, graph_is_tree() || graph_is_forest()))) { graph <- graph_to_tree(graph, mode = direction) graph <- permute(graph, match(seq_len(gorder(graph)), order(node_depth(graph, direction)))) } as_tbl_graph(graph) } #' @importFrom igraph degree unfold_tree components induced_subgraph vertex_attr vertex_attr<- is.directed simplify graph_to_tree <- function(graph, mode) { if (!is.directed(graph)) { stop('Graph must be directed') } graph <- simplify(graph, edge.attr.comb = 'first') parent_dir <- if (mode == 'out') 'in' else 'out' comp <- components(graph, 'weak') graphs <- lapply(seq_len(comp$no), function(i) { graph <- induced_subgraph(graph, which(comp$membership == i)) n_parents <- degree(graph, mode = parent_dir) if (!any(n_parents == 0)) { stop('No root in graph. Provide graph with one parentless node') } if (any(n_parents > 1)) { message('Multiple parents. Unfolding graph') root <- which(degree(graph, mode = parent_dir) == 0) if (length(root) > 1) { message('Multiple roots in graph. Choosing the first') root <- root[1] } tree <- unfold_tree(graph, mode = mode, roots = root) vattr <- lapply(vertex_attr(graph), `[`, i = tree$vertex_index) vertex_attr(tree$tree) <- vattr graph <- tree$tree } as_tbl_graph(graph) }) do.call(bind_graphs, graphs) } #' @importFrom igraph gorder as_edgelist delete_vertex_attr is.named tree_to_hierarchy <- function(graph, mode, sort.by, weight, height = NULL) { if (is.named(graph)) graph <- delete_vertex_attr(graph, 'name') parent_col <- if (mode == 'out') 1 else 2 node_col <- if (mode == 'out') 2 else 1 edges <- as_edgelist(graph) hierarchy <- new_data_frame(list(parent = rep(0, gorder(graph)))) hierarchy$parent[edges[, node_col]] <- edges[, parent_col] if (is.null(sort.by)) { hierarchy$order <- seq_len(nrow(hierarchy)) + 1 } else { hierarchy$order <- order(sort.by) + 1 } if (is.null(height)) { hierarchy$height <- 1 } else { hierarchy$height <- 0 hierarchy$height[edges[, node_col]] <- height } leaf <- degree(graph, mode = mode) == 0 if (is.null(weight)) { hierarchy$weight <- 0 hierarchy$weight[leaf] <- 1 } else { if (!is.numeric(weight)) { stop('Weight must be numeric') } hierarchy$weight <- weight if (any(hierarchy$weight[!leaf] != 0)) { message('Non-leaf weights ignored') } if (any(hierarchy$weight[leaf] == 0)) { stop('Leafs must have a weight') } hierarchy$weight[!leaf] <- 0 } hierarchy <- hierarchy[c(1, seq_len(nrow(hierarchy))), ] hierarchy$parent[1] <- -1 hierarchy$order[1] <- 1 hierarchy } #' @importFrom igraph bfs degree gorder node_depth <- function(graph, mode) { mode_rev <- switch( mode, `in` = 'out', out = 'in', stop('unknown mode') ) root <- which(degree(graph, mode = mode_rev) == 0) depth <- rep(NA_integer_, gorder(graph)) for (i in root) { depth[!is.finite(depth)] <- unname(bfs(graph, root = i, unreachable = FALSE, dist = TRUE)$dist)[!is.finite(depth)] } depth } #' @importFrom rlang .data add_direction <- function(graph, pos, direction = 'out') { graph <- activate(graph, 'edges') graph <- mutate(graph, direction = ifelse(pos$x[.data$to] < pos$x[.data$from], 'right', 'left')) if (direction == 'in') { graph <- mutate(graph, ifelse(.data$direction == 'left', 'right', 'left')) } graph <- mutate(graph, direction = factor(.data$direction)) graph } #' Convert a layout to a table #' #' This generic takes care of dispatching various layout types (names, #' functions, tables) to their respective functions that will return a valid #' layout table. #' #' @param layout A supported object #' @param graph A `tbl_graph` #' @param ... passed on to implementations #' #' @return A valid data.frame #' #' @keywords internal #' @export layout_to_table <- function(layout, graph, ...) { UseMethod('layout_to_table') } #' @export layout_to_table.default <- function(layout, graph, ...) { stop('Unknown layout', call. = FALSE) } #' @export layout_to_table.character <- function(layout, graph, circular, ...) { if (is.igraphlayout(layout)) { layout_tbl_graph_igraph(graph, layout, circular, ...) } else { layout_fun <- get(paste0('layout_tbl_graph_', layout)) layout_fun(graph, circular = circular, ...) } } #' @export layout_to_table.matrix <- function(layout, graph, ...) { layout <- new_data_frame(list(x = layout[, 1], y = layout[, 2])) layout_to_table(layout, graph, ...) } #' @export layout_to_table.data.frame <- function(layout, graph, ...) { cbind(layout, as_tibble(graph, active = 'nodes')) } #' @export layout_to_table.function <- function(layout, graph, circular, ...) { layout <- if ('circular' %in% names(formals(layout))) { layout(graph, circular = circular, ...) } else { layout(graph, ...) } if (!is.tbl_graph(layout) && !is.data.frame(layout)) { layout <- tryCatch( as.data.frame(layout, stringsAsFactors = FALSE), error = function(e) { tryCatch( as_tbl_graph(layout), error = function(e) { stop('layout function must return an object coerceble to either a data.frame or tbl_graph', call. = FALSE) } ) } ) } if (is.tbl_graph(layout)) { graph <- layout layout <- as_tibble(graph, active = 'nodes') attr(layout, 'graph') <- graph } else { layout <- cbind(layout, as_tibble(graph, active = 'nodes')) } layout } igraphlayouts <- c( 'as_bipartite', 'as_star', 'as_tree', 'in_circle', 'nicely', 'with_dh', 'with_drl', 'with_gem', 'with_graphopt', 'on_grid', 'with_mds', 'with_sugiyama', 'on_sphere', 'randomly', 'with_fr', 'with_kk', 'with_lgl' ) ggraph/R/layout_treemap.R0000644000176200001440000001010013527216724015101 0ustar liggesusers#' Calculate nodes as rectangles subdividing that of their parent #' #' A treemap is a space filling hierarchical layout that maps nodes to #' rectangles. The rectangles of the children of a node is packed into the #' rectangle of the node so that the size of a rectangle is a function of the #' size of the children. The size of the leaf nodes can be mapped arbitrarily #' (defaults to 1). Many different algorithms exists for dividing a rectangle #' into smaller bits, some optimizing the aspect ratio and some focusing on the #' ordering of the rectangles. See details for more discussions on this. The #' treemap layout was first developed by Ben Shneiderman for visualizing disk #' usage in the early '90 and has seen many improvements since. #' #' @details #' Different approaches to dividing the rectangles in a treemap exists; all with #' their strengths and weaknesses. Currently only the split algorithm is #' implemented which strikes a good balance between aspect ratio and order #' preservation, but other, more well-known, algorithms such as squarify and #' slice-and-dice will eventually be implemented. #' #' \strong{Algorithms} #' #' *Split* (default) #' #' The Split algorithm was developed by Bjorn Engdahl in order to address the #' downsides of both the original slice-and-dice algorithm (poor aspect ratio) #' and the popular squarify algorithm (no ordering of nodes). It works by #' finding the best cut in the ordered list of children in terms of making sure #' that the two rectangles associated with the split will have optimal aspect #' ratio. #' #' #' @note #' Treemap is a layout intended for trees, that is, graphs where nodes #' only have one parent and zero or more children. If the provided graph does #' not fit this format an attempt to convert it to such a format will be made. #' #' @param graph A `tbl_graph` object #' #' @param algorithm The name of the tiling algorithm to use. Defaults to 'split' #' #' @param weight An optional node variable to use as weight. Will only affect #' the weight of leaf nodes as the weight of non-leaf nodes are derived from #' their children. #' #' @param circular Logical. Should the layout be transformed to a circular #' representation. Ignored. #' #' @param sort.by The name of a node variables to sort the nodes by. #' #' @param direction The direction of the tree in the graph. `'out'` (default) #' means that parents point towards their children, while `'in'` means that #' children point towards their parent. #' #' @param height The height of the bounding rectangle #' #' @param width The width of the bounding rectangle #' #' @return A data.frame with the columns `x`, `y`, `width`, #' `height`, `leaf`, `depth`, `circular` as well as any #' information stored as node variables in the tbl_graph object. #' #' @references #' Engdahl, B. (2005). *Ordered and unordered treemap algorithms and their #' applications on handheld devices*. Master's Degree Project. #' #' Johnson, B., & Ben Shneiderman. (1991). *Tree maps: A Space-Filling #' Approach to the Visualization of Hierarchical Information Structures*. IEEE #' Visualization, 284-291. #' #' @family layout_tbl_graph_* #' layout_tbl_graph_treemap <- function(graph, algorithm = 'split', weight = NULL, circular = FALSE, sort.by = NULL, direction = 'out', height = 1, width = 1) { weight <- enquo(weight) weight <- eval_tidy(weight, .N()) sort.by <- enquo(sort.by) sort.by <- eval_tidy(sort.by) hierarchy <- tree_to_hierarchy(graph, direction, sort.by, weight) layout <- switch( algorithm, split = splitTreemap(hierarchy$parent, hierarchy$order, hierarchy$weight, width, height), stop('Unknown algorithm') )[-1, ] layout <- new_data_frame(list( x = layout[, 1] + layout[, 3] / 2, y = layout[, 2] + layout[, 4] / 2, width = layout[, 3], height = layout[, 4], circular = FALSE, leaf = degree(graph, mode = direction) == 0, depth = node_depth(graph, mode = direction) )) extra_data <- as_tibble(graph, active = 'nodes') layout <- cbind(layout, extra_data[, !names(extra_data) %in% names(layout), drop = FALSE]) layout } ggraph/R/layout_dendrogram.R0000644000176200001440000001044313525463414015576 0ustar liggesusers#' Apply a dendrogram layout to layout_tbl_graph #' #' This layout mimics the [igraph::layout_as_tree()] algorithm #' supplied by igraph, but puts all leaves at 0 and builds it up from there, #' instead of starting from the root and building it from there. The height of #' branch points are related to the maximum distance to an edge from the branch #' node, or read from a node variable. #' #' @note This function is not intended to be used directly but by setting #' `layout = 'dendrogram'` in [create_layout()] #' #' @param graph A `tbl_graph` object #' #' @param circular Logical. Should the layout be transformed to a circular #' representation. Defaults to `FALSE`. #' #' @param offset If `circular = TRUE`, where should it begin. Defaults to #' `pi/2` which is equivalent to 12 o'clock. #' #' @param height The node variable holding the height of each node in the #' dendrogram. If `NULL` it will be calculated as the maximal distance to a #' leaf. #' #' @param length An edge parameter giving the length of each edge. The node #' height will be calculated from the maximal length to the root node (ignored #' if `height` does not evaluate to `NULL`) #' #' @param repel Should leafs repel each other relative to the height of their #' common ancestor. Will emphasize clusters #' #' @param ratio The strength of repulsion if `repel = TRUE`. Higher values will #' give more defined clusters #' #' @param direction The direction to the leaves. Defaults to 'out' #' #' @return A data.frame with the columns `x`, `y`, `circular`, `depth` and #' `leaf` as well as any information stored as node variables on the #' tbl_graph #' #' @family layout_tbl_graph_* #' #' @importFrom igraph gorder degree neighbors distances #' @importFrom tidygraph node_is_root #' @importFrom rlang enquo eval_tidy #' layout_tbl_graph_dendrogram <- function(graph, circular = FALSE, offset = pi / 2, height = NULL, length = NULL, repel = FALSE, ratio = 1, direction = 'out') { height <- enquo(height) length <- enquo(length) if (quo_is_null(height)) { if (quo_is_null(length)) { height <- NA } else { length <- eval_tidy(length, .E()) full_lengths <- distances(graph, to = node_is_root(), weights = length) full_lengths[is.infinite(full_lengths)] <- 0 height <- unname(apply(full_lengths, 1, max)) height <- abs(height - max(height)) } } else { height <- eval_tidy(height, .N()) } reverse_dir <- if (direction == 'out') 'in' else 'out' nodes <- new_data_frame(list( x = rep(NA_real_, gorder(graph)), y = height, leaf = degree(graph, mode = direction) == 0 )) if (all(is.na(nodes$y))) { nodes$y <- vapply(seq_len(gorder(graph)), function(i) { max(bfs(graph, i, direction, unreachable = FALSE, order = FALSE, dist = TRUE)$dist, na.rm = TRUE) }, numeric(1)) } if (repel) { pad <- min(nodes$y[nodes$y != 0]) / 2 } else { pad <- 0 } startnode <- which(degree(graph, mode = reverse_dir) == 0) if (length(startnode) < 1) stop('No root nodes in graph') recurse_layout <- function(gr, node, layout, direction, offset = 0) { children <- as.numeric(neighbors(gr, node, direction)) if (length(children) == 0) { layout$x[node] <- offset layout } else { children_missing <- children[is.na(layout$x[children])] for (i in children_missing) { layout <- recurse_layout(gr, i, layout, direction, offset) offset <- if (repel) { max(layout$x[layout$leaf], na.rm = TRUE) + (layout$y[node] + pad) * ratio } else { max(layout$x[layout$leaf], na.rm = TRUE) + 1 + pad } } layout$x[node] <- mean(range(layout$x[children])) layout } } offset <- 0 for (i in startnode) { nodes <- recurse_layout(graph, i, nodes, direction = direction, offset) offset <- max(nodes$x[nodes$leaf], na.rm = TRUE) + 1 } graph <- add_direction(graph, nodes) if (circular) { radial <- radial_trans( r.range = rev(range(nodes$y)), a.range = range(nodes$x), offset = offset ) coords <- radial$transform(nodes$y, nodes$x) nodes$x <- coords$x nodes$y <- coords$y } extra_data <- as_tibble(graph, active = 'nodes') nodes <- cbind(nodes, extra_data[, !names(extra_data) %in% names(nodes), drop = FALSE]) nodes$circular <- circular attr(nodes, 'graph') <- graph nodes } ggraph/R/ggraph-package.R0000644000176200001440000000047213526711473014723 0ustar liggesusers#' @aliases ggraph-package '_PACKAGE' # The following block is used by usethis to automatically manage # roxygen namespace tags. Modify with care! ## usethis namespace: start #' @useDynLib ggraph #' @import ggplot2 tidygraph #' @importFrom Rcpp sourceCpp #' @importFrom rlang .data ## usethis namespace: end NULL ggraph/R/geom_edge_bend.R0000644000176200001440000002236713527431455014774 0ustar liggesusers#' Draw edges as diagonals #' #' This geom draws edges as cubic bezier curves with the control points #' positioned along the elbow edge. It has the appearance of a softened elbow #' edge with the hard angle substituted by a tapered bend. #' #' @inheritSection geom_edge_link Edge variants #' @inheritSection geom_edge_link Edge aesthetic name expansion #' #' @section Aesthetics: #' `geom_edge_bend` and `geom_edge_bend0` understand the following #' aesthetics. Bold aesthetics are automatically set, but can be overridden. #' #' - **x** #' - **y** #' - **xend** #' - **yend** #' - **circular** #' - edge_colour #' - edge_width #' - edge_linetype #' - edge_alpha #' - filter #' #' `geom_edge_bend2` understand the following aesthetics. Bold aesthetics are #' automatically set, but can be overridden. #' #' - **x** #' - **y** #' - **group** #' - **circular** #' - edge_colour #' - edge_width #' - edge_linetype #' - edge_alpha #' - filter #' #' `geom_edge_bend` and `geom_edge_bend2` furthermore takes the following #' aesthetics. #' #' - start_cap #' - end_cap #' - label #' - label_pos #' - label_size #' - angle #' - hjust #' - vjust #' - family #' - fontface #' - lineheight #' #' #' @section Computed variables: #' #' \describe{ #' \item{index}{The position along the path (not computed for the *0 version)} #' } #' #' @inheritParams geom_edge_diagonal #' #' @param strength The strength of the curvature of the bend. `0` will #' result in a straight line while `1` will give a strong arc. #' #' @author Thomas Lin Pedersen #' #' @family geom_edge_* #' #' @examples #' require(tidygraph) #' gr <- create_tree(20, 4) %>% #' mutate(class = sample(letters[1:3], n(), replace = TRUE)) %>% #' activate(edges) %>% #' mutate(class = sample(letters[1:3], n(), replace = TRUE)) #' #' ggraph(gr, 'tree') + #' geom_edge_bend(aes(alpha = stat(index))) #' #' ggraph(gr, 'tree') + #' geom_edge_bend2(aes(colour = node.class)) #' #' ggraph(gr, 'tree') + #' geom_edge_bend0(aes(colour = class)) #' @rdname geom_edge_bend #' @name geom_edge_bend #' NULL #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatBezier #' @export StatEdgeBend <- ggproto('StatEdgeBend', StatBezier, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } data <- remove_loop(data) if (nrow(data) == 0) return(NULL) data$group <- make_unique(data$group) data2 <- data data2$x <- data2$xend data2$y <- data2$yend create_bend(data, data2, params) }, required_aes = c('x', 'y', 'xend', 'yend', 'circular'), default_aes = aes(filter = TRUE), extra_params = c('na.rm', 'flipped', 'n', 'strength') ) #' @rdname geom_edge_bend #' #' @export geom_edge_bend <- function(mapping = NULL, data = get_edges(), position = 'identity', arrow = NULL, strength = 1, flipped = FALSE, n = 100, lineend = 'butt', linejoin = 'round', linemitre = 1, label_colour = 'black', label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = 'rot', force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes( x = x, y = y, xend = xend, yend = yend, circular = circular, group = edge.id )) layer( data = data, mapping = mapping, stat = StatEdgeBend, geom = GeomEdgePath, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, linejoin = linejoin, linemitre = linemitre, na.rm = FALSE, n = n, interpolate = FALSE, flipped = flipped, strength = strength, label_colour = label_colour, label_alpha = label_alpha, label_parse = label_parse, check_overlap = check_overlap, angle_calc = angle_calc, force_flip = force_flip, label_dodge = label_dodge, label_push = label_push, ... ) ) ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatBezier2 #' @export StatEdgeBend2 <- ggproto('StatEdgeBend2', StatBezier2, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } data <- remove_loop2(data) if (nrow(data) == 0) return(NULL) data <- data[order(data$group), ] data2 <- data[c(FALSE, TRUE), ] data <- data[c(TRUE, FALSE), ] create_bend(data, data2, params) }, required_aes = c('x', 'y', 'group', 'circular'), default_aes = aes(filter = TRUE), extra_params = c('na.rm', 'flipped', 'n', 'strength') ) #' @rdname geom_edge_bend #' #' @export geom_edge_bend2 <- function(mapping = NULL, data = get_edges('long'), position = 'identity', arrow = NULL, strength = 1, flipped = FALSE, n = 100, lineend = 'butt', linejoin = 'round', linemitre = 1, label_colour = 'black', label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = 'rot', force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes( x = x, y = y, group = edge.id, circular = circular )) layer( data = data, mapping = mapping, stat = StatEdgeBend2, geom = GeomEdgePath, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, linejoin = linejoin, linemitre = linemitre, na.rm = FALSE, n = n, interpolate = TRUE, flipped = flipped, strength = strength, label_colour = label_colour, label_alpha = label_alpha, label_parse = label_parse, check_overlap = check_overlap, angle_calc = angle_calc, force_flip = force_flip, label_dodge = label_dodge, label_push = label_push, ... ) ) ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatBezier0 #' @export StatEdgeBend0 <- ggproto('StatEdgeBend0', StatBezier0, setup_data = function(data, params) { StatEdgeBend$setup_data(data, params) }, required_aes = c('x', 'y', 'xend', 'yend', 'circular'), default_aes = aes(filter = TRUE), extra_params = c('na.rm', 'flipped', 'strength') ) #' @rdname geom_edge_bend #' #' @export geom_edge_bend0 <- function(mapping = NULL, data = get_edges(), position = 'identity', arrow = NULL, strength = 1, flipped = FALSE, lineend = 'butt', show.legend = NA, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes( x = x, y = y, xend = xend, yend = yend, circular = circular )) layer( data = data, mapping = mapping, stat = StatEdgeBend0, geom = GeomEdgeBezier, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, na.rm = FALSE, strength = strength, flipped = flipped, ... ) ) ) } create_bend <- function(from, to, params) { bezier_start <- seq(1, by = 4, length.out = nrow(from)) from$index <- bezier_start to$index <- bezier_start + 3 data2 <- from data3 <- to data2$index <- bezier_start + 1 data3$index <- bezier_start + 2 if (any(from$circular)) { r0 <- sqrt(from$x[from$circular]^2 + from$y[from$circular]^2) r1 <- sqrt(to$x[to$circular]^2 + to$y[to$circular]^2) root <- r0 == 0 | r1 == 0 dotprod <- from$x * to$x + from$y * to$y crossing <- r0 * r0 / dotprod cross_x <- to$x * crossing cross_y <- to$y * crossing data2$x[from$circular] <- from$x + (cross_x - from$x) * params$strength data2$y[from$circular] <- from$y + (cross_y - from$y) * params$strength data3$x[from$circular] <- to$x + (cross_x - to$x) * params$strength data3$y[from$circular] <- to$y + (cross_y - to$y) * params$strength data2$x[root] <- from$x[root] data2$y[root] <- from$y[root] data3$x[root] <- to$x[root] data3$y[root] <- to$y[root] } if (any(!from$circular)) { if (params$flipped) { h_diff <- from$x[!from$circular] - to$x[!from$circular] w_diff <- from$y[!from$circular] - to$y[!from$circular] data2$y[!from$circular] <- from$y[!from$circular] - w_diff * params$strength data3$x[!from$circular] <- to$x[!from$circular] + h_diff * params$strength } else { h_diff <- from$y[!from$circular] - to$y[!from$circular] w_diff <- from$x[!from$circular] - to$x[!from$circular] data2$x[!from$circular] <- from$x[!from$circular] - w_diff * params$strength data3$y[!from$circular] <- to$y[!from$circular] + h_diff * params$strength } } data <- rbind_dfs(list(from, data2, data3, to)) data[order(data$index), names(data) != 'index'] } ggraph/R/scale_edge_shape.R0000644000176200001440000000301413524253136015303 0ustar liggesusers#' Edge shape scales #' #' This set of scales defines new shape scales for edge geoms equivalent to the #' ones already defined by ggplot2. See [ggplot2::scale_shape()] for #' more information. The different geoms will know whether to use edge scales or #' the standard scales so it is not necessary to write `edge_shape` in #' the call to the geom - just use `shape`. #' #' @param guide Guide to use for this scale. #' #' @return A ggproto object inheriting from `Scale` #' #' @family scale_edge_* #' #' @name scale_edge_shape #' @rdname scale_edge_shape #' NULL #' @rdname scale_edge_shape #' #' @inheritParams ggplot2::scale_shape #' #' @importFrom scales shape_pal #' @export scale_edge_shape <- function(..., solid = TRUE) { discrete_scale('edge_shape', 'shape_d', shape_pal(solid), ...) } #' @rdname scale_edge_shape #' #' @export scale_edge_shape_discrete <- scale_edge_shape #' @rdname scale_edge_shape #' #' @export scale_edge_shape_continuous <- function(...) { stop('A continuous variable can not be mapped to shape', call. = FALSE) } #' @rdname scale_edge_shape #' #' @inheritParams ggplot2::scale_shape_manual #' #' @export scale_edge_shape_manual <- function(..., values) { manual_scale('edge_shape', values, ...) } #' @rdname scale_edge_shape #' #' @inheritParams ggplot2::scale_shape_identity #' #' @importFrom scales identity_pal #' @export scale_edge_shape_identity <- function(..., guide = 'none') { sc <- discrete_scale('edge_shape', 'identity', identity_pal(), ..., guide = guide, ScaleDiscreteIdentity ) sc } ggraph/R/geom_node_arc_bar.R0000644000176200001440000000406013524341060015451 0ustar liggesusers#' Show nodes as thick arcs #' #' This geom is equivalent in functionality to [ggforce::geom_arc_bar()] #' and allows for plotting of nodes as arcs with an inner and outer radius #' scaled by the coordinate system. Its main use is currently in sunburst plots #' as created with circular partition layouts #' #' @section Aesthetics: #' `geom_node_point` understand the following aesthetics. Bold aesthetics are #' automatically set, but can be overridden. #' #' - **x0** #' - **y0** #' - **r0** #' - **r** #' - **start** #' - **end** #' - alpha #' - colour #' - fill #' - shape #' - size #' - stroke #' - filter #' #' @inheritParams ggforce::geom_circle #' #' @param mapping Set of aesthetic mappings created by [ggplot2::aes()] #' or [ggplot2::aes_()]. By default x and y are mapped to x0 and y0 in #' the node data. #' #' @author Thomas Lin Pedersen #' #' @family geom_node_* #' #' @examples #' require(tidygraph) #' gr <- tbl_graph(flare$vertices, flare$edges) #' ggraph(gr, 'partition', circular = TRUE, weight = size) + #' geom_node_arc_bar() #' @export #' @importFrom ggforce GeomArcBar #' geom_node_arc_bar <- function(mapping = NULL, data = NULL, position = 'identity', show.legend = NA, ...) { mapping <- aes_intersect(mapping, aes(x0 = 0, y0 = 0, r0 = r0, r = r, start = start, end = end)) layer( data = data, mapping = mapping, stat = StatNodeArcBar, geom = GeomArcBar, position = position, show.legend = show.legend, inherit.aes = FALSE, params = list(na.rm = FALSE, ...) ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatArcBar #' @export StatNodeArcBar <- ggproto('StatNodeArcBar', StatArcBar, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } if (nrow(data) == 0) return(NULL) data }, default_aes = aes(filter = TRUE) ) ggraph/R/geom_edge_elbow.R0000644000176200001440000003522013527431601015155 0ustar liggesusers#' Draw edges as elbows #' #' This geom draws edges as an angle in the same manner as known from classic #' dendrogram plots of hierarchical clustering results. In case a circular #' transformation has been applied the first line segment will be drawn as an #' arc as expected. This geom is only applicable to layouts that return a #' direction for the edges (currently [layout_tbl_graph_dendrogram()], #' [layout_tbl_graph_partition()] and #' [layout_tbl_graph_igraph()] with the `"tree"` algorithm). #' #' @inheritSection geom_edge_link Edge variants #' @inheritSection geom_edge_link Edge aesthetic name expansion #' #' @section Aesthetics: #' `geom_edge_elbow` and `geom_edge_elbow0` understand the following #' aesthetics. Bold aesthetics are automatically set, but can be overridden. #' #' - **x** #' - **y** #' - **xend** #' - **yend** #' - **circular** #' - **direction** #' - edge_colour #' - edge_width #' - edge_linetype #' - edge_alpha #' - filter #' #' `geom_edge_elbow2` understand the following aesthetics. Bold aesthetics are #' automatically set, but can be overridden. #' #' - **x** #' - **y** #' - **group** #' - **circular** #' - **direction** #' - edge_colour #' - edge_width #' - edge_linetype #' - edge_alpha #' - filter #' #' `geom_edge_elbow` and `geom_edge_elbow2` furthermore takes the following #' aesthetics. #' #' - start_cap #' - end_cap #' - label #' - label_pos #' - label_size #' - angle #' - hjust #' - vjust #' - family #' - fontface #' - lineheight #' #' @section Computed variables: #' #' \describe{ #' \item{index}{The position along the path (not computed for the *0 version)} #' } #' #' @inheritParams geom_edge_link #' @inheritParams ggplot2::geom_path #' @param strength How bend the elbow should be. 1 will give a right angle, #' while `0` will give a straight line. Ignored for circular layouts #' @inheritParams geom_edge_diagonal #' #' @author Thomas Lin Pedersen #' #' @family geom_edge_* #' #' @examples #' require(tidygraph) #' irisDen <- hclust(dist(iris[1:4], method = 'euclidean'), method = 'ward.D2') %>% #' as_tbl_graph() %>% #' mutate(class = sample(letters[1:3], n(), TRUE)) %>% #' activate(edges) %>% #' mutate(class = sample(letters[1:3], n(), TRUE)) #' #' ggraph(irisDen, 'dendrogram', circular = TRUE) + #' geom_edge_elbow(aes(alpha = stat(index))) #' #' ggraph(irisDen, 'dendrogram') + #' geom_edge_elbow2(aes(colour = node.class)) #' #' ggraph(irisDen, 'dendrogram', height = height) + #' geom_edge_elbow0(aes(colour = class)) #' @rdname geom_edge_elbow #' @name geom_edge_elbow #' NULL #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce radial_trans #' @export StatEdgeElbow <- ggproto('StatEdgeElbow', Stat, compute_panel = function(data, scales, flipped = FALSE, n = 100, strength = 1) { data$group <- make_unique(data$group) if (data$circular[1] && n %% 2 == 1) { n <- n + 1 } if (!data$circular[1] && n %% 2 == 0) { n <- n + 1 } index <- seq(0, 1, length.out = n) if (any(data$circular)) { if (strength != 1) warning('strength is ignored for circular elbow edges', call. = FALSE) circ_id <- which(data$circular) data_circ <- data[circ_id, ] radial <- radial_trans(c(0, 1), c(2 * pi, 0), pad = 0, offset = 0) start <- atan2(data_circ$y, data_circ$x) radii_start <- sqrt(data_circ$x^2 + data_circ$y^2) radii_end <- sqrt(data_circ$xend^2 + data_circ$yend^2) angel_diff <- (data_circ$x * data_circ$xend + data_circ$y * data_circ$yend) / (radii_start * radii_end) angel_diff[is.nan(angel_diff)] <- 0 angel_diff <- suppressWarnings(acos(angel_diff)) angel_diff[is.nan(angel_diff)] <- 0 end <- start + ifelse(data_circ$direction == 'left', -angel_diff, angel_diff ) angles <- unlist(Map(seq, from = start, to = end, length.out = n / 2)) radii <- rep(sqrt(data$y[circ_id]^2 + data$x[circ_id]^2), each = n / 2) path_circ <- radial$transform(r = radii, a = angles) path_circ$.orig_index <- rep(circ_id, each = n / 2) path_circ$group <- rep(data_circ$group, each = n / 2) path_circ$index <- rep(index[seq_len(n / 2)], length(circ_id)) radii_rel <- radii_start / radii_end elbow_x <- data_circ$xend * radii_rel elbow_y <- data_circ$yend * radii_rel elbow_x <- unlist(Map(seq, from = elbow_x, to = data_circ$xend, length.out = n / 2 )) elbow_y <- unlist(Map(seq, from = elbow_y, to = data_circ$yend, length.out = n / 2 )) path_circ <- rbind_dfs(list( path_circ, new_data_frame(list( x = elbow_x, y = elbow_y, .orig_index = path_circ$.orig_index, group = path_circ$group, index = rep( index[seq_len(n / 2) + n / 2], length(circ_id) ) )) )) path_circ <- cbind(path_circ, data[path_circ$.orig_index, !names(data) %in% c('x', 'y', 'xend', 'yend')]) path_circ$.orig_index <- NULL } if (any(!data$circular)) { path_lin <- lapply(which(!data$circular), function(i) { if (flipped) { path <- new_data_frame(list( x = approx(c(data$x[i], data$xend[i] + (data$x[i] - data$xend[i]) * strength, data$xend[i]), n = n )$y, y = approx(c(data$y[i], data$yend[i], data$yend[i]), n = n )$y, group = data$group[i], index = index )) } else { path <- new_data_frame(list( x = approx(c(data$x[i], data$xend[i], data$xend[i]), n = n )$y, y = approx(c(data$y[i], data$yend[i] + (data$y[i] - data$yend[i]) * strength, data$yend[i]), n = n )$y, group = data$group[i], index = index )) } cbind(path, data[rep(i, nrow(path)), !names(data) %in% c('x', 'y', 'xend', 'yend')]) }) path_lin <- rbind_dfs(path_lin) if (any(data$circular)) { paths <- rbind_dfs(list(path_lin, path_circ)) } else { paths <- path_lin } } else { paths <- path_circ } paths[order(paths$group), ] }, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } data <- remove_loop(data) if (nrow(data) == 0) return(NULL) data }, default_aes = aes(filter = TRUE), required_aes = c('x', 'y', 'xend', 'yend', 'circular', 'direction') ) #' @rdname geom_edge_elbow #' #' @export geom_edge_elbow <- function(mapping = NULL, data = get_edges(), position = 'identity', arrow = NULL, strength = 1, flipped = FALSE, n = 100, lineend = 'butt', linejoin = 'round', linemitre = 1, label_colour = 'black', label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = 'rot', force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes( x = x, y = y, xend = xend, yend = yend, circular = circular, direction = direction, group = edge.id )) layer( data = data, mapping = mapping, stat = StatEdgeElbow, geom = GeomEdgePath, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, linejoin = linejoin, linemitre = linemitre, na.rm = FALSE, n = n, interpolate = FALSE, flipped = flipped, strength = strength, label_colour = label_colour, label_alpha = label_alpha, label_parse = label_parse, check_overlap = check_overlap, angle_calc = angle_calc, force_flip = force_flip, label_dodge = label_dodge, label_push = label_push, ... ) ) ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @export StatEdgeElbow2 <- ggproto('StatEdgeElbow2', Stat, compute_panel = function(data, scales, flipped = FALSE, n = 100, strength = 1) { pos_cols <- c('x', 'y', 'group', 'circular', 'direction', 'PANEL') data <- data[order(data$group), ] pos_data <- cbind(data[c(TRUE, FALSE), pos_cols], data[ c(FALSE, TRUE), c('x', 'y') ]) pos_data$group <- make_unique(pos_data$group) names(pos_data) <- c(pos_cols, 'xend', 'yend') new_data <- StatEdgeElbow$compute_panel(pos_data, scales, flipped, n, strength) extra_cols <- !names(data) %in% pos_cols index <- match(pos_data$group, new_data$group) index <- as.vector(matrix(c(index, index + 1), nrow = 2, byrow = T)) new_data$.interp <- TRUE new_data$.interp[index] <- FALSE if (sum(extra_cols) != 0) { for (i in names(data)[extra_cols]) { new_data[[i]] <- NA new_data[[i]][index] <- data[[i]] } } new_data }, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } data <- remove_loop2(data) if (nrow(data) == 0) return(NULL) data }, default_aes = aes(filter = TRUE), required_aes = c('x', 'y', 'group', 'circular', 'direction') ) #' @rdname geom_edge_elbow #' #' @export geom_edge_elbow2 <- function(mapping = NULL, data = get_edges('long'), position = 'identity', arrow = NULL, strength = 1, flipped = FALSE, n = 100, lineend = 'butt', linejoin = 'round', linemitre = 1, label_colour = 'black', label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = 'rot', force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes( x = x, y = y, group = edge.id, circular = circular, direction = direction )) layer( data = data, mapping = mapping, stat = StatEdgeElbow2, geom = GeomEdgePath, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, linejoin = linejoin, linemitre = linemitre, na.rm = FALSE, n = n, interpolate = TRUE, flipped = flipped, strength = strength, label_colour = label_colour, label_alpha = label_alpha, label_parse = label_parse, check_overlap = check_overlap, angle_calc = angle_calc, force_flip = force_flip, label_dodge = label_dodge, label_push = label_push, ... ) ) ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @export StatEdgeElbow0 <- ggproto('StatEdgeElbow0', Stat, compute_panel = function(data, scales, flipped = FALSE, strength = 1) { data$group <- make_unique(data$group) if (any(data$circular)) { if (strength != 1) warning('strength is ignored for circular elbow edges', call. = FALSE) circ_id <- which(data$circular) data_circ <- data[circ_id, ] radial <- radial_trans(c(0, 1), c(2 * pi, 0), pad = 0, offset = 0) start <- atan2(data_circ$y, data_circ$x) angel_diff <- (data_circ$x * data_circ$xend + data_circ$y * data_circ$yend) / (sqrt(data_circ$x^2 + data_circ$y^2) * sqrt(data_circ$xend^2 + data_circ$yend^2)) angel_diff[is.nan(angel_diff)] <- 0 angel_diff <- suppressWarnings(acos(angel_diff)) angel_diff[is.nan(angel_diff)] <- 0 end <- start + ifelse(data_circ$direction == 'left', -angel_diff, angel_diff ) angles <- unlist(Map(seq, from = start, to = end, length.out = 50)) radii <- rep(sqrt(data$y[circ_id]^2 + data$x[circ_id]^2), each = 50) path_circ <- radial$transform(r = radii, a = angles) path_circ$.orig_index <- rep(circ_id, each = 50) path_circ$group <- rep(data_circ$group, each = 50) path_circ <- rbind_dfs(list( path_circ, new_data_frame(list( x = data$xend[circ_id], y = data$yend[circ_id], .orig_index = circ_id, group = data_circ$group )) )) path_circ <- cbind(path_circ, data[path_circ$.orig_index, !names(data) %in% c('x', 'y', 'xend', 'yend')]) path_circ$.orig_index <- NULL } if (any(!data$circular)) { path_lin <- lapply(which(!data$circular), function(i) { if (flipped) { path <- new_data_frame(list( x = c(data$x[i], data$xend[i] + (data$x[i] - data$xend[i]) * strength, data$xend[i]), y = c(data$y[i], data$yend[i], data$yend[i]), group = data$group )) } else { path <- new_data_frame(list( x = c(data$x[i], data$xend[i], data$xend[i]), y = c(data$y[i], data$yend[i] + (data$y[i] - data$yend[i]) * strength, data$yend[i]), group = data$group[i] )) } cbind(path, data[rep(i, nrow(path)), !names(data) %in% c('x', 'y', 'xend', 'yend')]) }) path_lin <- rbind_dfs(path_lin) if (any(data$circular)) { paths <- rbind_dfs(list(path_lin, path_circ)) } else { paths <- path_lin } } else { paths <- path_circ } paths[order(paths$group), ] }, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } if (nrow(data) == 0) return(NULL) data }, default_aes = aes(filter = TRUE), required_aes = c('x', 'y', 'xend', 'yend', 'circular', 'direction') ) #' @rdname geom_edge_elbow #' #' @export geom_edge_elbow0 <- function(mapping = NULL, data = get_edges(), position = 'identity', arrow = NULL, flipped = FALSE, lineend = 'butt', show.legend = NA, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes( x = x, y = y, xend = xend, yend = yend, circular = circular, direction = direction )) layer( data = data, mapping = mapping, stat = StatEdgeElbow0, geom = GeomEdgePath, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, na.rm = FALSE, interpolate = FALSE, flipped = flipped, ... ) ) ) } ggraph/R/geom_conn_bundle.R0000644000176200001440000001724213526604405015356 0ustar liggesusers#' Create hierarchical edge bundles between node connections #' #' Hierarchical edge bundling is a technique to introduce some order into the #' hairball structure that can appear when there's a lot of overplotting and #' edge crossing in a network plot. The concept requires that the network has #' an intrinsic hierarchical structure that defines the layout but is not shown. #' Connections between points (that is, not edges) are then drawn so that they #' loosely follows the underlying hierarchical structure. This results in a #' flow-like structure where lines that partly move in the same direction will #' be bundled together. #' #' @note In order to avoid excessive typing edge aesthetic names are #' automatically expanded. Because of this it is not necessary to write #' `edge_colour` within the `aes()` call as `colour` will #' automatically be renamed appropriately. #' #' @section Aesthetics: #' geom_conn_bundle* understands the following aesthetics. Bold aesthetics are #' automatically set, but can be overridden. #' \itemize{ #' \item{\strong{x}} #' \item{\strong{y}} #' \item{\strong{group}} #' \item{\strong{circular}} #' \item{edge_colour} #' \item{edge_width} #' \item{edge_linetype} #' \item{edge_alpha} #' \item{filter} #' } #' #' @section Computed variables: #' #' \describe{ #' \item{index}{The position along the path (not computed for the *0 version)} #' } #' #' #' @inheritParams geom_edge_link #' @inheritParams ggplot2::geom_path #' #' @param data The result of a call to [get_con()] #' #' @param tension How "loose" should the bundles be. 1 will give very tight #' bundles, while 0 will turn of bundling completely and give straight lines. #' Defaults to 0.8 #' #' @author Thomas Lin Pedersen #' #' @family geom_conn_* #' #' @references #' Holten, D. (2006). *Hierarchical edge bundles: visualization #' of adjacency relations in hierarchical data.* IEEE Transactions on #' Visualization and Computer Graphics, **12**(5), 741-748. #' #' #' @examples #' # Create a graph of the flare class system #' library(tidygraph) #' flareGraph <- tbl_graph(flare$vertices, flare$edges) %>% #' mutate( #' class = map_bfs_chr(node_is_root(), .f = function(node, dist, path, ...) { #' if (dist <= 1) { #' return(shortName[node]) #' } #' path$result[[nrow(path)]] #' }) #' ) #' importFrom <- match(flare$imports$from, flare$vertices$name) #' importTo <- match(flare$imports$to, flare$vertices$name) #' #' # Use class inheritance for layout but plot class imports as bundles #' ggraph(flareGraph, 'dendrogram', circular = TRUE) + #' geom_conn_bundle(aes(colour = stat(index)), #' data = get_con(importFrom, importTo), #' edge_alpha = 0.25 #' ) + #' geom_node_point(aes(filter = leaf, colour = class)) + #' scale_edge_colour_distiller('', direction = 1, guide = 'edge_direction') + #' coord_fixed() + #' ggforce::theme_no_axes() #' @rdname geom_conn_bundle #' @name geom_conn_bundle #' NULL #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatBspline #' @export StatConnBundle <- ggproto('StatConnBundle', StatBspline, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } if (nrow(data) == 0) return(NULL) relax(data, params$tension) }, setup_params = function(data, params) { if (is.null(params$tension)) { params$tension <- 0.8 } if (params$tension < 0) params$tension <- 0 if (params$tension > 1) params$tension <- 1 params }, required_aes = c('x', 'y'), default_aes = aes(filter = TRUE), extra_params = c('na.rm', 'n', 'tension', 'type') ) #' @rdname geom_conn_bundle #' #' @export geom_conn_bundle <- function(mapping = NULL, data = get_con(), position = 'identity', arrow = NULL, lineend = 'butt', show.legend = NA, n = 100, tension = 0.8, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes(x = x, y = y, group = con.id)) layer( data = data, mapping = mapping, stat = StatConnBundle, geom = GeomEdgePath, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, na.rm = FALSE, n = n, interpolate = FALSE, tension = tension, type = 'clamped', ... ) ) ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatBspline2 #' @export StatConnBundle2 <- ggproto('StatConnBundle2', StatBspline2, setup_data = function(data, params) { StatConnBundle$setup_data(data, params) }, setup_params = function(data, params) { StatConnBundle$setup_params(data, params) }, required_aes = c('x', 'y'), default_aes = aes(filter = TRUE), extra_params = c('na.rm', 'n', 'tension', 'type') ) #' @rdname geom_conn_bundle #' #' @export geom_conn_bundle2 <- function(mapping = NULL, data = get_con(), position = 'identity', arrow = NULL, lineend = 'butt', show.legend = NA, n = 100, tension = 0.8, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes(x = x, y = y, group = con.id)) layer( data = data, mapping = mapping, stat = StatConnBundle2, geom = GeomEdgePath, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, na.rm = FALSE, n = n, interpolate = TRUE, tension = tension, type = 'clamped', ... ) ) ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @export StatConnBundle0 <- ggproto('StatConnBundle0', StatIdentity, setup_data = function(data, params) { StatConnBundle$setup_data(data, params) }, setup_params = function(data, params) { StatConnBundle$setup_params(data, params) }, required_aes = c('x', 'y'), default_aes = aes(filter = TRUE), extra_params = c('na.rm', 'n', 'tension', 'type') ) #' @rdname geom_conn_bundle #' #' @export geom_conn_bundle0 <- function(mapping = NULL, data = get_con(), position = 'identity', arrow = NULL, lineend = 'butt', show.legend = NA, tension = 0.8, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes(x = x, y = y, group = con.id)) layer( data = data, mapping = mapping, stat = StatConnBundle0, geom = GeomEdgeBspline, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, na.rm = FALSE, tension = tension, type = 'clamped', ... ) ) ) } #' @importFrom utils head tail relax <- function(data, strength) { formula <- function(p, startInd, endInd, pathLengths) { start <- rep(p[startInd], pathLengths) range <- rep(p[endInd] - p[startInd], pathLengths) ind <- unlist(lapply(pathLengths, seq_len)) - 1 length <- rep(pathLengths, pathLengths) strength * p + (1 - strength) * (start + (ind / (length - 1)) * range) } idInds <- split(seq_len(nrow(data)), data$group) pathLengths <- lengths(idInds) startInd <- vapply(idInds, head, integer(1), n = 1) endInd <- vapply(idInds, tail, integer(1), n = 1) data$x <- formula(data$x, startInd, endInd, pathLengths) data$y <- formula(data$y, startInd, endInd, pathLengths) data } ggraph/R/layout.R0000644000176200001440000000271513524245765013405 0ustar liggesusers#' @rdname ggraph #' #' @aliases layout_ggraph #' #' @export create_layout <- function(graph, layout, circular, ...) { UseMethod('create_layout', graph) } #' @rdname ggraph #' @export create_layout.default <- function(graph, layout, ...) { graph <- tryCatch(as_tbl_graph(graph), error = function(e) { stop('No layout function defined for objects of class ', class(graph), call. = FALSE) }) create_layout(graph, layout, ...) } #' @rdname ggraph #' @export create_layout.layout_ggraph <- function(graph, ...) { graph } #' @export as.data.frame.layout_ggraph <- function(x, ...) { extra_attr <- names(attributes(x)) extra_attr <- extra_attr[!extra_attr %in% c('names', 'row.names')] attributes(x)[extra_attr] <- NULL class(x) <- 'data.frame' x } check_layout <- function(layout) { if (!is.data.frame(layout)) { stop('layout must be a data.frame', call. = FALSE) } if (!(is.numeric(layout$x) && is.numeric(layout$y))) { stop('layout must contain numeric `x` and `y` columns', call. = FALSE) } graph <- attr(layout, 'graph') if (!is.tbl_graph(graph)) { stop('layout must have a tbl_graph as the `graph` attribute', call. = FALSE) } if (nrow(layout) != gorder(graph)) { stop('layout must contain the same number of rows as nodes', call. = FALSE) } if (!'circular' %in% names(layout)) { layout$circular <- FALSE } if (!is.logical(layout$circular)) { stop('circular column must be logical', call. = FALSE) } layout } ggraph/R/edges.R0000644000176200001440000001736413526574472013167 0ustar liggesusers#' Create edge extractor function #' #' This function returns another function that can extract edges from a #' ggraph_layout object. The functionality of the returned function is decided #' by the arguments to `get_edges`. The need for `get_edges` is mainly to #' pass to the `data` argument of the different `geom_edge_*` #' functions in order to present them with the right kind of data. In general #' each `geom_edge_*` has the default set correctly so there is only need #' to modify the data argument if parallel edges should be collapsed. #' #' @details #' There are two types of return formats possible for the result of the returned #' function: #' #' \describe{ #' \item{short}{In this format each edge is described in one line in the #' format expected for [ggplot2::geom_segment()], that is, the start #' node position is encoded in the `x` and `y` column and the end #' node position is encoded in the `xend` and `yend` column. If node #' parameters are added to the edge the name of the parameters will be #' prefixed with `node1.` for the start node and `node2.` for the #' end node.} #' \item{long}{In this format each edge consists of two rows with matching #' `edge.id` value. The start and end position are both encoded in the #' `x` and `y` column. The relative position of the rows determines #' which is the start and end node, the first occurring being the start node. #' If node parameters are added to the edge data the name of the parameters #' will be prefixed with `node.`.} #' } #' #' Node parameters are automatically added so it is possible to format edge #' aesthetics according to start or end node parameters, or interpolate edge #' aesthetics between start and end node parameters. Node parameters will be #' prefixed to avoid name clash with edge parameters. The prefix depends on the #' format (see above). #' #' If the graph is not simple (it contains at most one edge between each node #' pair) it can be collapsed so either all edges between two nodes or all edges #' of the same direction between two nodes are merged. The edge parameters are #' taken from the first occurring edge, so if some more sophisticated summary is #' needed it is suggested that the graph be tidied up before plotting with #' ggraph. #' #' @param format Either `'short'` (the default) or `'long'`. See #' details for a descriptions of the differences #' #' @param collapse Either `'none'` (the default), `'all'` or #' `'direction'`. Specifies whether parallel edges should be merged. See #' details for more information #' #' @param ... Additional data that will be cbind'ed together with the returned #' edge data. #' #' @return A data.frame with columns dependent on format as well as the graph #' type. In addition to the columns discussed in the details section, #' the data.frame will always contain the columns `from`, `to` and #' `circular`, the two former giving the indexes of the start and end node #' and the latter if the layout is circular (needed for correct formatting of #' some `geom_edge_*`). The graph dependent information is: #' #' \describe{ #' \item{dendrogram}{A `label` column will hold the value of the #' `edgetext` attribute. In addition any value stored in the #' `edgePar` attribute will be added. Lastly a `direction` column #' will hold the relative position between the start and end nodes (needed for #' correct formatting of [geom_edge_elbow()]).} #' \item{igraph}{All edge attributes of the original graph object is added as #' columns to the data.frame} #' } #' #' @family extractors #' #' @export #' get_edges <- function(format = 'short', collapse = 'none', ...) { if (!collapse %in% c('none', 'all', 'direction')) { stop('Collapse must be either "none", "all" or "direction"') } function(layout) { edges <- collect_edges(layout) edges <- switch( collapse, none = edges, all = collapse_all_edges(edges), direction = collapse_dir_edges(edges) ) edges <- switch( format, short = format_short_edges(edges, layout), long = format_long_edges(edges, layout), stop('Unknown format. Use either "short" or "long"') ) edges <- do.call( cbind, c( list(edges), lapply(list(...), rep, length.out = nrow(edges)), list(stringsAsFactors = FALSE) ) ) attr(edges, 'type_ggraph') <- 'edge_ggraph' edges } } #' @rdname internal_extractors #' @export collect_edges <- function(layout) { UseMethod('collect_edges', layout) } collect_edges.default <- function(layout) { attr(layout, 'edges') } check_short_edges <- function(edges) { if (!inherits(edges, 'data.frame')) { stop('edges must by of class data.frame', call. = FALSE) } if (!all(c('from', 'to', 'x', 'y', 'xend', 'yend', 'circular', 'edge.id') %in% names(edges))) { stop('edges must contain the columns from, to, x, y, xend, yend, circular, and edge.id', call. = FALSE) } if (!is.logical(edges$circular)) { stop('circular column must be logical', call. = FALSE) } edges } check_long_edges <- function(edges) { if (!inherits(edges, 'data.frame')) { stop('edges must by of class data.frame', call. = FALSE) } if (!all(c('edge.id', 'node', 'x', 'y', 'circular') %in% names(edges))) { stop('edges must contain the columns edge.id, node, x, y and circular', call. = FALSE) } if (!all(range(table(edges$edge.id)) == 2)) { stop('Each edge must consist of two rows') } if (!is.logical(edges$circular)) { stop('circular column must be logical', call. = FALSE) } edges } add_edge_coordinates <- function(edges, layout) { edges$x <- layout$x[edges$from] edges$y <- layout$y[edges$from] edges$xend <- layout$x[edges$to] edges$yend <- layout$y[edges$to] edges } format_short_edges <- function(edges, layout) { edges <- add_edge_coordinates(edges, layout) nodes1 <- layout[edges$from, , drop = FALSE] names(nodes1) <- paste0('node1.', names(nodes1)) nodes2 <- layout[edges$to, , drop = FALSE] names(nodes2) <- paste0('node2.', names(nodes2)) edges <- cbind(edges, nodes1, nodes2) rownames(edges) <- NULL edges$edge.id <- seq_len(nrow(edges)) check_short_edges(edges) } format_long_edges <- function(edges, layout) { from <- cbind( edge.id = seq_len(nrow(edges)), node = edges$from, layout[edges$from, c('x', 'y')], edges ) to <- cbind( edge.id = seq_len(nrow(edges)), node = edges$to, layout[edges$to, c('x', 'y')], edges ) edges <- rbind_dfs(list(from, to)) node <- layout[edges$node, , drop = FALSE] names(node) <- paste0('node.', names(node)) edges <- cbind(edges, node) rownames(edges) <- NULL check_long_edges(edges[order(edges$edge.id), ]) } complete_edge_aes <- function(aesthetics) { if (is.null(aesthetics)) { return(aesthetics) } if (any(names(aesthetics) == 'color')) { names(aesthetics)[names(aesthetics) == 'color'] <- 'colour' } expand_edge_aes(aesthetics) } expand_edge_aes <- function(x) { short_names <- names(x) %in% c( 'colour', 'color', 'fill', 'linetype', 'shape', 'size', 'width', 'alpha' ) names(x)[short_names] <- paste0('edge_', names(x)[short_names]) x } #' @importFrom dplyr %>% group_by top_n ungroup collapse_all_edges <- function(edges) { from <- pmin(edges$from, edges$to) to <- pmax(edges$to, edges$from) id <- paste(from, to, sep = '-') if (anyDuplicated(id)) { edges$.id <- id edges <- edges %>% group_by(.data$.id) %>% top_n(1) %>% ungroup() } as.data.frame(edges, stringsAsFactors = FALSE) } #' @importFrom dplyr %>% group_by top_n ungroup collapse_dir_edges <- function(edges) { id <- paste(edges$from, edges$to, sep = '-') if (anyDuplicated(id)) { edges$.id <- id edges <- edges %>% group_by(.data$.id) %>% top_n(1) %>% ungroup() } as.data.frame(edges, stringsAsFactors = FALSE) } ggraph/R/aaa.R0000644000176200001440000000252713527431322012600 0ustar liggesusersutils::globalVariables(c( 'angle', 'center_size', 'circular', 'con.id', 'direction', 'edge.id', 'edge_x', 'end', 'from', 'height', 'r', 'r0', 'section', 'start', 'to', 'width', 'x', 'xend', 'xmin', 'xmax', 'y', 'yend' )) #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @export StatFilter <- ggproto('StatFilter', StatIdentity, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } data }, default_aes = aes(filter = TRUE) ) aes_intersect <- function(aes1, aes2) { aes <- c(as.list(aes1), aes2[!names(aes2) %in% names(aes1)]) class(aes) <- 'uneval' aes } data_type <- function(data) { type <- attr(data, 'type_ggraph') if (is.null(type)) { if (inherits(data, 'layout_ggraph')) { return('node_ggraph') } else { return('other') } } type } # non-orderaltering version of make.unique make_unique <- function(x, sep = '.') { if (!anyDuplicated(x)) return(x) groups <- match(x, unique(x)) suffix <- unsplit(lapply(split(x, groups), seq_along), groups) max_chars <- nchar(max(suffix)) suffix_format <- paste0('%0', max_chars, 'd') paste0(x, sep, sprintf(suffix_format, suffix)) } ggraph/R/geom_node_range.R0000644000176200001440000000245713525247536015202 0ustar liggesusers#' Show nodes as a line spanning a horizontal range #' #' This geom is most useful together with the [fabric][layout_tbl_graph_fabric] #' layout for showing the horizontal span of each node. #' #' @section Aesthetics: #' `geom_node_point` understand the following aesthetics. Bold aesthetics are #' automatically set, but can be overridden. #' #' - **x** #' - **xend** #' - **y** #' - **yend** #' - alpha #' - colour #' - linetype #' - size #' - filter #' #' @inheritParams ggplot2::geom_linerange #' #' @param mapping Set of aesthetic mappings created by [ggplot2::aes()] #' or [ggplot2::aes_()]. By default x is mapped to xmin, xend is mapped to xmax #' and y and yend are mapped to y in the node data. #' #' @author Thomas Lin Pedersen #' #' @family geom_node_* #' #' @examples #' require(tidygraph) #' gr <- as_tbl_graph(highschool) #' #' ggraph(gr, layout = 'fabric') + #' geom_node_range() #' @export #' geom_node_range <- function(mapping = NULL, data = NULL, position = 'identity', show.legend = NA, ...) { mapping <- aes_intersect(mapping, aes(x = xmin, xend = xmax, y = y, yend = y)) layer( data = data, mapping = mapping, stat = StatFilter, geom = GeomSegment, position = position, show.legend = show.legend, inherit.aes = FALSE, params = list(na.rm = FALSE, ...) ) } ggraph/R/layout_stress.R0000644000176200001440000000637113617226142015001 0ustar liggesusers#' Place nodes using stress majorisation #' #' This layout is related to the stress-minimization algorithm known as #' Kamada-Kawai (available as the 'kk' layout), but uses another optimization #' strategy. It generally have better runtime, quality, and stability compared #' to the Kamada-Kawai layout and is thus generally preferred. The sparse #' version of the layout have better performance (especially on larger networks) #' at the expense of layout quality, but will generally outperform many other #' algorithms for large graphs in both runtime and quality (e.g. the 'drl' #' layout from igraph). #' #' @param graph a tbl_graph object #' @param weights An expression evaluated on the edge data to provide edge #' weights for the layout. Currently ignored for the sparse version #' @param pivots The number of pivot nodes. #' @param niter number of iterations during stress optimization #' @param tolerance stopping criterion for stress optimization #' @param mds should an MDS layout be used as initial layout (default: TRUE) #' @param bbox constrain dimension of output. Only relevant to determine the #' placement of disconnected graphs. #' @param circular ignored #' #' @return A data.frame with the columns `x`, `y`, `circular` as #' well as any information stored as node variables in the tbl_graph object. #' #' @references #' Gansner, E. R., Koren, Y., & North, S. (2004). *Graph drawing by stress #' majorization.* In International Symposium on Graph Drawing (pp. 239-250). Springer, Berlin, Heidelberg. #' #' Ortmann, M. and Klimenta, M. and Brandes, U. (2016). *A Sparse Stress Model.* https://arxiv.org/pdf/1608.08909.pdf #' #' @family layout_tbl_graph_* #' #' @author The underlying algorithm is implemented in the graphlayouts package #' by David Schoch #' #' @importFrom graphlayouts layout_with_stress #' @importFrom rlang eval_tidy enquo #' layout_tbl_graph_stress <- function(graph, weights = NULL, niter = 500, tolerance = 1e-4, mds = TRUE, bbox = 50, circular = FALSE) { weights <- eval_tidy(enquo(weights), .E()) if (is.null(weights)) { weights <- NA } else { weights <- 1 / weights } xy <- layout_with_stress(graph, weights = weights, iter = niter, tol = tolerance, mds = mds, bbox = bbox) nodes <- new_data_frame(list(x = xy[,1],y = xy[,2])) nodes$circular <- FALSE extra_data <- as_tibble(graph, active = 'nodes') nodes <- cbind(nodes, extra_data[, !names(extra_data) %in% names(nodes), drop = FALSE]) nodes } #' @rdname layout_tbl_graph_stress #' @importFrom graphlayouts layout_with_sparse_stress layout_tbl_graph_sparse_stress <- function(graph, pivots, weights = NULL, niter = 500, circular = FALSE) { weights <- eval_tidy(enquo(weights)) if (!is.null(weights)) { warning('weights is currently ignored for sparse stress layouts', call. = FALSE) } xy <- layout_with_sparse_stress(graph, pivots = pivots, weights = weights, iter = niter) nodes <- new_data_frame(list(x = xy[,1], y = xy[,2])) nodes$circular <- FALSE extra_data <- as_tibble(graph, active = 'nodes') nodes <- cbind(nodes, extra_data[, !names(extra_data) %in% names(nodes), drop = FALSE]) nodes } ggraph/R/geom_edge_hive.R0000644000176200001440000002357713527431477015027 0ustar liggesusers#' Draw edges in hive plots #' #' This geom is only intended for use together with the hive layout. It draws #' edges between nodes as bezier curves, with the control points positioned at #' the same radii as the start or end point, and at a distance defined by the #' curvature argument. #' #' @inheritSection geom_edge_link Edge variants #' @inheritSection geom_edge_link Edge aesthetic name expansion #' #' @section Aesthetics: #' `geom_edge_hive` and `geom_edge_hive0` understand the following #' aesthetics. Bold aesthetics are automatically set, but can be overridden. #' #' - **x** #' - **y** #' - **xend** #' - **yend** #' - edge_colour #' - edge_width #' - edge_linetype #' - edge_alpha #' - filter #' #' `geom_edge_hive2` understand the following aesthetics. Bold aesthetics are #' automatically set, but can be overridden. #' #' - **x** #' - **y** #' - **group** #' - edge_colour #' - edge_width #' - edge_linetype #' - edge_alpha #' - filter #' #' `geom_edge_hive` and `geom_edge_hive2` furthermore takes the following #' aesthetics. #' #' - start_cap #' - end_cap #' - label #' - label_pos #' - label_size #' - angle #' - hjust #' - vjust #' - family #' - fontface #' - lineheight #' #' @section Computed variables: #' #' \describe{ #' \item{index}{The position along the path (not computed for the *0 version)} #' } #' #' #' @inheritParams geom_edge_link #' @inheritParams ggplot2::geom_path #' #' @param strength The curvature of the bezier. Defines the distance from the #' control points to the midpoint between the start and end node. 1 means the #' control points are positioned halfway between the nodes and the middle of the #' two axes, while 0 means it coincide with the nodes (resulting in straight #' lines) #' #' @param curvature Deprecated. Use `strength` instead. #' #' @author Thomas Lin Pedersen #' #' @family geom_edge_* #' #' @examples #' # Plot the flare import graph as a hive plot #' library(tidygraph) #' flareGr <- as_tbl_graph(flare$imports) %>% #' mutate( #' type = dplyr::case_when( #' centrality_degree(mode = 'in') == 0 ~ 'Source', #' centrality_degree(mode = 'out') == 0 ~ 'Sink', #' TRUE ~ 'Both' #' ) #' ) %>% #' activate(edges) %>% #' mutate( #' type = dplyr::case_when( #' grepl('flare.analytics', paste(.N()$name[from], .N()$name[to])) ~ 'Analytics', #' TRUE ~ 'Other' #' ) #' ) #' #' ggraph(flareGr, 'hive', axis = type) + #' geom_edge_hive(aes(colour = type), edge_alpha = 0.1) + #' coord_fixed() #' @rdname geom_edge_hive #' @name geom_edge_hive #' NULL #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatBezier #' @export StatEdgeHive <- ggproto('StatEdgeHive', StatBezier, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } data <- remove_loop(data) if (nrow(data) == 0) return(NULL) data$group <- make_unique(data$group) data2 <- data data2$x <- data2$xend data2$y <- data2$yend keep <- atan2(data$y, data$x) != atan2(data2$y, data2$x) create_hive_bezier(data[keep, ], data2[keep, ], params) }, required_aes = c('x', 'y', 'xend', 'yend'), default_aes = aes(filter = TRUE), extra_params = c('na.rm', 'n', 'strength', 'curvature') ) #' @rdname geom_edge_hive #' #' @export geom_edge_hive <- function(mapping = NULL, data = get_edges(), position = 'identity', arrow = NULL, strength = 1, n = 100, lineend = 'butt', linejoin = 'round', linemitre = 1, label_colour = 'black', label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = 'rot', force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ..., curvature) { if (!missing(curvature)) { .Deprecated(msg = 'The curvature argument has been deprecated in favour of strength') strength <- curvature * 2 } mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes( x = x, y = y, xend = xend, yend = yend, group = edge.id )) layer( data = data, mapping = mapping, stat = StatEdgeHive, geom = GeomEdgePath, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, linejoin = linejoin, linemitre = linemitre, na.rm = FALSE, n = n, interpolate = FALSE, strength = strength, label_colour = label_colour, label_alpha = label_alpha, label_parse = label_parse, check_overlap = check_overlap, angle_calc = angle_calc, force_flip = force_flip, label_dodge = label_dodge, label_push = label_push, ... ) ) ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatBezier2 #' @export StatEdgeHive2 <- ggproto('StatEdgeHive2', StatBezier2, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } data <- remove_loop2(data) if (nrow(data) == 0) return(NULL) data <- data[order(data$group), ] data2 <- data[c(FALSE, TRUE), ] data <- data[c(TRUE, FALSE), ] keep <- atan2(data$y, data$x) != atan2(data2$y, data2$x) create_hive_bazier(data[keep, ], data2[keep, ], params) }, required_aes = c('x', 'y', 'group'), default_aes = aes(filter = TRUE), extra_params = c('na.rm', 'n', 'strength', 'curvature') ) #' @rdname geom_edge_hive #' #' @export geom_edge_hive2 <- function(mapping = NULL, data = get_edges('long'), position = 'identity', arrow = NULL, strength = 1, n = 100, lineend = 'butt', linejoin = 'round', linemitre = 1, label_colour = 'black', label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = 'rot', force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ..., curvature) { if (!missing(curvature)) { .Deprecated(msg = 'The curvature argument has been deprecated in favour of strength') strength <- curvature * 2 } mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes(x = x, y = y, group = edge.id)) layer( data = data, mapping = mapping, stat = StatEdgeHive2, geom = GeomEdgePath, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, linejoin = linejoin, linemitre = linemitre, na.rm = FALSE, n = n, interpolate = TRUE, strength = strength, label_colour = label_colour, label_alpha = label_alpha, label_parse = label_parse, check_overlap = check_overlap, angle_calc = angle_calc, force_flip = force_flip, label_dodge = label_dodge, label_push = label_push, ... ) ) ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatBezier0 #' @export StatEdgeHive0 <- ggproto('StatEdgeHive0', StatBezier0, setup_data = function(data, params) { StatEdgeHive$setup_data(data, params) }, required_aes = c('x', 'y', 'xend', 'yend'), default_aes = aes(filter = TRUE), extra_params = c('na.rm', 'strength', 'curvature') ) #' @rdname geom_edge_hive #' #' @export geom_edge_hive0 <- function(mapping = NULL, data = get_edges(), position = 'identity', arrow = NULL, strength = 1, lineend = 'butt', show.legend = NA, ..., curvature) { if (!missing(curvature)) { .Deprecated(msg = 'The curvature argument has been deprecated in favour of strength') strength <- curvature * 2 } mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes(x = x, y = y, xend = xend, yend = yend)) layer( data = data, mapping = mapping, stat = StatEdgeHive0, geom = GeomEdgeBezier, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, na.rm = FALSE, strength = strength, ... ) ) ) } create_hive_bezier <- function(from, to, params) { bezier_start <- seq(1, by = 4, length.out = nrow(from)) from$index <- bezier_start to$index <- bezier_start + 3 data2 <- from data3 <- to data2$index <- bezier_start + 1 data3$index <- bezier_start + 2 from_axis <- atan2(from$y, from$x) from_axis[from_axis < 0] <- from_axis[from_axis < 0] + 2 * pi to_axis <- atan2(to$y, to$x) to_axis[to_axis < 0] <- to_axis[to_axis < 0] + 2 * pi from_first <- ifelse(from_axis < to_axis, to_axis - from_axis < pi, to_axis - from_axis < -pi ) middle_axis1 <- ifelse(from_first, from_axis, to_axis) middle_axis2 <- ifelse(from_first, to_axis, from_axis) middle_axis2 <- ifelse(middle_axis2 < middle_axis1, middle_axis2 + 2 * pi, middle_axis2) mean_axis <- (middle_axis2 - middle_axis1) / 2 middle_axis1 <- middle_axis1 + mean_axis * params$strength / 2 middle_axis2 <- middle_axis2 - mean_axis * params$strength / 2 node_r2 <- sqrt(data2$x^2 + data2$y^2) node_r3 <- sqrt(data3$x^2 + data3$y^2) data2$x <- node_r2 * cos(ifelse(from_first, middle_axis1, middle_axis2)) data2$y <- node_r2 * sin(ifelse(from_first, middle_axis1, middle_axis2)) data3$x <- node_r3 * cos(ifelse(from_first, middle_axis2, middle_axis1)) data3$y <- node_r3 * sin(ifelse(from_first, middle_axis2, middle_axis1)) data <- rbind_dfs(list(from, data2, data3, to)) data[order(data$index), names(data) != 'index'] } ggraph/R/RcppExports.R0000644000176200001440000000474413617226232014354 0ustar liggesusers# Generated by using Rcpp::compileAttributes() -> do not edit by hand # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 #' Pack circles together #' #' This function is a direct interface to the circle packing algorithm used by #' \code{\link{layout_tbl_graph_circlepack}}. It takes a vector of sizes and #' returns the x and y position of each circle as a two-column matrix. #' #' @param areas A vector of circle areas #' #' @return A matrix with two columns and the same number of rows as the length #' of the "areas" vector. The matrix has the following attributes added: #' "enclosing_radius" giving the radius of the smallest enclosing circle, and #' "front_chain" giving the terminating members of the front chain (see #' Wang \emph{et al}. 2006). #' #' @references #' Wang, W., Wang, H. H., Dai, G., & Wang, H. (2006). \emph{Visualization of #' large hierarchical data by circle packing}. Chi, 517-520. #' #' @export #' #' @examples #' library(ggforce) #' sizes <- sample(10, 100, TRUE) #' #' position <- pack_circles(sizes) #' data <- data.frame(x = position[,1], y = position[,2], r = sqrt(sizes/pi)) #' #' ggplot() + #' geom_circle(aes(x0 = x, y0 = y, r = r), data = data, fill = 'steelblue') + #' geom_circle(aes(x0 = 0, y0 = 0, r = attr(position, 'enclosing_radius'))) + #' geom_polygon(aes(x = x, y = y), #' data = data[attr(position, 'front_chain'), ], #' fill = NA, #' colour = 'black') #' pack_circles <- function(areas) { .Call('_ggraph_pack', PACKAGE = 'ggraph', areas) } circlePackLayout <- function(parent, weight) { .Call('_ggraph_circlePackLayout', PACKAGE = 'ggraph', parent, weight) } partitionTree <- function(parent, order, weight, height) { .Call('_ggraph_partitionTree', PACKAGE = 'ggraph', parent, order, weight, height) } cut_lines <- function(x, y, id, start_width, start_height, end_width, end_height, start_type, end_type) { .Call('_ggraph_cut_lines', PACKAGE = 'ggraph', x, y, id, start_width, start_height, end_width, end_height, start_type, end_type) } pathAttr <- function(paths, ngroups) { .Call('_ggraph_pathAttr', PACKAGE = 'ggraph', paths, ngroups) } splitTreemap <- function(parent, order, weight, width, height) { .Call('_ggraph_splitTreemap', PACKAGE = 'ggraph', parent, order, weight, width, height) } unrooted <- function(parent, order, length, daylight, tol, rotation_mod, maxiter) { .Call('_ggraph_unrooted', PACKAGE = 'ggraph', parent, order, length, daylight, tol, rotation_mod, maxiter) } ggraph/R/layout_circlepack.R0000644000176200001440000000616413525463430015557 0ustar liggesusers#' Calculate nodes as circles packed within their parent circle #' #' The circle packing algorithm is basically a treemap using circles instead of #' rectangles. Due to the nature of circles they cannot be packed as efficiently #' leading to increased amount of "empty space" as compared to a treemap. This #' can be beneficial though, as the added empty space can aid in visually #' showing the hierarchy. #' #' @details #' The circle packing is based on the algorithm developed by Weixin Wang and #' collaborators which tries to find the most dense packing of circles as they #' are added, one by one. This makes the algorithm very dependent on the order #' in which circles are added and it is possible that layouts could sometimes #' be optimized by choosing a different ordering. The algorithm for finding the #' enclosing circle is the randomized incremental algorithm proposed by Emo #' Welzl. Both of the above algorithms are the same as used in the D3.js #' implementation of circle packing and their C++ implementation in ggraph is #' inspired by Mike Bostocks JavaScript implementation. #' #' @note #' Circle packing is a layout intended for trees, that is, graphs where nodes #' only have one parent and zero or more children. If the provided graph does #' not fit this format an attempt to convert it to such a format will be made. #' #' @param graph An `tbl_graph` object #' #' @param weight An optional node variable to use as weight. Will only affect #' the weight of leaf nodes as the weight of non-leaf nodes are derived from #' their children. #' #' @param circular Logical. Should the layout be transformed to a circular #' representation. Ignored. #' #' @param sort.by The name of a node variable to sort the nodes by. #' #' @param direction The direction of the tree in the graph. `'out'` (default) #' means that parents point towards their children, while `'in'` means that #' children point towards their parent. #' #' @return A data.frame with the columns `x`, `y`, `r`, `leaf`, #' `depth`, `circular` as well as any information stored as node #' variables in the tbl_graph object. #' #' @references #' Wang, W., Wang, H. H., Dai, G., & Wang, H. (2006). *Visualization of #' large hierarchical data by circle packing*. Chi, 517-520. #' #' Welzl, E. (1991). *Smallest enclosing disks (balls and ellipsoids)*. New #' Results and New Trends in Computer Science, 359-370. #' #' @family layout_tbl_graph_* #' layout_tbl_graph_circlepack <- function(graph, weight = NULL, circular = FALSE, sort.by = NULL, direction = 'out') { weight <- enquo(weight) weight <- eval_tidy(weight, .N()) sort.by <- enquo(sort.by) sort.by <- eval_tidy(sort.by, .N()) hierarchy <- tree_to_hierarchy(graph, direction, sort.by, weight) layout <- circlePackLayout(hierarchy$parent, hierarchy$weight)[-1, ] layout <- new_data_frame(list( x = layout[, 1], y = layout[, 2], r = layout[, 3], circular = FALSE, leaf = degree(graph, mode = direction) == 0, depth = node_depth(graph, mode = direction) )) extra_data <- as_tibble(graph, active = 'nodes') layout <- cbind(layout, extra_data[, !names(extra_data) %in% names(layout), drop = FALSE]) layout } ggraph/R/geom_edge.R0000644000176200001440000004765113617014311013773 0ustar liggesusers#' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom grid segmentsGrob polylineGrob gpar #' @importFrom dplyr %>% group_by_ do ungroup #' @importFrom ggforce interpolateDataFrame #' @export GeomEdgePath <- ggproto('GeomEdgePath', GeomPath, draw_panel = function(data, panel_scales, coord, arrow = NULL, lineend = 'butt', linejoin = 'round', linemitre = 1, na.rm = FALSE, interpolate = TRUE, label_colour = 'black', label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = 'none', force_flip = TRUE, label_dodge = NULL, label_push = NULL) { if (!anyDuplicated(data$group)) { message( 'geom_edge_path: Each group consists of only one observation. ', 'Do you need to adjust the group aesthetic?' ) } data <- data[order(data$group), , drop = FALSE] data$group <- match(data$group, unique(data$group)) if (interpolate) { geometries <- vapply(data, is.geometry, logical(1)) geom_cols <- data[geometries] data <- cbind(interpolateDataFrame(data[!geometries]), geom_cols) } data <- coord$transform(data, panel_scales) zero <- coord$transform(new_data_frame(list(x = 0, y = 0)), panel_scales) if (nrow(data) < 2) { return(zeroGrob()) } attr <- pathAttr(data, length(unique(data$group))) if (all(is.na(data$start_cap))) { start_captype <- 'circle' start_cap <- NULL start_cap2 <- NULL } else { scap <- data$start_cap[!duplicated(data$group)] start_captype <- geo_type(scap) start_cap <- geo_width(scap) start_cap2 <- geo_height(scap) if (any(attr(start_cap, 'unit') == 'native') || any(attr(start_cap2, 'unit') == 'native')) { recalc <- coord$transform( new_data_Frame(list(x = as.vector(start_cap), y = as.vector(start_cap2))), panel_scales ) start_cap[attr(start_cap, 'unit') == 'native'] <- unit(recalc$x - zero$x, 'npc') start_cap2[attr(start_cap2, 'unit') == 'native'] <- unit(recalc$y - zero$y, 'npc') } } if (all(is.na(data$end_cap))) { end_captype <- 'circle' end_cap <- NULL end_cap2 <- NULL } else { ecap <- data$end_cap[!duplicated(data$group)] end_captype <- geo_type(ecap) end_cap <- geo_width(ecap) end_cap2 <- geo_height(ecap) if (any(attr(end_cap, 'unit') == 'native') || any(attr(end_cap2, 'unit') == 'native')) { recalc <- coord$transform( new_data_frame(list(x = as.vector(end_cap), y = as.vector(end_cap2))), panel_scales ) end_cap[attr(end_cap, 'unit') == 'native'] <- unit(recalc$x - zero$x, 'native') end_cap2[attr(end_cap2, 'unit') == 'native'] <- unit(recalc$y - zero$y, 'native') } } if ((is.null(start_cap) && !is.null(end_cap)) || (!is.null(start_cap) && is.null(end_cap))) { if (is.null(start_cap)) start_cap <- 0 if (is.null(end_cap)) end_cap <- 0 } solid_lines <- all(attr$solid) constant <- all(attr$constant) if (!solid_lines && !constant) { stop('geom_edge_path: If you are using dotted or dashed lines', ', colour, size and linetype must be constant over the line', call. = FALSE ) } gp <- gpar( col = alpha(data$edge_colour, data$edge_alpha), fill = alpha(data$edge_colour, data$edge_alpha), lwd = data$edge_width * .pt, lty = data$edge_linetype, lineend = lineend, linejoin = linejoin, linemitre = linemitre ) edge_grob <- cappedPathGrob( x = data$x, y = data$y, id = data$group, arrow = arrow, start.cap = start_cap, start.cap2 = start_cap2, start.captype = start_captype, end.cap = end_cap, end.cap2 = end_cap2, end.captype = end_captype, default.units = 'native', gp = gp, constant = constant ) if (any(!is.na(data$label))) { if (angle_calc == 'none') angle_calc <- 'rot' edge_ind <- split(seq_len(nrow(data)), data$group) edge_length <- lengths(edge_ind) edge_start <- c(0, cumsum(edge_length)[-length(edge_length)]) + 1 label_pos <- data$label_pos[vapply(edge_ind, head, integer(1), n = 1)] label_pos <- 1 + floor((edge_length - 1) * label_pos) label_ind <- edge_start + label_pos label_data <- data[label_ind, ] label_data$label_colour <- if (is.na(label_colour)) { label_data$edge_colour } else { label_colour } label_data$label_alpha <- if (is.na(label_alpha)) { label_data$edge_alpha } else { label_alpha } angle_start <- ifelse(label_pos == 1, 1, label_pos - 1) angle_end <- ifelse(label_pos == edge_length, edge_length, label_pos + 1) label_x0 <- data$x[angle_start + edge_start] label_y0 <- data$y[angle_start + edge_start] label_x1 <- data$x[angle_end + edge_start] label_y1 <- data$y[angle_end + edge_start] lab <- as.character(label_data$label) if (label_parse) { lab_expressions <- sapply(X=lab, FUN=str2expression) lab_len <- sapply(X=lab_expressions, FUN=length) if (any(lab_len == 0)) { lab[lab_len == 0] <- "` `" lab <- sapply(X=lab, FUN=str2expression) } } label_grob <- textAlongGrob( lab, label_data$x, label_data$y, default.units = 'native', hjust = label_data$hjust, vjust = label_data$vjust, rot = label_data$angle, gp = gpar( col = alpha(label_colour, label_alpha), fontsize = label_data$label_size * .pt, fontfamily = label_data$family, fontface = label_data$fontface, lineheight = label_data$lineheight ), check.overlap = check_overlap, rot.type = angle_calc, x0 = label_x0, y0 = label_y0, x1 = label_x1, y1 = label_y1, force.rot = force_flip, dodge = label_dodge, push = label_push ) gList(edge_grob, label_grob) } else { edge_grob } }, draw_key = function(data, params, size) { segmentsGrob(0.1, 0.5, 0.9, 0.5, gp = gpar( col = alpha(data$edge_colour, data$edge_alpha), lwd = data$edge_width * .pt, lty = data$edge_linetype, lineend = 'butt' ), arrow = params$arrow ) }, handle_na = function(data, params) { if (params$interpolate) { return(data) } keep <- function(x) { first <- match(FALSE, x, nomatch = 1) - 1 last <- length(x) - match(FALSE, rev(x), nomatch = 1) + 1 c( rep(FALSE, first), rep(TRUE, last - first), rep(FALSE, length(x) - last) ) } missing <- !stats::complete.cases( data[c('x', 'y', 'edge_width', 'edge_colour', 'edge_linetype')] ) kept <- stats::ave(missing, data$group, FUN = keep) data <- data[kept, ] if (!all(kept) && !params$na.rm) { warning('Removed ', sum(!kept), ' rows containing missing values', ' (geom_edge_path).', call. = FALSE ) } data }, default_aes = aes( edge_colour = 'black', edge_width = 0.5, edge_linetype = 'solid', edge_alpha = NA, start_cap = NA, end_cap = NA, label = NA, label_pos = 0.5, label_size = 3.88, angle = 0, hjust = 0.5, vjust = 0.5, family = '', fontface = 1, lineheight = 1.2 ) ) #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom grid gpar segmentsGrob #' @export GeomEdgeParallelPath <- ggproto('GeomEdgeParallelPath', GeomEdgePath, draw_panel = function(data, panel_scales, coord, arrow = NULL, sep = unit(2, 'mm'), lineend = 'butt', linejoin = 'round', linemitre = 1, na.rm = FALSE, interpolate = TRUE, label_colour = 'black', label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = 'none', force_flip = TRUE, label_dodge = NULL, label_push = NULL) { data <- data[order(data$group), , drop = FALSE] panel <- GeomEdgePath$draw_panel(data, panel_scales, coord, arrow = arrow, lineend = lineend, linejoin = linejoin, linemitre = linemitre, na.rm = na.rm, interpolate = interpolate, label_colour = label_colour, label_alpha = label_alpha, label_parse = label_parse, check_overlap = check_overlap, angle_calc = angle_calc, force_flip = force_flip, label_dodge = label_dodge, label_push = label_push) if (inherits(panel, 'gList')) { panel[[1]]$sep <- (data$.position * sep)[panel[[1]]$id] class(panel[[1]]) <- c('parallelPath', class(panel[[1]])) } else { panel$sep <- (data$.position[!duplicated(data$group)] * sep)[panel$id] class(panel) <- c('parallelPath', class(panel)) } panel } ) #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom grid gpar segmentsGrob #' @export GeomEdgeSegment <- ggproto('GeomEdgeSegment', GeomSegment, draw_panel = function(data, panel_scales, coord, arrow = NULL, lineend = 'butt', na.rm = FALSE) { if (is.null(data) || nrow(data) == 0 || ncol(data) == 0) { return(zeroGrob()) } coord <- coord$transform(data, panel_scales) segmentsGrob(coord$x, coord$y, coord$xend, coord$yend, default.units = 'native', gp = gpar( col = alpha(coord$edge_colour, coord$edge_alpha), fill = alpha(coord$edge_colour, coord$edge_alpha), lwd = coord$edge_width * .pt, lty = coord$edge_linetype, lineend = lineend ), arrow = arrow ) }, draw_key = function(data, params, size) { segmentsGrob(0.1, 0.5, 0.9, 0.5, gp = gpar( col = alpha(data$edge_colour, data$edge_alpha), lwd = data$edge_width * .pt, lty = data$edge_linetype, lineend = 'butt' ), arrow = params$arrow ) }, default_aes = aes( edge_colour = 'black', edge_width = 0.5, edge_linetype = 1, edge_alpha = NA ) ) #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @export GeomEdgeParallelSegment <- ggproto('GeomEdgeParallelSegment', GeomEdgeSegment, draw_panel = function(data, panel_scales, coord, arrow = NULL, lineend = 'butt', na.rm = FALSE, sep = unit(2, 'mm')) { data <- data[order(data$group), , drop = FALSE] panel <- GeomEdgeSegment$draw_panel(data, panel_scales, coord, arrow = arrow, lineend = lineend, na.rm = na.rm) panel$sep <- data$.position * sep class(panel) <- c('parallelPath', class(panel)) panel } ) #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom grid pointsGrob unit gpar grobTree #' @export GeomEdgeSpanPath <- ggproto('GeomEdgeSpanPath', GeomEdgePath, draw_panel = function(data, panel_scales, coord, arrow = NULL, end_shape = NA, lineend = 'butt', linejoin = 'round', linemitre = 1, na.rm = FALSE, interpolate = TRUE, label_colour = 'black', label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = 'none', force_flip = TRUE, label_dodge = NULL, label_push = NULL) { panel <- GeomEdgePath$draw_panel(data, panel_scales, coord, arrow = arrow, lineend = lineend, linejoin = linejoin, linemitre = linemitre, na.rm = na.rm, interpolate = interpolate, label_colour = label_colour, label_alpha = label_alpha, label_parse = label_parse, check_overlap = FALSE, angle_calc = 'none', force_flip = TRUE, label_dodge = label_dodge, label_push = label_push) if (is.na(end_shape)) return(panel) data <- data[data$index == 0 | data$index == 1, ] end_shape <- translate_pch(end_shape) coords <- coord$transform(data, panel_scales) ends <- pointsGrob(coords$x, coords$y, pch = end_shape, size = unit(1 / (panel_scales$x.range[2] - (panel_scales$x.range[1])), 'npc'), gp = gpar( col = alpha(coords$edge_colour, coords$edge_alpha), fill = alpha(coords$edge_colour, coords$edge_alpha), lwd = 0 ) ) grobTree(panel, ends) } ) #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom grid pointsGrob unit gpar grobTree #' @export GeomEdgeSpanSegment <- ggproto('GeomEdgeSpanSegment', GeomEdgeSegment, draw_panel = function(data, panel_scales, coord, arrow = NULL, lineend = 'butt', na.rm = FALSE, end_shape = NA) { panel <- GeomEdgeSegment$draw_panel(data, panel_scales, coord, arrow = arrow, lineend = lineend, na.rm = na.rm) if (is.na(end_shape)) return(panel) data2 <- data data2$x <- data2$xend data2$y <- data2$yend data <- rbind_dfs(list(data, data2)) data$xend <- NULL data$yend <- NULL end_shape <- translate_pch(end_shape) coords <- coord$transform(data, panel_scales) ends <- pointsGrob(coords$x, coords$y, pch = end_shape, size = unit(1 / (panel_scales$x.range[2] - (panel_scales$x.range[1])), 'npc'), gp = gpar( col = alpha(coords$edge_colour, coords$edge_alpha), fill = alpha(coords$edge_colour, coords$edge_alpha), lwd = 0 ) ) grobTree(panel, ends) } ) #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom grid gpar pointsGrob #' @export GeomEdgePoint <- ggproto('GeomEdgePoint', GeomPoint, setup_data = function(data, params) { if (params$mirror) { data2 <- data data2[, c('x', 'y')] <- data2[, c('y', 'x'), drop = FALSE] data2$x <- abs(data2$x) * sign(data$x) data2$y <- abs(data2$y) * sign(data$y) data <- rbind_dfs(list(data, data2)) } data }, draw_panel = function(data, panel_scales, coord, na.rm = FALSE) { if (is.null(data) || nrow(data) == 0 || ncol(data) == 0) { return(zeroGrob()) } data$edge_shape <- translate_pch(data$edge_shape) coords <- coord$transform(data, panel_scales) coords <- coords[order(coords$edge_size, decreasing = TRUE), , drop = FALSE] pointsGrob(coords$x, coords$y, pch = coords$edge_shape, gp = gpar( col = alpha(coords$edge_colour, coords$edge_alpha), fill = alpha(coords$edge_fill, coords$edge_alpha), fontsize = coords$edge_size * .pt + coords$stroke * .stroke / 2, lwd = coords$stroke * .stroke / 2 ) ) }, draw_key = function(data, params, size) { pointsGrob(0.5, 0.5, pch = data$edge_shape, gp = gpar( col = alpha(data$edge_colour, data$edge_alpha), fill = alpha(data$edge_fill, data$edge_alpha), fontsize = data$edge_size * .pt + data$stroke * .stroke / 2, lwd = data$stroke * .stroke / 2 ) ) }, default_aes = aes( edge_shape = 19, edge_colour = 'black', edge_size = 1.5, edge_fill = NA, edge_alpha = NA, stroke = 0.5 ), extra_params = c('na.rm', 'mirror') ) #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom grid gpar rectGrob #' @export GeomEdgeTile <- ggproto('GeomEdgeTile', GeomTile, setup_data = function(data, params) { if (params$mirror) { data2 <- data data2[, c('x', 'y')] <- data2[, c('y', 'x'), drop = FALSE] data2$x <- abs(data2$x) * sign(data$x) data2$y <- abs(data2$y) * sign(data$y) data <- rbind_dfs(list(data, data2)) } data }, draw_panel = function(data, panel_params, coord, na.rm = FALSE) { if (is.null(data) || nrow(data) == 0 || ncol(data) == 0) { return(zeroGrob()) } data$xmin <- data$x - 0.5 data$xmax <- data$x + 0.5 data$ymin <- data$y - 0.5 data$ymax <- data$y + 0.5 coords <- coord$transform(data, panel_params) rectGrob( coords$xmin, coords$ymax, width = coords$xmax - coords$xmin, height = coords$ymax - coords$ymin, default.units = "native", just = 'centre', gp = gpar( col = coords$edge_colour, fill = alpha(coords$edge_fill, coords$edge_alpha), lwd = coords$edge_size * .pt, lty = coords$edge_linetype, linejoin = 'mitre', # `lineend` is a workaround for Windows and intentionally kept unexposed # as an argument. (c.f. https://github.com/tidyverse/ggplot2/issues/3037#issuecomment-457504667) lineend = "square" ) ) }, draw_key = function(data, params, size) { if (is.null(data$edge_size)) { data$edge_size <- 0.5 } lwd <- min(data$edge_size, min(size) / 4) rectGrob( width = unit(1, "npc") - unit(lwd, "mm"), height = unit(1, "npc") - unit(lwd, "mm"), gp = gpar( col = data$edge_colour %||% NA, fill = alpha(data$edge_fill %||% "grey20", data$edge_alpha), lty = data$edge_linetype %||% 1, lwd = lwd * .pt, linejoin = "mitre", # `lineend` is a workaround for Windows and intentionally kept unexposed # as an argument. (c.f. https://github.com/tidyverse/ggplot2/issues/3037#issuecomment-457504667) lineend = "square" )) }, default_aes = aes( edge_fill = 'grey20', edge_colour = NA, edge_size = 0.1, edge_linetype = 1, edge_alpha = NA ), extra_params = c('na.rm', 'mirror') ) #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce GeomBezier0 #' @export GeomEdgeBezier <- ggproto('GeomEdgeBezier', GeomBezier0, draw_panel = function(data, panel_scales, coord, arrow = NULL, lineend = 'butt', linejoin = 'round', linemitre = 1, na.rm = FALSE) { names(data) <- sub('edge_', '', names(data)) names(data)[names(data) == 'width'] <- 'size' GeomBezier0$draw_panel(data, panel_scales, coord, arrow, lineend, linejoin, linemitre, na.rm) }, draw_key = function(data, params, size) { segmentsGrob(0.1, 0.5, 0.9, 0.5, gp = gpar( col = alpha(data$edge_colour, data$edge_alpha), lwd = data$edge_width * .pt, lty = data$edge_linetype, lineend = 'butt' ), arrow = params$arrow ) }, default_aes = aes( edge_colour = 'black', edge_width = 0.5, edge_linetype = 1, edge_alpha = NA ), handle_na = function(data, ...) { data } ) #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce GeomBspline0 #' @export GeomEdgeBspline <- ggproto('GeomEdgeBspline', GeomBspline0, draw_panel = function(data, panel_scales, coord, arrow = NULL, lineend = 'butt', linejoin = 'round', linemitre = 1, na.rm = FALSE) { names(data) <- sub('edge_', '', names(data)) names(data)[names(data) == 'width'] <- 'size' GeomBspline0$draw_panel(data, panel_scales, coord, arrow, lineend, linejoin, linemitre, na.rm) }, draw_key = function(data, params, size) { segmentsGrob(0.1, 0.5, 0.9, 0.5, gp = gpar( col = alpha(data$edge_colour, data$edge_alpha), lwd = data$edge_width * .pt, lty = data$edge_linetype, lineend = 'butt' ), arrow = params$arrow ) }, default_aes = aes( edge_colour = 'black', edge_width = 0.5, edge_linetype = 1, edge_alpha = NA ), handle_na = function(data, ...) { data } ) remove_loop <- function(data) { if (nrow(data) == 0) return(data) data[!(data$x == data$xend & data$y == data$yend), , drop = FALSE] } remove_loop2 <- function(data) { if (nrow(data) == 0) return(data) data <- data[order(data$group), ] loop <- data$x[c(TRUE, FALSE)] == data$x[c(FALSE, TRUE)] & data$y[c(TRUE, FALSE)] == data$y[c(FALSE, TRUE)] data[rep(!loop, each = 2), , drop = FALSE] } ggraph/R/layout_hive.R0000644000176200001440000002270313530724027014405 0ustar liggesusers#' Place nodes in a Hive Plot layout #' #' Hive plots were invented by Martin Krzywinski as a perceptually uniform and #' scalable alternative to standard node-edge layouts. In hive plots nodes are #' positioned on axes radiating out from a center based on their own information #' e.g. membership of a class, size of neighborhood, etc. Edges are then drawn #' between nodes as bezier curves. As the placement of nodes is not governed by #' convoluted algorithms but directly reflects the qualities of the nodes itself #' the resulting plot can be easier to interpret as well as compare to other #' graphs. #' #' @details #' In order to be able to draw all edges without edges crossing axes you should #' not assign nodes to axes based on a variable with more than three levels. #' #' @param graph An `tbl_graph` object #' #' @param axis The node attribute to use for assigning nodes to axes #' #' @param axis.pos The relative distance to the prior axis. Default #' (`NULL`) places axes equidistant. #' #' @param sort.by The node attribute to use for placing nodes along their axis. #' Defaults (`NULL`) places nodes sequentially. #' #' @param divide.by An optional node attribute to subdivide each axis by. #' #' @param divide.order The order the axis subdivisions should appear in #' #' @param normalize Logical. Should axis lengths be equal or reflect the number #' of nodes in each axis. Defaults to `TRUE`. #' #' @param center.size The size of the blank center, that is, the start position #' of the axes. #' #' @param divide.size The distance between subdivided axis segments. #' #' @param use.numeric Logical, If the `sort.by` attribute is numeric, #' should these values be used directly in positioning the nodes along the axes. #' Defaults to `FALSE` which sorts the numeric values and positions them #' equidistant from each other. #' #' @param offset Change the overall rotation of the hive plot by changing the #' offset of the first axis. #' #' @param split.axes Should axes be split to show edges between nodes on the #' same axis? One of: #' \describe{ #' \item{`'none'`}{Do not split axes and show in-between edges} #' \item{`'loops'`}{Only split axes that contain in-between edges} #' \item{`'all'`}{Split all axes} #' } #' #' @param split.angle The angular distance between the two axes resulting from a #' split. #' #' @param circular Ignored. #' #' @return A data.frame with the columns `x`, `y`, `r`, #' `center_size`, `split`, `axis`, `section`, `angle`, #' `circular` as well as any information stored as node variables in the #' tbl_graph object. #' #' @references #' Krzywinski, M., Birol, I., Jones, SJM., and Marra, MA. (2012). *Hive #' plots-rational approach to visualizing networks*. Brief Bioinform 13 (5): #' 627-644. https://doi.org/10.1093/bib/bbr069 #' #' #' #' @family layout_tbl_graph_* #' #' @importFrom igraph gorder vertex_attr gsize induced_subgraph add_vertices E ends add_edges delete_edges %--% edge_attr #' @importFrom utils tail layout_tbl_graph_hive <- function(graph, axis, axis.pos = NULL, sort.by = NULL, divide.by = NULL, divide.order = NULL, normalize = TRUE, center.size = 0.1, divide.size = 0.05, use.numeric = FALSE, offset = pi / 2, split.axes = 'none', split.angle = pi / 6, circular = FALSE) { axis <- enquo(axis) axis <- eval_tidy(axis, .N()) sort.by <- enquo(sort.by) sort.by <- eval_tidy(sort.by, .N()) divide.by <- enquo(divide.by) divide.by <- eval_tidy(divide.by, .N()) axes <- split(seq_len(gorder(graph)), axis) if (is.null(axis.pos)) { axis.pos <- rep(1, length(axes)) } else { if (length(axis.pos) != length(axes)) { warning('Number of axes not matching axis.pos argument. Recycling as needed') axis.pos <- rep(axis.pos, length.out = length(axes)) } } axis.pos <- -cumsum(axis.pos) axis.pos <- c(0, axis.pos[-length(axis.pos)]) / -tail(axis.pos, 1) * 2 * pi + offset if (use.numeric) { if (is.null(sort.by) || !is.numeric(sort.by)) { stop('sort.by must be a numeric vertex attribute when use.numeric = TRUE') } numeric.range <- range(sort.by) } if (normalize) { normalize_to <- rep(1, length(axes)) } else { normalize_to <- lengths(axes) / max(lengths(axes)) } node.pos <- Map(function(nodes, axis_length, axis, angle) { split_axis <- switch( split.axes, all = TRUE, loops = gsize(induced_subgraph(graph, nodes)) > 0, none = FALSE, stop('Unknown split argument. Use "all", "loops" or "none"') ) node_div <- axis_length / length(nodes) if (is.null(divide.by)) { node_split <- list(`1` = nodes) } else { if (use.numeric) { stop('Cannot divide axis while use.numeric = TRUE') } node_split <- split(nodes, divide.by[nodes]) if (!is.null(divide.order)) { if (!all(divide.order %in% names(node_split))) { stop('All ', divide.by, ' levels must be present in divide.order') } node_split <- node_split[order(match(names(node_split), divide.order))] } } node_pos <- lapply(node_split, function(nodes) { if (length(nodes) == 0) { return(numeric()) } if (is.null(sort.by)) { pos <- match(seq_along(nodes), order(nodes)) - 1 pos <- pos * node_div } else { pos <- sort.by[nodes] if (use.numeric) { if (!is.numeric(pos)) { stop('sort.by must contain numeric data when use.numeric = TRUE') } if (normalize) { if (diff(range(pos)) == 0) { pos <- rep(0.5, length.out = length(pos)) } else { pos <- (pos - min(pos)) / diff(range(pos)) } } else { pos <- (pos - numeric.range[1]) / diff(numeric.range) } } else { pos <- match(seq_along(pos), order(pos)) - 1 pos <- pos * node_div } } pos }) node_pos <- Reduce(function(l, r) { append(l, list(r + node_div + divide.size + max(l[[length(l)]]))) }, x = node_pos[-1], init = node_pos[1]) node_pos <- unlist(node_pos) + center.size new_data_frame(list( node = nodes, r = node_pos[match(nodes, unlist(node_split))], center_size = center.size, split = split_axis, axis = axis, section = rep(names(node_split), lengths(node_split))[match(nodes, unlist(node_split))], angle = angle, circular = FALSE )) }, nodes = axes, axis_length = normalize_to, axis = names(axes), angle = axis.pos) for (i in seq_along(node.pos)) { if (node.pos[[i]]$split[1]) { n_new_nodes <- nrow(node.pos[[i]]) new_node_start <- gorder(graph) + 1 extra_nodes <- node.pos[[i]] extra_nodes$node <- seq(new_node_start, length.out = n_new_nodes) vattr <- lapply(vertex_attr(graph), `[`, i = node.pos[[i]]$node) graph <- add_vertices(graph, n_new_nodes, attr = vattr) loop_edges <- E(graph)[node.pos[[i]]$node %--% node.pos[[i]]$node] if (length(loop_edges) != 0) { loop_edges_ends <- ends(graph, loop_edges, names = FALSE) correct_order_ends <- node.pos[[i]]$r[match(loop_edges_ends[, 1], node.pos[[i]]$node)] < node.pos[[i]]$r[match(loop_edges_ends[, 2], node.pos[[i]]$node)] loop_edges_ends <- new_data_frame(list( from = ifelse(correct_order_ends, loop_edges_ends[, 1], loop_edges_ends[, 2]), to = ifelse(correct_order_ends, loop_edges_ends[, 2], loop_edges_ends[, 1]) )) loop_edges_ends$to <- extra_nodes$node[match(loop_edges_ends$to, node.pos[[i]]$node)] loop_edges_ends <- matrix(c( ifelse(correct_order_ends, loop_edges_ends$from, loop_edges_ends$to), ifelse(correct_order_ends, loop_edges_ends$to, loop_edges_ends$from) ), nrow = 2, byrow = TRUE) eattr <- lapply(edge_attr(graph), `[`, i = as.numeric(loop_edges)) graph <- add_edges(graph, as.vector(loop_edges_ends), attr = eattr) graph <- delete_edges(graph, as.numeric(loop_edges)) } node_correction <- unlist(lapply(node.pos[-i], function(ax) { correct <- if (ax$angle[1] < node.pos[[i]]$angle[1]) { ax$angle[1] - node.pos[[i]]$angle[1] < -pi } else { ax$angle[1] - node.pos[[i]]$angle[1] < pi } if (correct) ax$node })) if (length(node_correction) != 0) { correct_edges <- E(graph)[node.pos[[i]]$node %--% node_correction] correct_edges_ends <- ends(graph, correct_edges, names = FALSE) new_node_ind <- correct_edges_ends %in% node.pos[[i]]$node correct_edges_ends[new_node_ind] <- extra_nodes$node[match(correct_edges_ends[new_node_ind], node.pos[[i]]$node)] eattr <- lapply(edge_attr(graph), `[`, i = as.numeric(correct_edges)) graph <- add_edges(graph, as.vector(t(correct_edges_ends)), attr = eattr) graph <- delete_edges(graph, as.numeric(correct_edges)) } node.pos[[i]]$angle <- node.pos[[i]]$angle - split.angle / 2 extra_nodes$angle <- extra_nodes$angle + split.angle / 2 node.pos <- append(node.pos, list(extra_nodes)) } } node.pos <- lapply(node.pos, function(nodes) { nodes$x <- nodes$r * cos(nodes$angle) nodes$y <- nodes$r * sin(nodes$angle) nodes }) node.pos <- rbind_dfs(node.pos) node.pos <- node.pos[order(node.pos$node), names(node.pos) != 'node'] extra_data <- as_tibble(as_tbl_graph(graph), active = 'nodes') node.pos <- cbind(node.pos, extra_data[, !names(extra_data) %in% names(node.pos), drop = FALSE]) attr(node.pos, 'graph') <- as_tbl_graph(graph) node.pos } ggraph/R/theme_graph.R0000644000176200001440000002167013531220623014334 0ustar liggesusers#' A theme tuned for graph visualizations #' #' When plotting graphs, networks, and trees the coordinate values are often of #' no importance and axes are thus a distraction. `ggraph` comes with a #' build-in theme that removes redundant elements in order to put focus on the #' data. Furthermore the default behaviour is to use a narrow font so text takes #' up less space. Theme colour is defined by a background and foreground colour #' where the background defines the colour of the whole graphics area and the #' foreground defines the colour of the strip and border. By default strip and #' border is turned off as it is an unnecessary element unless facetting is #' used. To add a foreground colour to a plot that is already using #' `theme_graph` the `th_foreground` helper is provided. In order to #' use this appearance as default use the `set_graph_style` function. An #' added benefit of this is that it also changes the default text-related values #' in the different geoms for a completely coherent look. #' `unset_graph_style` can be used to revert the defaults back to their #' default settings (that is, they are not necessarily reverted back to what #' they were prior to calling `set_graph_style`). The `th_no_axes()` helper is #' provided to modify an existing theme so that grid and axes are removed. #' #' @param background The colour to use for the background. This theme sets all #' background elements except for plot.background to `element_blank` so #' this controls the background for all elements of the plot. Set to `NA` #' to remove the background (thus making the plot transparent) #' #' @param foreground The colour of foreground elements, specifically strip and #' border. Set to `NA` to remove. #' #' @param border Logical. Should border be drawn if a foreground colour is #' provided? #' #' @param family,base_family,title_family,subtitle_family,strip_text_family,caption_family The font to use for the different elements #' #' @param base_size,size,text_size,title_size,subtitle_size,strip_text_size,caption_size The size to use for the various text elements. `text_size` will be used as geom defaults #' #' @param face,title_face,subtitle_face,strip_text_face,caption_face The fontface to use for the various text elements #' #' @param title_margin,subtitle_margin,caption_margin The margin to use between the text elements and the plot area #' #' @param text_colour,bg_text_colour,fg_text_colour,title_colour,subtitle_colour,strip_text_colour,caption_colour The colour of the text in the various text elements #' #' @param plot_margin The plot margin #' #' @export #' #' @examples #' library(tidygraph) #' graph <- as_tbl_graph(highschool) #' #' ggraph(graph) + geom_edge_link() + geom_node_point() + theme_graph() theme_graph <- function(base_family = 'Arial Narrow', base_size = 11, background = 'white', foreground = NULL, border = TRUE, text_colour = 'black', bg_text_colour = text_colour, fg_text_colour = text_colour, title_family = base_family, title_size = 18, title_face = 'bold', title_margin = 10, title_colour = bg_text_colour, subtitle_family = base_family, subtitle_size = 12, subtitle_face = 'plain', subtitle_margin = 15, subtitle_colour = bg_text_colour, strip_text_family = base_family, strip_text_size = 10, strip_text_face = 'bold', strip_text_colour = fg_text_colour, caption_family = base_family, caption_size = 9, caption_face = 'italic', caption_margin = 10, caption_colour = bg_text_colour, plot_margin = margin(30, 30, 30, 30)) { style <- theme_bw(base_size = base_size, base_family = base_family) style <- style + theme( text = element_text(colour = text_colour), plot.title = element_text( family = title_family, size = title_size, face = title_face, colour = title_colour, margin = margin(b = title_margin) ), plot.subtitle = element_text( family = subtitle_family, size = subtitle_size, face = subtitle_face, colour = subtitle_colour, margin = margin(b = subtitle_margin) ), plot.caption = element_text( family = caption_family, size = caption_size, face = caption_face, colour = caption_colour, margin = margin(t = caption_margin) ), strip.text = element_text( family = strip_text_family, size = strip_text_size, face = strip_text_face, colour = strip_text_colour ), plot.margin = plot_margin, legend.background = element_blank(), legend.box.background = element_blank(), legend.key = element_blank(), panel.background = element_blank(), axis.title = element_blank(), axis.text = element_blank(), axis.line = element_blank(), axis.ticks = element_blank(), panel.grid = element_blank(), strip.background = if (is.null(foreground)) element_blank() else element_rect(fill = foreground, colour = foreground), plot.background = if (is.null(background)) element_blank() else element_rect(fill = background, colour = NA), panel.border = if (border && !is.null(foreground)) element_rect(fill = NA, colour = foreground) else element_blank() ) style } #' @rdname theme_graph #' #' @export th_foreground <- function(foreground = 'grey80', fg_text_colour = NULL, border = FALSE) { th <- theme( strip.background = if (is.null(foreground)) element_blank() else element_rect(fill = foreground, colour = foreground), panel.border = if (border && !is.null(foreground)) element_rect(fill = NA, colour = foreground) else element_blank() ) if (!is.null(fg_text_colour)) { th <- th + theme(strip.text = element_text(colour = fg_text_colour)) } th } #' @rdname theme_graph #' #' @export th_no_axes <- function() { theme( panel.grid = element_blank(), #panel.grid.major = element_blank(), #panel.grid.major.x = element_blank(), #panel.grid.major.y = element_blank(), #panel.grid.minor = element_blank(), #panel.grid.minor.x = element_blank(), #panel.grid.minor.y = element_blank(), axis.title = element_blank(), #axis.title.x = element_blank(), #axis.title.x.bottom = element_blank(), #axis.title.x.top = element_blank(), #axis.title.y = element_blank(), #axis.title.y.left = element_blank(), #axis.title.y.right = element_blank(), axis.text = element_blank(), #axis.text.x = element_blank(), #axis.text.x.top = element_blank(), #axis.text.x.bottom = element_blank(), #axis.text.y = element_blank(), #axis.text.y.left = element_blank(), #axis.text.y.right = element_blank(), axis.ticks = element_blank(), #axis.ticks.x = element_blank(), #axis.ticks.x.top = element_blank(), #axis.ticks.x.bottom = element_blank(), #axis.ticks.y = element_blank(), #axis.ticks.y.left = element_blank(), #axis.ticks.y.right = element_blank(), axis.line = element_blank(), #axis.line.x = element_blank(), #axis.line.x.bottom = element_blank(), #axis.line.x.top = element_blank(), #axis.line.y = element_blank(), #axis.line.y.left = element_blank(), #axis.line.y.right = element_blank(), NULL ) } #' @rdname theme_graph #' #' @param ... Parameters passed on the `theme_graph` #' #' @export set_graph_style <- function(family = 'Arial Narrow', face = 'plain', size = 11, text_size = 11, text_colour = 'black', ...) { style <- theme_graph( base_family = family, base_size = size, text_colour = text_colour, ... ) theme_set(style) text_size <- text_size / .pt update_geom_defaults(GeomEdgePath, list( family = family, fontface = face, label_size = text_size )) update_geom_defaults(GeomText, list( family = family, fontface = face, size = text_size )) update_geom_defaults(GeomTextRepel, list( family = family, fontface = face, size = text_size )) update_geom_defaults(GeomLabel, list( family = family, fontface = face, size = text_size )) update_geom_defaults(GeomLabelRepel, list( family = family, fontface = face, size = text_size )) } #' @rdname theme_graph #' #' @export unset_graph_style <- function() { style <- theme_gray() theme_set(style) update_geom_defaults(GeomEdgePath, list( family = '', fontface = 1, label_size = 3.88 )) update_geom_defaults(GeomText, list( family = '', fontface = 1, size = 3.88 )) update_geom_defaults(GeomTextRepel, list( family = '', fontface = 1, size = 3.88 )) update_geom_defaults(GeomLabel, list( family = '', fontface = 1, size = 3.88 )) update_geom_defaults(GeomLabelRepel, list( family = '', fontface = 1, size = 3.88 )) update_geom_defaults(GeomAxisHive, list( family = '', fontface = 1, size = 3.88 )) } ggraph/R/geom_edge_fan.R0000644000176200001440000002273213527431526014623 0ustar liggesusers#' Draw edges as curves of different curvature #' #' This geom draws edges as cubic beziers with the control point positioned #' half-way between the nodes and at an angle dependent on the presence of #' parallel edges. This results in parallel edges being drawn in a #' non-overlapping fashion resembling the standard approach used in #' [igraph::plot.igraph()]. Before calculating the curvature the edges #' are sorted by direction so that edges going the same way will be adjacent. #' This geom is currently the only choice for non-simple graphs if edges should #' not be overplotted. #' #' @inheritSection geom_edge_link Edge variants #' @inheritSection geom_edge_link Edge aesthetic name expansion #' #' @section Aesthetics: #' `geom_edge_fan` and `geom_edge_fan0` understand the following #' aesthetics. Bold aesthetics are automatically set, but can be overridden. #' #' - **x** #' - **y** #' - **xend** #' - **yend** #' - **from** #' - **to** #' - edge_colour #' - edge_width #' - edge_linetype #' - edge_alpha #' - filter #' #' `geom_edge_fan2` understand the following aesthetics. Bold aesthetics are #' automatically set, but can be overridden. #' #' - **x** #' - **y** #' - **group** #' - **from** #' - **to** #' - edge_colour #' - edge_width #' - edge_linetype #' - edge_alpha #' - filter #' #' `geom_edge_fan` and `geom_edge_fan2` furthermore takes the following #' aesthetics. #' #' - start_cap #' - end_cap #' - label #' - label_pos #' - label_size #' - angle #' - hjust #' - vjust #' - family #' - fontface #' - lineheight #' #' @section Computed variables: #' #' \describe{ #' \item{index}{The position along the path (not computed for the *0 version)} #' } #' #' @inheritParams geom_edge_link #' @inheritParams ggplot2::geom_path #' #' @param strength Modify the width of the fans `strength > 1` will create #' wider fans while the reverse will make them more narrow. #' #' @param spread Deprecated. Use `strength` instead. #' #' @author Thomas Lin Pedersen #' #' @family geom_edge_* #' #' @examples #' require(tidygraph) #' gr <- create_notable('bull') %>% #' convert(to_directed) %>% #' bind_edges(data.frame(from = c(1, 2, 2, 3), to = c(2, 1, 3, 2))) %E>% #' mutate(class = sample(letters[1:3], 9, TRUE)) %N>% #' mutate(class = sample(c('x', 'y'), 5, TRUE)) #' #' ggraph(gr, 'stress') + #' geom_edge_fan(aes(alpha = stat(index))) #' #' ggraph(gr, 'stress') + #' geom_edge_fan2(aes(colour = node.class)) #' #' ggraph(gr, 'stress') + #' geom_edge_fan0(aes(colour = class)) #' @rdname geom_edge_fan #' @name geom_edge_fan #' NULL #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatBezier #' @export StatEdgeFan <- ggproto('StatEdgeFan', StatBezier, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } data <- remove_loop(data) if (nrow(data) == 0) return(NULL) data$group <- make_unique(data$group) data2 <- data data2$x <- data2$xend data2$y <- data2$yend create_fans(data, data2, params) }, required_aes = c('x', 'y', 'xend', 'yend', 'from', 'to'), default_aes = aes(filter = TRUE), extra_params = c('na.rm', 'n', 'strength', 'spread') ) #' @rdname geom_edge_fan #' #' @export geom_edge_fan <- function(mapping = NULL, data = get_edges(), position = 'identity', arrow = NULL, strength = 1, n = 100, lineend = 'butt', linejoin = 'round', linemitre = 1, label_colour = 'black', label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = 'rot', force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ..., spread) { if (!missing(spread)) { .Deprecated(msg = 'The spread argument has been deprecated in favour of strength') strength <- spread } mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes( x = x, y = y, xend = xend, yend = yend, from = from, to = to, group = edge.id )) layer( data = data, mapping = mapping, stat = StatEdgeFan, geom = GeomEdgePath, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, linejoin = linejoin, linemitre = linemitre, na.rm = FALSE, n = n, interpolate = FALSE, strength = strength, label_colour = label_colour, label_alpha = label_alpha, label_parse = label_parse, check_overlap = check_overlap, angle_calc = angle_calc, force_flip = force_flip, label_dodge = label_dodge, label_push = label_push, ... ) ) ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatBezier2 #' @export StatEdgeFan2 <- ggproto('StatEdgeFan2', StatBezier2, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } data <- remove_loop2(data) if (nrow(data) == 0) return(NULL) data <- data[order(data$group), ] data2 <- data[c(FALSE, TRUE), ] data <- data[c(TRUE, FALSE), ] create_fans(data, data2, params) }, required_aes = c('x', 'y', 'group', 'from', 'to'), default_aes = aes(filter = TRUE), extra_params = c('na.rm', 'n', 'strength', 'spread') ) #' @rdname geom_edge_fan #' #' @export geom_edge_fan2 <- function(mapping = NULL, data = get_edges('long'), position = 'identity', arrow = NULL, strength = 1, n = 100, lineend = 'butt', linejoin = 'round', linemitre = 1, label_colour = 'black', label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = 'rot', force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ..., spread) { if (!missing(spread)) { .Deprecated(msg = 'The spread argument has been deprecated in favour of strength') strength <- spread } mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes( x = x, y = y, group = edge.id, from = from, to = to )) layer( data = data, mapping = mapping, stat = StatEdgeFan2, geom = GeomEdgePath, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, linejoin = linejoin, linemitre = linemitre, na.rm = FALSE, n = n, interpolate = TRUE, strength = strength, label_colour = label_colour, label_alpha = label_alpha, label_parse = label_parse, check_overlap = check_overlap, angle_calc = angle_calc, force_flip = force_flip, label_dodge = label_dodge, label_push = label_push, ... ) ) ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatBezier0 #' @export StatEdgeFan0 <- ggproto('StatEdgeFan0', StatBezier0, setup_data = function(data, params) { StatEdgeFan$setup_data(data, params) }, required_aes = c('x', 'y', 'xend', 'yend', 'from', 'to'), default_aes = aes(filter = TRUE), extra_params = c('na.rm', 'strength', 'spread') ) #' @rdname geom_edge_fan #' #' @export geom_edge_fan0 <- function(mapping = NULL, data = get_edges(), position = 'identity', arrow = NULL, strength = 1, lineend = 'butt', show.legend = NA, ..., spread) { if (!missing(spread)) { .Deprecated(msg = 'The spread argument has been deprecated in favour of strength') strength <- spread } mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes( x = x, y = y, xend = xend, yend = yend, from = from, to = to )) layer( data = data, mapping = mapping, stat = StatEdgeFan0, geom = GeomEdgeBezier, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, na.rm = FALSE, strength = strength, ... ) ) ) } #' @importFrom dplyr %>% group_by arrange mutate n ungroup transmute create_fans <- function(from, to, params) { from$.id <- paste(pmin(from$from, to$to), pmax(from$from, to$to), sep = '-') from$.orig_ind <- seq_len(nrow(from)) position <- from %>% group_by(.data$PANEL, .data$.id) %>% arrange(.data$from) %>% mutate(position = seq_len(n()) - 0.5 - n() / 2) %>% mutate(position = .data$position * ifelse(.data$from < .data$to, 1, -1)) %>% ungroup() %>% arrange(.data$.orig_ind) %>% transmute(position = .data$position) position <- position$position max_fans <- max(table(from$.id)) from$.id <- NULL from$.orig_ind <- NULL mean_x <- rowMeans(cbind(from$x, to$x)) mean_y <- rowMeans(cbind(from$y, to$y)) step_x <- -(params$strength * (to$y - from$y) / (2 * max_fans)) step_y <- params$strength * (to$x - from$x) / (2 * max_fans) data <- from data$x <- mean_x + step_x * position data$y <- mean_y + step_y * position bezier_start <- seq(1, by = 3, length.out = nrow(from)) from$index <- bezier_start to$index <- bezier_start + 2 data$index <- bezier_start + 1 data <- rbind_dfs(list(from, data, to)) data[order(data$index), names(data) != 'index'] } ggraph/R/layout_eigen.R0000644000176200001440000000253213526517064014545 0ustar liggesusers#' Place nodes according to their eigenvalues #' #' This layout is based on the idea of spectral layouts where node coordinates #' are calculated directly by decomposing a matrix representation of the graph #' and extracting the eigenvectors. #' #' @param graph A tbl_graph object #' @param type The type of matrix to extract the eigenvectors from. Either #' `'laplacian'` or `'adjacency'` #' @param eigenvector The eigenvector to use for coordinates. Either `'smallest'` #' or `'largest'` #' @param circular ignored #' #' @return A data.frame with the columns `x`, `y`, `circular` as #' well as any information stored as node variables in the tbl_graph object. #' #' @family layout_tbl_graph_* #' #' @author The underlying algorithm is implemented in the graphlayouts package #' by David Schoch #' #' @importFrom graphlayouts layout_with_eigen layout_tbl_graph_eigen <- function(graph, type = 'laplacian', eigenvector = 'smallest', circular = FALSE) { type <- match.arg(type, c('laplacian', 'adjacency')) eigenvector <- match.arg(eigenvector, c('smallest', 'largest')) xy <- layout_with_eigen(graph, type = type, ev = eigenvector) nodes <- new_data_frame(list(x = xy[,1],y = xy[,2])) nodes$circular <- FALSE extra_data <- as_tibble(graph, active = 'nodes') nodes <- cbind(nodes, extra_data[, !names(extra_data) %in% names(nodes), drop = FALSE]) nodes } ggraph/R/nodes.R0000644000176200001440000000212413525467255013173 0ustar liggesusers#' Create a node extractor function #' #' This function returns another function that can extract nodes from a #' ggraph_layout object. As a ggraph_layout object is essentially a data.frame #' of nodes it might seem useless to provide this function, but since the node #' data is not necessarily available until after the `ggraph()` call it #' can be beneficial to be able to add information to the node data on a #' per-layer basis. Unlike [get_edges()] the use of `get_nodes` is not #' mandatory and is only required if additional data should be added to selected #' node layers. #' #' @param ... Additional data that should be cbind'ed together with the node #' data. #' #' @return A data.frame with the node data as well of any additional data #' supplied through `...` #' #' @family extractors #' #' @export #' get_nodes <- function(...) { function(layout) { nodes <- do.call( cbind, c( list(layout), lapply(list(...), rep, length.out = nrow(layout)), list(stringsAsFactors = FALSE) ) ) attr(nodes, 'type_ggraph') <- 'node_ggraph' nodes } } ggraph/R/layout_fabric.R0000644000176200001440000001014113525462374014701 0ustar liggesusers#' Create a fabric layout #' #' This layout is a bit unusual in that it shows nodes as horizontal line ranges #' end edges as evenly spaced vertical spans connecting the nodes. As with the #' matrix layout the strength comes from better scalability but its use require #' some experience recognising the patterns that different connectivity features #' gives rise to. As with matrix layouts the ordering of nodes have huge power #' over the look of the plot. The `node_rank_fabric()` mimics the default #' ordering from the original BioFabric implementation, but other ranking #' algorithms from tidygraph can be used with the `sort.by` argument as well. #' Fabric layouts tend to become quite wide as the graph grows which is #' something that should be handled with care - e.g. by only zooming in on a #' specific region. #' #' @param graph An `tbl_graph` object #' #' @param circular Ignored #' #' @param sort.by An expression providing the sorting of the nodes. If `NULL` #' the nodes will be ordered by their index in the graph. #' #' @param shadow.edges Should shadow edges be shown. #' #' @return A data.frame with the columns `x`, `xmin`, `xmax`, `y`, `circular` as #' well as any information stored as node variables in the tbl_graph object. #' Further, the edges of the graph will gain a `edge_x` variable giving the #' horizontal position of the edge as well as a `shadow_edge` variable denoting #' whether the edge is a shadow edge added by the layout. #' #' @family layout_tbl_graph_* #' #' @importFrom igraph incident_edges V #' @importFrom rlang enquo eval_tidy #' #' @references #' BioFabric website: #' #' Longabaugh, William J.R. (2012). #' *Combing the hairball with BioFabric: a new approach for visualization of large networks*. #' BMC Bioinformatics, 13: 275. #' layout_tbl_graph_fabric <- function(graph, circular = FALSE, sort.by = NULL, shadow.edges = FALSE) { sort.by <- enquo(sort.by) sort.by <- eval_tidy(sort.by, .N()) if (!is.null(sort.by)) { pos <- order(order(sort.by)) } else { pos <- seq_len(gorder(graph)) } edges <- as_edgelist(graph, names = FALSE) edges <- cbind(pos[edges[,1]], pos[edges[,2]]) first_node <- pmin(edges[,1], edges[,2]) second_node <- pmax(edges[,1], edges[,2]) edge_order <- order(first_node, second_node) if (shadow.edges) { shadow_order <- order(second_node, first_node) edge_order <- split(edge_order, factor(first_node[edge_order], seq_along(pos))) shadow_order <- split(shadow_order + length(second_node), factor(second_node[shadow_order], seq_along(pos))) edge_order <- unlist(c(shadow_order, edge_order)[matrix(seq_len(length(pos) * 2), nrow = 2, byrow = T)]) graph <- bind_edges(graph, as_tibble(graph, active = 'edges')) shadow <- rep(c(FALSE, TRUE), each = length(first_node)) } else { shadow <- rep_len(FALSE, length(first_node)) } edge_rank <- match(seq_along(edge_order), edge_order) node_span <- vapply(incident_edges(graph, V(graph), mode = 'all'), function(e) { range(edge_rank[as.integer(e)]) }, numeric(2)) nodes <- new_data_frame(list(x = colMeans(node_span), xmin = node_span[1,], xmax = node_span[2,], y = abs(pos - max(pos)))) extra_data <- as_tibble(graph, active = 'nodes') nodes <- cbind(nodes, extra_data[, !names(extra_data) %in% names(nodes), drop = FALSE]) nodes$circular <- FALSE graph <- activate(graph, 'edges') graph <- mutate(graph, edge_x = edge_rank, shadow_edge = shadow) graph <- activate(graph, 'nodes') attr(nodes, 'graph') <- graph nodes } #' @rdname layout_tbl_graph_fabric #' @importFrom igraph bfs degree #' @importFrom tidygraph activate .G arrange #' @export node_rank_fabric <- function() { graph <- activate(.G(), 'nodes') graph <- mutate(graph, node_order_orig = seq_len(n())) graph <- arrange(graph, -degree(graph)) node_order_orig <- pull(graph, node_order_orig) graph <- activate(graph, 'edges') graph <- arrange(graph, pmin(from, to), pmax(from, to)) order <- as.integer(bfs(graph, 1, 'all', order = TRUE)$order) order <- node_order_orig[order] match(seq_along(order), order) } ggraph/R/facet_graph.R0000644000176200001440000001577413525472730014337 0ustar liggesusers#' Create a grid of small multiples by node and/or edge attributes #' #' This function is equivalent to [ggplot2::facet_grid()] in that it #' allows for building a grid of small multiples where rows and columns #' correspond to a specific data value. While [ggplot2::facet_grid()] #' could be used it would lead to unexpected results as it is not possible to #' specify whether you are referring to a node or an edge attribute. Furthermore #' [ggplot2::facet_grid()] will draw edges in panels even though the #' panel does not contain both terminal nodes. `facet_graph` takes care of #' all of these issues, allowing you to define which data type the rows and #' columns are referencing as well as filtering the edges based on the nodes in #' each panel (even when nodes are not drawn). #' #' @param row_type,col_type Either `'node'` or `'edge'`. Which data #' type is being facetted in the rows and columns. Default is to facet on nodes #' column wise and on edges row wise. #' #' @inheritParams ggplot2::facet_grid #' #' @family ggraph-facets #' #' @examples #' library(tidygraph) #' gr <- as_tbl_graph(highschool) %>% #' mutate(popularity = as.character(cut(centrality_degree(mode = 'in'), #' breaks = 3, #' labels = c('low', 'medium', 'high') #' ))) #' ggraph(gr) + #' geom_edge_link() + #' geom_node_point() + #' facet_graph(year ~ popularity) #' @export #' facet_graph <- function(facets, row_type = 'edge', col_type = 'node', margins = FALSE, scales = 'fixed', space = 'fixed', shrink = TRUE, labeller = 'label_value', as.table = TRUE, switch = NULL, drop = TRUE) { facet <- facet_grid(facets, margins = margins, scales = scales, space = space, shrink = shrink, labeller = labeller, as.table = as.table, switch = switch, drop = drop ) ggproto(NULL, FacetGraph, shrink = shrink, params = c(facet$params, list( row_type = row_type, col_type = col_type )) ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @export FacetGraph <- ggproto('FacetGraph', FacetGrid, compute_layout = function(data, params) { plot_data <- data[[1]] data <- split(data, vapply(data, data_type, character(1))) rows <- params$rows cols <- params$cols row_data <- switch( params$row_type, node = data$node_ggraph, edge = data$edge_ggraph, stop('row_type must be either "node" or "edge"', call. = FALSE) ) col_data <- switch( params$col_type, node = data$node_ggraph, edge = data$edge_ggraph, stop('col_type must be either "node" or "edge"', call. = FALSE) ) base_rows <- combine_vars(row_data, params$plot_env, rows, drop = params$drop) if (!params$as.table) { rev_order <- function(x) factor(x, levels = rev(ulevels(x))) base_rows[] <- lapply(base_rows, rev_order) } base_cols <- combine_vars(col_data, params$plot_env, cols, drop = params$drop) base <- df.grid( as.data.frame(base_rows, stringsAsFactors = FALSE), as.data.frame(base_cols, stringsAsFactors = FALSE) ) # Add margins base <- reshape2::add_margins(base, list(names(rows), names(cols)), params$margins) # Work around bug in reshape2 base <- unique(base) # Create panel info dataset panel <- id(base, drop = TRUE) panel <- factor(panel, levels = seq_len(attr(panel, 'n'))) rows <- if (is.null(names(rows))) 1L else id(base[names(rows)], drop = TRUE) cols <- if (is.null(names(cols))) 1L else id(base[names(cols)], drop = TRUE) panels <- new_data_frame(c(list( PANEL = panel, ROW = rows, COL = cols), base )) panels <- panels[order(panels$PANEL), , drop = FALSE] rownames(panels) <- NULL panels$SCALE_X <- if (params$free$x) panels$COL else 1L panels$SCALE_Y <- if (params$free$y) panels$ROW else 1L node_placement <- if (nrow(base_cols) == 0) { list(`1` = plot_data$ggraph_index) } else { if (params$row_type == 'edge') params$rows <- NULL if (params$col_type == 'edge') params$cols <- NULL node_map <- FacetGrid$map_data(plot_data, panels, params) node_map <- expand_facet_map(node_map, panels) node_map <- node_map[order(node_map$.ggraph.index), , drop = FALSE] split(node_map$.ggraph.index, node_map$PANEL) } attr(panels, 'node_placement') <- node_placement panels }, map_data = function(data, layout, params) { switch( data_type(data), edge_ggraph = { if (params$row_type == 'node') params$rows <- NULL if (params$col_type == 'node') params$cols <- NULL edge_map <- FacetGrid$map_data(data, layout, params) edge_map <- expand_facet_map(edge_map, layout) edge_map <- Map(function(map, nodes) { map[map$from %in% nodes & map$to %in% nodes, , drop = FALSE] }, map = split(edge_map, edge_map$PANEL), nodes = attr(layout, 'node_placement')) rbind_dfs(edge_map) }, node_ggraph = { node_map <- lapply(attr(layout, 'node_placement'), function(nodes) { data[data$.ggraph.index %in% nodes, , drop = FALSE] }) panel <- rep(seq_along(node_map), vapply(node_map, nrow, numeric(1))) node_map <- rbind_dfs(node_map) node_map$PANEL <- as.factor(panel) node_map }, { FacetGrid$map_data(data, layout, params) } ) } ) expand_facet_map <- function(map, layout) { ncols <- max(layout$COL) nrows <- max(layout$ROW) if (!(ncols == 1 && nrows == 1)) { map$PANEL[map$PANEL == -1] <- 1 } if (ncols != 1) { data_cols <- unique(layout$COL[layout$PANEL %in% map$PANEL]) if (length(data_cols) == 1) { map <- lapply(split(map, as.integer(map$PANEL)), function(data) { row <- layout$ROW[layout$PANEL == data$PANEL[1]] panels <- layout$PANEL[layout$ROW == row] data_expand <- data[rep(seq_len(nrow(data)), length(panels)), , drop = FALSE] data_expand$PANEL <- rep(panels, each = nrow(data)) data_expand }) map <- rbind_dfs(map) } } if (nrows != 1) { data_rows <- unique(layout$ROW[layout$PANEL %in% map$PANEL]) if (length(data_rows) == 1) { map <- lapply(split(map, as.integer(map$PANEL)), function(data) { col <- layout$COL[layout$PANEL == data$PANEL[1]] panels <- layout$PANEL[layout$COL == col] data_expand <- data[rep(seq_len(nrow(data)), length(panels)), , drop = FALSE] data_expand$PANEL <- rep(panels, each = nrow(data)) data_expand }) map <- rbind_dfs(map) } } map } df.grid <- function(a, b) { if (is.null(a) || nrow(a) == 0) { return(b) } if (is.null(b) || nrow(b) == 0) { return(a) } indexes <- expand.grid(i_a = seq_len(nrow(a)), i_b = seq_len(nrow(b))) grid <- cbind(a[indexes$i_a, , drop = FALSE], b[indexes$i_b, , drop = FALSE]) attr(grid, 'row.names') <- .set_row_names(nrow(grid)) grid } ulevels <- function(x) { if (is.factor(x)) { x <- addNA(x, TRUE) factor(levels(x), levels(x), exclude = NULL) } else { sort(unique(x)) } } ggraph/R/facet_edges.R0000644000176200001440000000373413525473415014317 0ustar liggesusers#' Create small multiples based on edge attributes #' #' This function is equivalent to [ggplot2::facet_wrap()] but only #' facets edges. Nodes are repeated in every panel. #' #' @inheritParams ggplot2::facet_wrap #' #' @family ggraph-facets #' #' @examples #' gr <- tidygraph::as_tbl_graph(highschool) #' #' ggraph(gr) + #' geom_edge_link() + #' geom_node_point() + #' facet_edges(~year) #' @export #' facet_edges <- function(facets, nrow = NULL, ncol = NULL, scales = 'fixed', shrink = TRUE, labeller = 'label_value', as.table = TRUE, switch = NULL, drop = TRUE, dir = 'h', strip.position = 'top') { facet <- facet_wrap( facets = facets, nrow = nrow, ncol = ncol, scales = scales, shrink = shrink, labeller = labeller, as.table = as.table, switch = switch, drop = drop, dir = dir, strip.position = strip.position ) ggproto(NULL, FacetEdges, shrink = shrink, params = facet$params ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @export FacetEdges <- ggproto('FacetEdges', FacetWrap, compute_layout = function(data, params) { plot_data <- data[[1]] data <- split(data, vapply(data, data_type, character(1))) facet_data <- data$edge_ggraph panels <- FacetWrap$compute_layout(facet_data, params) node_placement <- rep(list(plot_data$.ggraph.index), nrow(panels)) names(node_placement) <- as.character(seq_along(node_placement)) attr(panels, 'node_placement') <- node_placement panels }, map_data = function(data, layout, params) { switch( data_type(data), node_ggraph = { node_map <- rep(list(data), length(attr(layout, 'node_placement'))) panel <- rep(seq_along(node_map), vapply(node_map, nrow, numeric(1))) node_map <- rbind_dfs(node_map) node_map$PANEL <- as.factor(panel) node_map }, edge_ggraph = , { FacetWrap$map_data(data, layout, params) } ) } ) ggraph/R/layout_auto.R0000644000176200001440000000425613527222413014423 0ustar liggesusers#' Automatically pick a layout based on graph type #' #' This function infers the layout from the graph structure and is the default #' when calling [ggraph()]. If an `x` and `y` argument is passed along, the #' manual layout is chosen. Otherwise if the graph is either a rooted tree or a #' rooted forest the layout will be `dendrogram` if the nodes contains a height #' variable or `tree` if not. If the tree is unrooted the `unrooted` layout will #' be used. If the tree is a DAG the `sygiyama` layout will be used. Otherwise #' the `stress` layout will be used (or `sparse_tree` if the graph contains more #' than 2000 nodes). #' #' @param graph A tbl_graph object #' #' @param circular Logical. Should the layout be transformed to a circular #' representation. Defaults to `FALSE`. Only applicable if the graph is a tree #' structure #' #' @param ... Arguments passed on to the chosen layout #' #' @return A data.frame with the columns `x`, `y`, `circular` as #' well as any information stored as node variables in the tbl_graph object. #' #' @family layout_tbl_graph_* #' #' @importFrom rlang .data quos #' layout_tbl_graph_auto <- function(graph, circular, ...) { if (all(c('x', 'y') %in% names(quos(...)))) { layout_tbl_graph_manual(graph, circular = circular, ...) } else if (with_graph(graph, graph_is_tree() || graph_is_forest())) { if (is.null(with_graph(graph, .N()[['height']]))) { message('Using `tree` as default layout') layout_tbl_graph_igraph(graph, algorithm = 'tree', circular = circular, ...) } else { message('Using `dendrogram` as default layout') layout_tbl_graph_dendrogram(graph, circular = circular, height = .data$height, ...) } } else if (with_graph(graph, graph_is_dag())) { message('Using `sugiyama` as default layout') layout_tbl_graph_igraph(graph, algorithm = 'sugiyama', circular = circular, ...) } else if (with_graph(graph, graph_order()) < 2000) { message('Using `stress` as default layout') layout_tbl_graph_stress(graph, circular = circular, ...) } else { message('Using `sparse_stress` with 250 pivots as default layout') layout_tbl_graph_sparse_stress(graph, pivots = 250, circular = circular, ...) } } ggraph/R/geom_node_voronoi.R0000644000176200001440000000520113524613767015571 0ustar liggesusers#' Show nodes as voronoi tiles #' #' This geom is equivalent in functionality to [ggforce::geom_voronoi_tile()] #' and allows for plotting of nodes as tiles from a voronoi tesselation. As with #' [ggforce::geom_voronoi_tile()] it is possible to restrict the size of the #' tile to a fixed radius, as well as round corners and expand/contract the #' tile. #' #' @section Aesthetics: #' `geom_node_voronoi` understand the following aesthetics. Bold aesthetics are #' automatically set, but can be overridden. #' #' - **x** #' - **y** #' - alpha #' - colour #' - fill #' - shape #' - size #' - stroke #' - filter #' #' @inheritParams ggforce::geom_voronoi_tile #' #' @param mapping Set of aesthetic mappings created by [ggplot2::aes()] #' or [ggplot2::aes_()]. By default x and y are mapped to x and y in #' the node data and group set to `-1`. #' #' @author Thomas Lin Pedersen #' #' @family geom_node_* #' #' @examples #' require(tidygraph) #' gr <- create_notable('meredith') %>% #' mutate(group = sample(letters[1:4], n(), TRUE)) #' #' ggraph(gr) + #' geom_node_voronoi(aes(fill = group, colour = group), alpha = 0.3) + #' geom_edge_link(alpha = 0.3) + #' geom_node_point() #' #' # Use max.radius to make the tesselation more "node"-like #' ggraph(gr) + #' geom_node_voronoi(aes(fill = group, colour = group), alpha = 0.3, max.radius = 1) + #' geom_edge_link(alpha = 0.3) + #' geom_node_point() #' @export #' @importFrom ggforce GeomShape #' geom_node_voronoi <- function(mapping = NULL, data = NULL, position = 'identity', show.legend = NA, bound = NULL, eps = 1e-09, max.radius = NULL, normalize = FALSE, asp.ratio = 1, expand = 0, radius = 0, ...) { mapping <- aes_intersect(mapping, aes(x = x, y = y, group = -1)) layer( data = data, mapping = mapping, stat = StatNodeVoronoi, geom = GeomShape, position = position, show.legend = show.legend, inherit.aes = FALSE, params = list(na.rm = FALSE, bound = bound, eps = eps, max.radius = max.radius, normalize = normalize, asp.ratio = asp.ratio, expand = expand, radius = radius, ...) ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatVoronoiTile #' @export StatNodeVoronoi <- ggproto('StatNodeVoronoi', StatVoronoiTile, finish_layer = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } if (nrow(data) == 0) return(NULL) data }, default_aes = aes(filter = TRUE) ) ggraph/R/facet_nodes.R0000644000176200001440000000476613525466714014352 0ustar liggesusers#' Create small multiples based on node attributes #' #' This function is equivalent to [ggplot2::facet_wrap()] but only #' facets nodes. Edges are drawn if their terminal nodes are both present in a #' panel. #' #' @inheritParams ggplot2::facet_wrap #' #' @family ggraph-facets #' #' @examples #' library(tidygraph) #' gr <- as_tbl_graph(highschool) %>% #' mutate(popularity = as.character(cut(centrality_degree(mode = 'in'), #' breaks = 3, #' labels = c('low', 'medium', 'high') #' ))) #' ggraph(gr) + #' geom_edge_link() + #' geom_node_point() + #' facet_nodes(~popularity) #' @export #' facet_nodes <- function(facets, nrow = NULL, ncol = NULL, scales = 'fixed', shrink = TRUE, labeller = 'label_value', as.table = TRUE, switch = NULL, drop = TRUE, dir = 'h', strip.position = 'top') { facet <- facet_wrap( facets = facets, nrow = nrow, ncol = ncol, scales = scales, shrink = shrink, labeller = labeller, as.table = as.table, switch = switch, drop = drop, dir = dir, strip.position = strip.position ) ggproto(NULL, FacetNodes, shrink = shrink, params = facet$params ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @export FacetNodes <- ggproto('FacetNodes', FacetWrap, compute_layout = function(data, params) { plot_data <- data[[1]] data <- split(data, vapply(data, data_type, character(1))) facet_data <- data$node_ggraph panels <- FacetWrap$compute_layout(facet_data, params) node_placement <- if (nrow(panels) == 1) { list(`1` = plot_data$ggraph_index) } else { node_map <- FacetWrap$map_data(plot_data, panels, params) node_map <- node_map[order(node_map$.ggraph.index), , drop = FALSE] split(node_map$.ggraph.index, node_map$PANEL) } attr(panels, 'node_placement') <- node_placement panels }, map_data = function(data, layout, params) { switch( data_type(data), edge_ggraph = { node_placement <- attr(layout, 'node_placement') edge_map <- Map(function(map, nodes) { map[map$from %in% nodes & map$to %in% nodes, , drop = FALSE] }, map = rep(list(data), length(node_placement)), nodes = node_placement) panel <- rep(seq_along(edge_map), vapply(edge_map, nrow, numeric(1))) edge_map <- rbind_dfs(edge_map) edge_map$PANEL <- as.factor(panel) edge_map }, node_ggraph = , { FacetWrap$map_data(data, layout, params) } ) } ) ggraph/R/geom_edge_loop.R0000644000176200001440000001350113527431540015016 0ustar liggesusers#' Draw edges as diagonals #' #' This geom draws edge loops (edges starting and ending at the same node). #' Loops are drawn as bezier curves starting and ending at the position of the #' node and with control points protruding at an angle and in a direction #' specified in the call. As the start and end node is always the same no *2 #' method is provided. Loops can severely clutter up your visualization which is #' why they are decoupled from the other edge drawings. Only plot them if they #' are of importance. If the graph doesn't contain any loops the geom adds #' nothing silently. #' #' @inheritSection geom_edge_link Edge variants #' @inheritSection geom_edge_link Edge aesthetic name expansion #' #' @section Aesthetics: #' `geom_edge_loop` and `geom_edge_loop0` understand the following #' aesthetics. Bold aesthetics are automatically set, but can be overridden. #' #' - **x** #' - **y** #' - **from** #' - **to** #' - **span** *90* #' - **direction** *45* #' - **strength** *1* #' - edge_colour #' - edge_width #' - edge_linetype #' - edge_alpha #' - filter #' #' `geom_edge_loop` furthermore takes the following aesthetics. #' #' - start_cap #' - end_cap #' - label #' - label_pos #' - label_size #' - angle #' - hjust #' - vjust #' - family #' - fontface #' - lineheight #' #' @section Computed variables: #' #' \describe{ #' \item{index}{The position along the path (not computed for the *0 version)} #' } #' #' @inheritParams geom_edge_link #' @inheritParams ggplot2::geom_path #' #' @author Thomas Lin Pedersen #' #' @family geom_edge_* #' #' @examples #' require(tidygraph) #' gr <- as_tbl_graph( #' data.frame(from = c(1, 1, 2, 2, 3, 3, 3), to = c(1, 2, 2, 3, 3, 1, 2)) #' ) #' #' ggraph(gr, 'stress') + #' geom_edge_loop(aes(alpha = stat(index))) + #' geom_edge_fan(aes(alpha = stat(index))) #' #' ggraph(gr, 'stress') + #' geom_edge_loop0() + #' geom_edge_fan0() #' @rdname geom_edge_loop #' @name geom_edge_loop #' NULL #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatBezier #' @export StatEdgeLoop <- ggproto('StatEdgeLoop', StatBezier, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } if (nrow(data) == 0) return(NULL) data <- data[data$from == data$to, ] data$group <- make_unique(data$group) if (nrow(data) != 0) { create_loops(data, params) } else { NULL } }, required_aes = c('x', 'y', 'from', 'to', 'span', 'direction', 'strength'), default_aes = aes(filter = TRUE), extra_params = c('na.rm', 'n') ) #' @rdname geom_edge_loop #' #' @export geom_edge_loop <- function(mapping = NULL, data = get_edges(), position = 'identity', arrow = NULL, n = 100, lineend = 'butt', linejoin = 'round', linemitre = 1, label_colour = 'black', label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = 'rot', force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes( x = x, y = y, from = from, to = to, group = edge.id, span = 90, direction = 45, strength = 1 )) layer( data = data, mapping = mapping, stat = StatEdgeLoop, geom = GeomEdgePath, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, linejoin = linejoin, linemitre = linemitre, na.rm = FALSE, n = n, interpolate = FALSE, label_colour = label_colour, label_alpha = label_alpha, label_parse = label_parse, check_overlap = check_overlap, angle_calc = angle_calc, force_flip = force_flip, label_dodge = label_dodge, label_push = label_push, ... ) ) ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatBezier0 #' @export StatEdgeLoop0 <- ggproto('StatEdgeLoop0', StatBezier0, setup_data = function(data, params) { StatEdgeLoop$setup_data(data, params) }, required_aes = c('x', 'y', 'from', 'to', 'span', 'direction', 'strength'), default_aes = aes(filter = TRUE), extra_params = c('na.rm') ) #' @rdname geom_edge_loop #' #' @export geom_edge_loop0 <- function(mapping = NULL, data = get_edges(), position = 'identity', arrow = NULL, lineend = 'butt', show.legend = NA, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes( x = x, y = y, from = from, to = to, span = 90, direction = 45, strength = 1 )) layer( data = data, mapping = mapping, stat = StatEdgeLoop0, geom = GeomEdgeBezier, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list(arrow = arrow, lineend = lineend, na.rm = FALSE, ...) ) ) } create_loops <- function(loops, params) { control_angle1 <- loops$direction - loops$span / 2 control_angle2 <- loops$direction + loops$span / 2 controls1 <- find_loop_controls(loops, control_angle1) controls2 <- find_loop_controls(loops, control_angle2) end <- loops bezier_start <- seq(1, by = 4, length.out = nrow(loops)) loops$index <- bezier_start controls1$index <- bezier_start + 1 controls2$index <- bezier_start + 2 end$index <- bezier_start + 3 loops <- rbind_dfs(list(loops, controls1, controls2, end)) loops[order(loops$index), names(loops) != 'index'] } find_loop_controls <- function(loops, angle) { angle <- angle / 360 * 2 * pi loops$x <- loops$x + cos(angle) * loops$strength loops$y <- loops$y + sin(angle) * loops$strength loops } ggraph/R/layout_unrooted.R0000644000176200001440000000520713617226142015312 0ustar liggesusers#' Create an unrooted layout using equal-angle or equal-daylight #' #' When drawing unrooted trees the standard dendrogram layout is a bad fit as it #' implicitly creates a visual root node. Instead it is possible to spread the #' leafs out on the plane without putting any special emphasis on a particular #' node using an unrooted layout. The standard algorithm is the equal angle #' algorithm, but it can struggle with optimising the leaf distribution for #' large trees trees with very uneven branch length. The equal daylight #' algorithm modifies the output of the equal angle algorithm to better disperse #' the leaves, at the cost of higher computational cost and the possibility of #' edge crossings for very large unbalanced trees. For standard sized trees the #' daylight algorithm is far superior and not too heavy so it is the default. #' #' @note #' Unrooted is a layout intended for undirected trees, that is, graphs with no #' cycles. If the provided graph does not fit this format an attempt to convert #' it to such a format will be made. #' #' @param graph A tbl_graph object #' @param daylight Should equal-daylight adjustments be made #' @param length An expression evaluating to the branch length of each edge #' @param tolerance The threshold for mean angular adjustment before terminating #' the daylight adjustment #' @param rotation_mod A modifier for the angular adjustment of each branch. Set #' it below 1 to let the daylight adjustment progress more slowly #' @param maxiter The maximum number of iterations in the the daylight #' adjustment #' @param circular ignored #' #' @return A data.frame with the columns `x`, `y`, `circular`, `leaf` as well as #' any information stored as node variables in the tbl_graph object. #' #' @references #' Felsenstein, J. (2004) *Drawing Trees*, in Inferring Phylogenies. Sinauer #' Assoc., pp 573-584 #' #' @family layout_tbl_graph_* #' #' @importFrom igraph as.undirected gorder layout_tbl_graph_unrooted <- function(graph, daylight = TRUE, length = NULL, tolerance = 0.05, rotation_mod = 1, maxiter = 100, circular = FALSE) { extra_data <- as_tibble(graph, active = 'nodes') graph <- as.undirected(graph) length <- enquo(length) length <- eval_tidy(length, .E()) hierarchy <- tree_to_hierarchy(graph, 'out', seq_len(gorder(graph)), weight = NULL, length) layout <- unrooted(hierarchy$parent, hierarchy$order, hierarchy$height, daylight, tolerance, rotation_mod, maxiter)[-1, ] layout <- new_data_frame(list( x = layout[, 1], y = layout[, 2], circular = FALSE, leaf = degree(graph) == 1 )) layout <- cbind(layout, extra_data[, !names(extra_data) %in% names(layout), drop = FALSE]) layout } ggraph/R/connections.R0000644000176200001440000000601613617226142014377 0ustar liggesusers#' Create a connection extractor function #' #' Connections within the ggraph terminology are links between nodes that are #' not part of the network structure itself. In that sense connections do not #' affect the layout calculation in any way and will not be drawn by the #' standard `geom_edge_*` functions. A connection does not need to only be #' defined by a start and end node, but can include intermediary nodes. #' `get_con` helps in creating connection data by letting you specify start #' and end nodes and automatically finds the shortest path within the graph #' structure that connects the given points. If this is not what is needed it is #' also possible to supply a list of vectors giving node indices that define a #' connection. #' #' @param from,to The index of the start and end nodes for the connections #' #' @param paths A list of integer vectors giving the index of nodes defining #' connections #' #' @param ... Additional information to be added to the final data output #' #' @param weight An expression to be evaluated on the edge data to provide #' weights for the shortest path calculations #' #' @inheritParams igraph::shortest_paths #' #' @return A function that takes a layout_ggraph object and returns the given #' connections #' #' @family extractors #' #' @export get_con <- function(from = integer(), to = integer(), paths = NULL, ..., weight = NULL, mode = 'all') { if (length(from) != length(to)) { stop('from and to must be of equal length') } function(layout) { if (length(from) == 0) { return(NULL) } connections <- collect_connections( layout = layout, from = from, to = to, weight = { { weight } }, mode = mode ) nodes <- as.data.frame(layout, stringsAsFactors = FALSE)[unlist(connections), ] nodes$con.id <- rep(seq_along(connections), lengths(connections)) if (!is.null(paths)) { extra <- as.data.frame(layout, stringsAsFactors = FALSE)[unlist(paths), ] extra$con.id <- rep( seq_along(paths) + length(connections), lengths(paths) ) nodes <- rbind_dfs(list(nodes, extra)) } nodes <- do.call( cbind, c( list(nodes), lapply(list(...), function(x) rep_len(x, length(from))[nodes$con.id]), list(stringsAsFactors = FALSE) ) ) attr(nodes, 'type') <- 'connection_ggraph' nodes } } #' Internal data extractors #' #' These functions exists for supporting different data structures. There is no #' need to call these directly #' #' @param layout The layout data #' #' @param from,to A numeric vector giving the indexes of the start and end nodes #' #' @param ... Additional parameters passed on to the specific method #' #' @keywords internal #' @export #' @rdname internal_extractors #' @name internal_extractors collect_connections <- function(layout, from, to, ...) { UseMethod('collect_connections', layout) } collect_connections.default <- function(layout, ...) { stop('Don\'t know how to get connections from an object of class ', class(layout)) } ggraph/R/edge_direction.R0000644000176200001440000002766613526604503015037 0ustar liggesusers#' Edge direction guide #' #' This guide is intended to show the direction of edges based on the aesthetics #' mapped to its progression, such as changing width, colour and opacity. #' #' @inheritParams ggplot2::guide_colourbar #' #' @param arrow Logical. Should an arrow be drawn to illustrate the direction. #' Defaults to `TRUE` #' #' @param arrow.position The position of the arrow relative to the example edge. #' #' @param override.aes A list specifying aesthetic parameters of legend key. #' #' @importFrom grid is.unit unit #' @importFrom digest digest #' @export #' #' @examples #' gr <- tidygraph::as_tbl_graph(highschool) #' ggraph(gr, layout = 'kk') + #' geom_edge_fan(aes(alpha = stat(index))) + #' guides(edge_alpha = guide_edge_direction()) guide_edge_direction <- function(title = waiver(), title.position = NULL, title.theme = NULL, title.hjust = NULL, title.vjust = NULL, arrow = TRUE, arrow.position = NULL, barwidth = NULL, barheight = NULL, nbin = 500, direction = NULL, default.unit = 'line', reverse = FALSE, order = 0, override.aes = list(), ...) { if (!is.null(barwidth) && !is.unit(barwidth)) { barwidth <- unit(barwidth, default.unit) } if (!is.null(barheight) && !is.unit(barheight)) { barheight <- unit(barheight, default.unit) } guide <- list( title = title, title.position = title.position, title.theme = title.theme, title.hjust = title.hjust, title.vjust = title.vjust, arrow = arrow, arrow.position = arrow.position, barwidth = barwidth, barheight = barheight, nbin = nbin, direction = direction, default.unit = default.unit, reverse = reverse, order = order, available_aes = c('edge_colour', 'edge_alpha', 'edge_width'), override.aes = expand_edge_aes(rename_aes(override.aes)), ..., name = 'edge_direction' ) class(guide) <- c('guide', 'edge_direction') guide } #' Helper methods for guides #' #' @importFrom scales discard #' @export #' @rdname guide-helpers #' @keywords internal guide_train.edge_direction <- function(guide, scale, aesthetic = NULL) { if (length(intersect(scale$aesthetics, c( 'edge_colour', 'edge_alpha', 'edge_width' ))) == 0) { warning('edge_colourbar guide needs edge_colour or edge_alpha or edge_width scales.') return(NULL) } if (scale$is_discrete()) { warning('edge_colourbar guide needs continuous scales.') return(NULL) } breaks <- scale$get_breaks() if (length(breaks) == 0 || all(is.na(breaks))) { return() } ticks <- as.data.frame(setNames( list(scale$map(breaks)), aesthetic %||% scale$aesthetics[1] ), stringsAsFactors = FALSE) ticks$.value <- breaks ticks$.label <- scale$get_labels(breaks) guide$key <- ticks .limits <- scale$get_limits() .bar <- discard(pretty(.limits, n = guide$nbin), scale$get_limits()) if (length(.bar) == 0) { .bar <- unique(.limits) } guide$bar <- as.data.frame(setNames( list(scale$map(.bar)), scale$aesthetics[1] ), stringsAsFactors = FALSE) guide$bar$.value <- .bar guide$bar <- guide$bar[order(.bar), ] if (guide$reverse) { guide$key <- guide$key[nrow(guide$key):1, ] guide$bar <- guide$bar[nrow(guide$bar):1, ] } guide$hash <- with(guide, digest::digest(list( title, bar$.value, direction, name ))) guide } #' @rdname guide-helpers #' @export guide_merge.edge_direction <- function(guide, new_guide) { guide$bar <- merge(guide$bar, new_guide$bar, sort = FALSE) guide$override.aes <- c(guide$override.aes, new_guide$override.aes) if (any(duplicated(names(guide$override.aes)))) warning('Duplicated override.aes is ignored.') guide$override.aes <- guide$override.aes[!duplicated(names(guide$override.aes))] guide } #' @export #' @rdname guide-helpers guide_geom.edge_direction <- function(guide, layers, default_mapping) { guide$geoms <- lapply(layers, function(layer) { all <- names(c( layer$mapping, if (layer$inherit.aes) default_mapping, layer$stat$default_aes )) geom <- c(layer$geom$required_aes, names(layer$geom$default_aes)) matched <- intersect(intersect(all, geom), names(guide$bar)) matched <- setdiff(matched, names(layer$geom_params)) matched <- setdiff(matched, names(layer$aes_params)) if (length(matched) > 0) { if (is.na(layer$show.legend) || layer$show.legend) { data <- layer$geom$use_defaults( guide$bar[matched], layer$aes_params ) } else { return(NULL) } } else { if (is.na(layer$show.legend) || !layer$show.legend) { return(NULL) } else { data <- layer$geom$use_defaults( NULL, layer$aes_params )[rep(1, nrow(guide$bar)), ] } } data <- utils::modifyList(data, guide$override.aes) list(data = data, params = c(layer$geom_params, layer$stat_params)) }) guide$geoms <- guide$geoms[!vapply(guide$geoms, is.null, logical(1))] if (length(guide$geoms) == 0) { guide <- NULL } guide } #' @importFrom grid convertWidth convertHeight unit grobWidth grobHeight arrow grobName #' @importFrom gtable gtable gtable_add_grob #' @importFrom utils tail #' @importFrom stats setNames #' @rdname guide-helpers #' @export guide_gengrob.edge_direction <- function(guide, theme) { switch(guide$direction, horizontal = { arrow.position <- guide$arrow.position %||% 'bottom' if (!arrow.position %in% c('top', 'bottom')) { stop('label position "', arrow.position, '" is invalid') } }, vertical = { arrow.position <- guide$arrow.position %||% 'right' if (!arrow.position %in% c('left', 'right')) { stop('label position "', arrow.position, '" is invalid') } }) arrowlength <- convertWidth(guide$arrowlength %||% (theme$legend.key.width * 5), 'mm') arrowwidth <- convertWidth(unit(sin(30 / 360 * 2 * pi) * 0.25 * 2, 'in'), 'mm') edgewidth <- max(vapply(guide$geoms, function(g) { max(g$data$edge_width) }, numeric(1))) * .pt edgewidth <- convertWidth(unit(edgewidth, 'points'), 'mm') hgap <- c(convertWidth(unit(0.3, 'lines'), 'mm')) vgap <- hgap x <- rep(c(edgewidth) / 2, nrow(guide$bar)) xend <- x y <- seq(0, 1, length.out = nrow(guide$bar) + 1) * c(arrowlength) yend <- y[-1] y <- y[-length(y)] grob.bar <- switch( guide$direction, horizontal = { lapply(guide$geoms, function(g) { segmentsGrob( x0 = y, y0 = x, x1 = yend, y1 = xend, default.units = 'mm', gp = gpar( col = alpha( g$data$edge_colour, g$data$edge_alpha ), lwd = g$data$edge_width * .pt, lty = g$data$edge_linetype, lineend = 'butt' ) ) }) }, vertical = { lapply(guide$geoms, function(g) { segmentsGrob( x0 = x, y0 = y, x1 = xend, y1 = yend, default.units = 'mm', gp = gpar( col = alpha( g$data$edge_colour, g$data$edge_alpha ), lwd = g$data$edge_width * .pt, lty = g$data$edge_linetype, lineend = 'butt' ) ) }) } ) grob.bar <- do.call(gList, grob.bar) grob.title <- ggname( 'guide.title', element_grob(guide$title.theme %||% calc_element('legend.title', theme), label = guide$title, hjust = guide$title.hjust %||% theme$legend.title.align %||% 0, vjust = guide$title.vjust %||% 0.5 ) ) title_width <- convertWidth(grobWidth(grob.title), 'mm') title_width.c <- c(title_width) title_height <- convertHeight(grobHeight(grob.title), 'mm') title_height.c <- c(title_height) grob.arrow <- { if (!guide$arrow) { zeroGrob() } else { switch( guide$direction, horizontal = { segmentsGrob( x0 = y[1], y0 = c(arrowwidth) / 2, x1 = tail(yend, 1), y1 = c(arrowwidth) / 2, default.units = 'mm', gp = gpar( col = 'black', lwd = 0.5 * .pt, lty = 'solid', lineend = 'round' ), arrow = arrow(ends = if (guide$reverse) { 'first' } else { 'last' }) ) }, vertical = { segmentsGrob( x0 = c(arrowwidth) / 2, y0 = y[1], x1 = c(arrowwidth) / 2, y1 = tail(yend, 1), default.units = 'mm', gp = gpar( col = 'black', lwd = 0.5 * .pt, lty = 'solid', lineend = 'round' ), arrow = arrow(ends = if (guide$reverse) { 'first' } else { 'last' }) ) } ) } } switch(guide$direction, horizontal = { switch(arrow.position, top = { bl_widths <- c(arrowlength) bl_heights <- c(c(arrowwidth), vgap, c(edgewidth)) vps <- list( bar.row = 3, bar.col = 1, label.row = 1, label.col = 1 ) }, bottom = { bl_widths <- c(arrowlength) bl_heights <- c(c(edgewidth), vgap, c(arrowwidth)) vps <- list( bar.row = 1, bar.col = 1, label.row = 3, label.col = 1 ) }) }, vertical = { switch(arrow.position, left = { bl_widths <- c(c(arrowwidth), vgap, c(edgewidth)) bl_heights <- c(arrowlength) vps <- list( bar.row = 1, bar.col = 3, label.row = 1, label.col = 1 ) }, right = { bl_widths <- c(c(edgewidth), vgap, c(arrowwidth)) bl_heights <- c(arrowlength) vps <- list( bar.row = 1, bar.col = 1, label.row = 1, label.col = 3 ) }) }) switch(guide$title.position, top = { widths <- c(bl_widths, max(0, title_width.c - sum(bl_widths))) heights <- c(title_height.c, vgap, bl_heights) vps <- with(vps, list( bar.row = bar.row + 2, bar.col = bar.col, label.row = label.row + 2, label.col = label.col, title.row = 1, title.col = 1:length(widths) )) }, bottom = { widths <- c(bl_widths, max(0, title_width.c - sum(bl_widths))) heights <- c(bl_heights, vgap, title_height.c) vps <- with(vps, list( bar.row = bar.row, bar.col = bar.col, label.row = label.row, label.col = label.col, title.row = length(heights), title.col = 1:length(widths) )) }, left = { widths <- c(title_width.c, hgap, bl_widths) heights <- c(bl_heights, max(0, title_height.c - sum(bl_heights))) vps <- with(vps, list(bar.row = bar.row, bar.col = bar.col + 2, label.row = label.row, label.col = label.col + 2, title.row = 1:length(heights), title.col = 1)) }, right = { widths <- c(bl_widths, hgap, title_width.c) heights <- c(bl_heights, max(0, title_height.c - sum(bl_heights))) vps <- with(vps, list( bar.row = bar.row, bar.col = bar.col, label.row = label.row, label.col = label.col, title.row = 1:length(heights), title.col = length(widths) )) }) grob.background <- element_render(theme, 'legend.background') padding <- unit(1.5, 'mm') widths <- c(padding, widths, padding) heights <- c(padding, heights, padding) gt <- gtable(widths = unit(widths, 'mm'), heights = unit( heights, 'mm' )) gt <- gtable_add_grob(gt, grob.background, name = 'background', clip = 'off', t = 1, r = -1, b = -1, l = 1 ) gt <- gtable_add_grob(gt, grob.bar, name = 'bar', clip = 'off', t = 1 + min(vps$bar.row), r = 1 + max(vps$bar.col), b = 1 + max(vps$bar.row), l = 1 + min(vps$bar.col) ) gt <- gtable_add_grob(gt, grob.arrow, name = 'label', clip = 'off', t = 1 + min(vps$label.row), r = 1 + max(vps$label.col), b = 1 + max(vps$label.row), l = 1 + min(vps$label.col) ) gt <- gtable_add_grob(gt, grob.title, name = 'title', clip = 'off', t = 1 + min(vps$title.row), r = 1 + max(vps$title.col), b = 1 + max(vps$title.row), l = 1 + min(vps$title.col) ) gt } ggraph/R/geom_node_circle.R0000644000176200001440000000374113527321702015332 0ustar liggesusers#' Show nodes as circles #' #' This geom is equivalent in functionality to [ggforce::geom_circle()] #' and allows for plotting of nodes as circles with a radius scaled by the #' coordinate system. Because of the geoms reliance on the coordinate system #' it will only produce true circles when combined with #' [ggplot2::coord_fixed()] #' #' @section Aesthetics: #' `geom_node_circle` understand the following aesthetics. Bold aesthetics are #' automatically set, but can be overridden. #' #' - **x0** #' - **y0** #' - **r** #' - alpha #' - colour #' - fill #' - shape #' - size #' - stroke #' - filter #' #' @inheritParams ggforce::geom_circle #' #' @param mapping Set of aesthetic mappings created by [ggplot2::aes()] #' or [ggplot2::aes_()]. By default x and y are mapped to x0 and y0 in #' the node data. #' #' @author Thomas Lin Pedersen #' #' @family geom_node_* #' #' @examples #' require(tidygraph) #' gr <- tbl_graph(flare$vertices, flare$edges) #' ggraph(gr, 'circlepack', weight = size) + #' geom_node_circle() + #' coord_fixed() #' @export #' @importFrom ggforce GeomCircle #' geom_node_circle <- function(mapping = NULL, data = NULL, position = 'identity', show.legend = NA, ...) { mapping <- aes_intersect(mapping, aes(x0 = x, y0 = y, r = r)) layer( data = data, mapping = mapping, stat = StatNodeCircle, geom = GeomCircle, position = position, show.legend = show.legend, inherit.aes = FALSE, params = list(na.rm = FALSE, ...) ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatCircle #' @export StatNodeCircle <- ggproto('StatNodeCircle', StatCircle, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } if (nrow(data) == 0) return(NULL) data$group <- make_unique(data$group) data }, default_aes = aes(filter = TRUE) ) ggraph/R/textAlong.R0000644000176200001440000000557213524253151014025 0ustar liggesuserstextAlongGrob <- function(label, x = unit(0.5, 'npc'), y = unit(0.5, 'npc'), just = 'centre', hjust = NULL, vjust = NULL, rot = 0, check.overlap = FALSE, rot.type = 'rot', x0 = 0, y0 = 0, x1 = 0, y1 = 0, force.rot = TRUE, dodge = NULL, push = NULL, default.units = 'npc', name = NULL, gp = gpar(), vp = NULL) { if (!is.unit(x)) { x <- unit(x, default.units) } if (!is.unit(y)) { y <- unit(y, default.units) } if (rot.type == 'rot') { textGrob( label = label, x = x, y = y, just = just, hjust = hjust, vjust = vjust, rot = rot, check.overlap = check.overlap, default.units = default.units, name = name, gp = gp, vp = vp ) } else { if (!rot.type %in% c('along', 'across')) { stop('rot.type must be either `rot`, `along`, or `across`', call. = FALSE ) } if (!is.unit(x0)) { x0 <- unit(x0, default.units) } if (!is.unit(y0)) { y0 <- unit(y0, default.units) } if (!is.unit(x1)) { x1 <- unit(x1, default.units) } if (!is.unit(y1)) { y1 <- unit(y1, default.units) } grob( label = label, x = x, y = y, just = just, hjust = hjust, vjust = vjust, rot.type = rot.type, x0 = x0, y0 = y0, x1 = x1, y1 = y1, force.rot = force.rot, dodge = dodge, push = push, check.overlap = check.overlap, name = name, gp = gp, vp = vp, cl = 'textalong' ) } } #' Text angled according to line #' #' This function takes care of recalculating the angle of the text as the device #' size changes #' #' @importFrom grid makeContent #' @export #' @keywords internal makeContent.textalong <- function(x) { x0 <- convertX(x$x0, 'mm', TRUE) y0 <- convertY(x$y0, 'mm', TRUE) x1 <- convertX(x$x1, 'mm', TRUE) y1 <- convertY(x$y1, 'mm', TRUE) xpos <- convertX(x$x, 'mm', TRUE) ypos <- convertY(x$y, 'mm', TRUE) angle <- edge_angle(x0, y0, x1, y1) if (x$rot.type == 'across') { angle <- angle - 90 } if (!is.null(x$dodge)) { dodge <- convertHeight(x$dodge, 'mm', TRUE) dodge_angle <- (angle + 90) / 360 * 2 * pi dodge_x <- cos(dodge_angle) * dodge dodge_y <- sin(dodge_angle) * dodge xpos <- xpos + dodge_x ypos <- ypos + dodge_y } if (!is.null(x$push)) { push <- convertHeight(x$push, 'mm', TRUE) push_angle <- angle / 360 * 2 * pi push_x <- cos(push_angle) * push push_y <- sin(push_angle) * push xpos <- xpos + push_x ypos <- ypos + push_y } if (x$force.rot) { fix <- angle > 90 & angle < 270 angle[fix] <- angle[fix] + 180 } grob( label = x$label, x = unit(xpos, 'mm'), y = unit(ypos, 'mm'), just = x$just, hjust = x$hjust, vjust = x$vjust, rot = angle, check.overlap = x$check.overlap, name = x$name, gp = x$gp, vp = x$vp, cl = 'text' ) } ggraph/R/scale_edge_size.R0000644000176200001440000000464313524253140015161 0ustar liggesusers#' Edge size scales #' #' This set of scales defines new size scales for edge geoms equivalent to the #' ones already defined by ggplot2. See [ggplot2::scale_size()] for #' more information. The different geoms will know whether to use edge scales or #' the standard scales so it is not necessary to write `edge_size` in #' the call to the geom - just use `size`. #' #' @note In ggplot2 size conflates both line width and point size into one #' scale. In ggraph there is also a width scale ([scale_edge_width()]) #' that is used for linewidth. As edges are often represented by lines the width #' scale is the most common. #' #' @return A ggproto object inheriting from `Scale` #' #' @family scale_edge_* #' #' @name scale_edge_size #' @rdname scale_edge_size #' NULL #' @rdname scale_edge_size #' #' @inheritParams ggplot2::scale_size_continuous #' #' @importFrom scales area_pal #' @export scale_edge_size_continuous <- function(..., range = c(1, 6)) { continuous_scale('edge_size', 'area', area_pal(range), ...) } #' @rdname scale_edge_size #' #' @inheritParams ggplot2::scale_radius #' #' @importFrom scales rescale_pal #' @export scale_edge_radius <- function(..., range = c(1, 6)) { continuous_scale('edge_size', 'radius', rescale_pal(range), ...) } #' @rdname scale_edge_size #' #' @export scale_edge_size <- scale_edge_size_continuous #' @rdname scale_edge_size #' #' @inheritParams ggplot2::scale_size_discrete #' #' @importFrom scales rescale_pal #' @export scale_edge_size_discrete <- function(..., range = c(2, 6)) { discrete_scale('edge_size', 'size_d', function(n) { area <- seq(range[1]^2, range[2]^2, length.out = n) sqrt(area) }, ...) } #' @rdname scale_edge_size #' #' #' @inheritParams ggplot2::scale_size_area #' #' @importFrom scales abs_area rescale_max #' @export scale_edge_size_area <- function(..., max_size = 6) { continuous_scale('edge_size', 'area', palette = abs_area(max_size), rescaler = rescale_max, ... ) } #' @rdname scale_edge_size #' #' @inheritParams ggplot2::scale_size_manual #' #' @export scale_edge_size_manual <- function(..., values) { manual_scale('edge_size', values, ...) } #' @rdname scale_edge_size #' #' @inheritParams ggplot2::scale_size_identity #' #' @importFrom scales identity_pal #' @export scale_edge_size_identity <- function(..., guide = 'none') { sc <- discrete_scale('edge_size', 'identity', identity_pal(), ..., guide = guide, super = ScaleDiscreteIdentity ) sc } ggraph/R/geom_node_text.R0000644000176200001440000000672613527221776015075 0ustar liggesusers#' Annotate nodes with text #' #' These geoms are equivalent in functionality to [ggplot2::geom_text()] and #' [ggplot2::geom_label()] and allows for simple annotation of nodes. #' #' @section Aesthetics: #' `geom_node_text` understands the following aesthetics. Bold aesthetics are #' automatically set, but can be overridden. Italic aesthetics are required but #' not set by default #' #' - **x** #' - **y** #' - *label* #' - alpha #' - angle #' - colour #' - family #' - fontface #' - hjust #' - lineheight #' - size #' - vjust #' #' @inheritParams ggplot2::geom_text #' #' @param mapping Set of aesthetic mappings created by [ggplot2::aes()] #' or [ggplot2::aes_()]. By default x and y are mapped to x and y in #' the node data. #' #' @param nudge_x,nudge_y Horizontal and vertical adjustment to nudge labels by. #' Useful for offsetting text from points, particularly on discrete scales. #' #' @param repel If `TRUE`, text labels will be repelled from each other #' to avoid overlapping, using the `GeomTextRepel` geom from the #' ggrepel package. #' #' @author Thomas Lin Pedersen #' #' @family geom_node_* #' #' @examples #' require(tidygraph) #' gr <- create_notable('bull') %>% #' mutate(class = sample(letters[1:3], n(), replace = TRUE)) #' #' ggraph(gr, 'stress') + #' geom_node_point(aes(label = class)) #' #' ggraph(gr, 'stress') + #' geom_node_label(aes(label = class), repel = TRUE) #' @importFrom ggrepel GeomTextRepel #' @export #' geom_node_text <- function(mapping = NULL, data = NULL, position = 'identity', parse = FALSE, nudge_x = 0, nudge_y = 0, check_overlap = FALSE, show.legend = NA, repel = FALSE, ...) { if (!missing(nudge_x) || !missing(nudge_y)) { if (!missing(position)) { stop('Specify either `position` or `nudge_x`/`nudge_y`', call. = FALSE ) } position <- position_nudge(nudge_x, nudge_y) } params <- list(parse = parse, na.rm = FALSE, ...) if (repel) { geom <- GeomTextRepel } else { geom <- GeomText params$check_overlap <- check_overlap } mapping <- aes_intersect(mapping, aes(x = x, y = y)) layer( data = data, mapping = mapping, stat = StatFilter, geom = geom, position = position, show.legend = show.legend, inherit.aes = FALSE, params = params ) } #' @rdname geom_node_text #' #' @inheritParams ggplot2::geom_label #' #' @importFrom ggrepel GeomLabelRepel #' @export #' geom_node_label <- function(mapping = NULL, data = NULL, position = 'identity', parse = FALSE, nudge_x = 0, nudge_y = 0, label.padding = unit(0.25, 'lines'), label.r = unit(0.15, 'lines'), label.size = 0.25, show.legend = NA, repel = FALSE, ...) { if (!missing(nudge_x) || !missing(nudge_y)) { if (!missing(position)) { stop('Specify either `position` or `nudge_x`/`nudge_y`', call. = FALSE ) } position <- position_nudge(nudge_x, nudge_y) } params <- list( parse = parse, label.padding = label.padding, label.r = label.r, label.size = label.size, na.rm = FALSE, ... ) if (repel) { geom <- GeomLabelRepel } else { geom <- GeomLabel } mapping <- aes_intersect(mapping, aes(x = x, y = y)) layer( data = data, mapping = mapping, stat = StatFilter, geom = geom, position = position, show.legend = show.legend, inherit.aes = FALSE, params = params ) } ggraph/R/geom_edge_point.R0000644000176200001440000000456413524341333015204 0ustar liggesusers#' Draw edges as glyphs #' #' This geom draws edges as glyphs with their x-position defined by the #' x-position of the start node, and the y-position defined by the y-position of #' the end node. As such it will result in a matrix layout when used in #' conjunction with [layout_tbl_graph_matrix()] #' #' @inheritSection geom_edge_link Edge aesthetic name expansion #' #' @section Aesthetics: #' `geom_edge_point` understands the following #' aesthetics. Bold aesthetics are automatically set, but can be overridden. #' #' - **x** #' - **y** #' - edge_shape #' - edge_colour #' - edge_size #' - edge_alpha #' - filter #' #' @inheritParams ggplot2::geom_point #' #' @param mapping Set of aesthetic mappings created by [ggplot2::aes()] #' or [ggplot2::aes_()]. By default x, y, xend, yend, group and #' circular are mapped to x, y, xend, yend, edge.id and circular in the edge #' data. #' #' @param data The return of a call to `get_edges()` or a data.frame #' giving edges in correct format (see details for for guidance on the format). #' See [get_edges()] for more details on edge extraction. #' #' @param mirror Logical. Should edge points be duplicated on both sides of the #' diagonal. Intended for undirected graphs. Default to `FALSE` #' #' @author Thomas Lin Pedersen #' #' @family geom_edge_* #' #' @rdname geom_edge_point #' @name geom_edge_point #' #' @examples #' require(tidygraph) #' gr <- create_notable('zachary') %>% #' mutate(group = group_infomap()) %>% #' morph(to_split, group) %>% #' activate(edges) %>% #' mutate(edge_group = as.character(.N()$group[1])) %>% #' unmorph() #' #' ggraph(gr, 'matrix', sort.by = node_rank_hclust()) + #' geom_edge_point(aes(colour = edge_group), mirror = TRUE, edge_size = 3) + #' scale_y_reverse() + #' coord_fixed() + #' labs(edge_colour = 'Infomap Cluster') + #' ggtitle("Zachary' Karate Club") NULL #' @rdname geom_edge_point #' #' @export geom_edge_point <- function(mapping = NULL, data = get_edges(), position = 'identity', mirror = FALSE, show.legend = NA, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes(x = x, y = yend)) layer( data = data, mapping = mapping, stat = StatFilter, geom = GeomEdgePoint, position = position, show.legend = show.legend, inherit.aes = FALSE, params = list(na.rm = FALSE, mirror = mirror, ...) ) } ggraph/R/gganimate.R0000644000176200001440000000036113526561212014005 0ustar liggesusers# We need this so that edge geoms is consider 'points' by gganimate. This is # safe as all edges have the same number of points. We don't want to depend on # gganimate so we register it on load layer_type.GeomEdgePath <- function(x) 'point' ggraph/R/autograph.R0000644000176200001440000001045213617214223014043 0ustar liggesusers#' Quickplot wrapper for networks #' #' This function is intended to quickly show an overview of your network data. #' While it returns a ggraph object that layers etc can be added to it is #' limited in use and should not be used as a foundation for more complicated #' plots. It allows colour, labeling and sizing of nodes and edges, and the #' exact combination of layout and layers will depend on these as well as the #' features of the network. The output of this function may be fine-tuned at any #' release and should not be considered stable. If a plot should be reproducible #' it should be created manually. #' #' @param graph An object coercible to a tbl_graph #' @param ... arguments passed on to methods #' #' @importFrom tidygraph as_tbl_graph .register_graph_context graph_is_tree graph_is_forest #' @importFrom rlang quo_is_null quo_text as_quosure sym #' @export #' #' @examples #' library(tidygraph) #' gr <- create_notable('herschel') %>% #' mutate(class = sample(letters[1:3], n(), TRUE)) %E>% #' mutate(weight = runif(n())) #' #' # Standard graph #' autograph(gr) #' #' # Adding node labels will cap edges #' autograph(gr, node_label = class) #' #' # Use tidygraph calls for mapping #' autograph(gr, node_size = centrality_pagerank()) #' #' # Trees are plotted as dendrograms #' iris_tree <- hclust(dist(iris[1:4], method = 'euclidean'), method = 'ward.D2') #' autograph(iris_tree) #' autograph <- function(graph, ...) { UseMethod('autograph') } #' @export #' @rdname autograph #' @param node_colour,edge_colour Colour mapping for nodes and edges #' @param node_size,edge_width Size/width mapping for nodes and edges #' @param node_label,edge_label Label mapping for nodes and edges autograph.default <- function(graph, ..., node_colour = NULL, edge_colour = NULL, node_size = NULL, edge_width = NULL, node_label = NULL, edge_label = NULL) { node_colour <- enquo(node_colour) edge_colour <- enquo(edge_colour) node_size <- enquo(node_size) edge_width <- enquo(edge_width) node_label <- enquo(node_label) edge_label <- enquo(edge_label) graph <- as_tbl_graph(graph) .register_graph_context(graph, TRUE) if (graph_is_tree() || graph_is_forest()) { node_names <- names(as_tibble(graph, active = 'nodes')) height <- if ('height' %in% node_names) quo(height) else quo(NULL) p <- ggraph(graph, 'dendrogram', height = !!height) + geom_edge_elbow0(aes(colour = !!edge_colour, width = !!edge_width)) if (!quo_is_null(node_colour) || !quo_is_null(node_size)) { p <- p + geom_node_point(aes(filter = leaf, colour = !!node_colour, size = !!node_size)) } if (!quo_is_null(node_label)) { p <- p + geom_node_text(aes(filter = leaf, label = !!node_label, colour = !!node_colour), angle = 40, hjust = 1, vjust = 1) + coord_cartesian(clip = 'off') } } else { p <- suppressMessages(ggraph(graph) + coord_fixed()) if (!quo_is_null(node_label)) { label_col <- quo_text(node_label) label_col <- paste0(c('node1.', 'node2.'), label_col) start_label <- sym(label_col[1]) end_label <- sym(label_col[2]) p <- p + geom_edge_link(aes(colour = !!edge_colour, label = !!edge_label, width = !!edge_width, start_cap = label_rect(!!start_label), end_cap = label_rect(!!end_label)), angle_calc = 'along', label_dodge = unit(2.5, 'mm')) } else if (!quo_is_null(edge_label)) { p <- p + geom_edge_link(aes(colour = !!edge_colour, label = !!edge_label, width = !!edge_width), angle_calc = 'along', label_dodge = unit(2.5, 'mm')) } else { p <- p + geom_edge_link0(aes(colour = !!edge_colour, width = !!edge_width)) } if (quo_is_null(node_label)) { p <- p + geom_node_point(aes(colour = !!node_colour, size = !!node_size)) } else { p <- p + geom_node_text(aes(label = !!node_label, colour = !!node_colour)) } } p } #' Deprecated autograph predecessor #' #' @export #' @keywords internal qgraph <- function(...) { .Deprecated('autograph', msg = 'To avoid name clashing with qgraph::qgraph(), ggraph::qgraph() has been renamed to ggraph::autograph()') } utils::globalVariables(c( 'leaf' )) ggraph/R/layout_linear.R0000644000176200001440000000363613525463025014732 0ustar liggesusers#' Place nodes on a line or circle #' #' This layout puts all nodes on a line, possibly sorted by a node attribute. If #' `circular = TRUE` the nodes will be laid out on the unit circle instead. #' In the case where the `sort.by` attribute is numeric, the numeric values #' will be used as the x-position and it is thus possible to have uneven spacing #' between the nodes. #' #' @param graph An `tbl_graph` object #' #' @param circular Logical. Should the layout be transformed to a circular #' representation. Defaults to `FALSE`. #' #' @param sort.by The name of a node variable to sort the nodes by. #' #' @param use.numeric Logical. Should a numeric sort.by attribute be used as the #' actual x-coordinates in the layout. May lead to overlapping nodes. Defaults #' to FALSE #' #' @param offset If `circular = TRUE`, where should it begin. Defaults to #' `pi/2` which is equivalent to 12 o'clock. #' #' @return A data.frame with the columns `x`, `y`, `circular` as #' well as any information stored as node variables in the tbl_graph object. #' #' @family layout_tbl_graph_* #' #' @importFrom igraph gorder #' layout_tbl_graph_linear <- function(graph, circular, sort.by = NULL, use.numeric = FALSE, offset = pi / 2) { sort.by <- enquo(sort.by) sort.by <- eval_tidy(sort.by, .N()) if (!is.null(sort.by)) { if (is.numeric(sort.by) && use.numeric) { x <- sort.by } else { x <- order(order(sort.by)) } } else { x <- seq_len(gorder(graph)) } nodes <- new_data_frame(list(x = x, y = 0)) if (circular) { radial <- radial_trans( r.range = rev(range(nodes$y)), a.range = range(nodes$x), offset = offset ) coords <- radial$transform(nodes$y, nodes$x) nodes$x <- coords$x nodes$y <- coords$y } extra_data <- as_tibble(graph, active = 'nodes') nodes <- cbind(nodes, extra_data[, !names(extra_data) %in% names(nodes), drop = FALSE]) nodes$circular <- circular nodes } ggraph/R/geom_node_tile.R0000644000176200001440000000440513524341142015021 0ustar liggesusers#' Draw the rectangles in a treemap #' #' A treemap is a space filling layout that recursively divides a rectangle to #' the children of the node. Often only the leaf nodes are drawn as nodes higher #' up in the hierarchy would obscure what is below. `geom_treemap` is a #' shorthand for `geom_node_treemap` as node is implicit in the case of #' treemap drawing #' #' @section Aesthetics: #' `geom_treemap` understand the following aesthetics. Bold aesthetics are #' automatically set, but can be overridden. #' #' - **x** #' - **y** #' - **width** #' - **height** #' - alpha #' - colour #' - fill #' - size #' - stroke #' - filter #' #' @inheritParams ggplot2::geom_tile #' #' @param mapping Set of aesthetic mappings created by [ggplot2::aes()] #' or [ggplot2::aes_()]. By default x, y, width and height are mapped to #' x, y, width and height in the node data. #' #' @author Thomas Lin Pedersen #' #' @family geom_node_* #' #' @examples #' # Create a graph of the flare class system #' library(tidygraph) #' flareGraph <- tbl_graph(flare$vertices, flare$edges) %>% #' mutate( #' class = map_bfs_chr(node_is_root(), .f = function(node, dist, path, ...) { #' if (dist <= 1) { #' return(shortName[node]) #' } #' path$result[[nrow(path)]] #' }) #' ) #' #' ggraph(flareGraph, 'treemap', weight = size) + #' geom_node_tile(aes(fill = class, filter = leaf, alpha = depth), colour = NA) + #' geom_node_tile(aes(size = depth), colour = 'white') + #' scale_alpha(range = c(1, 0.5), guide = 'none') + #' scale_size(range = c(4, 0.2), guide = 'none') #' @export #' geom_node_tile <- function(mapping = NULL, data = NULL, position = 'identity', show.legend = NA, ...) { mapping <- aes_intersect(mapping, aes( x = x, y = y, width = width, height = height )) layer( data = data, mapping = mapping, stat = StatFilter, geom = GeomNodeTile, position = position, show.legend = show.legend, inherit.aes = FALSE, params = list(na.rm = FALSE, ...) ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @export #' GeomNodeTile <- ggproto('GeomNodeTile', GeomTile, default_aes = aes( fill = NA, colour = 'black', size = 0.5, linetype = 1, alpha = NA, width = 1, height = 1 ), required_aes = c('x', 'y') ) ggraph/R/layout_centrality.R0000644000176200001440000000344213526513120015622 0ustar liggesusers#' Place nodes in circles according to centrality measure #' #' This layout places nodes in circles with the radii relative to a given #' centrality measure. Under the hood it use stress majorisation to place nodes #' optimally given the radius constraint. #' #' @param graph A tbl_graph object #' @param centrality An expression evaluating to a centrality measure for the #' nodes. See the different `centrality_*()` algorithms in tidygraph for a #' selection. #' @param scale Should the centrality measure be scaled between 0 and 100 #' @param tseq Transitioning steps #' @inheritParams layout_tbl_graph_stress #' #' @return A data.frame with the columns `x`, `y`, `circular`, `centrality` as #' well as any information stored as node variables in the tbl_graph object. #' #' @references #' Brandes, U., & Pich, C. (2011). *More flexible radial layout.* Journal of #' Graph Algorithms and Applications, 15(1), 157-173. #' #' @family layout_tbl_graph_* #' #' @author The underlying algorithm is implemented in the graphlayouts package #' by David Schoch #' #' @importFrom graphlayouts layout_with_centrality #' @importFrom rlang eval_tidy enquo layout_tbl_graph_centrality <- function(graph, centrality, scale = TRUE, niter = 500, tolerance = 1e-4, tseq = seq(0,1,0.2), circular = FALSE) { centrality <- eval_tidy(enquo(centrality), .N()) xy <- layout_with_centrality(graph, cent = centrality, scale = scale, iter = niter, tol = tolerance, tseq = tseq) nodes <- new_data_frame(list(x = xy[,1],y = xy[,2], centrality = centrality)) nodes$circular <- FALSE extra_data <- as_tibble(graph, active = 'nodes') nodes <- cbind(nodes, extra_data[, !names(extra_data) %in% names(nodes), drop = FALSE]) nodes } ggraph/R/data_flare.R0000644000176200001440000000253313523247752014145 0ustar liggesusers#' The class hierarchy of the flare visualization library #' #' This dataset contains the graph that describes the class hierarchy for the #' [Flare](http://flare.prefuse.org) ActionScript visualization library. It #' contains both the class hierarchy as well as the import connections between #' classes. This dataset has been used extensively in the D3.js documentation #' and examples and are included here to make it easy to redo the examples in #' ggraph. #' #' @format A list of three data.frames describing the software structure of #' flare: #' \describe{ #' \item{edges}{This data.frame maps the hierarchical structure of the class #' hierarchy as an edgelist, with the class in `from` being the superclass #' of the class in `to`.} #' \item{vertices}{This data.frame gives additional information on the classes. #' It contains the full name, size and short name of each class.} #' \item{imports}{This data.frame contains the class imports for each class #' implementation. The `from` column gives the importing class and the #' `to` column gives the import.} #' } #' #' @source The data have been adapted from the JSON downloaded from #' #' courtesy of Mike Bostock. The Flare framework is the work of the #' [UC Berkeley Visualization Lab](http://vis.berkeley.edu/). #' 'flare' ggraph/R/layout_focus.R0000644000176200001440000000327613526566562014612 0ustar liggesusers#' Place nodes in circles based on distance to a specific node #' #' This layout constrains node placement to a radius relative to its distance to #' a given node. It then uses stress majorisation to find an optimal node #' distribution according to this constraint. #' #' @param focus An expression evaluating to a selected node. Can either be a #' single integer or a logical vector with a single `TRUE` element. #' @inheritParams layout_tbl_graph_stress #' #' @return A data.frame with the columns `x`, `y`, `circular`, `distance` as #' well as any information stored as node variables in the tbl_graph object. #' #' @references #' Brandes, U., & Pich, C. (2011). *More flexible radial layout.* Journal of #' Graph Algorithms and Applications, 15(1), 157-173. #' #' @family layout_tbl_graph_* #' #' @author The underlying algorithm is implemented in the graphlayouts package #' by David Schoch #' #' @importFrom graphlayouts layout_with_focus #' @importFrom rlang eval_tidy enquo layout_tbl_graph_focus <- function(graph, focus, weights = NULL, niter = 500, tolerance = 1e-4, circular = TRUE) { focus <- eval_tidy(enquo(focus), .N()) if (is.logical(focus)) focus <- which(focus)[1] weights <- eval_tidy(enquo(weights), .E()) if (is.null(weights)) { weights <- NA } layout <- layout_with_focus(graph, v = focus, weights = weights, iter = niter, tol = tolerance) xy <- layout$xy nodes <- new_data_frame(list(x = xy[,1],y = xy[,2], distance = layout$distance)) nodes$circular <- FALSE extra_data <- as_tibble(graph, active = 'nodes') nodes <- cbind(nodes, extra_data[, !names(extra_data) %in% names(nodes), drop = FALSE]) nodes } ggraph/R/scale_edge_fill.R0000644000176200001440000001226513524253135015140 0ustar liggesusers#' Edge fill scales #' #' This set of scales defines new fill scales for edge geoms equivalent to the #' ones already defined by ggplot2. The parameters are equivalent to the ones #' from ggplot2 so there is nothing new under the sun. The different geoms will #' know whether to use edge scales or the standard scales so it is not necessary #' to write `edge_fill` in the call to the geom - just use `fill`. #' #' @return A ggproto object inheriting from `Scale` #' #' @family scale_edge_* #' #' @name scale_edge_fill #' @rdname scale_edge_fill #' NULL #' @rdname scale_edge_fill #' #' @inheritParams ggplot2::scale_fill_hue #' #' @importFrom scales hue_pal #' @export scale_edge_fill_hue <- function(..., h = c(0, 360) + 15, c = 100, l = 65, h.start = 0, direction = 1, na.value = 'grey50') { discrete_scale('edge_fill', 'hue', hue_pal(h, c, l, h.start, direction), na.value = na.value, ... ) } #' @rdname scale_edge_fill #' #' @inheritParams ggplot2::scale_fill_brewer #' #' @importFrom scales brewer_pal #' @export scale_edge_fill_brewer <- function(..., type = 'seq', palette = 1, direction = 1) { discrete_scale('edge_fill', 'brewer', brewer_pal(type, palette, direction), ...) } #' @rdname scale_edge_fill #' #' @inheritParams ggplot2::scale_fill_distiller #' #' @importFrom scales gradient_n_pal brewer_pal #' @export scale_edge_fill_distiller <- function(..., type = 'seq', palette = 1, direction = -1, values = NULL, space = 'Lab', na.value = 'grey50', guide = 'edge_colourbar') { # warn about using a qualitative brewer palette to generate the gradient type <- match.arg(type, c('seq', 'div', 'qual')) if (type == 'qual') { warning('Using a discrete fill palette in a continuous scale.\n Consider using type = "seq" or type = "div" instead', call. = FALSE) } continuous_scale('edge_fill', 'distiller', gradient_n_pal(brewer_pal(type, palette, direction)(6), values, space), na.value = na.value, guide = guide, ... ) # NB: 6 colours per palette gives nice gradients; more results in more saturated colours which do not look as good } #' @rdname scale_edge_fill #' #' @inheritParams ggplot2::scale_fill_gradient #' @param low,high Colours for low and high ends of the gradient. #' #' @importFrom scales seq_gradient_pal #' @export scale_edge_fill_gradient <- function(..., low = '#132B43', high = '#56B1F7', space = 'Lab', na.value = 'grey50', guide = 'edge_colourbar') { continuous_scale('edge_fill', 'gradient', seq_gradient_pal(low, high, space), na.value = na.value, guide = guide, ... ) } #' @rdname scale_edge_fill #' #' @inheritParams ggplot2::scale_fill_gradient2 #' #' @importFrom scales div_gradient_pal muted #' @export scale_edge_fill_gradient2 <- function(..., low = muted('red'), mid = 'white', high = muted('blue'), midpoint = 0, space = 'Lab', na.value = 'grey50', guide = 'edge_colourbar') { continuous_scale('edge_fill', 'gradient2', div_gradient_pal(low, mid, high, space), na.value = na.value, guide = guide, ..., rescaler = mid_rescaler(mid = midpoint) ) } #' @rdname scale_edge_fill #' #' @inheritParams ggplot2::scale_fill_gradientn #' @param colours,colors Vector of colours to use for n-colour gradient. #' #' @importFrom scales gradient_n_pal #' @export scale_edge_fill_gradientn <- function(..., colours, values = NULL, space = 'Lab', na.value = 'grey50', guide = 'edge_colourbar', colors) { colours <- if (missing(colours)) colors else colours continuous_scale('edge_fill', 'gradientn', gradient_n_pal(colours, values, space), na.value = na.value, guide = guide, ... ) } #' @rdname scale_edge_fill #' #' @inheritParams ggplot2::scale_fill_grey #' #' @importFrom scales grey_pal #' @export scale_edge_fill_grey <- function(..., start = 0.2, end = 0.8, na.value = 'red') { discrete_scale('edge_fill', 'grey', grey_pal(start, end), na.value = na.value, ... ) } #' @rdname scale_edge_fill #' #' @inheritParams ggplot2::scale_fill_identity #' #' @importFrom scales identity_pal #' @export scale_edge_fill_identity <- function(..., guide = 'none') { sc <- discrete_scale('edge_fill', 'identity', identity_pal(), ..., guide = guide, super = ScaleDiscreteIdentity ) sc } #' @rdname scale_edge_fill #' #' @inheritParams ggplot2::scale_fill_manual #' #' @export scale_edge_fill_manual <- function(..., values) { manual_scale('edge_fill', values, ...) } #' @rdname scale_edge_fill #' #' @inheritParams viridis::scale_fill_viridis #' #' @importFrom viridis viridis viridis_pal #' @export scale_edge_fill_viridis <- function(..., alpha = 1, begin = 0, end = 1, discrete = FALSE, option = 'D', direction = 1) { if (direction == -1) { tmp <- begin begin <- end end <- tmp } if (discrete) { discrete_scale( 'edge_fill', 'viridis', viridis_pal( alpha = alpha, begin = begin, end = end, option = option ), ... ) } else { scale_edge_fill_gradientn(colours = viridis(256, alpha = alpha, begin = begin, end = end, option = option ), ...) } } #' @rdname scale_edge_fill #' #' @export scale_edge_fill_continuous <- scale_edge_fill_gradient #' @rdname scale_edge_fill #' #' @export scale_edge_fill_discrete <- scale_edge_fill_hue ggraph/R/data_whigs.R0000644000176200001440000000145413523247752014176 0ustar liggesusers#' Membership network of American Whigs #' #' This dataset shows the membership of 136 colonial Americans in 5 whig #' organization and is a bipartite graph. The data appeared in the appendix to #' David Hackett Fischer's *Paul Revere's Ride* (Oxford University Press, #' 1995) and compiled by Kieran Healy for the blog post #' [Using Metadata to Find Paul Revere](http://kieranhealy.org/blog/archives/2013/06/09/using-metadata-to-find-paul-revere/). #' #' @format #' The data is stored as an incidence matrix with persons as rows and #' organizations as columns. A 0 means no membership while a one means #' membership. #' #' @source #' adapted from: #' #' Fischer, David H. (1995) *Paul Revere's Ride*. Oxford University Press #' 'whigs' ggraph/R/geom_edge_link.R0000644000176200001440000002435213526604474015017 0ustar liggesusers#' Draw edges as straight lines between nodes #' #' This geom draws edges in the simplest way - as straight lines between the #' start and end nodes. Not much more to say about that... #' #' @section Edge variants: #' Many geom_edge_* layers comes in 3 flavors depending on the level of control #' needed over the drawing. The default (no numeric postfix) generate a number #' of points (`n`) along the edge and draws it as a path. Each point along #' the line has a numeric value associated with it giving the position along the #' path, and it is therefore possible to show the direction of the edge by #' mapping to this e.g. `colour = stat(index)`. The version postfixed with a #' "2" uses the "long" edge format (see [get_edges()]) and makes it #' possible to interpolate node parameter between the start and end node along #' the edge. It is considerable less performant so should only be used if this #' is needed. The version postfixed with a "0" draws the edge in the most #' performant way, often directly using an appropriate grob from the grid #' package, but does not allow for gradients along the edge. #' #' Often it is beneficial to stop the drawing of the edge before it reaches the #' node, for instance in cases where an arrow should be drawn and the arrowhead #' shouldn't lay on top or below the node point. geom_edge_* and geom_edge_*2 #' supports this through the start_cap and end_cap aesthetics that takes a #' [geometry()] specification and dynamically caps the termini of the #' edges based on the given specifications. This means that if #' `end_cap = circle(1, 'cm')` the edges will end at a distance of 1cm even #' during resizing of the plot window. #' #' All `geom_edge_*` and `geom_edge_*2` have the ability to draw a #' label along the edge. The reason this is not a separate geom is that in order #' for the label to know the location of the edge it needs to know the edge type #' etc. Labels are drawn by providing a label aesthetic. The label_pos can be #' used to specify where along the edge it should be drawn by supplying a number #' between 0 and 1. The label_size aesthetic can be used to control the size of #' the label. Often it is needed to have the label written along the direction #' of the edge, but since the actual angle is dependent on the plot dimensions #' this cannot be calculated beforehand. Using the angle_calc argument allows #' you to specify whether to use the supplied angle aesthetic or whether to draw #' the label along or across the edge. #' #' @section Edge aesthetic name expansion: #' In order to avoid excessive typing edge aesthetic names are #' automatically expanded. Because of this it is not necessary to write #' `edge_colour` within the `aes()` call as `colour` will #' automatically be renamed appropriately. #' #' @section Aesthetics: #' `geom_edge_link` and `geom_edge_link0` understand the following #' aesthetics. Bold aesthetics are automatically set, but can be overridden. #' #' - **x** #' - **y** #' - **xend** #' - **yend** #' - edge_colour #' - edge_width #' - edge_linetype #' - edge_alpha #' - filter #' #' `geom_edge_link2` understand the following aesthetics. Bold aesthetics are #' automatically set, but can be overridden. #' #' - **x** #' - **y** #' - **group** #' - edge_colour #' - edge_width #' - edge_linetype #' - edge_alpha #' - filter #' #' `geom_edge_link` and `geom_edge_link2` furthermore takes the following #' aesthetics. #' #' - start_cap #' - end_cap #' - label #' - label_pos #' - label_size #' - angle #' - hjust #' - vjust #' - family #' - fontface #' - lineheight #' #' @section Computed variables: #' #' \describe{ #' \item{index}{The position along the path (not computed for the *0 version)} #' } #' #' @inheritParams ggplot2::geom_path #' @inheritParams ggplot2::geom_text #' #' @param mapping Set of aesthetic mappings created by [ggplot2::aes()] #' or [ggplot2::aes_()]. By default x, y, xend, yend, group and #' circular are mapped to x, y, xend, yend, edge.id and circular in the edge #' data. #' #' @param data The return of a call to `get_edges()` or a data.frame #' giving edges in correct format (see details for for guidance on the format). #' See [get_edges()] for more details on edge extraction. #' #' @param n The number of points to create along the path. #' #' @param label_colour The colour of the edge label. If `NA` it will use #' the colour of the edge. #' #' @param label_alpha The opacity of the edge label. If `NA` it will use #' the opacity of the edge. #' #' @param label_parse If `TRUE`, the labels will be parsed into expressions #' and displayed as described in [grDevices::plotmath()]. #' #' @param angle_calc Either 'none', 'along', or 'across'. If 'none' the label will #' use the angle aesthetic of the geom. If 'along' The label will be written #' along the edge direction. If 'across' the label will be written across the #' edge direction. #' #' @param force_flip Logical. If `angle_calc` is either 'along' or 'across' #' should the label be flipped if it is on it's head. Default to `TRUE`. #' #' @param label_dodge A [grid::unit()] giving a fixed vertical shift #' to add to the label in case of `angle_calc` is either 'along' or 'across' #' #' @param label_push A [grid::unit()] giving a fixed horizontal shift #' to add to the label in case of `angle_calc` is either 'along' or 'across' #' #' @author Thomas Lin Pedersen #' #' @family geom_edge_* #' #' @examples #' require(tidygraph) #' gr <- create_notable('bull') %>% #' mutate(class = sample(letters[1:3], n(), replace = TRUE)) %>% #' activate(edges) %>% #' mutate(class = sample(letters[1:3], n(), replace = TRUE)) #' #' ggraph(gr, 'stress') + #' geom_edge_link(aes(alpha = stat(index))) #' #' ggraph(gr, 'stress') + #' geom_edge_link2(aes(colour = node.class)) #' #' ggraph(gr, 'stress') + #' geom_edge_link0(aes(colour = class)) #' @rdname geom_edge_link #' @name geom_edge_link #' NULL #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatLink #' @export StatEdgeLink <- ggproto('StatEdgeLink', StatLink, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } data <- remove_loop(data) if (nrow(data) == 0) return(NULL) StatLink$setup_data(data, params) }, default_aes = aes(filter = TRUE) ) #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatLink2 #' @export StatEdgeLink2 <- ggproto('StatEdgeLink2', StatLink2, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } data <- remove_loop2(data) if (nrow(data) == 0) return(NULL) StatLink2$setup_data(data, params) }, default_aes = aes(filter = TRUE) ) #' @rdname geom_edge_link #' #' @importFrom ggforce StatLink #' @export geom_edge_link <- function(mapping = NULL, data = get_edges('short'), position = 'identity', arrow = NULL, n = 100, lineend = 'butt', linejoin = 'round', linemitre = 1, label_colour = 'black', label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = 'rot', force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes( x = x, y = y, xend = xend, yend = yend, group = edge.id )) layer( data = data, mapping = mapping, stat = StatEdgeLink, geom = GeomEdgePath, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, linejoin = linejoin, linemitre = linemitre, na.rm = FALSE, n = n, interpolate = FALSE, label_colour = label_colour, label_alpha = label_alpha, label_parse = label_parse, check_overlap = check_overlap, angle_calc = angle_calc, force_flip = force_flip, label_dodge = label_dodge, label_push = label_push, ... ) ) ) } #' @rdname geom_edge_link #' #' @importFrom ggforce StatLink2 #' @export geom_edge_link2 <- function(mapping = NULL, data = get_edges('long'), position = 'identity', arrow = NULL, n = 100, lineend = 'butt', linejoin = 'round', linemitre = 1, label_colour = 'black', label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = 'rot', force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes(x = x, y = y, group = edge.id)) layer( data = data, mapping = mapping, stat = StatEdgeLink2, geom = GeomEdgePath, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, linejoin = linejoin, linemitre = linemitre, na.rm = FALSE, n = n, interpolate = TRUE, label_colour = label_colour, label_alpha = label_alpha, label_parse = label_parse, check_overlap = check_overlap, angle_calc = angle_calc, force_flip = force_flip, label_dodge = label_dodge, label_push = label_push, ... ) ) ) } #' @rdname geom_edge_link #' #' @importFrom ggforce StatLink2 #' @export geom_edge_link0 <- function(mapping = NULL, data = get_edges(), position = 'identity', arrow = NULL, lineend = 'butt', show.legend = NA, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes(x = x, y = y, xend = xend, yend = yend)) layer( data = data, mapping = mapping, stat = StatFilter, geom = GeomEdgeSegment, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list(arrow = arrow, lineend = lineend, na.rm = FALSE, ...) ) ) } ggraph/R/geom_edge_diagonal.R0000644000176200001440000002326013530723671015631 0ustar liggesusers#' Draw edges as diagonals #' #' This geom draws edges as diagonal bezier curves. The name comes from D3.js #' where this shape was called diagonals until it was renamed to #' [links](https://github.com/d3/d3-shape/blob/v1.3.5/README.md#links). #' A diagonal in this context is a quadratic bezier with the control points #' positioned halfway between the start and end points but on the same axis. #' This produces a pleasing fan-in, fan-out line that is mostly relevant for #' hierarchical layouts as it implies an overall directionality in the plot. #' #' @inheritSection geom_edge_link Edge variants #' @inheritSection geom_edge_link Edge aesthetic name expansion #' #' @section Aesthetics: #' `geom_edge_diagonal` and `geom_edge_diagonal0` understand the following #' aesthetics. Bold aesthetics are automatically set, but can be overridden. #' #' - **x** #' - **y** #' - **xend** #' - **yend** #' - **circular** #' - edge_colour #' - edge_width #' - edge_linetype #' - edge_alpha #' - filter #' #' `geom_edge_diagonal2` understand the following aesthetics. Bold aesthetics are #' automatically set, but can be overridden. #' #' - **x** #' - **y** #' - **group** #' - **circular** #' - edge_colour #' - edge_width #' - edge_linetype #' - edge_alpha #' - filter #' #' `geom_edge_diagonal` and `geom_edge_diagonal2` furthermore takes the following #' aesthetics. #' #' - start_cap #' - end_cap #' - label #' - label_pos #' - label_size #' - angle #' - hjust #' - vjust #' - family #' - fontface #' - lineheight #' #' #' @section Computed variables: #' #' \describe{ #' \item{index}{The position along the path (not computed for the *0 version)} #' } #' #' @inheritParams geom_edge_link #' @inheritParams ggplot2::geom_path #' #' @param flipped Logical, Has the layout been flipped by reassigning the #' mapping of x, y etc? #' #' @param strength The strength of the curvature of the diagonal. `0` will #' result in a straight line while `1` will give the familiar S-shape. #' #' @author Thomas Lin Pedersen #' #' @family geom_edge_* #' #' @examples #' require(tidygraph) #' gr <- create_tree(20, 4) %>% #' mutate(class = sample(letters[1:3], n(), replace = TRUE)) %>% #' activate(edges) %>% #' mutate(class = sample(letters[1:3], n(), replace = TRUE)) #' #' ggraph(gr, 'tree') + #' geom_edge_diagonal(aes(alpha = stat(index))) #' #' ggraph(gr, 'tree') + #' geom_edge_diagonal2(aes(colour = node.class)) #' #' ggraph(gr, 'tree') + #' geom_edge_diagonal0(aes(colour = class)) #' @rdname geom_edge_diagonal #' @name geom_edge_diagonal #' NULL #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatBezier #' @export StatEdgeDiagonal <- ggproto('StatEdgeDiagonal', StatBezier, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } data <- remove_loop(data) if (nrow(data) == 0) return(NULL) data$group <- make_unique(data$group) data2 <- data data2$x <- data2$xend data2$y <- data2$yend create_diagonal(data, data2, params) }, required_aes = c('x', 'y', 'xend', 'yend', 'circular'), default_aes = aes(filter = TRUE), extra_params = c('na.rm', 'flipped', 'n', 'strength') ) #' @rdname geom_edge_diagonal #' #' @export geom_edge_diagonal <- function(mapping = NULL, data = get_edges(), position = 'identity', arrow = NULL, strength = 1, flipped = FALSE, n = 100, lineend = 'butt', linejoin = 'round', linemitre = 1, label_colour = 'black', label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = 'rot', force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes( x = x, y = y, xend = xend, yend = yend, circular = circular, group = edge.id )) layer( data = data, mapping = mapping, stat = StatEdgeDiagonal, geom = GeomEdgePath, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, linejoin = linejoin, linemitre = linemitre, na.rm = FALSE, n = n, interpolate = FALSE, flipped = flipped, strength = strength, label_colour = label_colour, label_alpha = label_alpha, label_parse = label_parse, check_overlap = check_overlap, angle_calc = angle_calc, force_flip = force_flip, label_dodge = label_dodge, label_push = label_push, ... ) ) ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatBezier2 #' @export StatEdgeDiagonal2 <- ggproto('StatEdgeDiagonal2', StatBezier2, setup_data = function(data, params) { if (any(names(data) == 'filter')) { if (!is.logical(data$filter)) { stop('filter must be logical') } data <- data[data$filter, names(data) != 'filter'] } data <- remove_loop2(data) if (nrow(data) == 0) return(NULL) data <- data[order(data$group), ] data2 <- data[c(FALSE, TRUE), ] data <- data[c(TRUE, FALSE), ] create_diagonal(data, data2, params) }, required_aes = c('x', 'y', 'group', 'circular'), default_aes = aes(filter = TRUE), extra_params = c('na.rm', 'flipped', 'n', 'strength') ) #' @rdname geom_edge_diagonal #' #' @export geom_edge_diagonal2 <- function(mapping = NULL, data = get_edges('long'), position = 'identity', arrow = NULL, strength = 1, flipped = FALSE, n = 100, lineend = 'butt', linejoin = 'round', linemitre = 1, label_colour = 'black', label_alpha = 1, label_parse = FALSE, check_overlap = FALSE, angle_calc = 'rot', force_flip = TRUE, label_dodge = NULL, label_push = NULL, show.legend = NA, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes( x = x, y = y, group = edge.id, circular = circular )) layer( data = data, mapping = mapping, stat = StatEdgeDiagonal2, geom = GeomEdgePath, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, linejoin = linejoin, linemitre = linemitre, na.rm = FALSE, n = n, interpolate = TRUE, flipped = flipped, strength = strength, label_colour = label_colour, label_alpha = label_alpha, label_parse = label_parse, check_overlap = check_overlap, angle_calc = angle_calc, force_flip = force_flip, label_dodge = label_dodge, label_push = label_push, ... ) ) ) } #' @rdname ggraph-extensions #' @format NULL #' @usage NULL #' @importFrom ggforce StatBezier0 #' @export StatEdgeDiagonal0 <- ggproto('StatEdgeDiagonal0', StatBezier0, setup_data = function(data, params) { StatEdgeDiagonal$setup_data(data, params) }, required_aes = c('x', 'y', 'xend', 'yend', 'circular'), default_aes = aes(filter = TRUE), extra_params = c('na.rm', 'flipped', 'strength') ) #' @rdname geom_edge_diagonal #' #' @export geom_edge_diagonal0 <- function(mapping = NULL, data = get_edges(), position = 'identity', arrow = NULL, strength = 1, flipped = FALSE, lineend = 'butt', show.legend = NA, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes( x = x, y = y, xend = xend, yend = yend, circular = circular )) layer( data = data, mapping = mapping, stat = StatEdgeDiagonal0, geom = GeomEdgeBezier, position = position, show.legend = show.legend, inherit.aes = FALSE, params = expand_edge_aes( list( arrow = arrow, lineend = lineend, na.rm = FALSE, strength = strength, flipped = flipped, ... ) ) ) } create_diagonal <- function(from, to, params) { bezier_start <- seq(1, by = 4, length.out = nrow(from)) from$index <- bezier_start to$index <- bezier_start + 3 data2 <- from data3 <- to data2$index <- bezier_start + 1 data3$index <- bezier_start + 2 if (any(from$circular)) { r0 <- sqrt(from$x[from$circular]^2 + from$y[from$circular]^2) r1 <- sqrt(to$x[to$circular]^2 + to$y[to$circular]^2) root <- r0 == 0 | r1 == 0 r_mod <- params$strength * (r1 - r0) / 2 data2$x[from$circular] <- from$x[from$circular] / (r0 / (r0 + r_mod)) data2$y[from$circular] <- from$y[from$circular] / (r0 / (r0 + r_mod)) data3$x[from$circular] <- to$x[from$circular] / (r1 / (r1 - r_mod)) data3$y[from$circular] <- to$y[from$circular] / (r1 / (r1 - r_mod)) data2$x[root] <- from$x[root] data2$y[root] <- from$y[root] data3$x[root] <- to$x[root] data3$y[root] <- to$y[root] } if (any(!from$circular)) { if (params$flipped) { h_diff <- from$x[!from$circular] - to$x[!from$circular] data2$x[!from$circular] <- from$x[!from$circular] - h_diff / 2 * params$strength data3$x[!from$circular] <- to$x[!from$circular] + h_diff / 2 * params$strength } else { h_diff <- from$y[!from$circular] - to$y[!from$circular] data2$y[!from$circular] <- from$y[!from$circular] - h_diff / 2 * params$strength data3$y[!from$circular] <- to$y[!from$circular] + h_diff / 2 * params$strength } } data <- rbind_dfs(list(from, data2, data3, to)) data[order(data$index), names(data) != 'index'] } ggraph/R/cappedPath.R0000644000176200001440000001053213525315334014124 0ustar liggesusers#' @importFrom grid grob is.unit unit gTree cappedPathGrob <- function(x, y, id = NULL, id.lengths = NULL, arrow = NULL, start.cap = NULL, start.cap2 = NULL, start.captype = 'circle', end.cap = NULL, end.cap2 = NULL, end.captype = 'circle', default.units = 'npc', name = NULL, gp = gpar(), vp = NULL, constant = TRUE) { if (!is.unit(x)) { x <- unit(x, default.units) } if (!is.unit(y)) { y <- unit(y, default.units) } if (is.null(id)) { if (!is.null(id.lengths)) { id <- rep(seq_along(id.lengths), id.lengths) } else { id <- rep(1, length(x)) } } n <- length(unique(id)) start.cap <- validate_cap(start.cap, default.units, n) if (is.null(start.cap2)) { start.cap2 <- start.cap } else { start.cap2 <- validate_cap(start.cap2, default.units, n) } end.cap <- validate_cap(end.cap, default.units, n) if (is.null(end.cap2)) { end.cap2 <- end.cap } else { end.cap2 <- validate_cap(end.cap2, default.units, n) } if (!all(c(start.captype, end.captype) %in% c('circle', 'rect'))) { stop('captype must be either `circle` or `rect`', call. = FALSE) } start.captype <- rep(start.captype, length.out = n) end.captype <- rep(end.captype, length.out = n) n_points <- length(x) group_diff <- id[-1] != id[-n_points] start <- c(TRUE, group_diff) end <- c(group_diff, TRUE) if (constant) { gp <- lapply(gp, function(par) { if (length(par) == length(end)) { par[start] } else { par } }) } else { gp <- lapply(gp, function(par) { if (length(par) == length(end)) { par[!end] } else { par } }) } class(gp) <- 'gpar' if (is.null(start.cap) && is.null(end.cap)) { if (constant) { grob( x = x, y = y, id = id, id.lengths = NULL, arrow = arrow, name = name, gp = gp, vp = vp, cl = 'polyline' ) } else { grob( x0 = x[!end], y0 = y[!end], x1 = x[!start], y1 = y[!start], id = id[!end], arrow = arrow, name = name, gp = gp, vp = vp, cl = 'segments' ) } } else { gTree( x = x, y = y, id = id, arrow = arrow, constant = constant, start = start, end = end, start.cap = start.cap, start.cap2 = start.cap2, start.captype = start.captype, end.cap = end.cap, end.cap2 = end.cap2, end.captype = end.captype, name = name, gp = gp, vp = vp, cl = 'cappedpathgrob' ) } } #' Dynamic capping of paths #' #' This function takes care of updating which parts of the paths get removed #' when the device dimensions are updated. #' #' @importFrom grid convertX convertY convertWidth convertHeight unit grob makeContent setChildren gList #' @export #' @keywords internal makeContent.cappedpathgrob <- function(x) { x_new <- convertX(x$x, 'mm', TRUE) y_new <- convertY(x$y, 'mm', TRUE) start.cap <- convertWidth(x$start.cap, 'mm', TRUE) end.cap <- convertWidth(x$end.cap, 'mm', TRUE) start.cap2 <- convertHeight(x$start.cap2, 'mm', TRUE) end.cap2 <- convertHeight(x$end.cap2, 'mm', TRUE) truncated <- cut_lines( x_new, y_new, as.integer(x$id), start.cap, start.cap2, end.cap, end.cap2, x$start.captype, x$end.captype ) keep <- !is.na(truncated$x) if (!any(keep)) { lines <- zeroGrob() } else if (x$constant) { x_new <- truncated$x[keep] y_new <- truncated$y[keep] id <- x$id[keep] all_id <- unique(id) gp <- lapply(x$gp, function(par) { if (length(par) == 1) { par } else { par[all_id] } }) class(gp) <- 'gpar' id <- match(id, all_id) lines <- grob( x = unit(x_new, 'mm'), y = unit(y_new, 'mm'), id = id, id.lengths = NULL, arrow = x$arrow, name = x$name, gp = gp, vp = x$vp, cl = 'polyline' ) } else { x0 <- truncated$x[!x$end] y0 <- truncated$y[!x$end] x1 <- truncated$x[!x$start] y1 <- truncated$y[!x$start] lines <- grob( x0 = unit(x0, 'mm'), y0 = unit(y0, 'mm'), x1 = unit(x1, 'mm'), id = x$id[!x$end], y1 = unit(y1, 'mm'), arrow = x$arrow, name = x$name, gp = x$gp, vp = x$vp, cl = 'segments' ) } setChildren(x, gList(lines)) } #' @importFrom grid is.unit unit validate_cap <- function(cap, default.units, n) { if (is.null(cap)) { return() } if (!is.unit(cap)) cap <- unit(cap, default.units) rep(cap, length.out = n) } ggraph/R/scale_edge_linetype.R0000644000176200001440000000313013524253136016033 0ustar liggesusers#' Edge linetype scales #' #' This set of scales defines new linetype scales for edge geoms equivalent to #' the ones already defined by ggplot2. See #' [ggplot2::scale_linetype()] for more information. The different #' geoms will know whether to use edge scales or the standard scales so it is #' not necessary to write `edge_linetype` in the call to the geom - just #' use `linetype`. #' #' @return A ggproto object inheriting from `Scale` #' #' @family scale_edge_* #' #' @name scale_edge_linetype #' @rdname scale_edge_linetype #' NULL #' @rdname scale_edge_linetype #' #' @inheritParams ggplot2::scale_linetype #' #' @importFrom scales linetype_pal #' @export scale_edge_linetype <- function(..., na.value = 'blank') { discrete_scale('edge_linetype', 'linetype_d', linetype_pal(), na.value = na.value, ... ) } #' @rdname scale_edge_linetype #' #' @export scale_edge_linetype_continuous <- function(...) { stop('A continuous variable can not be mapped to linetype', call. = FALSE) } #' @rdname scale_edge_linetype #' #' @export scale_edge_linetype_discrete <- scale_edge_linetype #' @rdname scale_edge_linetype #' #' @inheritParams ggplot2::scale_linetype_manual #' #' @export scale_edge_linetype_manual <- function(..., values) { manual_scale('edge_linetype', values, ...) } #' @rdname scale_edge_linetype #' #' @inheritParams ggplot2::scale_linetype_identity #' #' @importFrom scales identity_pal #' @export scale_edge_linetype_identity <- function(..., guide = 'none') { sc <- discrete_scale('edge_linetype', 'identity', identity_pal(), ..., guide = guide, super = ScaleDiscreteIdentity ) sc } ggraph/R/ggproto-classes.R0000644000176200001440000000315413526604315015172 0ustar liggesusers#' ggraph extensions to ggplot2 #' #' This help page lists all exported ggproto classes defined by ggraph. In #' general these should be of no concern to the user as the main interface to #' the functionality is, as with ggplot2, the `geom_*` format. As opposed #' to ggplot2 there really aren't any use for separate `stat_*` functions #' as they are intimately linked to each geom and mixing and matching stats and #' geoms would only cause a lot of trouble. #' #' @details #' Many of the `geom_edge_*` geoms comes in different flavors dependent on #' the functionality required. There will always be a base geom and some will #' have a `geom_edge_*0` and `geom_edge_*2` version. The base geom #' will, in the case of multiple versions, draw the edge as a sequence of small #' segments. The different aesthetics will be repeated for each segment and a #' counter will be added the enumerates the progression of segments, so that a #' gradient of colour or size can be added along the edge by assigning the #' respective aesthetic to `stat(index)`. `geom_edge_*2` will also draw #' the edge as segments but will interpolate between the aesthetics at the end #' points. This makes it possible for an edge to interpolate node properties of #' its end nodes. `geom_edge_*2` is less performant than the base geom so #' use only when interpolation is needed. `geom_edge_*0` is a #' high-performance version that usually maps directly to a grid grob. It does #' not allow for drawing gradients or interpolations though, so use only for #' simple edge drawings. #' #' @name ggraph-extensions #' @rdname ggraph-extensions #' @keywords internal #' NULL ggraph/R/geom_edge_tile.R0000644000176200001440000000453113524625741015012 0ustar liggesusers#' Draw edges as glyphs #' #' This geom draws edges as tiles with their x-position defined by the #' x-position of the start node, and the y-position defined by the y-position of #' the end node. As such it will result in a matrix layout when used in #' conjunction with [layout_tbl_graph_matrix()] #' #' @inheritSection geom_edge_link Edge aesthetic name expansion #' #' @section Aesthetics: #' `geom_edge_tile` understands the following #' aesthetics. Bold aesthetics are automatically set, but can be overridden. #' #' - **x** #' - **y** #' - edge_fill #' - edge_colour #' - edge_size #' - edge_alpha #' - filter #' #' @inheritParams ggplot2::geom_tile #' #' @param mapping Set of aesthetic mappings created by [ggplot2::aes()] #' or [ggplot2::aes_()]. By default x, y, xend, yend, group and #' circular are mapped to x, y, xend, yend, edge.id and circular in the edge #' data. #' #' @param data The return of a call to `get_edges()` or a data.frame #' giving edges in correct format (see details for for guidance on the format). #' See [get_edges()] for more details on edge extraction. #' #' @param mirror Logical. Should edge points be duplicated on both sides of the #' diagonal. Intended for undirected graphs. Default to `FALSE` #' #' @author Thomas Lin Pedersen #' #' @family geom_edge_* #' #' @rdname geom_edge_tile #' @name geom_edge_tile #' #' @examples #' require(tidygraph) #' gr <- create_notable('zachary') %>% #' mutate(group = group_infomap()) %>% #' morph(to_split, group) %>% #' activate(edges) %>% #' mutate(edge_group = as.character(.N()$group[1])) %>% #' unmorph() #' #' ggraph(gr, 'matrix', sort.by = node_rank_hclust()) + #' geom_edge_tile(aes(fill = edge_group), mirror = TRUE) + #' scale_y_reverse() + #' coord_fixed() + #' labs(edge_colour = 'Infomap Cluster') + #' ggtitle("Zachary' Karate Club") NULL #' @rdname geom_edge_tile #' #' @export geom_edge_tile <- function(mapping = NULL, data = get_edges(), position = 'identity', mirror = FALSE, show.legend = NA, ...) { mapping <- complete_edge_aes(mapping) mapping <- aes_intersect(mapping, aes(x = x, y = yend)) layer( data = data, mapping = mapping, stat = StatFilter, geom = GeomEdgeTile, position = position, show.legend = show.legend, inherit.aes = FALSE, params = list(na.rm = FALSE, mirror = mirror, ...) ) } ggraph/NEWS.md0000644000176200001440000001200413617237365012632 0ustar liggesusers# ggraph 2.0.1 * Fix bug in ggraph that will surface with the next grid release * Deprecate `qgraph()` in favour of `autograph()` to avoid name collision with `qgraph::qgraph()`. `autograph()` is now also a generic with a default method, so you can provide your own specific ggraph plot method for your network classes # ggraph 2.0.0 This release is a major release including many new features, bug fixes and some breaking changes. ## Breaking changes * Use tidygraph as the central data format. The results of this are several: - All graph object supported by tidygraph are now supported on even footing in ggraph. All layouts are now available to any graph class - **BREAKING** The `"even"` layout for dendrograms are no more, but can be obtained by using the `"dendrogram"` layout with `height = NULL` - **BREAKING** All layouts uses NSE for arguments that refer to node and edge variables, instead of passing in strings that refer to the variable name. - All examples and vignettes now uses tidygraph for graph manipulation resulting in much cleaner code. - `tree_apply` has been removed in favour of using `tidygraph::map_bfs_*` - `geom_edge_elbow` is no longer only available to dendrogram objects - tidygraph algorithms can now be used directly within ggraph functions. E.g. you can have `sort.by = node_rank_hclust()` in your specification of a linear layout, or `aes(colour = group_infomap())` in node geoms This big change fixes #21, #72, #79, and #81. A vignette has been added to describe the integration in more detail * The `curvature` argument from `geom_edge_arc()` and `geom_edge_hive()` as well as the `spread` argument from `geom_edge_fan()` has been deprecated in favor of the new `strength` argument. * ggraph plots now gets constructed with grid and axes removed from the default theme. ## New features * Add equal-angle and equal-daylight unrooted tree layouts (#59) * Interface with the graphlayouts package and make all its layouts available. The auto layout now uses the stress layout for standard graphs (sparse stress) for larger graphs. * Added length argument to the dendrogram layout to allow the layout to be based on edge length rather than node heights (#124). * Added `geom_edge_parallel()` for drawing multiedges as parallel lines (#191) * Added fabric layout to create biofabric plots. Also added `geom_node_range()` and `geom_edge_span()` for visualising such layouts (#47) * Added `geom_edge_bend()` for drawing soft elbows (#45) * Added `geom_node_voronoi()` for displaying nodes as voronoi tiles (#100) * Added `qgraph()` for quickly creating a standard network plot for explorative purpose (#94) * All non-straight line-based edge geoms now has a `strength` parameter that controls their deviation from a straight line. `0` will always give a straight line while `1` will be their natural look. Numbers outside this range may look weird (#97) * Added `weight` and `mode` arguments to `get_con()` that are passed on to the shortest path calculations (#89). * Add matrix layout and `geom_edge_point()` (#23) * Added `geom_edge_tile()` for use with matrix layouts (#141) * Manual layouts are now easier to specify. You can pass a matrix or data.frame to the layout parameter that will then be used. Also, if an `x` and `y` argument is present, the `auto` layout will choose the `manual` layout. (#91) * Custom layout functions are now much more flexible. And can either return a `data.frame` or an object coercible to a `tbl_graph`. In the latter case the node table will be used as layout and the graph will be attached. This allows direct use of the particles package as a layout engine as a side effect. (#88) ## Bug fixes * Fixed numerous bugs and issues pertaining to the group aesthetic handling in many edge geoms (#190, #193). * Edge geoms no longer throws an error when all edges are completely capped (#176) * Fixed a bug that prevented edge capping from being used with *2 variants of edge geoms (#167) * Fix bug in edge capping that could lead to edges extending to (0,0) (#163) * Fix bug affecting faceting of capped edges (#140) * Character aesthetics are no longer cast to factors in edge geoms (#131) * Fix a bug where start capping was ignored if the previous edge had been completely removed by capping (#150) * Fix offsetting bug in edge label drawing when some labels are empty strings and `label_parse = TRUE` (#159) * Fixed a bug resulting in the wrong mapping of additional values to connections (#122, #134) * Fixed a bug when using `facet_graph()` with data from both tbl_df and data.frame * Added `override.aes` to `guide_edge_direction()` * Fix bug with extracting edges from an empty graph (#76) * Fixed bug causing shifts in edge aesthetics when one or more edges were completely clipped (#62, #115) * Fixed bug when clipping completely orthogonal points that produced additional lines going to (0,0) (#70, #84, #103) ## Other * Changed license to MIT * Update roxygen documentation to use markdown and reduce duplication (#95) ggraph/MD50000644000176200001440000002407513617251652012052 0ustar liggesusersf1102c52e1dc736e6a27544b141ffe24 *DESCRIPTION 390aa971d748d2d8595f400ac17045c9 *LICENSE 59ee5c1b908eedbb9faee53b49e22558 *LICENSE.note a7ded00b66057c3e4ea605a027e7ff08 *NAMESPACE 37be82268affb9c6b553d1f26726af71 *NEWS.md 8c092dab2029447032733ab811040342 *R/RcppExports.R 9bc94bd11ea9b725f0c4919ebcd96ed5 *R/aaa.R 79e2a09bee6d57c9cd69289c237f3354 *R/autograph.R 74fa484314ce1c0b72b4882353c55bf4 *R/cappedPath.R c5af7f1ad13fdeb6579a301b60f7389a *R/connections.R 17b33afa8584dc94a2428cbf5a3c3800 *R/data_flare.R 5515895b294f48df0442263f8edecb8f *R/data_highschool.R 53a4a3dc6b4da5e57ea48e41964f2b9b *R/data_whigs.R 84d129340b61ae60225fdd34017cb2a2 *R/edge_colourbar.R 0c751e915701aeb2ff51f5fbb17f1324 *R/edge_direction.R 49f2ffc57c766b3d25fbd832b8bad86c *R/edges.R bc0dab2ba59e4498b214c48550d2ac2b *R/facet_edges.R 6035e963375a28a9fdde2070fe166da8 *R/facet_graph.R 2edb9cde243570d689310e3be984687a *R/facet_nodes.R 225c354694b80dc157b6dffc90891671 *R/geom_axis_hive.R 1852efbd4ad4418dc125c1f5d26d16f2 *R/geom_conn_bundle.R 46b32c5afe9e1f535e21b49387ee7c0a *R/geom_edge.R d617319fc8e48a82ea0605767055596e *R/geom_edge_arc.R 0be2be588811a68ab01e760d8b6e6357 *R/geom_edge_bend.R d64609a225605802a8c0870d38b8f548 *R/geom_edge_density.R ddf5d7db5934da43797e04a7d8ad8279 *R/geom_edge_diagonal.R cc3f0a8e0a1326c769120a21a487c355 *R/geom_edge_elbow.R 8b137f51414ca045a380cbdbbe9f4412 *R/geom_edge_fan.R bd661d2c13ab9e0fe58bb58e53195a8e *R/geom_edge_hive.R 61270bc970f8aea10f17f500ea361c28 *R/geom_edge_link.R c173c86f3f847b6588cf15029dee58f5 *R/geom_edge_loop.R 48a6415e02aed524825a792ed2119a4f *R/geom_edge_parallel.R a3cbb4fec42dfd576c9037b4462c4f2b *R/geom_edge_point.R bb082cb714115c100245e19d89fa2a74 *R/geom_edge_span.R 85d3d8600e6931dbfc7a6a675074daf9 *R/geom_edge_tile.R 6a0115397bcf2092b5f247bbbab7160f *R/geom_node_arc_bar.R f0d517ec5ef41b4803718dc8fe703e58 *R/geom_node_circle.R 885ea8095ea0e982eaf38a68f63f998e *R/geom_node_point.R 2e914bbedcf71eb3216f5f103a0d9e9f *R/geom_node_range.R ee997858d734b04b675a8fa37cc6d933 *R/geom_node_text.R bd8affc263c5bcabe2fe7dda4101ab29 *R/geom_node_tile.R 5b6685c7e9629bf1563ae7b4e1316cf7 *R/geom_node_voronoi.R 1bb1abd44fc677c80b7483c66bfb9164 *R/geometry.R 7c3bf9fc84ae924289f9676dbd9e38c7 *R/gganimate.R 16e17c22b391413ce69668b39c1a6129 *R/ggproto-classes.R cfcf87f35077a1d554a595fddd27f78c *R/ggraph-package.R f596fea4db01c3d9d985e661c5fca17e *R/ggraph.R 09cd89295eb8d29261f896c76b39f79c *R/layout.R 65e4f9a09eb0420ef422f1b5e62a92a8 *R/layout_auto.R 8eadc07bc4b50b4d9dd31664dd307a67 *R/layout_backbone.R bfbb4fdc40248c73732af5171a533fad *R/layout_centrality.R 311f80e161e9376b2fc8481d0ea5cb23 *R/layout_circlepack.R d727ccff5641501b7db33410dc424e7e *R/layout_dendrogram.R 5f3bd1e4d8d251eb257846258236ab3b *R/layout_eigen.R d8e28b2aca55bb9e30bd6b0e473e6294 *R/layout_fabric.R f62514b7f9a08fa20794041334fa2aac *R/layout_focus.R 09b151616efb166c063e31a35c02e18c *R/layout_hive.R 9c7bc8e36d9e4265c81c0463ef8bb46a *R/layout_igraph.R 5cdef03eaff9d7ee598a697e266d8358 *R/layout_linear.R 25d9cecc63bab3cedfeb857287f7e6d2 *R/layout_manual.R ad024ac71160f25d22de7afdc089cfad *R/layout_matrix.R 47b7dfdadff7f1f962c214b9a4a3f058 *R/layout_partition.R 49a89f0f69d159e095a99305bfcb1a85 *R/layout_pmds.R fbb7f20aaa0eb01d20fd4c330e38cbcd *R/layout_stress.R ca9f428416ea6d5b4b0dcdd51b44f0a4 *R/layout_treemap.R 77fe2095250516fbbe05e50d40b50b8b *R/layout_unrooted.R b211eca5e39268a9d36af76039a5fe39 *R/nodes.R b722214f76cb50471e56ef9727452a7d *R/parallelPath.R d0b15c96a876f2d19427491ca5cbd96c *R/scale_edge_alpha.R 798dd62dce1d84eabc90490899ce9f2d *R/scale_edge_colour.R 81766a4a85a860beb03383c2f62bb1a4 *R/scale_edge_fill.R 79ec3f54a6f30e1cfd6b09b6451bf0a2 *R/scale_edge_linetype.R 07aa046a654c9989546b0c0517e1a314 *R/scale_edge_shape.R cf623e403f1d3c90d217bcc97862dd7a *R/scale_edge_size.R ad75cb641a63673502912a94c0552405 *R/scale_edge_width.R 0249622b2ea0386150edfa1322356856 *R/scale_label_size.R 80ecac46c04926f6f7b1846f0f9c2400 *R/tbl_graph.R 028ccba34a8318ee488811fa0e28eb89 *R/textAlong.R 15c5e81c5ce5a075268219b91123274a *R/theme_graph.R 886c5cf1fd3d85fb6a372245ee8451b3 *R/utils.R 623179db11eed44303cc8ed996a1b46b *R/zzz.R 6a75e8c30809f134b84d325565ed903e *README.md 35cba49a33e00e66edb6c097b339a704 *build/vignette.rds 1bc9dd3b58429479ae99af336f006297 *data/flare.rda f69db9aac415341c308621a6b7f28156 *data/highschool.rda 570c963eb86d150dce21fd30de9de804 *data/whigs.rda b392ab6135b2d6d260b9ef0c07e48a25 *inst/doc/Edges.R 8f73fb7951839abd9bc44cb8b03c1170 *inst/doc/Edges.Rmd 98e7e792c7d89ca01f1dd21b9f3d87f9 *inst/doc/Edges.html 259dc902ee637d2b425025fc65911db7 *inst/doc/Layouts.R ff5f31b333475e9ad8515de93565b842 *inst/doc/Layouts.Rmd 59b367caf3675fb4cacbd391115dc733 *inst/doc/Layouts.html 4f8b41e8b47a45d6deecca44a5d36e78 *inst/doc/Nodes.R 1a049ca91ff8050596ef4efeb40b569a *inst/doc/Nodes.Rmd e7043fc29bdc147c720be20b864e3bd4 *inst/doc/Nodes.html 7454b35a90f08f8b291701734a5f24a4 *inst/doc/tidygraph.R 123ad892ab7ec9fb1a4ba89d06a29418 *inst/doc/tidygraph.Rmd 3c6f03bd4da56dbdd268ff9a1b076bbe *inst/doc/tidygraph.html 6218b2f72afdd5675995ccacd4f3334c *man/autograph.Rd bdc828e274bd545020bac49a89bfb3b5 *man/facet_edges.Rd 8f6ec4f2794ec686003864ff9e3ac6c7 *man/facet_graph.Rd 23819cb39bd9522988f9af70d692305b *man/facet_nodes.Rd 50bed72b5eb003aa4c2f996c0f5f85ab *man/figures/README-unnamed-chunk-2-1.png 23da6e1ed002380152b3aa3e468d7f02 *man/figures/logo.png 47255546fb1af68448fae2af07faac49 *man/flare.Rd 17687aa15a4e84835f5d607603feccb6 *man/geom_axis_hive.Rd 71e4a2a226c6d4fc38bb6cf5423469d9 *man/geom_conn_bundle.Rd b894b4698e16e43df27fe32182318811 *man/geom_edge_arc.Rd 336f0d23ff95abc46b201ece49f959ba *man/geom_edge_bend.Rd ae861f939dac33526dfd7aca02e6997f *man/geom_edge_density.Rd c95d2cdb8d6f63db8ca336a87ca63da1 *man/geom_edge_diagonal.Rd 86c61ab650b8aab9fdf019cce160c219 *man/geom_edge_elbow.Rd 458253f3ac0eae9e9256d2ca2a24822e *man/geom_edge_fan.Rd 1d4761bb99782dac79a27670e7cf1fa1 *man/geom_edge_hive.Rd 65849498547d143daead3f2feaada3c5 *man/geom_edge_link.Rd 2e5004d2117392a98adcae8a16aae900 *man/geom_edge_loop.Rd 66d2c26d8d972377c91c5c9951bfe91f *man/geom_edge_parallel.Rd 8f076de846da5e12552a5ae6c39e1eb1 *man/geom_edge_point.Rd e4852afca93f4023d3c53394460331c3 *man/geom_edge_span.Rd f93a0eebd444c76898ffd0d014c63578 *man/geom_edge_tile.Rd 1206ef3c75b687adcbbe307516e21b5d *man/geom_node_arc_bar.Rd 1899b81c654e80b53629284b53c3b99c *man/geom_node_circle.Rd 099d7b9f1b39a2189506f82f3c734837 *man/geom_node_point.Rd 737d2ef001c728258fab5ff7b9e58f81 *man/geom_node_range.Rd 427fbd9ecee8ff18887e32e9ef667e22 *man/geom_node_text.Rd 638633ccc49be534b11f9cdba86803cb *man/geom_node_tile.Rd 9af7b973c3064d7b1fe25ed500242ed5 *man/geom_node_voronoi.Rd 15ce2a0edd3504f4f2471af2534d7326 *man/geometry.Rd 248467d657802cb39262b74e885458ae *man/get_con.Rd daf61b26fbd33eb4b3cd884213a4ad5b *man/get_edges.Rd b0d78017f3e4065ea3d856d37dec580d *man/get_nodes.Rd 965f0f9f723489918321a6ffef5c4b85 *man/ggraph-extensions.Rd aa9c7bdf5b0f61dea36ae8a22b02e4f7 *man/ggraph-package.Rd 2aeb92b8291f5170b63a0187cc208241 *man/ggraph.Rd b924bf0dba43febcadaea0dc2e451af7 *man/guide-helpers.Rd 8e436ce9905784569f76701599d90d39 *man/guide_edge_colourbar.Rd 6fa2b6f568ed952833311e9e87e8e599 *man/guide_edge_direction.Rd 681d41e4a09d32a489ba639e471d8113 *man/highschool.Rd f5b7dbb2c7a339e0b75ffeae85963843 *man/internal_extractors.Rd 053c2110db04eda052c2f5a2dcb3bd49 *man/layout_tbl_graph_auto.Rd dc618c133da8e9b0482cffe36e95d1b4 *man/layout_tbl_graph_backbone.Rd 4f6e01500111d2bcb5cdd9810d65492d *man/layout_tbl_graph_centrality.Rd 6c5e86f37cb40ff8901432b6be0f46d1 *man/layout_tbl_graph_circlepack.Rd 0942fba6ce6c083d41711b37577a7de1 *man/layout_tbl_graph_dendrogram.Rd fdd605e17957b4178311db905bee7bf6 *man/layout_tbl_graph_eigen.Rd 74bf07ce1d9f4fd2ca1dd1f8cd6122c2 *man/layout_tbl_graph_fabric.Rd 304d525f0509f82b555e0e770e613566 *man/layout_tbl_graph_focus.Rd 6ca55491802c4073f4c784b568131766 *man/layout_tbl_graph_hive.Rd 836247d794a7c5eb2e088b75322885f1 *man/layout_tbl_graph_igraph.Rd 8d3e698b6a9235ef852580e55d561c4e *man/layout_tbl_graph_linear.Rd cf3cdc6d6c62c40ff73b409b30d41fd5 *man/layout_tbl_graph_manual.Rd c95bb2c9e7ff9bf64d24659fc4a7fc7b *man/layout_tbl_graph_matrix.Rd ce502eee47b62fe530948117fc30a6fe *man/layout_tbl_graph_partition.Rd da1e460df2808daf528b68039c4eb915 *man/layout_tbl_graph_pmds.Rd d508ca1bf0369498627097f1d597abd6 *man/layout_tbl_graph_stress.Rd 8d0b8710ff9fe6d72573bb148c3088e1 *man/layout_tbl_graph_treemap.Rd 843ed34ff87fd8cdf08927eed8d4293d *man/layout_tbl_graph_unrooted.Rd 0c817db3a372f1e2081ebb6da76c0148 *man/layout_to_table.Rd f06150ad780ebfc293bb9c6b65b57fe9 *man/makeContent.cappedpathgrob.Rd adfa6d7b71555c20e9c3a0bfc52f716b *man/makeContent.textalong.Rd e60ac2f40c31028768b8cf10698506a8 *man/node_angle.Rd 1e87527f65946d558aa8f9de1a166c53 *man/pack_circles.Rd 9fe82148008673da4df12378d00d1cd8 *man/qgraph.Rd b6884a33edf4f6021916418212dc640f *man/reexports.Rd 954e5e43934ba5edd82fb7dae887645a *man/scale_edge_alpha.Rd 368a3cc5ce2f66fd46307d4b3080b50f *man/scale_edge_colour.Rd 07cdab1aa6402afd2a6ecf9a89ce554a *man/scale_edge_fill.Rd 2429541f0ad507c5ee44ae616fac4a65 *man/scale_edge_linetype.Rd d0876fb9263548566a5a8cd9d0f61f94 *man/scale_edge_shape.Rd 636da5f3a730bf9548fda304a0af97f6 *man/scale_edge_size.Rd ae3b0f72e5b30d198cf3a94b5c9f36c9 *man/scale_edge_width.Rd 5e56f0bc2147a41980c1f8578fc5a27b *man/scale_label_size.Rd 6ffb964e584c1744558c1fd14b13cb7e *man/scale_type.geometry.Rd 7eac74e4b06f88760f1430af4ce2e3a0 *man/theme_graph.Rd 6a373d790ec622690107a42f884a36c4 *man/whigs.Rd f6cb6c5834d1545e030b866e4a5bf333 *src/RcppExports.cpp bd95c5153ce513b337eaea41330fb3b6 *src/circlePack.cpp 777c35ee89f1f0e9a39c9f513e4b0c1e *src/iciclePlot.cpp a0e856f7e45c99be2221f30070c34b39 *src/lineCutter.cpp 872203cf7a96b5ae566ec2a62dcaad16 *src/nodes.cpp 64a0935532c0428631cd4290fccd3399 *src/nodes.h 06a0359aac2e1031c50c36cad2191b35 *src/pathAttr.cpp c8ca6ebfed0c8a4f3b12dbee7d6a6476 *src/treemap.cpp ddfe69bc4e82da5523781c2027fdcd35 *src/unrooted.cpp 8f73fb7951839abd9bc44cb8b03c1170 *vignettes/Edges.Rmd ff5f31b333475e9ad8515de93565b842 *vignettes/Layouts.Rmd 1a049ca91ff8050596ef4efeb40b569a *vignettes/Nodes.Rmd ab4990600c00df5927c3655a9a90e13a *vignettes/edge_meme_wide.jpg 123ad892ab7ec9fb1a4ba89d06a29418 *vignettes/tidygraph.Rmd ggraph/inst/0000755000176200001440000000000013617237522012507 5ustar liggesusersggraph/inst/doc/0000755000176200001440000000000013617237522013254 5ustar liggesusersggraph/inst/doc/Layouts.R0000644000176200001440000001274513617237515015052 0ustar liggesusers## ---- message=FALSE----------------------------------------------------------- library(ggraph) library(tidygraph) set_graph_style(plot_margin = margin(1,1,1,1)) graph <- as_tbl_graph(highschool) # Not specifying the layout - defaults to "auto" ggraph(graph) + geom_edge_link(aes(colour = factor(year))) + geom_node_point() ## ----------------------------------------------------------------------------- ggraph(graph, layout = 'kk') + geom_edge_link(aes(colour = factor(year))) + geom_node_point() ## ----------------------------------------------------------------------------- ggraph(graph, layout = 'kk', maxiter = 100) + geom_edge_link(aes(colour = factor(year))) + geom_node_point() ## ----------------------------------------------------------------------------- layout <- create_layout(graph, layout = 'eigen') ggraph(layout) + geom_edge_link(aes(colour = factor(year))) + geom_node_point() ## ----------------------------------------------------------------------------- head(layout) ## ----------------------------------------------------------------------------- attributes(layout) ## ----------------------------------------------------------------------------- # An arc diagram ggraph(graph, layout = 'linear') + geom_edge_arc(aes(colour = factor(year))) ## ----------------------------------------------------------------------------- # A coord diagram ggraph(graph, layout = 'linear', circular = TRUE) + geom_edge_arc(aes(colour = factor(year))) + coord_fixed() ## ----------------------------------------------------------------------------- graph <- tbl_graph(flare$vertices, flare$edges) # An icicle plot ggraph(graph, 'partition') + geom_node_tile(aes(fill = depth), size = 0.25) ## ----------------------------------------------------------------------------- # A sunburst plot ggraph(graph, 'partition', circular = TRUE) + geom_node_arc_bar(aes(fill = depth), size = 0.25) + coord_fixed() ## ---- fig.show='hold', results='hide'----------------------------------------- graph <- as_tbl_graph(highschool) %>% mutate(degree = centrality_degree()) lapply(c('stress', 'fr', 'lgl', 'graphopt'), function(layout) { ggraph(graph, layout = layout) + geom_edge_link(aes(colour = factor(year)), show.legend = FALSE) + geom_node_point() + labs(caption = paste0('Layout: ', layout)) }) ## ----------------------------------------------------------------------------- graph <- graph %>% mutate(friends = ifelse( centrality_degree(mode = 'in') < 5, 'few', ifelse(centrality_degree(mode = 'in') >= 15, 'many', 'medium') )) ggraph(graph, 'hive', axis = friends, sort.by = degree) + geom_edge_hive(aes(colour = factor(year))) + geom_axis_hive(aes(colour = friends), size = 2, label = FALSE) + coord_fixed() ## ----------------------------------------------------------------------------- ggraph(graph, 'focus', focus = node_is_center()) + ggforce::geom_circle(aes(x0 = 0, y0 = 0, r = r), data.frame(r = 1:5), colour = 'grey') + geom_edge_link() + geom_node_point() + coord_fixed() ## ----------------------------------------------------------------------------- graph <- tbl_graph(flare$vertices, flare$edges) set.seed(1) ggraph(graph, 'circlepack', weight = size) + geom_node_circle(aes(fill = depth), size = 0.25, n = 50) + coord_fixed() ## ----------------------------------------------------------------------------- set.seed(1) ggraph(graph, 'circlepack', weight = size) + geom_edge_link() + geom_node_point(aes(colour = depth)) + coord_fixed() ## ----------------------------------------------------------------------------- ggraph(graph, 'treemap', weight = size) + geom_node_tile(aes(fill = depth), size = 0.25) ## ----------------------------------------------------------------------------- ggraph(graph, 'treemap', weight = size) + geom_edge_link() + geom_node_point(aes(colour = depth)) ## ----------------------------------------------------------------------------- ggraph(graph, 'tree') + geom_edge_diagonal() ## ----------------------------------------------------------------------------- dendrogram <- hclust(dist(iris[, 1:4])) ggraph(dendrogram, 'dendrogram', height = height) + geom_edge_elbow() ## ----------------------------------------------------------------------------- ggraph(dendrogram, 'dendrogram', circular = TRUE) + geom_edge_elbow() + coord_fixed() ## ----------------------------------------------------------------------------- ggraph(dendrogram, 'unrooted') + geom_edge_link() ## ----------------------------------------------------------------------------- graph <- create_notable('zachary') ggraph(graph, 'matrix', sort.by = node_rank_leafsort()) + geom_edge_point(mirror = TRUE) + coord_fixed() ## ----------------------------------------------------------------------------- ggraph(graph, 'matrix', sort.by = node_rank_spectral()) + geom_edge_point(mirror = TRUE) + coord_fixed() ## ----------------------------------------------------------------------------- ggraph(graph, 'fabric', sort.by = node_rank_fabric()) + geom_node_range(colour = 'grey') + geom_edge_span(end_shape = 'square') + coord_fixed() ## ----------------------------------------------------------------------------- ggraph(graph, 'fabric', sort.by = node_rank_fabric(), shadow.edges =TRUE) + geom_node_range(colour = 'grey') + geom_edge_span(aes(filter = shadow_edge), colour ='lightblue' , end_shape = 'square') + geom_edge_span(aes(filter = !shadow_edge), end_shape = 'square') + coord_fixed() ggraph/inst/doc/Edges.R0000644000176200001440000001575013617237472014442 0ustar liggesusers## ---- message=FALSE----------------------------------------------------------- library(ggraph) library(tidygraph) library(purrr) library(rlang) set_graph_style(plot_margin = margin(1,1,1,1)) hierarchy <- as_tbl_graph(hclust(dist(iris[, 1:4]))) %>% mutate(Class = map_bfs_back_chr(node_is_root(), .f = function(node, path, ...) { if (leaf[node]) { as.character(iris$Species[as.integer(label[node])]) } else { species <- unique(unlist(path$result)) if (length(species) == 1) { species } else { NA_character_ } } })) hairball <- as_tbl_graph(highschool) %>% mutate( year_pop = map_local(mode = 'in', .f = function(neighborhood, ...) { neighborhood %E>% pull(year) %>% table() %>% sort(decreasing = TRUE) }), pop_devel = map_chr(year_pop, function(pop) { if (length(pop) == 0 || length(unique(pop)) == 1) return('unchanged') switch(names(pop)[which.max(pop)], '1957' = 'decreased', '1958' = 'increased') }), popularity = map_dbl(year_pop, ~ .[1]) %|% 0 ) %>% activate(edges) %>% mutate(year = as.character(year)) ## ----------------------------------------------------------------------------- ggraph(hairball, layout = 'stress') + geom_edge_link(aes(colour = year)) ## ----------------------------------------------------------------------------- ggraph(hairball, layout = 'stress') + geom_edge_fan(aes(colour = year)) ## ----------------------------------------------------------------------------- ggraph(hairball, layout = 'stress') + geom_edge_parallel(aes(colour = year)) ## ----------------------------------------------------------------------------- # let's make some of the student love themselves loopy_hairball <- hairball %>% bind_edges(tibble::tibble(from = 1:5, to = 1:5, year = rep('1957', 5))) ggraph(loopy_hairball, layout = 'stress') + geom_edge_link(aes(colour = year), alpha = 0.25) + geom_edge_loop(aes(colour = year)) ## ----------------------------------------------------------------------------- ggraph(hairball, layout = 'stress') + geom_edge_density(aes(fill = year)) + geom_edge_link(alpha = 0.25) ## ----------------------------------------------------------------------------- ggraph(hairball, layout = 'linear') + geom_edge_arc(aes(colour = year)) ## ----------------------------------------------------------------------------- ggraph(hairball, layout = 'linear', circular = TRUE) + geom_edge_arc(aes(colour = year)) + coord_fixed() ## ----------------------------------------------------------------------------- ggraph(hierarchy, layout = 'dendrogram', height = height) + geom_edge_elbow() ## ----------------------------------------------------------------------------- ggraph(hierarchy, layout = 'dendrogram', height = height) + geom_edge_diagonal() ## ----------------------------------------------------------------------------- ggraph(hierarchy, layout = 'dendrogram', height = height) + geom_edge_bend() ## ----------------------------------------------------------------------------- ggraph(hairball, layout = 'hive', axis = pop_devel, sort.by = popularity) + geom_edge_hive(aes(colour = year)) + geom_axis_hive(label = FALSE) + coord_fixed() ## ----------------------------------------------------------------------------- ggraph(hairball, layout = 'fabric', sort.by = node_rank_fabric()) + geom_node_range(colour = 'grey') + geom_edge_span(end_shape = 'circle') + coord_fixed() ## ----------------------------------------------------------------------------- ggraph(hairball, layout = 'matrix', sort.by = bfs_rank()) + geom_edge_point() + coord_fixed() ## ----------------------------------------------------------------------------- ggraph(hairball, layout = 'matrix', sort.by = bfs_rank()) + geom_edge_tile() + coord_fixed() ## ----------------------------------------------------------------------------- ggraph(hairball, layout = 'linear') + geom_edge_arc(aes(colour = year, alpha = stat(index))) + scale_edge_alpha('Edge direction', guide = 'edge_direction') ## ----------------------------------------------------------------------------- ggraph(hierarchy, layout = 'dendrogram', height = height) + geom_edge_elbow2(aes(colour = node.Class)) ## ----------------------------------------------------------------------------- small_tree <- create_tree(5, 2) ggraph(small_tree, 'dendrogram') + geom_edge_elbow(strength = 0.75) ## ----------------------------------------------------------------------------- ggraph(small_tree, 'dendrogram') + geom_edge_diagonal(strength = 0.5) ## ----------------------------------------------------------------------------- # Random names - I swear simple <- create_notable('bull') %>% mutate(name = c('Thomas', 'Bob', 'Hadley', 'Winston', 'Baptiste')) %>% activate(edges) %>% mutate(type = sample(c('friend', 'foe'), 5, TRUE)) ## ----------------------------------------------------------------------------- ggraph(simple, layout = 'graphopt') + geom_edge_link(arrow = arrow(length = unit(4, 'mm'))) + geom_node_point(size = 5) ## ----------------------------------------------------------------------------- ggraph(simple, layout = 'graphopt') + geom_edge_link(arrow = arrow(length = unit(4, 'mm')), end_cap = circle(3, 'mm')) + geom_node_point(size = 5) ## ----------------------------------------------------------------------------- ggraph(simple, layout = 'linear', circular = TRUE) + geom_edge_arc(arrow = arrow(length = unit(4, 'mm')), start_cap = circle(3, 'mm'), end_cap = circle(3, 'mm')) + geom_node_point(size = 5) + coord_fixed() ## ----------------------------------------------------------------------------- ggraph(simple, layout = 'graphopt') + geom_edge_link(aes(start_cap = label_rect(node1.name), end_cap = label_rect(node2.name)), arrow = arrow(length = unit(4, 'mm'))) + geom_node_text(aes(label = name)) ## ----------------------------------------------------------------------------- ggraph(simple, layout = 'graphopt') + geom_edge_link(aes(label = type), arrow = arrow(length = unit(4, 'mm')), end_cap = circle(3, 'mm')) + geom_node_point(size = 5) ## ----------------------------------------------------------------------------- ggraph(simple, layout = 'graphopt') + geom_edge_link(aes(label = type), angle_calc = 'along', label_dodge = unit(2.5, 'mm'), arrow = arrow(length = unit(4, 'mm')), end_cap = circle(3, 'mm')) + geom_node_point(size = 5) ## ----------------------------------------------------------------------------- flaregraph <- tbl_graph(flare$vertices, flare$edges) from <- match(flare$imports$from, flare$vertices$name) to <- match(flare$imports$to, flare$vertices$name) ggraph(flaregraph, layout = 'dendrogram', circular = TRUE) + geom_conn_bundle(data = get_con(from = from, to = to), alpha = 0.1) + coord_fixed() ggraph/inst/doc/Edges.Rmd0000644000176200001440000004072513617226142014753 0ustar liggesusers--- title: "Edges" author: "Thomas Lin Pedersen" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Edges} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- If the natural `ggplot2` equivalent to nodes is `geom_point()`, then surely the equivalent to edges must be `geom_segment()`? Well, sort of, but there's a bit more to it than that. ![One does not simply draw a line between two nodes](edge_meme_wide.jpg) While nodes are the sensible, mature, and predictably geoms, edges are the edgy (sorry), younger cousins that pushes the boundaries. To put it bluntly: > On the ggraph savannah you definitely want to be an edge! ## Meet the `geom_edge_*()` family While the introduction might feel a bit over-the-top it is entirely true. An edge is an abstract concept denoting a relationship between two entities. A straight line is simply just one of many ways this relationship can be visualised. As we saw when [discussing nodes](Nodes.html) sometimes it is not drawn at all but impied using containment or position (treemap, circle packing, and partition layouts), but more often it is shown using a line of some sort. This use-case is handled by the large family of edge geoms provided in `ggraph`. Some of the edges are general while others are dedicated to specific layouts. Let's creates some graphs for illustrative purposes first: ```{r, message=FALSE} library(ggraph) library(tidygraph) library(purrr) library(rlang) set_graph_style(plot_margin = margin(1,1,1,1)) hierarchy <- as_tbl_graph(hclust(dist(iris[, 1:4]))) %>% mutate(Class = map_bfs_back_chr(node_is_root(), .f = function(node, path, ...) { if (leaf[node]) { as.character(iris$Species[as.integer(label[node])]) } else { species <- unique(unlist(path$result)) if (length(species) == 1) { species } else { NA_character_ } } })) hairball <- as_tbl_graph(highschool) %>% mutate( year_pop = map_local(mode = 'in', .f = function(neighborhood, ...) { neighborhood %E>% pull(year) %>% table() %>% sort(decreasing = TRUE) }), pop_devel = map_chr(year_pop, function(pop) { if (length(pop) == 0 || length(unique(pop)) == 1) return('unchanged') switch(names(pop)[which.max(pop)], '1957' = 'decreased', '1958' = 'increased') }), popularity = map_dbl(year_pop, ~ .[1]) %|% 0 ) %>% activate(edges) %>% mutate(year = as.character(year)) ``` ### Link While you don't have to use a straight line for edges it is certainly possible and `geom_edge_link()` is here to serve your needs: ```{r} ggraph(hairball, layout = 'stress') + geom_edge_link(aes(colour = year)) ``` There's really not much more to it --- every edge is simply a straight line between the terminal nodes. Moving on... ### Fan Sometimes the graph is not simple, i.e. it has multiple edges between the same nodes. Using links is a bad choice here because edges will overlap and the viewer will be unable to discover parallel edges. `geom_edge_fan()` got you covered here. If there are no parallel edges it behaves like `geom_edge_link()` and draws a straight line, but if parallel edges exists it will spread them out as arcs with different curvature. Parallel edges will be sorted by directionality prior to plotting so edges flowing in the same direction will be plotted together: ```{r} ggraph(hairball, layout = 'stress') + geom_edge_fan(aes(colour = year)) ``` ### Parallel An alternative to `geom_edge_fan()` is `geom_edge_parallel()`. It will draw edges as straight lines but in the case of multi-edges it will offset each edge a bit so they run parallel to each other. As with `geom_edge_fan()` the edges will be sorted by direction first. The offset is done at draw time and will thus remain constant even during resizing: ```{r} ggraph(hairball, layout = 'stress') + geom_edge_parallel(aes(colour = year)) ``` ### Loops Loops cannot be shown with regular edges as they have no length. A dedicated `geom_edge_loop()` exists for these cases: ```{r} # let's make some of the student love themselves loopy_hairball <- hairball %>% bind_edges(tibble::tibble(from = 1:5, to = 1:5, year = rep('1957', 5))) ggraph(loopy_hairball, layout = 'stress') + geom_edge_link(aes(colour = year), alpha = 0.25) + geom_edge_loop(aes(colour = year)) ``` The direction, span, and strength of the loop can all be controlled, but in general loops will add a lot of visual clutter to your plot unless the graph is very simple. ### Density This one is definitely strange, and I'm unsure of it's usefulness, but it is here and it deserves an introduction. Consider the case where it is of interest to see which types of edges dominates certain areas of the graph. You can colour the edges, but edges can tend to get overplotted, thus reducing readability. `geom_edge_density()` lets you add a shading to your plot based on the density of edges in a certain area: ```{r} ggraph(hairball, layout = 'stress') + geom_edge_density(aes(fill = year)) + geom_edge_link(alpha = 0.25) ``` ### Arcs While some insists that curved edges should be used in standard *"hairball"* graph visualisations it really is a poor choice, as it increases overplotting and decreases interpretability for virtually no gain (unless complexity is your thing). That doesn't mean arcs have no use in graph visualizations. Linear and circular layouts can benefit greatly from them and `geom_edge_arc()` is provided precisely for this scenario: ```{r} ggraph(hairball, layout = 'linear') + geom_edge_arc(aes(colour = year)) ``` Arcs behave differently in circular layouts as they will always bend towards the center no matter the direction of the edge (the same thing can be achieved in a linear layout by setting `fold = TRUE`). ```{r} ggraph(hairball, layout = 'linear', circular = TRUE) + geom_edge_arc(aes(colour = year)) + coord_fixed() ``` ### Elbow Aah... The classic dendrogram with its right angle bends. Of course such visualizations are also supported with the `geom_edge_elbow()`. It goes without saying that this type of edge requires a layout that flows in a defined direction, such as a tree: ```{r} ggraph(hierarchy, layout = 'dendrogram', height = height) + geom_edge_elbow() ``` ### Diagonals If right angles aren't really your thing `ggraph` provides a smoother version in the form of `geom_edge_diagonal()`. This edge is a quadratic bezier with control points positioned at the same x-value as the terminal nodes and halfway in-between the nodes on the y-axis. The result is more organic than the elbows: ```{r} ggraph(hierarchy, layout = 'dendrogram', height = height) + geom_edge_diagonal() ``` It tends to look a bit weird with hugely unbalanced trees so use with care... ### Bends An alternative to diagonals are bend edges which are elbow edges with a smoothed corner. It is implemented as a quadratic bezier with control points at the location of the expected elbow corner: ```{r} ggraph(hierarchy, layout = 'dendrogram', height = height) + geom_edge_bend() ``` ### Hive This is certainly a very specific type of edge, intended only for use with hive plots. It draws edges as quadratic beziers with control point positioned perpendicular to the axes of the hive layout: ```{r} ggraph(hairball, layout = 'hive', axis = pop_devel, sort.by = popularity) + geom_edge_hive(aes(colour = year)) + geom_axis_hive(label = FALSE) + coord_fixed() ``` ### Span As with the hive edge the `geom_edge_span()` is made in particular for a specific layout - the fabric layout. It draws the edge as a vertical line connecting the horizontal node lines of the layout, potentially with a terminal shape. ```{r} ggraph(hairball, layout = 'fabric', sort.by = node_rank_fabric()) + geom_node_range(colour = 'grey') + geom_edge_span(end_shape = 'circle') + coord_fixed() ``` ### Point and tile It may seem weird to have edge geoms that doesn't have any span, but the matrix layout calls for exactly that. The terminal nodes of the edge are determined by the vertical and horizontal position of the mark, and for that reason the geom doesn't need any extend. The point and tile geoms serve the same purpose but are simply different geometry types: ```{r} ggraph(hairball, layout = 'matrix', sort.by = bfs_rank()) + geom_edge_point() + coord_fixed() ``` ```{r} ggraph(hairball, layout = 'matrix', sort.by = bfs_rank()) + geom_edge_tile() + coord_fixed() ``` ## The three types of edge geoms Almost all edge geoms comes in three variants. The basic variant (no suffix) as well as the variant suffixed with 2 (e.g. `geom_edge_link2()`) calculates a number (`n`) of points along the edge and draws it as a path. The variant suffixed with 0 (e.g. `geom_edge_diagonal0()`) uses the build in grid grobs to draw the edges directly (in case of a diagonal it uses `bezierGrob()`). It might seem strange to have so many different implementations of the same geoms but there's a reason to the insanity... ### Base variant The basic edge geom is drawn by calculating a number of points along the edge path and draw a line between these. This means that you're in control of the detail level of curved edges and that all complex calculations happens up front. Generally you will see better performance using the base variant rather than the 0-variant that uses grid grobs, unless you set the number of points to calculate to something huge (50--100 is usually sufficient for a smooth look). Apart from better performance you also get a nice bonus (you actually get several, but only one is discussed here): The possibility of drawing a gradient along the edge. Each calculated point gets an index value between 0 and 1 that specifies how far along the edge it is positioned and this value can be used to e.g. map to an alpha level to show the direction of the edge: ```{r} ggraph(hairball, layout = 'linear') + geom_edge_arc(aes(colour = year, alpha = stat(index))) + scale_edge_alpha('Edge direction', guide = 'edge_direction') ``` ### 2-variant Like the base variant the 2-variant calculates points along the edge and draws a path along them. The difference here is that in this variant you can map node attributes to the edge and the aesthetics are then interpolated along the edge. This is easier to show than to explain: ```{r} ggraph(hierarchy, layout = 'dendrogram', height = height) + geom_edge_elbow2(aes(colour = node.Class)) ``` There are considerably more computation going on than in the base variant so unless you need to interpolate values between the terminal nodes you should go with the base variant. ### 0-variant This is, sadly, the boring one at the end. You don't get the luxury of smooth gradients over the edge and often you have a considerably worse performance. What you gain though is tack sharp resolution in the curves so if this is of utmost importance you are covered by this variant. ## Edge strength Many of the edge geoms takes a strength argument that denotes their deviation from a straight line. Setting `strength = 0` will always result in a straight line, while `strength = 1` is the default look. Anything in between can be used to modify the look of the edge, while values outside that range will probably result in some weird looks. Some examples are shown below: ```{r} small_tree <- create_tree(5, 2) ggraph(small_tree, 'dendrogram') + geom_edge_elbow(strength = 0.75) ``` ```{r} ggraph(small_tree, 'dendrogram') + geom_edge_diagonal(strength = 0.5) ``` ## Decorating edges An edge is so much more than a line... Well at least it is also potentially an arrow and a label. This section will go into how these can be added. To clearly see the effect here we will use a slightly simpler graph ```{r} # Random names - I swear simple <- create_notable('bull') %>% mutate(name = c('Thomas', 'Bob', 'Hadley', 'Winston', 'Baptiste')) %>% activate(edges) %>% mutate(type = sample(c('friend', 'foe'), 5, TRUE)) ``` ### Arrows While we saw above that direction can be encoded as a gradient, the good old arrow is still available. As with the standard `ggplot2` geoms an arrow can be added using the arrow argument: ```{r} ggraph(simple, layout = 'graphopt') + geom_edge_link(arrow = arrow(length = unit(4, 'mm'))) + geom_node_point(size = 5) ``` I hope you think *Ugh* at the sight of this. The edges naturally extend to the node center and nodes are thus drawn on top of the arrow heads. There's a solution to this in the form of the `start_cap` and `end_cap` aesthetics in the base and 2-variant edge geoms (sorry 0-variant). This can be used to start and stop the edge drawing at an absolute distance from the terminal nodes. Watch this: ```{r} ggraph(simple, layout = 'graphopt') + geom_edge_link(arrow = arrow(length = unit(4, 'mm')), end_cap = circle(3, 'mm')) + geom_node_point(size = 5) ``` Using the `circle()`, `square()`, `ellipsis()`, and `rectangle()` helpers it is possible to get a lot of control over how edges are capped at either end. This works for any edge, curved or not: ```{r} ggraph(simple, layout = 'linear', circular = TRUE) + geom_edge_arc(arrow = arrow(length = unit(4, 'mm')), start_cap = circle(3, 'mm'), end_cap = circle(3, 'mm')) + geom_node_point(size = 5) + coord_fixed() ``` When plotting node labels you often want to avoid that incoming and outgoing edges overlaps with the labels. `ggraph` provides a helper that calculates the bounding rectangle of the labels and cap edges based on that: ```{r} ggraph(simple, layout = 'graphopt') + geom_edge_link(aes(start_cap = label_rect(node1.name), end_cap = label_rect(node2.name)), arrow = arrow(length = unit(4, 'mm'))) + geom_node_text(aes(label = name)) ``` The capping of edges is dynamic and responds to resizing of the plot so the absolute size of the cap areas are maintained at all time. #### A quick note on directionality In `ggraph` there is no such thing as an undirected graph. Every edge has a start and an end node. For undirected graphs the start and end of edges is arbitrary but still exists and it is thus possible to add arrowheads to undirected graphs as well. This should not be done of course, but this is the responsibility of the user as `ggraph` does not make any checks during rendering. ### Labels You would expect that edge labels would be their own geom(s), but `ggraph` departs from the stringent grammar interpretation here. This is because the label placement is dependent on the choice of edge. Because of this edge labeling is bundled with each edge geom (but not the 0-variant) through the label aesthetic ```{r} ggraph(simple, layout = 'graphopt') + geom_edge_link(aes(label = type), arrow = arrow(length = unit(4, 'mm')), end_cap = circle(3, 'mm')) + geom_node_point(size = 5) ``` Usually you would like the labels to run along the edges, but providing a fixed angle will only work at a very specific aspect ratio. Instead `ggraph` offers to calculate the correct angle dynamically so the labels always runs along the edge. Furthermore it can offset the label by an absolute length: ```{r} ggraph(simple, layout = 'graphopt') + geom_edge_link(aes(label = type), angle_calc = 'along', label_dodge = unit(2.5, 'mm'), arrow = arrow(length = unit(4, 'mm')), end_cap = circle(3, 'mm')) + geom_node_point(size = 5) ``` `ggraph` offers a lot of additional customization of the edge labels but this shows the main features. As with arrowheads labels can severely clutter your visualization so it is only advisable on very simple graphs. ## Connections The estranged cousin of edges are connections. While edges show the relational nature of the nodes in the graph structure, connections connect nodes that are not connected in the graph. This is done by finding the shortest path between the two nodes. Currently the only connection geom available is `geom_conn_bundle()` that implements the hierarchical edge bundling technique: ```{r} flaregraph <- tbl_graph(flare$vertices, flare$edges) from <- match(flare$imports$from, flare$vertices$name) to <- match(flare$imports$to, flare$vertices$name) ggraph(flaregraph, layout = 'dendrogram', circular = TRUE) + geom_conn_bundle(data = get_con(from = from, to = to), alpha = 0.1) + coord_fixed() ``` The connection concept is underutilized at the moment but I expect to add more support for this in coming releases. ## Want more? Check out the other vignettes for more information on how to specify [layouts](Layouts.html) and draw [nodes](Nodes.html)... ggraph/inst/doc/Layouts.html0000644000176200001440000606743413617237516015631 0ustar liggesusers Layouts

Layouts

Thomas Lin Pedersen

2020-02-07

In very short terms, a layout is the vertical and horizontal placement of nodes when plotting a particular graph structure. Conversely, a layout algorithm is an algorithm that takes in a graph structure (and potentially some additional parameters) and return the vertical and horizontal position of the nodes. Often, when people think of network visualizations, they think of node-edge diagrams where strongly connected nodes are attempted to be plotted in close proximity. Layouts can be a lot of other things too though — e.g. hive plots and treemaps. One of the driving factors behind ggraph has been to develop an API where any type of visual representation of graph structures is supported. In order to achieve this we first need a flexible way of defining the layout…

The ggraph() and create_layout() functions

As the layout is a global specification of the spatial position of the nodes it spans all layers in the plot and should thus be defined outside of calls to geoms or stats. In ggraph it is often done as part of the plot initialization using ggraph() — a function equivalent in intent to ggplot(). As a minimum ggraph() must be passed a graph object supported by ggraph:

Not specifying a layout will make ggraph pick one for you. This is only intended to get quickly up and running. The choice of layout should be deliberate on the part of the user as it will have a great effect on what the end result will communicate. From now on all calls to ggraph() will contain a specification of the layout:

If the layout algorithm accepts additional parameters (most do), they can be supplied in the call to ggraph() as well:

If any layout parameters refers to node or edge variables they must be supplied as unquoted expression (like inside aes() and tidyverse verbs)

In addition to specifying the layout during plot creation it can also happen separately using create_layout(). This function takes the same arguments as ggraph() but returns a layout_ggraph object that can later be used in place of a graph structure in ggraph call:

## Warning in layout_with_eigen(graph, type = type, ev = eigenvector): g is
## directed. undirected version is used for the layout.

Examining the return of create_layout() we see that it is really just a data.frame of node positions and (possible) attributes. Furthermore the original graph object along with other relevant information is passed along as attributes:

##             x           y circular name .ggraph.orig_index .ggraph.index
## 1 -0.04317633 -0.15611549    FALSE    1                  1             1
## 2 -0.03414457 -0.20843892    FALSE    2                  2             2
## 3 -0.04963286 -0.30081095    FALSE    3                  3             3
## 4  0.18211675  0.03597610    FALSE    4                  4             4
## 5  0.18048193 -0.01111635    FALSE    5                  5             5
## 6  0.01389960 -0.19521616    FALSE    6                  6             6
## $names
## [1] "x"                  "y"                  "circular"          
## [4] "name"               ".ggraph.orig_index" ".ggraph.index"     
## 
## $row.names
##  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
## [26] 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
## [51] 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
## 
## $class
## [1] "layout_tbl_graph" "layout_ggraph"    "data.frame"      
## 
## $graph
## # A tbl_graph: 70 nodes and 506 edges
## #
## # A directed multigraph with 1 component
## #
## # Node Data: 70 x 2 (active)
##   name  .ggraph.orig_index
##   <chr>              <int>
## 1 1                      1
## 2 2                      2
## 3 3                      3
## 4 4                      4
## 5 5                      5
## 6 6                      6
## # … with 64 more rows
## #
## # Edge Data: 506 x 3
##    from    to  year
##   <int> <int> <dbl>
## 1     1    13  1957
## 2     1    14  1957
## 3     1    20  1957
## # … with 503 more rows
## 
## $circular
## [1] FALSE

As it is just a data.frame it means that any standard ggplot2 call will work by addressing the nodes. Still, use of the geom_node_*() family provided by ggraph is encouraged as it makes it explicit which part of the data structure is being worked with.

Adding support for new data sources

Out of the box ggraph supports tbl_graph objects from tidygraph natively. Any other type of object will be attempted to be coerced to a tbl_graph object automatically. Tidygraph provide conversions for most known graph structure in R so almost any data type is supported by ggraph by extension. If there is wish for support for additional classes this can be achieved by providing a as_tbl_graph() method for the class. If you do this, consider submitting the method to tidygraph so others can benefit from your work.

Layouts abound

There’s a lot of different layouts in ggraph — All layouts from the graphlayouts and igraph packages are available, an ggraph itself also provide some of the more specialised layouts itself. All in all ggraph provides well above 20 different layouts to choose from, far more than we can cover in this text. I urge you to explore the different layout types. Blindly running along with the default layouts is a sad but common mistake in network visualisation that can cloud or distort the insight the network might hold. If ggraph lacks the needed layout it is always possible to supply your own layout function that takes a tbl_graph object and returns a data.frame of node positions, or supply the positions directly by passing a matrix or data.frame to the layout argument.

A note on circularity

Some layouts can be shown effectively both in a standard Cartesian projection as well as in a polar projection. The standard approach in ggplot2 has been to change the coordinate system with the addition of e.g. coord_polar(). This approach — while consistent with the grammar — is not optimal for ggraph as it does not allow layers to decide how to respond to circularity. The prime example of this is trying to draw straight lines in a plot using coord_polar(). Instead circularity is part of the layout specification and gets communicated to the layers with the circular column in the data, allowing each layer to respond appropriately. Sometimes standard and circular representations of the same layout get used so often that they get different names. In ggraph they’ll have the same name and only differ in whether or not circular is set to TRUE:

Not every layout has a meaningful circular representation in which cases the circular argument will be ignored.

Node-edge diagram layouts

Both graphlayout and igraph provides a range of different layout algorithms for classic node-edge diagrams (colloquially referred to as hairballs). Some of these are incredibly simple such as randomly, grid, circle, and star, while others tries to optimize the position of nodes based on different characteristics of the graph. There is no such thing as “the best layout algorithm” as algorithms have been optimized for different scenarios. Experiment with the choices at hand and remember to take the end result with a grain of salt, as it is just one of a range of possible “optimal node position” results. Below is a sample of some of the layouts available through igraph applied to the highschool graph.

Hive plots

A hive plot, while still technically a node-edge diagram, is a bit different from the rest as it uses information pertaining to the nodes, rather than the connection information in the graph. This means that hive plots, to a certain extent are more interpretable as well as less vulnerable to small changes in the graph structure. They are less common though, so use will often require some additional explanation.

Focal layouts

Some layouts can put focus on a single node or a group of nodes by defining all other positions relative to that. An example of this is the focus layout, but the centrality layout is very akin to it:

Hierarchical layouts

Trees and hierarchies are an important subset of graph structures, and ggraph provides a range of layouts optimized for their visual representation. Some of these use enclosure and position rather than edges to communicate relations (e.g. treemaps and circle packing). Still, these layouts can just as well be used for drawing edges if you wish to:

The most recognized tree plot is probably dendrograms though. If nothing else is stated the height of each node is calculated based on the distance to its farthest sibling (the tree layout, on the other hand, puts all nodes at a certain depth at the same level):

The height of each branch point can be set to a variable — e.g. the height provided by hclust and dendrogram objects:

Dendrograms are one of the layouts that are amenable for circular transformations, which can be effective in giving more space at the leafs of the tree at the expense of the space given to the root:

A type of trees known especially in phylogeny is unrooted trees, where no node is considered the root. Often a dendrogram layout will not be faithful as it implicitly position a node at the root. To avoid that you can use the unrooted layout instead.

Often unrooted trees have a branch length attached - this can be passed to both the dendrogram and unrooted layout to determine the length of each edge.

Matrix layouts

Many node-edge diagram layouts suffer from poor scalability, where edges will eventually begin to overlap to the extend that the plot becomes unintellible. One way to combat this is by only plotting subsets of the larger plot, but another approach is to choose a layout that avoids overlapping edges altogether. The matrix layout places each node on a diagonal and draws edges by drawing points or tiles at the intersection of the vertical and horizontal position of its terminal nodes. Using matrix layouts efficiently requires that you begin to recognise the specific patterns that different network topologies gives rise to. Further, it is important to recognise the large effect that the node order has on the look of the matrix layout:

## Registered S3 method overwritten by 'seriation':
##   method         from 
##   reorder.hclust gclus

Fabric layouts

Another special layout that promises scalability is the biofabric layout (here named fabric to avoid it being ignored for non-biological networks). The fabric layout is special in that it positions nodes evenly spaced on the y-axis and then draws edges as vertical (and by extension, parallel) lines evenly separated as well, connecting the nodes. Nodes are drawn as horizontal lines spanning the extent of the edges that departs from it. As with matrix layouts the node ordering have a huge impact on the final look and interpreting the plot may take some getting used to.

Fabric layouts allow something called shadow edges where all edges are duplicated to make it easier to follow all edges originating from each node.

Want more?

Check out the other vignettes for more information on how to draw nodes and edges

ggraph/inst/doc/Layouts.Rmd0000644000176200001440000003235713617226142015366 0ustar liggesusers--- title: "Layouts" author: "Thomas Lin Pedersen" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Layouts} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- In very short terms, a layout is the vertical and horizontal placement of nodes when plotting a particular graph structure. Conversely, a layout algorithm is an algorithm that takes in a graph structure (and potentially some additional parameters) and return the vertical and horizontal position of the nodes. Often, when people think of network visualizations, they think of node-edge diagrams where strongly connected nodes are attempted to be plotted in close proximity. Layouts can be a lot of other things too though --- e.g. hive plots and treemaps. One of the driving factors behind `ggraph` has been to develop an API where any type of visual representation of graph structures is supported. In order to achieve this we first need a flexible way of defining the layout... ## The ggraph() and create_layout() functions As the layout is a global specification of the spatial position of the nodes it spans all layers in the plot and should thus be defined outside of calls to geoms or stats. In `ggraph` it is often done as part of the plot initialization using `ggraph()` --- a function equivalent in intent to `ggplot()`. As a minimum `ggraph()` must be passed a graph object supported by `ggraph`: ```{r, message=FALSE} library(ggraph) library(tidygraph) set_graph_style(plot_margin = margin(1,1,1,1)) graph <- as_tbl_graph(highschool) # Not specifying the layout - defaults to "auto" ggraph(graph) + geom_edge_link(aes(colour = factor(year))) + geom_node_point() ``` Not specifying a layout will make `ggraph` pick one for you. This is only intended to get quickly up and running. The choice of layout should be deliberate on the part of the user as it will have a great effect on what the end result will communicate. From now on all calls to `ggraph()` will contain a specification of the layout: ```{r} ggraph(graph, layout = 'kk') + geom_edge_link(aes(colour = factor(year))) + geom_node_point() ``` If the layout algorithm accepts additional parameters (most do), they can be supplied in the call to `ggraph()` as well: ```{r} ggraph(graph, layout = 'kk', maxiter = 100) + geom_edge_link(aes(colour = factor(year))) + geom_node_point() ``` If any layout parameters refers to node or edge variables they must be supplied as unquoted expression (like inside `aes()` and `tidyverse` verbs) In addition to specifying the layout during plot creation it can also happen separately using `create_layout()`. This function takes the same arguments as `ggraph()` but returns a `layout_ggraph` object that can later be used in place of a graph structure in ggraph call: ```{r} layout <- create_layout(graph, layout = 'eigen') ggraph(layout) + geom_edge_link(aes(colour = factor(year))) + geom_node_point() ``` Examining the return of `create_layout()` we see that it is really just a `data.frame` of node positions and (possible) attributes. Furthermore the original graph object along with other relevant information is passed along as attributes: ```{r} head(layout) ``` ```{r} attributes(layout) ``` As it is just a `data.frame` it means that any standard `ggplot2` call will work by addressing the nodes. Still, use of the `geom_node_*()` family provided by `ggraph` is encouraged as it makes it explicit which part of the data structure is being worked with. ## Adding support for new data sources Out of the box `ggraph` supports `tbl_graph` objects from tidygraph natively. Any other type of object will be attempted to be coerced to a `tbl_graph` object automatically. Tidygraph provide conversions for most known graph structure in R so almost any data type is supported by ggraph by extension. If there is wish for support for additional classes this can be achieved by providing a `as_tbl_graph()` method for the class. If you do this, consider submitting the method to tidygraph so others can benefit from your work. ## Layouts abound There's a lot of different layouts in `ggraph` --- All layouts from the graphlayouts and igraph packages are available, an ggraph itself also provide some of the more specialised layouts itself. All in all ggraph provides well above 20 different layouts to choose from, far more than we can cover in this text. I urge you to explore the different layout types. Blindly running along with the default layouts is a sad but common mistake in network visualisation that can cloud or distort the insight the network might hold. If ggraph lacks the needed layout it is always possible to supply your own layout function that takes a tbl_graph object and returns a data.frame of node positions, or supply the positions directly by passing a matrix or data.frame to the layout argument. ### A note on circularity Some layouts can be shown effectively both in a standard Cartesian projection as well as in a polar projection. The standard approach in `ggplot2` has been to change the coordinate system with the addition of e.g. `coord_polar()`. This approach --- while consistent with the grammar --- is not optimal for `ggraph` as it does not allow layers to decide how to respond to circularity. The prime example of this is trying to draw straight lines in a plot using `coord_polar()`. Instead circularity is part of the layout specification and gets communicated to the layers with the `circular` column in the data, allowing each layer to respond appropriately. Sometimes standard and circular representations of the same layout get used so often that they get different names. In `ggraph` they'll have the same name and only differ in whether or not `circular` is set to `TRUE`: ```{r} # An arc diagram ggraph(graph, layout = 'linear') + geom_edge_arc(aes(colour = factor(year))) ``` ```{r} # A coord diagram ggraph(graph, layout = 'linear', circular = TRUE) + geom_edge_arc(aes(colour = factor(year))) + coord_fixed() ``` ```{r} graph <- tbl_graph(flare$vertices, flare$edges) # An icicle plot ggraph(graph, 'partition') + geom_node_tile(aes(fill = depth), size = 0.25) ``` ```{r} # A sunburst plot ggraph(graph, 'partition', circular = TRUE) + geom_node_arc_bar(aes(fill = depth), size = 0.25) + coord_fixed() ``` Not every layout has a meaningful circular representation in which cases the `circular` argument will be ignored. ### Node-edge diagram layouts Both `graphlayout` and `igraph` provides a range of different layout algorithms for classic node-edge diagrams (colloquially referred to as hairballs). Some of these are incredibly simple such as *randomly*, *grid*, *circle*, and *star*, while others tries to optimize the position of nodes based on different characteristics of the graph. There is no such thing as "the best layout algorithm" as algorithms have been optimized for different scenarios. Experiment with the choices at hand and remember to take the end result with a grain of salt, as it is just one of a range of possible "optimal node position" results. Below is a sample of some of the layouts available through `igraph` applied to the highschool graph. ```{r, fig.show='hold', results='hide'} graph <- as_tbl_graph(highschool) %>% mutate(degree = centrality_degree()) lapply(c('stress', 'fr', 'lgl', 'graphopt'), function(layout) { ggraph(graph, layout = layout) + geom_edge_link(aes(colour = factor(year)), show.legend = FALSE) + geom_node_point() + labs(caption = paste0('Layout: ', layout)) }) ``` ### Hive plots A hive plot, while still technically a node-edge diagram, is a bit different from the rest as it uses information pertaining to the nodes, rather than the connection information in the graph. This means that hive plots, to a certain extent are more interpretable as well as less vulnerable to small changes in the graph structure. They are less common though, so use will often require some additional explanation. ```{r} graph <- graph %>% mutate(friends = ifelse( centrality_degree(mode = 'in') < 5, 'few', ifelse(centrality_degree(mode = 'in') >= 15, 'many', 'medium') )) ggraph(graph, 'hive', axis = friends, sort.by = degree) + geom_edge_hive(aes(colour = factor(year))) + geom_axis_hive(aes(colour = friends), size = 2, label = FALSE) + coord_fixed() ``` ### Focal layouts Some layouts can put focus on a single node or a group of nodes by defining all other positions relative to that. An example of this is the `focus` layout, but the `centrality` layout is very akin to it: ```{r} ggraph(graph, 'focus', focus = node_is_center()) + ggforce::geom_circle(aes(x0 = 0, y0 = 0, r = r), data.frame(r = 1:5), colour = 'grey') + geom_edge_link() + geom_node_point() + coord_fixed() ``` ### Hierarchical layouts Trees and hierarchies are an important subset of graph structures, and `ggraph` provides a range of layouts optimized for their visual representation. Some of these use enclosure and position rather than edges to communicate relations (e.g. treemaps and circle packing). Still, these layouts can just as well be used for drawing edges if you wish to: ```{r} graph <- tbl_graph(flare$vertices, flare$edges) set.seed(1) ggraph(graph, 'circlepack', weight = size) + geom_node_circle(aes(fill = depth), size = 0.25, n = 50) + coord_fixed() ``` ```{r} set.seed(1) ggraph(graph, 'circlepack', weight = size) + geom_edge_link() + geom_node_point(aes(colour = depth)) + coord_fixed() ``` ```{r} ggraph(graph, 'treemap', weight = size) + geom_node_tile(aes(fill = depth), size = 0.25) ``` ```{r} ggraph(graph, 'treemap', weight = size) + geom_edge_link() + geom_node_point(aes(colour = depth)) ``` The most recognized tree plot is probably dendrograms though. If nothing else is stated the height of each node is calculated based on the distance to its farthest sibling (the tree layout, on the other hand, puts all nodes at a certain depth at the same level): ```{r} ggraph(graph, 'tree') + geom_edge_diagonal() ``` The height of each branch point can be set to a variable --- e.g. the height provided by hclust and dendrogram objects: ```{r} dendrogram <- hclust(dist(iris[, 1:4])) ggraph(dendrogram, 'dendrogram', height = height) + geom_edge_elbow() ``` Dendrograms are one of the layouts that are amenable for circular transformations, which can be effective in giving more space at the leafs of the tree at the expense of the space given to the root: ```{r} ggraph(dendrogram, 'dendrogram', circular = TRUE) + geom_edge_elbow() + coord_fixed() ``` A type of trees known especially in phylogeny is unrooted trees, where no node is considered the root. Often a dendrogram layout will not be faithful as it implicitly position a node at the root. To avoid that you can use the `unrooted` layout instead. ```{r} ggraph(dendrogram, 'unrooted') + geom_edge_link() ``` Often unrooted trees have a branch length attached - this can be passed to both the `dendrogram` and `unrooted` layout to determine the length of each edge. ### Matrix layouts Many node-edge diagram layouts suffer from poor scalability, where edges will eventually begin to overlap to the extend that the plot becomes unintellible. One way to combat this is by only plotting subsets of the larger plot, but another approach is to choose a layout that avoids overlapping edges altogether. The matrix layout places each node on a diagonal and draws edges by drawing points or tiles at the intersection of the vertical and horizontal position of its terminal nodes. Using matrix layouts efficiently requires that you begin to recognise the specific patterns that different network topologies gives rise to. Further, it is important to recognise the large effect that the node order has on the look of the matrix layout: ```{r} graph <- create_notable('zachary') ggraph(graph, 'matrix', sort.by = node_rank_leafsort()) + geom_edge_point(mirror = TRUE) + coord_fixed() ``` ```{r} ggraph(graph, 'matrix', sort.by = node_rank_spectral()) + geom_edge_point(mirror = TRUE) + coord_fixed() ``` ### Fabric layouts Another special layout that promises scalability is the biofabric layout (here named `fabric` to avoid it being ignored for non-biological networks). The fabric layout is special in that it positions nodes evenly spaced on the y-axis and then draws edges as vertical (and by extension, parallel) lines evenly separated as well, connecting the nodes. Nodes are drawn as horizontal lines spanning the extent of the edges that departs from it. As with matrix layouts the node ordering have a huge impact on the final look and interpreting the plot may take some getting used to. ```{r} ggraph(graph, 'fabric', sort.by = node_rank_fabric()) + geom_node_range(colour = 'grey') + geom_edge_span(end_shape = 'square') + coord_fixed() ``` Fabric layouts allow something called shadow edges where all edges are duplicated to make it easier to follow all edges originating from each node. ```{r} ggraph(graph, 'fabric', sort.by = node_rank_fabric(), shadow.edges =TRUE) + geom_node_range(colour = 'grey') + geom_edge_span(aes(filter = shadow_edge), colour ='lightblue' , end_shape = 'square') + geom_edge_span(aes(filter = !shadow_edge), end_shape = 'square') + coord_fixed() ``` ## Want more? Check out the other vignettes for more information on how to draw [nodes](Nodes.html) and [edges](Edges.html)... ggraph/inst/doc/tidygraph.Rmd0000644000176200001440000001331313527251470015712 0ustar liggesusers--- title: "tidygraph and ggraph" author: "Thomas Lin Pedersen" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{tidygraph and ggraph} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include=FALSE} library(ggraph) set_graph_style(family = 'Arial', size = 7, foreground = 'lightgrey', plot_margin = margin(0, 0, 0, 0)) ``` Following `ggraph` v2.0 the `tidygraph` package has been used as the central data structure. The integration goes beyond using it as a simple background engine and has deep implications for what you can do and how you can do it when plotting with `ggraph`. This vignette will go into the details of the `ggraph`/`tidygraph` relationship — buckle up... ## Supported data structures Prior to v2 `ggraph` had two main supported data structures, namely `dendrogram` and `igraph`. In addition `hclust` and `network` were supported by automatic conversion to `dendrogram` and `igraph` respectively. Each of the two data structures had their own layouts and under the hood two different set of functionality had to be maintained to extract nodes and edges etc. In v2 and going forward this has been simplified and `ggraph` now uses only `tbl_graph` as a graph representation. This does not mean that you're out of luck if you're not buying into the whole `tidygraph` idea. Every object supported by `tidygraph` is supported directly in `ggraph` by automatic conversion to `tbl_graph`. This means that `igraph`, `dendrogram`, `hclust`, and `network` is still supported in addition to `data.tree`, `phylo`, and `graph` as well as a number of `data.frame`, `matrix`, and `list` representations. The change has reduced internal code complexity quite a bit which will make it easier to provide new features in future. From a user point of view it has the benefit of simplifying the API in that `ggraph` doesn't really care what type of network object you pass in - every layout and geom just works with every data structure. Further, it simplifies how to add `ggraph` support to additional data structures: just write an `as_tbl_graph()` method for the class!. Due to the large support of classes and data structures in `tidygraph` this should relatively straightforward. If you're developer of a package that defines a custom network class simply export an `as_tbl_graph()` method for the class to gain native `ggraph` (and `tidygraph`) support, or add it directly to `tidygraph` through a [PR](https://github.com/thomasp85/tidygraph/pulls). This simplification for both me and the users have really been the motivation for the integration of `tidygraph` but as it were it has also allowed or instigated a number of cool new features that will be explored below. ## NSE in layout specifications In `ggraph` the initiation will need to specify a layout to use for the subsequent node and edge geoms. Many of these layouts use different node and edge variables in their calculations e.g. a node size or an edge weight. Prior to v2 these arguments would simply take a string naming the respective variable to use, but following the v2 update these arguments implement Non-Standard Evaluation (NSE) in a manner known from both `dplyr` and `ggplot2` where it is used inside `aes()` calls. Depending on whether the argument refers to a node or edge value the provided expression will be evaluated in the context of nodes or edges respectively. The bottomline is that given a network such as this: ```{r, message=FALSE} library(tidygraph) graph <- as_tbl_graph( data.frame( from = sample(5, 20, TRUE), to = sample(5, 20, TRUE), weight = runif(20) ) ) graph ``` Then, instead of writing: ```{r, eval=FALSE} ggraph(graph, layout = 'fr', weights = "weight") + geom_edge_link() + geom_node_point() ``` You would simply write: ```{r} ggraph(graph, layout = 'fr', weights = weight) + geom_edge_link() + geom_node_point() ``` This change means that it is much easier to experiment with modifications to node and edge parameters affecting layouts as it is not necessary to modify the underlying graph but only the plotting code, e.g.: ```{r} ggraph(graph, layout = 'fr', weights = log(weight)) + geom_edge_link() + geom_node_point() ``` ## Access to tidygraph algorithms in ggraph code The most important improvement resulting from the integration of `tidygraph` and `ggraph` is that `tidygraph` algorithms are now directly usable within `ggraph` calls. This means that it is no longer necessary to precompute and store derived node and edge variables on the graph in order to use them in a plot: ```{r} graph <- create_notable('zachary') ggraph(graph, layout = 'fr') + geom_edge_link() + geom_node_point(aes(size = centrality_pagerank())) + theme(legend.position = 'bottom') ``` here it is not necessary to first compute the pagerank centrality and store it as a node variable in order to plot it, and if you're interested in looking at one of the myriad of other centrality measures you simply change the plotting code. This feature makes it much easier and painfree to investigate the effect of different graph measures on your plots and is a huge benefit when iterating on your visualisation. Access to `tidygraph` is available within `ggraph()` and `aes()` calls, and within `facet` formulas. It is thus possible to use algorithms when specifying layouts, adding aesthetics to geoms and splitting into subplots - all areas were ease of iteration is vital: ```{r, message=FALSE} ggraph(graph, 'matrix', sort.by = node_rank_leafsort()) + geom_edge_point(aes(colour = centrality_edge_betweenness()), mirror = TRUE) + theme(legend.position = 'bottom') ``` ```{r} ggraph(graph, 'fr') + geom_edge_link() + geom_node_point() + facet_nodes(~ group_infomap()) ``` ggraph/inst/doc/Nodes.Rmd0000644000176200001440000001313213527251314014763 0ustar liggesusers--- title: "Nodes" author: "Thomas Lin Pedersen" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Nodes} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- Nodes in a network are the entities that are connected. Sometimes these are also referred to as vertices, but `ggraph` has opted for this nomenclature and uses it consistently. While the nodes in a graph are the abstract concepts of entities, and the layout is their physical placement, the node geoms are the visual manifestation of the entities. Conceptually one can simply think of it in terms of a scatter plot --- the layout provides the x and y coordinates, and these can be used to draw nodes in different ways in the plotting window. Actually, due to the design of `ggraph` the standard *scatterplot-like* geoms from `ggplot2` can be used directly for plotting nodes: ```{r, message=FALSE} library(ggraph) library(tidygraph) set_graph_style(plot_margin = margin(1,1,1,1)) gr <- as_tbl_graph(highschool) ggraph(gr, layout = 'kk') + geom_point(aes(x = x, y = y)) ``` The reason this works is that, as discussed in the Layout vignette, layouts return a `data.frame` of node positions and metadata and this is used as the default plot data: ```{r} head(create_layout(gr, layout = 'kk')) ``` ## `geom_node_*()` While usage of the default `ggplot2` is absolutely allowed, `ggraph` comes with its own set of node geoms. Many of these are direct translations of `ggplot2` own geoms like `geom_point()` so one could wonder why bother to use them. The first reason is to provide clear code. It is not apparent anywhere that the standard geoms are addressing the nodes and using `geom_node_*()` makes it clear that this layer will draw nodes. The second reason is that it will save typing. Since `ggraph` is in control of the shape of the input data through the layout calculations, it knows that *x* and *y* position is encoded in an `x` and `y` column. This means that `geom_node_*` can default the x and y aesthetics so there's no need to type them: ```{r} ggraph(gr, layout = 'kk') + geom_node_point() ``` sometimes there is a need for addressing the x and y aesthetics, which is still possible, for instance if a partition layout should be inverted: ```{r} gr <- tbl_graph(flare$vertices, flare$edges) ggraph(gr, layout = 'partition') + geom_node_tile(aes(y = -y, fill = depth)) ``` of course this could also be accomplished by reversing the y-axis using `scale_y_reverse()` so this is just to illustrate that the defaults are easily overwritten if needed. The third reason is for the added functionality. All `ggraph` geoms get a `filter` aesthetic that allows you to quickly filter the input data. The use of this can be illustrated when plotting a tree: ```{r} ggraph(gr, layout = 'dendrogram', circular = TRUE) + geom_edge_diagonal() + geom_node_point(aes(filter = leaf)) + coord_fixed() ``` In the above plot only the terminal nodes are drawn by filtering on the logical leaf column provided by the dendrogram layout. ## The different node geoms The usual suspects are of course provided in the form of `geom_node_point()` (showcased above), `geom_node_text()`, and `geom_node_label()`. These work as expected, taking in the usual aesthetics (plus *filter*). Only x and y are defaulted so everything else must be provided e.g. label which does not default to the `name` column like is done in `igraph`. One feature sets `geom_node_text()` and `geom_node_label()` apart from their `ggplot2` counterparts: both have a `repel` argument that, when set to `TRUE`, will use the repel functionality provided by the [ggrepel](https://CRAN.R-project.org/package=ggrepel) package to avoid overlapping text. There is also `geom_node_voronoi()` that plots nodes as cells from a voronoi tesselation. This is useful for e.g. showing dominance of certain node types in an area as overlapping is avoided: ```{r} graph <- create_notable('meredith') %>% mutate(group = sample(c('A', 'B'), n(), TRUE)) ggraph(graph, 'stress') + geom_node_voronoi(aes(fill = group), max.radius = 1) + geom_node_point() + geom_edge_link() + coord_fixed() ``` Apart from these geoms there's a set of geoms mainly useful for spatial node layouts such as treemaps, partition, circle packing, and fabric. `geom_node_tile()` and `geom_node_range()` are the `ggraph` counterpart to `ggplot2`s `geom_tile()` and `geom_linerange()` while `geom_node_circle()` and `geom_node_arc_bar()` maps to `ggforce`s `geom_circle()` and `geom_arc_bar()`. Collective for these is that the spatial dimensions of the geoms (e.g. radius, width, and height) are precalculated by their intended layouts and defaulted by the geoms: ```{r} ggraph(gr, layout = 'treemap', weight = size) + geom_node_tile(aes(fill = depth)) ``` All spatial node geoms will be center-based, meaning that the x and y value of the layout will refer to the center of the layout and not e.g. the bottom-left corner. This makes it easier to add labels to spatial layouts as well as using spatial layouts in a non-spatial way: ```{r} l <- ggraph(gr, layout = 'partition', circular = TRUE) l + geom_node_arc_bar(aes(fill = depth)) + coord_fixed() ``` ```{r} l + geom_edge_diagonal() + geom_node_point(aes(colour = depth)) + coord_fixed() ``` More node geoms are sure to appear in `ggraph` with time but they will generally be quite easily comprehensible due to their strong resemblance to the standard `ggplot2` geoms. After all it is just points on a plane... ## Want more? Check out the other vignettes for more information on how to specify [layouts](Layouts.html) and draw [edges](Edges.html)... ggraph/inst/doc/tidygraph.R0000644000176200001440000000321713617237522015375 0ustar liggesusers## ---- include=FALSE----------------------------------------------------------- library(ggraph) set_graph_style(family = 'Arial', size = 7, foreground = 'lightgrey', plot_margin = margin(0, 0, 0, 0)) ## ---- message=FALSE----------------------------------------------------------- library(tidygraph) graph <- as_tbl_graph( data.frame( from = sample(5, 20, TRUE), to = sample(5, 20, TRUE), weight = runif(20) ) ) graph ## ---- eval=FALSE-------------------------------------------------------------- # ggraph(graph, layout = 'fr', weights = "weight") + # geom_edge_link() + # geom_node_point() ## ----------------------------------------------------------------------------- ggraph(graph, layout = 'fr', weights = weight) + geom_edge_link() + geom_node_point() ## ----------------------------------------------------------------------------- ggraph(graph, layout = 'fr', weights = log(weight)) + geom_edge_link() + geom_node_point() ## ----------------------------------------------------------------------------- graph <- create_notable('zachary') ggraph(graph, layout = 'fr') + geom_edge_link() + geom_node_point(aes(size = centrality_pagerank())) + theme(legend.position = 'bottom') ## ---- message=FALSE----------------------------------------------------------- ggraph(graph, 'matrix', sort.by = node_rank_leafsort()) + geom_edge_point(aes(colour = centrality_edge_betweenness()), mirror = TRUE) + theme(legend.position = 'bottom') ## ----------------------------------------------------------------------------- ggraph(graph, 'fr') + geom_edge_link() + geom_node_point() + facet_nodes(~ group_infomap()) ggraph/inst/doc/tidygraph.html0000644000176200001440000050541713617237522016151 0ustar liggesusers tidygraph and ggraph

tidygraph and ggraph

Thomas Lin Pedersen

2020-02-07

Following ggraph v2.0 the tidygraph package has been used as the central data structure. The integration goes beyond using it as a simple background engine and has deep implications for what you can do and how you can do it when plotting with ggraph. This vignette will go into the details of the ggraph/tidygraph relationship — buckle up…

Supported data structures

Prior to v2 ggraph had two main supported data structures, namely dendrogram and igraph. In addition hclust and network were supported by automatic conversion to dendrogram and igraph respectively. Each of the two data structures had their own layouts and under the hood two different set of functionality had to be maintained to extract nodes and edges etc. In v2 and going forward this has been simplified and ggraph now uses only tbl_graph as a graph representation. This does not mean that you’re out of luck if you’re not buying into the whole tidygraph idea. Every object supported by tidygraph is supported directly in ggraph by automatic conversion to tbl_graph. This means that igraph, dendrogram, hclust, and network is still supported in addition to data.tree, phylo, and graph as well as a number of data.frame, matrix, and list representations.

The change has reduced internal code complexity quite a bit which will make it easier to provide new features in future. From a user point of view it has the benefit of simplifying the API in that ggraph doesn’t really care what type of network object you pass in - every layout and geom just works with every data structure. Further, it simplifies how to add ggraph support to additional data structures: just write an as_tbl_graph() method for the class!. Due to the large support of classes and data structures in tidygraph this should relatively straightforward. If you’re developer of a package that defines a custom network class simply export an as_tbl_graph() method for the class to gain native ggraph (and tidygraph) support, or add it directly to tidygraph through a PR.

This simplification for both me and the users have really been the motivation for the integration of tidygraph but as it were it has also allowed or instigated a number of cool new features that will be explored below.

NSE in layout specifications

In ggraph the initiation will need to specify a layout to use for the subsequent node and edge geoms. Many of these layouts use different node and edge variables in their calculations e.g. a node size or an edge weight. Prior to v2 these arguments would simply take a string naming the respective variable to use, but following the v2 update these arguments implement Non-Standard Evaluation (NSE) in a manner known from both dplyr and ggplot2 where it is used inside aes() calls. Depending on whether the argument refers to a node or edge value the provided expression will be evaluated in the context of nodes or edges respectively. The bottomline is that given a network such as this:

## # A tbl_graph: 5 nodes and 20 edges
## #
## # A directed multigraph with 1 component
## #
## # Node Data: 5 x 1 (active)
##   name 
##   <chr>
## 1 2    
## 2 3    
## 3 4    
## 4 1    
## 5 5    
## #
## # Edge Data: 20 x 3
##    from    to weight
##   <int> <int>  <dbl>
## 1     1     3  0.237
## 2     2     3  0.516
## 3     3     4  0.807
## # … with 17 more rows

Then, instead of writing:

You would simply write:

This change means that it is much easier to experiment with modifications to node and edge parameters affecting layouts as it is not necessary to modify the underlying graph but only the plotting code, e.g.:

Access to tidygraph algorithms in ggraph code

The most important improvement resulting from the integration of tidygraph and ggraph is that tidygraph algorithms are now directly usable within ggraph calls. This means that it is no longer necessary to precompute and store derived node and edge variables on the graph in order to use them in a plot:

here it is not necessary to first compute the pagerank centrality and store it as a node variable in order to plot it, and if you’re interested in looking at one of the myriad of other centrality measures you simply change the plotting code. This feature makes it much easier and painfree to investigate the effect of different graph measures on your plots and is a huge benefit when iterating on your visualisation.

Access to tidygraph is available within ggraph() and aes() calls, and within facet formulas. It is thus possible to use algorithms when specifying layouts, adding aesthetics to geoms and splitting into subplots - all areas were ease of iteration is vital:

ggraph/inst/doc/Nodes.R0000644000176200001440000000337413617237521014455 0ustar liggesusers## ---- message=FALSE----------------------------------------------------------- library(ggraph) library(tidygraph) set_graph_style(plot_margin = margin(1,1,1,1)) gr <- as_tbl_graph(highschool) ggraph(gr, layout = 'kk') + geom_point(aes(x = x, y = y)) ## ----------------------------------------------------------------------------- head(create_layout(gr, layout = 'kk')) ## ----------------------------------------------------------------------------- ggraph(gr, layout = 'kk') + geom_node_point() ## ----------------------------------------------------------------------------- gr <- tbl_graph(flare$vertices, flare$edges) ggraph(gr, layout = 'partition') + geom_node_tile(aes(y = -y, fill = depth)) ## ----------------------------------------------------------------------------- ggraph(gr, layout = 'dendrogram', circular = TRUE) + geom_edge_diagonal() + geom_node_point(aes(filter = leaf)) + coord_fixed() ## ----------------------------------------------------------------------------- graph <- create_notable('meredith') %>% mutate(group = sample(c('A', 'B'), n(), TRUE)) ggraph(graph, 'stress') + geom_node_voronoi(aes(fill = group), max.radius = 1) + geom_node_point() + geom_edge_link() + coord_fixed() ## ----------------------------------------------------------------------------- ggraph(gr, layout = 'treemap', weight = size) + geom_node_tile(aes(fill = depth)) ## ----------------------------------------------------------------------------- l <- ggraph(gr, layout = 'partition', circular = TRUE) l + geom_node_arc_bar(aes(fill = depth)) + coord_fixed() ## ----------------------------------------------------------------------------- l + geom_edge_diagonal() + geom_node_point(aes(colour = depth)) + coord_fixed() ggraph/inst/doc/Edges.html0000644000176200001440000575362413617237473015224 0ustar liggesusers Edges

Edges

Thomas Lin Pedersen

2020-02-07

If the natural ggplot2 equivalent to nodes is geom_point(), then surely the equivalent to edges must be geom_segment()? Well, sort of, but there’s a bit more to it than that.

One does not simply draw a line between two nodes

One does not simply draw a line between two nodes

While nodes are the sensible, mature, and predictably geoms, edges are the edgy (sorry), younger cousins that pushes the boundaries. To put it bluntly:

On the ggraph savannah you definitely want to be an edge!

Meet the geom_edge_*() family

While the introduction might feel a bit over-the-top it is entirely true. An edge is an abstract concept denoting a relationship between two entities. A straight line is simply just one of many ways this relationship can be visualised. As we saw when discussing nodes sometimes it is not drawn at all but impied using containment or position (treemap, circle packing, and partition layouts), but more often it is shown using a line of some sort. This use-case is handled by the large family of edge geoms provided in ggraph. Some of the edges are general while others are dedicated to specific layouts. Let’s creates some graphs for illustrative purposes first:

Fan

Sometimes the graph is not simple, i.e. it has multiple edges between the same nodes. Using links is a bad choice here because edges will overlap and the viewer will be unable to discover parallel edges. geom_edge_fan() got you covered here. If there are no parallel edges it behaves like geom_edge_link() and draws a straight line, but if parallel edges exists it will spread them out as arcs with different curvature. Parallel edges will be sorted by directionality prior to plotting so edges flowing in the same direction will be plotted together:

Parallel

An alternative to geom_edge_fan() is geom_edge_parallel(). It will draw edges as straight lines but in the case of multi-edges it will offset each edge a bit so they run parallel to each other. As with geom_edge_fan() the edges will be sorted by direction first. The offset is done at draw time and will thus remain constant even during resizing:

Loops

Loops cannot be shown with regular edges as they have no length. A dedicated geom_edge_loop() exists for these cases:

The direction, span, and strength of the loop can all be controlled, but in general loops will add a lot of visual clutter to your plot unless the graph is very simple.

Density

This one is definitely strange, and I’m unsure of it’s usefulness, but it is here and it deserves an introduction. Consider the case where it is of interest to see which types of edges dominates certain areas of the graph. You can colour the edges, but edges can tend to get overplotted, thus reducing readability. geom_edge_density() lets you add a shading to your plot based on the density of edges in a certain area:

Arcs

While some insists that curved edges should be used in standard “hairball” graph visualisations it really is a poor choice, as it increases overplotting and decreases interpretability for virtually no gain (unless complexity is your thing). That doesn’t mean arcs have no use in graph visualizations. Linear and circular layouts can benefit greatly from them and geom_edge_arc() is provided precisely for this scenario:

Arcs behave differently in circular layouts as they will always bend towards the center no matter the direction of the edge (the same thing can be achieved in a linear layout by setting fold = TRUE).

Elbow

Aah… The classic dendrogram with its right angle bends. Of course such visualizations are also supported with the geom_edge_elbow(). It goes without saying that this type of edge requires a layout that flows in a defined direction, such as a tree:

Diagonals

If right angles aren’t really your thing ggraph provides a smoother version in the form of geom_edge_diagonal(). This edge is a quadratic bezier with control points positioned at the same x-value as the terminal nodes and halfway in-between the nodes on the y-axis. The result is more organic than the elbows:

It tends to look a bit weird with hugely unbalanced trees so use with care…

Bends

An alternative to diagonals are bend edges which are elbow edges with a smoothed corner. It is implemented as a quadratic bezier with control points at the location of the expected elbow corner:

Hive

This is certainly a very specific type of edge, intended only for use with hive plots. It draws edges as quadratic beziers with control point positioned perpendicular to the axes of the hive layout:

Span

As with the hive edge the geom_edge_span() is made in particular for a specific layout - the fabric layout. It draws the edge as a vertical line connecting the horizontal node lines of the layout, potentially with a terminal shape.

Point and tile

It may seem weird to have edge geoms that doesn’t have any span, but the matrix layout calls for exactly that. The terminal nodes of the edge are determined by the vertical and horizontal position of the mark, and for that reason the geom doesn’t need any extend. The point and tile geoms serve the same purpose but are simply different geometry types:

The three types of edge geoms

Almost all edge geoms comes in three variants. The basic variant (no suffix) as well as the variant suffixed with 2 (e.g. geom_edge_link2()) calculates a number (n) of points along the edge and draws it as a path. The variant suffixed with 0 (e.g. geom_edge_diagonal0()) uses the build in grid grobs to draw the edges directly (in case of a diagonal it uses bezierGrob()). It might seem strange to have so many different implementations of the same geoms but there’s a reason to the insanity…

Base variant

The basic edge geom is drawn by calculating a number of points along the edge path and draw a line between these. This means that you’re in control of the detail level of curved edges and that all complex calculations happens up front. Generally you will see better performance using the base variant rather than the 0-variant that uses grid grobs, unless you set the number of points to calculate to something huge (50–100 is usually sufficient for a smooth look). Apart from better performance you also get a nice bonus (you actually get several, but only one is discussed here): The possibility of drawing a gradient along the edge. Each calculated point gets an index value between 0 and 1 that specifies how far along the edge it is positioned and this value can be used to e.g. map to an alpha level to show the direction of the edge:

2-variant

Like the base variant the 2-variant calculates points along the edge and draws a path along them. The difference here is that in this variant you can map node attributes to the edge and the aesthetics are then interpolated along the edge. This is easier to show than to explain:

There are considerably more computation going on than in the base variant so unless you need to interpolate values between the terminal nodes you should go with the base variant.

0-variant

This is, sadly, the boring one at the end. You don’t get the luxury of smooth gradients over the edge and often you have a considerably worse performance. What you gain though is tack sharp resolution in the curves so if this is of utmost importance you are covered by this variant.

Edge strength

Many of the edge geoms takes a strength argument that denotes their deviation from a straight line. Setting strength = 0 will always result in a straight line, while strength = 1 is the default look. Anything in between can be used to modify the look of the edge, while values outside that range will probably result in some weird looks. Some examples are shown below:

Decorating edges

An edge is so much more than a line… Well at least it is also potentially an arrow and a label. This section will go into how these can be added. To clearly see the effect here we will use a slightly simpler graph

Arrows

While we saw above that direction can be encoded as a gradient, the good old arrow is still available. As with the standard ggplot2 geoms an arrow can be added using the arrow argument:

I hope you think Ugh at the sight of this. The edges naturally extend to the node center and nodes are thus drawn on top of the arrow heads. There’s a solution to this in the form of the start_cap and end_cap aesthetics in the base and 2-variant edge geoms (sorry 0-variant). This can be used to start and stop the edge drawing at an absolute distance from the terminal nodes. Watch this:

Using the circle(), square(), ellipsis(), and rectangle() helpers it is possible to get a lot of control over how edges are capped at either end. This works for any edge, curved or not:

When plotting node labels you often want to avoid that incoming and outgoing edges overlaps with the labels. ggraph provides a helper that calculates the bounding rectangle of the labels and cap edges based on that:

The capping of edges is dynamic and responds to resizing of the plot so the absolute size of the cap areas are maintained at all time.

A quick note on directionality

In ggraph there is no such thing as an undirected graph. Every edge has a start and an end node. For undirected graphs the start and end of edges is arbitrary but still exists and it is thus possible to add arrowheads to undirected graphs as well. This should not be done of course, but this is the responsibility of the user as ggraph does not make any checks during rendering.

Labels

You would expect that edge labels would be their own geom(s), but ggraph departs from the stringent grammar interpretation here. This is because the label placement is dependent on the choice of edge. Because of this edge labeling is bundled with each edge geom (but not the 0-variant) through the label aesthetic

Usually you would like the labels to run along the edges, but providing a fixed angle will only work at a very specific aspect ratio. Instead ggraph offers to calculate the correct angle dynamically so the labels always runs along the edge. Furthermore it can offset the label by an absolute length:

ggraph offers a lot of additional customization of the edge labels but this shows the main features. As with arrowheads labels can severely clutter your visualization so it is only advisable on very simple graphs.

Connections

The estranged cousin of edges are connections. While edges show the relational nature of the nodes in the graph structure, connections connect nodes that are not connected in the graph. This is done by finding the shortest path between the two nodes. Currently the only connection geom available is geom_conn_bundle() that implements the hierarchical edge bundling technique:

The connection concept is underutilized at the moment but I expect to add more support for this in coming releases.

Want more?

Check out the other vignettes for more information on how to specify layouts and draw nodes

ggraph/inst/doc/Nodes.html0000644000176200001440000113510013617237521015212 0ustar liggesusers Nodes

Nodes

Thomas Lin Pedersen

2020-02-07

Nodes in a network are the entities that are connected. Sometimes these are also referred to as vertices, but ggraph has opted for this nomenclature and uses it consistently. While the nodes in a graph are the abstract concepts of entities, and the layout is their physical placement, the node geoms are the visual manifestation of the entities. Conceptually one can simply think of it in terms of a scatter plot — the layout provides the x and y coordinates, and these can be used to draw nodes in different ways in the plotting window. Actually, due to the design of ggraph the standard scatterplot-like geoms from ggplot2 can be used directly for plotting nodes:

The reason this works is that, as discussed in the Layout vignette, layouts return a data.frame of node positions and metadata and this is used as the default plot data:

##            x          y name .ggraph.orig_index circular .ggraph.index
## 1  0.2782438  2.4944195    1                  1    FALSE             1
## 2  0.1365268  3.1063039    2                  2    FALSE             2
## 3  0.9329938  3.2168940    3                  3    FALSE             3
## 4 -2.5457734 -1.5139415    4                  4    FALSE             4
## 5 -2.8447634 -0.2267242    5                  5    FALSE             5
## 6 -2.9897376  1.7369304    6                  6    FALSE             6

geom_node_*()

While usage of the default ggplot2 is absolutely allowed, ggraph comes with its own set of node geoms. Many of these are direct translations of ggplot2 own geoms like geom_point() so one could wonder why bother to use them.

The first reason is to provide clear code. It is not apparent anywhere that the standard geoms are addressing the nodes and using geom_node_*() makes it clear that this layer will draw nodes.

The second reason is that it will save typing. Since ggraph is in control of the shape of the input data through the layout calculations, it knows that x and y position is encoded in an x and y column. This means that geom_node_* can default the x and y aesthetics so there’s no need to type them:

sometimes there is a need for addressing the x and y aesthetics, which is still possible, for instance if a partition layout should be inverted:

of course this could also be accomplished by reversing the y-axis using scale_y_reverse() so this is just to illustrate that the defaults are easily overwritten if needed.

The third reason is for the added functionality. All ggraph geoms get a filter aesthetic that allows you to quickly filter the input data. The use of this can be illustrated when plotting a tree:

In the above plot only the terminal nodes are drawn by filtering on the logical leaf column provided by the dendrogram layout.

The different node geoms

The usual suspects are of course provided in the form of geom_node_point() (showcased above), geom_node_text(), and geom_node_label(). These work as expected, taking in the usual aesthetics (plus filter). Only x and y are defaulted so everything else must be provided e.g. label which does not default to the name column like is done in igraph. One feature sets geom_node_text() and geom_node_label() apart from their ggplot2 counterparts: both have a repel argument that, when set to TRUE, will use the repel functionality provided by the ggrepel package to avoid overlapping text. There is also geom_node_voronoi() that plots nodes as cells from a voronoi tesselation. This is useful for e.g. showing dominance of certain node types in an area as overlapping is avoided:

Apart from these geoms there’s a set of geoms mainly useful for spatial node layouts such as treemaps, partition, circle packing, and fabric. geom_node_tile() and geom_node_range() are the ggraph counterpart to ggplot2s geom_tile() and geom_linerange() while geom_node_circle() and geom_node_arc_bar() maps to ggforces geom_circle() and geom_arc_bar(). Collective for these is that the spatial dimensions of the geoms (e.g. radius, width, and height) are precalculated by their intended layouts and defaulted by the geoms:

All spatial node geoms will be center-based, meaning that the x and y value of the layout will refer to the center of the layout and not e.g. the bottom-left corner. This makes it easier to add labels to spatial layouts as well as using spatial layouts in a non-spatial way:

More node geoms are sure to appear in ggraph with time but they will generally be quite easily comprehensible due to their strong resemblance to the standard ggplot2 geoms. After all it is just points on a plane…

Want more?

Check out the other vignettes for more information on how to specify layouts and draw edges