iplotx 1.2.1__tar.gz → 1.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (275) hide show
  1. {iplotx-1.2.1 → iplotx-1.4.0}/PKG-INFO +11 -4
  2. {iplotx-1.2.1 → iplotx-1.4.0}/README.md +10 -3
  3. {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/api/complete_style_specification.md +8 -3
  4. {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/code_of_conduct.rst +1 -1
  5. {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/sg_execution_times.rst +9 -3
  6. iplotx-1.4.0/gallery/basic/plot_arcs.py +42 -0
  7. iplotx-1.4.0/gallery/tree/plot_equalangle.py +35 -0
  8. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/edge/__init__.py +7 -0
  9. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/edge/geometry.py +113 -0
  10. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/providers/tree/biopython.py +2 -1
  11. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/providers/tree/cogent3.py +4 -1
  12. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/providers/tree/dendropy.py +10 -1
  13. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/providers/tree/ete4.py +2 -1
  14. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/providers/tree/simple.py +11 -1
  15. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/providers/tree/skbio.py +4 -1
  16. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/typing.py +49 -7
  17. iplotx-1.4.0/iplotx/layout/__init__.py +9 -0
  18. iplotx-1.4.0/iplotx/layout/tree/__init__.py +72 -0
  19. iplotx-1.2.1/iplotx/layout.py → iplotx-1.4.0/iplotx/layout/tree/rooted.py +3 -45
  20. iplotx-1.4.0/iplotx/layout/tree/unrooted.py +444 -0
  21. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/style/leaf_info.py +1 -0
  22. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/tree/__init__.py +5 -2
  23. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/version.py +1 -1
  24. iplotx-1.4.0/tests/baseline_images/test_biopython/leafedges.png +0 -0
  25. iplotx-1.4.0/tests/baseline_images/test_biopython/tree_radial.png +0 -0
  26. iplotx-1.4.0/tests/baseline_images/test_cogent3/tree_radial.png +0 -0
  27. iplotx-1.4.0/tests/baseline_images/test_ete4/equalangle_layout.png +0 -0
  28. iplotx-1.4.0/tests/baseline_images/test_ete4/tree_radial.png +0 -0
  29. iplotx-1.4.0/tests/baseline_images/test_igraph/arcs.png +0 -0
  30. iplotx-1.4.0/tests/baseline_images/test_igraph/large_arcs.png +0 -0
  31. iplotx-1.4.0/tests/baseline_images/test_skbio/tree_radial.png +0 -0
  32. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_ete4.py +22 -4
  33. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_igraph.py +29 -0
  34. iplotx-1.2.1/tests/baseline_images/test_biopython/leafedges.png +0 -0
  35. iplotx-1.2.1/tests/baseline_images/test_biopython/tree_radial.png +0 -0
  36. iplotx-1.2.1/tests/baseline_images/test_cogent3/tree_radial.png +0 -0
  37. iplotx-1.2.1/tests/baseline_images/test_ete4/tree_radial.png +0 -0
  38. iplotx-1.2.1/tests/baseline_images/test_skbio/tree_radial.png +0 -0
  39. {iplotx-1.2.1 → iplotx-1.4.0}/.github/workflows/publish.yml +0 -0
  40. {iplotx-1.2.1 → iplotx-1.4.0}/.github/workflows/test.yml +0 -0
  41. {iplotx-1.2.1 → iplotx-1.4.0}/.gitignore +0 -0
  42. {iplotx-1.2.1 → iplotx-1.4.0}/.pre-commit-config.yaml +0 -0
  43. {iplotx-1.2.1 → iplotx-1.4.0}/.readthedocs.yaml +0 -0
  44. {iplotx-1.2.1 → iplotx-1.4.0}/LICENSE +0 -0
  45. {iplotx-1.2.1 → iplotx-1.4.0}/MANIFEST.in +0 -0
  46. {iplotx-1.2.1 → iplotx-1.4.0}/assets/pylint.svg +0 -0
  47. {iplotx-1.2.1 → iplotx-1.4.0}/docs/Makefile +0 -0
  48. {iplotx-1.2.1 → iplotx-1.4.0}/docs/make.bat +0 -0
  49. {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/_static/banner.png +0 -0
  50. {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/_static/custom-icons.js +0 -0
  51. {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/_static/custom.css +0 -0
  52. {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/_static/graph_basic.png +0 -0
  53. {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/_templates/layout.html +0 -0
  54. {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/api/artists.md +0 -0
  55. {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/api/plotting.md +0 -0
  56. {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/api/providers.md +0 -0
  57. {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/api/style.md +0 -0
  58. {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/api.md +0 -0
  59. {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/conf.py +0 -0
  60. {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/images/sphx_glr_plot_basic_001.png +0 -0
  61. {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/images/thumb/sphx_glr_plot_basic_thumb.png +0 -0
  62. {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/index.md +0 -0
  63. {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/installing.md +0 -0
  64. {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/providers.md +0 -0
  65. {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/style.md +0 -0
  66. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/GALLERY_HEADER.rst +0 -0
  67. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/basic/GALLERY_HEADER.rst +0 -0
  68. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/basic/plot_3d.py +0 -0
  69. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/basic/plot_basic.py +0 -0
  70. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/basic/plot_big_curves.py +0 -0
  71. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/basic/plot_dag.py +0 -0
  72. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/basic/plot_directed.py +0 -0
  73. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/basic/plot_grouping.py +0 -0
  74. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/basic/plot_house.py +0 -0
  75. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/basic/plot_loops.py +0 -0
  76. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/basic/plot_simple_path.py +0 -0
  77. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/GALLERY_HEADER.rst +0 -0
  78. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/data/80201010000000001.mst +0 -0
  79. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/data/GN-tree.json +0 -0
  80. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/data/breast_cancer_string_interactions_short.tsv +0 -0
  81. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/data/breast_cancer_string_network_coordinates.tsv +0 -0
  82. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/data/cell_cycle_arrest_string_interactions_short.tsv +0 -0
  83. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/data/cell_cycle_arrest_string_network_coordinates.tsv +0 -0
  84. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/data/fevo-08-588430_DataSheet1_S1.csv +0 -0
  85. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/plot_animal_phylogeny.py +0 -0
  86. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/plot_antibody_clone.py +0 -0
  87. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/plot_breast_cancer_ppi.py +0 -0
  88. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/plot_cell_cycle_arrest.py +0 -0
  89. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/plot_food_network.py +0 -0
  90. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/plot_foraging_table.py +0 -0
  91. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/plot_pollinators.py +0 -0
  92. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/plot_ppi.py +0 -0
  93. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/plot_tca_cycle.py +0 -0
  94. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/GALLERY_HEADER.rst +0 -0
  95. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/data/chess_masters_WCC.pgn.bz2 +0 -0
  96. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/data/knuth_miles.txt.gz +0 -0
  97. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_arrowlawn.py +0 -0
  98. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_chess_masters.py +0 -0
  99. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_cliques.py +0 -0
  100. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_cluster_layout.py +0 -0
  101. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_company_structure.py +0 -0
  102. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_complex.py +0 -0
  103. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_financial_network.py +0 -0
  104. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_knuth_miles.py +0 -0
  105. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_labels_and_colors.py +0 -0
  106. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_max_bipartite_matching.py +0 -0
  107. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_minimum_spanning_trees.py +0 -0
  108. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_multipartite_layout.py +0 -0
  109. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_parallel_igraph_networkx.py +0 -0
  110. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_redblack.py +0 -0
  111. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_shortest_path.py +0 -0
  112. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_simple_networkx.py +0 -0
  113. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_social_network_circles.py +0 -0
  114. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_traveling_salesman.py +0 -0
  115. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_with_colorbar.py +0 -0
  116. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/other/GALLERY_HEADER.rst +0 -0
  117. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/other/plot_animation.py +0 -0
  118. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/other/plot_edit_artists.py +0 -0
  119. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/other/plot_feedbacks.py +0 -0
  120. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/other/plot_graph.py +0 -0
  121. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/other/plot_mouse_hover.py +0 -0
  122. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/other/plot_train.py +0 -0
  123. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/GALLERY_HEADER.rst +0 -0
  124. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_arrows.py +0 -0
  125. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_depthshade.py +0 -0
  126. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_edgepadding.py +0 -0
  127. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_elements.py +0 -0
  128. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_four_grids.py +0 -0
  129. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_halfarrows.py +0 -0
  130. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_multistyle.py +0 -0
  131. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_ports.py +0 -0
  132. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_style.py +0 -0
  133. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_tension.py +0 -0
  134. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_vertexmarkers.py +0 -0
  135. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_voronoi.py +0 -0
  136. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_waypoints.py +0 -0
  137. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/GALLERY_HEADER.rst +0 -0
  138. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/data/tree-with-support.json +0 -0
  139. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_angular_waypoints.py +0 -0
  140. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_biopython_tree.py +0 -0
  141. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_cladeedges.py +0 -0
  142. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_cogent3_layouts.py +0 -0
  143. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_cogent3_tree.py +0 -0
  144. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_dendropy.py +0 -0
  145. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_double_tree.py +0 -0
  146. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_elements_tree.py +0 -0
  147. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_ete4.py +0 -0
  148. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_leafedges.py +0 -0
  149. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_leafedges_and_cascades.py +0 -0
  150. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_scalebar.py +0 -0
  151. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_skbio_tree.py +0 -0
  152. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_split_edges.py +0 -0
  153. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_style_tree.py +0 -0
  154. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_support.py +0 -0
  155. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_tree_node_background.py +0 -0
  156. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_tree_style_clades.py +0 -0
  157. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_trees_of_trees.py +0 -0
  158. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/zero_dependency/GALLERY_HEADER.rst +0 -0
  159. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/zero_dependency/plot_simplenetworkdataprovider.py +0 -0
  160. {iplotx-1.2.1 → iplotx-1.4.0}/gallery/zero_dependency/plot_simpletreedataprovider.py +0 -0
  161. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/__init__.py +0 -0
  162. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/art3d/edge/__init__.py +0 -0
  163. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/art3d/edge/arrow.py +0 -0
  164. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/art3d/edge/geometry.py +0 -0
  165. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/art3d/vertex.py +0 -0
  166. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/artists.py +0 -0
  167. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/edge/arrow.py +0 -0
  168. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/edge/leaf.py +0 -0
  169. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/edge/ports.py +0 -0
  170. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/__init__.py +0 -0
  171. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/heuristics.py +0 -0
  172. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/providers/network/graph_tool.py +0 -0
  173. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/providers/network/igraph.py +0 -0
  174. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/providers/network/networkx.py +0 -0
  175. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/providers/network/simple.py +0 -0
  176. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/label.py +0 -0
  177. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/network/__init__.py +0 -0
  178. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/network/groups.py +0 -0
  179. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/plotting.py +0 -0
  180. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/style/__init__.py +0 -0
  181. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/style/library.py +0 -0
  182. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/tree/cascades.py +0 -0
  183. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/tree/scalebar.py +0 -0
  184. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/typing.py +0 -0
  185. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/utils/geometry.py +0 -0
  186. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/utils/internal.py +0 -0
  187. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/utils/matplotlib.py +0 -0
  188. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/utils/style.py +0 -0
  189. {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/vertex.py +0 -0
  190. {iplotx-1.2.1 → iplotx-1.4.0}/pyproject.toml +0 -0
  191. {iplotx-1.2.1 → iplotx-1.4.0}/scripts/copy_github_release_into_version.sh +0 -0
  192. {iplotx-1.2.1 → iplotx-1.4.0}/scripts/make_banner.py +0 -0
  193. {iplotx-1.2.1 → iplotx-1.4.0}/scripts/update_pylint_badge.sh +0 -0
  194. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_biopython/cascades.png +0 -0
  195. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_biopython/directed_child.png +0 -0
  196. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_biopython/leaf_labels.png +0 -0
  197. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_biopython/leaf_labels_hmargin.png +0 -0
  198. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_biopython/show_support.png +0 -0
  199. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_biopython/tree_basic.png +0 -0
  200. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_cogent3/leaf_labels.png +0 -0
  201. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_cogent3/leaf_labels_hmargin.png +0 -0
  202. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_cogent3/tree_basic.png +0 -0
  203. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/cascades.png +0 -0
  204. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/directed_child.png +0 -0
  205. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/leaf_labels.png +0 -0
  206. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/leaf_labels_hmargin.png +0 -0
  207. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/leafedges.png +0 -0
  208. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/tree_basic.png +0 -0
  209. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/tree_radial.png +0 -0
  210. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_doubletree/tree_gap.png +0 -0
  211. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_doubletree/tree_nogap.png +0 -0
  212. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_ete4/leaf_labels.png +0 -0
  213. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_ete4/leaf_labels_hmargin.png +0 -0
  214. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_ete4/split_edges.png +0 -0
  215. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_ete4/tree_basic.png +0 -0
  216. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_graph_tool/graph_basic.png +0 -0
  217. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_graph_tool/graph_directed.png +0 -0
  218. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/clustering_directed.png +0 -0
  219. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/clustering_directed_large.png +0 -0
  220. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_basic.png +0 -0
  221. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_directed.png +0 -0
  222. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_directed_curved_loops.png +0 -0
  223. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_edit_children.png +0 -0
  224. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_labels.png +0 -0
  225. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_layout_attribute.png +0 -0
  226. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_null.png +0 -0
  227. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_squares_directed.png +0 -0
  228. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_vertexsize.png +0 -0
  229. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_with_curved_edges.png +0 -0
  230. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/igraph_layout_object.png +0 -0
  231. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/multigraph_with_curved_edges_undirected.png +0 -0
  232. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph_3d/directed.png +0 -0
  233. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph_3d/undirected.png +0 -0
  234. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph_3d/vertex_labels.png +0 -0
  235. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_networkx/cluster-layout.png +0 -0
  236. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_networkx/complex.png +0 -0
  237. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_networkx/complex_rotatelabels.png +0 -0
  238. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_networkx/directed_graph.png +0 -0
  239. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_networkx/directed_graph_with_colorbar.png +0 -0
  240. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_networkx/empty_graph.png +0 -0
  241. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_networkx/flat_style.png +0 -0
  242. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_networkx/house_with_colors.png +0 -0
  243. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_networkx/labels_and_colors.png +0 -0
  244. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_networkx/shortest_path.png +0 -0
  245. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_networkx/simple_graph.png +0 -0
  246. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_simple_network_provider/graph_basic.png +0 -0
  247. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_simple_network_provider/graph_directed.png +0 -0
  248. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_simple_network_provider/graph_labels.png +0 -0
  249. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_skbio/leaf_labels.png +0 -0
  250. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_skbio/leaf_labels_hmargin.png +0 -0
  251. {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_skbio/tree_basic.png +0 -0
  252. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_arrows.py +0 -0
  253. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_biopython.py +0 -0
  254. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_cascades.py +0 -0
  255. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_cogent3.py +0 -0
  256. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_dendropy.py +0 -0
  257. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_doubletree.py +0 -0
  258. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_edge.py +0 -0
  259. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_edge_geometry.py +0 -0
  260. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_geometry.py +0 -0
  261. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_graph_tool.py +0 -0
  262. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_heuristics.py +0 -0
  263. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_igraph_3d.py +0 -0
  264. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_ingest_protocols.py +0 -0
  265. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_matplotlib_utils.py +0 -0
  266. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_network_hotload.py +0 -0
  267. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_networkx.py +0 -0
  268. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_ports.py +0 -0
  269. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_simple_network_provider.py +0 -0
  270. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_simple_tree_provider.py +0 -0
  271. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_skbio.py +0 -0
  272. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_style.py +0 -0
  273. {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_vertex.py +0 -0
  274. {iplotx-1.2.1 → iplotx-1.4.0}/tests/utils.py +0 -0
  275. {iplotx-1.2.1 → iplotx-1.4.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iplotx
3
- Version: 1.2.1
3
+ Version: 1.4.0
4
4
  Summary: Plot networkx from igraph and networkx.
5
5
  Project-URL: Homepage, https://github.com/fabilab/iplotx
6
6
  Project-URL: Documentation, https://readthedocs.org/iplotx
@@ -36,9 +36,9 @@ Provides-Extra: networkx
36
36
  Requires-Dist: networkx>=2.0.0; extra == 'networkx'
37
37
  Description-Content-Type: text/markdown
38
38
 
39
- ![Github Actions](https://github.com/fabilab/iplotx/actions/workflows/test.yml/badge.svg)
40
- ![PyPI - Version](https://img.shields.io/pypi/v/iplotx)
41
- ![RTD](https://readthedocs.org/projects/iplotx/badge/?version=latest)
39
+ [![Github Actions](https://github.com/fabilab/iplotx/actions/workflows/test.yml/badge.svg)](https://github.com/fabilab/iplotx/actions/workflows/test.yml)
40
+ [![PyPI - Version](https://img.shields.io/pypi/v/iplotx)](https://pypi.org/project/iplotx/)
41
+ [![RTD](https://readthedocs.org/projects/iplotx/badge/?version=latest)](https://iplotx.readthedocs.io/en/latest/)
42
42
  [![Coverage Status](https://coveralls.io/repos/github/fabilab/iplotx/badge.svg?branch=main)](https://coveralls.io/github/fabilab/iplotx?branch=main)
43
43
  ![pylint](assets/pylint.svg)
44
44
  [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.16599333.svg)](https://doi.org/10.5281/zenodo.16599333)
@@ -89,6 +89,13 @@ See [readthedocs](https://iplotx.readthedocs.io/en/latest/) for the full documen
89
89
  ## Gallery
90
90
  See [gallery](https://iplotx.readthedocs.io/en/latest/gallery/index.html).
91
91
 
92
+ ## Citation
93
+ If you use `iplotx` for publication figures, please cite the [zenodo preprint](https://doi.org/10.5281/zenodo.16599333):
94
+
95
+ ```
96
+ F. Zanini. (2025). Unified network visualisation in Python. Zenodo [PREPRINT]. https://doi.org/10.5281/zenodo.16599333
97
+ ```
98
+
92
99
  ## Features
93
100
  - Plot networks from multiple libraries including networkx, igraph and graph-tool, using Matplotlib. ✅
94
101
  - Plot trees from multiple libraries such as cogent3, ETE4, skbio, biopython, and dendropy. ✅
@@ -1,6 +1,6 @@
1
- ![Github Actions](https://github.com/fabilab/iplotx/actions/workflows/test.yml/badge.svg)
2
- ![PyPI - Version](https://img.shields.io/pypi/v/iplotx)
3
- ![RTD](https://readthedocs.org/projects/iplotx/badge/?version=latest)
1
+ [![Github Actions](https://github.com/fabilab/iplotx/actions/workflows/test.yml/badge.svg)](https://github.com/fabilab/iplotx/actions/workflows/test.yml)
2
+ [![PyPI - Version](https://img.shields.io/pypi/v/iplotx)](https://pypi.org/project/iplotx/)
3
+ [![RTD](https://readthedocs.org/projects/iplotx/badge/?version=latest)](https://iplotx.readthedocs.io/en/latest/)
4
4
  [![Coverage Status](https://coveralls.io/repos/github/fabilab/iplotx/badge.svg?branch=main)](https://coveralls.io/github/fabilab/iplotx?branch=main)
5
5
  ![pylint](assets/pylint.svg)
6
6
  [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.16599333.svg)](https://doi.org/10.5281/zenodo.16599333)
@@ -51,6 +51,13 @@ See [readthedocs](https://iplotx.readthedocs.io/en/latest/) for the full documen
51
51
  ## Gallery
52
52
  See [gallery](https://iplotx.readthedocs.io/en/latest/gallery/index.html).
53
53
 
54
+ ## Citation
55
+ If you use `iplotx` for publication figures, please cite the [zenodo preprint](https://doi.org/10.5281/zenodo.16599333):
56
+
57
+ ```
58
+ F. Zanini. (2025). Unified network visualisation in Python. Zenodo [PREPRINT]. https://doi.org/10.5281/zenodo.16599333
59
+ ```
60
+
54
61
  ## Features
55
62
  - Plot networks from multiple libraries including networkx, igraph and graph-tool, using Matplotlib. ✅
56
63
  - Plot trees from multiple libraries such as cogent3, ETE4, skbio, biopython, and dendropy. ✅
@@ -102,9 +102,14 @@
102
102
 
103
103
  "curved": bool, # Whether the edge is curved (True) or straight (False)
104
104
 
105
- # Tension for curved edges (0.0 for straight, higher values position the
106
- # Bezier control points further away from the nodes, creating more wiggly lines)
105
+ # Tension for curved edges and arcs.
106
+ # For Bezier (curved) edges, 0.0 means straight, higher values position the
107
+ # Bezier control points further away from the nodes, creating more wiggly lines.
107
108
  # Negative values bend the curve on the other side of the straight line.
109
+ # For arc edges, 0.0 means straight, higher values draw larger arcs. 1.0
110
+ # means a semicircle, and numbers above 5 create very large arcs, almost full
111
+ # circles. The exact definition of tension for arcs is the tangent of a
112
+ # quarter of the angle spanned by the arc.
108
113
  "tension": float,
109
114
 
110
115
  # Tension for self-loops (higher values create more bigger loops).
@@ -281,7 +286,7 @@
281
286
  # For horizontal and vertical layouts:
282
287
  # start: Starting position in data units (tuple of two floats)
283
288
  # span: Breadth in data units (float)
284
- "start": float | tuple[float, float],
289
+ "start": float | tuple[float margins=0.2,
285
290
  "span": float,
286
291
  },
287
292
  ############################################################################
@@ -1,6 +1,6 @@
1
1
  .. _scipy-coc:
2
2
 
3
- ilpotx Code of Conduct
3
+ iplotx Code of Conduct
4
4
  ======================
5
5
 
6
6
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  Computation times
8
8
  =================
9
- **00:00.565** total execution time for 77 files **from all galleries**:
9
+ **00:00.117** total execution time for 79 files **from all galleries**:
10
10
 
11
11
  .. container::
12
12
 
@@ -32,8 +32,8 @@ Computation times
32
32
  * - Example
33
33
  - Time
34
34
  - Mem (MB)
35
- * - :ref:`sphx_glr_gallery_tree_plot_double_tree.py` (``../../gallery/tree/plot_double_tree.py``)
36
- - 00:00.565
35
+ * - :ref:`sphx_glr_gallery_basic_plot_arcs.py` (``../../gallery/basic/plot_arcs.py``)
36
+ - 00:00.117
37
37
  - 0.0
38
38
  * - :ref:`sphx_glr_gallery_basic_plot_3d.py` (``../../gallery/basic/plot_3d.py``)
39
39
  - 00:00.000
@@ -221,9 +221,15 @@ Computation times
221
221
  * - :ref:`sphx_glr_gallery_tree_plot_dendropy.py` (``../../gallery/tree/plot_dendropy.py``)
222
222
  - 00:00.000
223
223
  - 0.0
224
+ * - :ref:`sphx_glr_gallery_tree_plot_double_tree.py` (``../../gallery/tree/plot_double_tree.py``)
225
+ - 00:00.000
226
+ - 0.0
224
227
  * - :ref:`sphx_glr_gallery_tree_plot_elements_tree.py` (``../../gallery/tree/plot_elements_tree.py``)
225
228
  - 00:00.000
226
229
  - 0.0
230
+ * - :ref:`sphx_glr_gallery_tree_plot_equalangle.py` (``../../gallery/tree/plot_equalangle.py``)
231
+ - 00:00.000
232
+ - 0.0
227
233
  * - :ref:`sphx_glr_gallery_tree_plot_ete4.py` (``../../gallery/tree/plot_ete4.py``)
228
234
  - 00:00.000
229
235
  - 0.0
@@ -0,0 +1,42 @@
1
+ """
2
+ Arcs
3
+ ====
4
+
5
+ This example showcases the ability of `iplotx` to curve edges into arcs, using tension to define the angular span of the arc.
6
+ """
7
+
8
+ import igraph as ig
9
+ import matplotlib.pyplot as plt
10
+ import iplotx as ipx
11
+
12
+
13
+ g = ig.Graph.Full(4)
14
+ layout = [[0, 0], [1, 0], [2, 0], [3, 0]]
15
+
16
+ fig, ax = plt.subplots()
17
+ ipx.plot(
18
+ g,
19
+ layout=layout,
20
+ ax=ax,
21
+ edge_arc=True,
22
+ edge_tension=-1,
23
+ )
24
+
25
+ # %%
26
+ # ..tip::
27
+ # Specifically, tension is defined as the tangent of the quarter of the angle spanned by the arc. A tension of 0 gives
28
+ # a straight line, a tension of 1 gives a semicircle, and higher tensions give increasingly larger arcs. A tension of
29
+ # infinity would give you a straight line away from the target vertex, but is not accepted by the library. Negative
30
+ # tensions result in arcs curving in the opposite direction.
31
+
32
+ fig, axs = plt.subplots(2, 2, figsize=(8, 8))
33
+ for tension, ax in zip([0.5, 2.5, -0.5, -2.5], axs.ravel()):
34
+ ipx.plot(
35
+ g,
36
+ layout=layout,
37
+ ax=ax,
38
+ edge_arc=True,
39
+ edge_tension=tension,
40
+ margins=0.15,
41
+ title=f"Edge tension = {tension:.2f}",
42
+ )
@@ -0,0 +1,35 @@
1
+ """
2
+ Equal angle layout
3
+ ==================
4
+
5
+ This example showcases the "equal angle" layout. This layout is inspired by the `ggtree <https://yulab-smu.top/treedata-book/chapter4.html>`_ layout with the same name, which originally comes from Joseph Felsenstein's book "Inferring Phylogenies".
6
+ """
7
+
8
+ from cogent3.phylo import nj
9
+ import numpy as np
10
+ import iplotx as ipx
11
+ import matplotlib.pyplot as plt
12
+
13
+ nleaves = 14
14
+ distance_dict = {}
15
+ for i in range(nleaves):
16
+ for j in range(i):
17
+ distance_dict[(str(i), str(j))] = np.random.rand()
18
+ tree = nj.nj(distance_dict)
19
+
20
+ ipx.plotting.tree(
21
+ tree,
22
+ layout="equalangle",
23
+ )
24
+
25
+
26
+ # %%
27
+ # The "equal daylight" layout is an adjustment of the equal angle layout that attempts to spread out leaves more evenly
28
+ # for imbalanced trees. ``iplotx`` has an experimental implementation of this layout.
29
+ #
30
+ # .. warning:: "Experimental" means you can use it but the API and resulting layout may change in future releases.
31
+
32
+ ipx.plotting.tree(
33
+ tree,
34
+ layout="daylight",
35
+ )
@@ -370,6 +370,9 @@ class EdgeCollection(mpl.collections.PatchCollection):
370
370
  if edge_stylei.get("curved", False):
371
371
  tension = edge_stylei.get("tension", 5)
372
372
  ports = edge_stylei.get("ports", (None, None))
373
+ elif edge_stylei.get("arc", False):
374
+ tension = edge_stylei.get("tension", 1)
375
+ ports = None
373
376
  else:
374
377
  tension = 0
375
378
  ports = None
@@ -391,6 +394,8 @@ class EdgeCollection(mpl.collections.PatchCollection):
391
394
  if waypoints != "none":
392
395
  ports = edge_stylei.get("ports", (None, None))
393
396
 
397
+ arc = edge_stylei.get("arc", False)
398
+
394
399
  # Compute actual edge path
395
400
  path, angles = _compute_edge_path(
396
401
  vcoord_data,
@@ -401,6 +406,7 @@ class EdgeCollection(mpl.collections.PatchCollection):
401
406
  tension=tension,
402
407
  waypoints=waypoints,
403
408
  ports=ports,
409
+ arc=arc,
404
410
  layout_coordinate_system=self._vertex_collection.get_layout_coordinate_system(),
405
411
  shrink=shrink,
406
412
  )
@@ -712,6 +718,7 @@ def make_stub_patch(**kwargs):
712
718
  "split",
713
719
  "shrink",
714
720
  "depthshade",
721
+ "arc",
715
722
  # DEPRECATED
716
723
  "padding",
717
724
  ]
@@ -393,6 +393,108 @@ def _compute_edge_path_waypoints(
393
393
  return path, angles
394
394
 
395
395
 
396
+ def _compute_edge_path_arc(
397
+ tension,
398
+ vcoord_data,
399
+ vpath_fig,
400
+ vsize_fig,
401
+ trans,
402
+ trans_inv,
403
+ ports: Pair[Optional[str]] = (None, None),
404
+ shrink: float = 0,
405
+ ):
406
+ """Shorten the edge path along an arc.
407
+
408
+ Parameters:
409
+ tension: the tension of the arc. This is defined, for this function, as the tangent
410
+ of the angle spanning the arc. For instance, for a semicircle, the angle is
411
+ 180 degrees, so the tension is +-1 (depending on the orientation).
412
+ """
413
+
414
+ # Coordinates in figure (default) coords
415
+ vcoord_fig = trans(vcoord_data)
416
+
417
+ dv = vcoord_fig[1] - vcoord_fig[0]
418
+
419
+ # Tension is the fraction of the semicircle covered by the
420
+ # arc. Values are clipped between -1 (left-hand semicircle)
421
+ # and 1 (right-hand semicircle). 0 means a straight line,
422
+ # which is a (degenerate) arc too.
423
+ if tension == 0:
424
+ vs = [None, None]
425
+ thetas = [atan2(dv[1], dv[0])]
426
+ thetas.append(-thetas[0])
427
+ for i in range(2):
428
+ vs[i] = (
429
+ _get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], thetas[i], shrink)
430
+ + vcoord_fig[i]
431
+ )
432
+ auxs = []
433
+
434
+ else:
435
+ edge_straight_length = np.sqrt((dv**2).sum())
436
+ theta_straight = atan2(dv[1], dv[0])
437
+ theta_tension = 4 * np.arctan(tension)
438
+ # print(f"theta_straight: {np.degrees(theta_straight):.2f}")
439
+ # print(f"theta_tension: {np.degrees(theta_tension):.2f}")
440
+ # NOTE: positive tension means an arc shooting off to the right of the straight
441
+ # line, same convensio as for tension elsewhere in the codebase.
442
+ thetas = [theta_straight - theta_tension / 2, np.pi + theta_straight + theta_tension / 2]
443
+ # This is guaranteed to be finite because tension == 0 is taken care of above,
444
+ # and tension = np.inf is not allowed.
445
+ mid = vcoord_fig.mean(axis=0)
446
+ # print(f"theta_s: {thetas}")
447
+ # print(f"mid: {mid}")
448
+ theta_offset = theta_straight + np.pi / 2
449
+ if np.abs(tension) <= 1:
450
+ offset_length = edge_straight_length / 2 / np.tan(theta_tension / 2)
451
+ else:
452
+ # print("Large tension arc")
453
+ offset_length = -edge_straight_length / 2 * np.tan(theta_tension / 2 - np.pi / 2)
454
+ # print(f"theta_offset: {np.degrees(theta_offset):.2f}")
455
+ offset = offset_length * np.array([np.cos(theta_offset), np.sin(theta_offset)])
456
+ # print(f"offset: {offset}")
457
+ center = mid + offset
458
+ # print(f"center: {center}")
459
+
460
+ # Compute shorter start and end points
461
+ vs = [None, None]
462
+ for i in range(2):
463
+ vs[i] = (
464
+ _get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], thetas[i], shrink)
465
+ + vcoord_fig[i]
466
+ )
467
+ angle_start = atan2(*(vs[0] - center)[::-1])
468
+ angle_end = atan2(*(vs[1] - center)[::-1])
469
+ if (np.abs(tension) > 1) and (np.abs(angle_end - angle_start) < np.pi):
470
+ if angle_end > angle_start:
471
+ angle_start += 2 * np.pi
472
+ else:
473
+ angle_end += 2 * np.pi
474
+ # print(f"angle_start: {np.degrees(angle_start):.2f}")
475
+ # print(f"angle_end: {np.degrees(angle_end):.2f}")
476
+
477
+ naux = 30
478
+ angles = np.linspace(angle_start, angle_end, naux + 2)[1:-1]
479
+ auxs = center + np.array([np.cos(angles), np.sin(angles)]).T * np.linalg.norm(
480
+ vs[0] - center
481
+ )
482
+
483
+ path = {
484
+ "vertices": [vs[0]] + list(auxs) + [vs[1]],
485
+ "codes": ["MOVETO"] + ["LINETO"] * (len(auxs) + 1),
486
+ }
487
+
488
+ path = mpl.path.Path(
489
+ path["vertices"],
490
+ codes=[getattr(mpl.path.Path, x) for x in path["codes"]],
491
+ )
492
+
493
+ # Return to data transform
494
+ path.vertices = trans_inv(path.vertices)
495
+ return path, tuple(thetas)
496
+
497
+
396
498
  def _compute_edge_path_curved(
397
499
  tension,
398
500
  vcoord_data,
@@ -483,12 +585,15 @@ def _compute_edge_path(
483
585
  tension: float = 0,
484
586
  waypoints: str | tuple[float, float] | Sequence[tuple[float, float]] | np.ndarray = "none",
485
587
  ports: Pair[Optional[str]] = (None, None),
588
+ arc: bool = False,
486
589
  layout_coordinate_system: str = "cartesian",
487
590
  **kwargs,
488
591
  ):
489
592
  """Compute the edge path in a few different ways."""
490
593
  if (waypoints != "none") and (tension != 0):
491
594
  raise ValueError("Waypoints not supported for curved edges.")
595
+ if (waypoints != "none") and arc:
596
+ raise ValueError("Waypoint not supported for arc edges.")
492
597
 
493
598
  if waypoints != "none":
494
599
  return _compute_edge_path_waypoints(
@@ -506,6 +611,14 @@ def _compute_edge_path(
506
611
  **kwargs,
507
612
  )
508
613
 
614
+ if arc:
615
+ return _compute_edge_path_arc(
616
+ tension,
617
+ *args,
618
+ ports=ports,
619
+ **kwargs,
620
+ )
621
+
509
622
  return _compute_edge_path_curved(
510
623
  tension,
511
624
  *args,
@@ -21,8 +21,9 @@ class BiopythonDataProvider(TreeDataProvider):
21
21
 
22
22
  preorder = partialmethod(_traverse, order="preorder")
23
23
  postorder = partialmethod(_traverse, order="postorder")
24
+ levelorder = partialmethod(_traverse, order="level")
24
25
 
25
- def get_leaves(self) -> Sequence[Any]:
26
+ def _get_leaves(self) -> Sequence[Any]:
26
27
  return self.tree.get_terminals()
27
28
 
28
29
  @staticmethod
@@ -16,7 +16,10 @@ class Cogent3DataProvider(TreeDataProvider):
16
16
  def postorder(self) -> Sequence[Any]:
17
17
  return self.tree.postorder()
18
18
 
19
- def get_leaves(self) -> Sequence[Any]:
19
+ def levelorder(self) -> Sequence[Any]:
20
+ return self.tree.levelorder()
21
+
22
+ def _get_leaves(self) -> Sequence[Any]:
20
23
  return self.tree.tips()
21
24
 
22
25
  @staticmethod
@@ -36,7 +36,16 @@ class DendropyDataProvider(TreeDataProvider):
36
36
  return self.tree.postorder_node_iter()
37
37
  return self.tree.postorder_iter()
38
38
 
39
- def get_leaves(self) -> Sequence[Any]:
39
+ def levelorder(self) -> Any:
40
+ """Levelorder traversal of the tree.
41
+
42
+ NOTE: This will work on both entire Trees and Nodes (which means a subtree including self).
43
+ """
44
+ if hasattr(self.tree, "levelorder_node_iter"):
45
+ return self.tree.levelorder_node_iter()
46
+ return self.tree.levelorder_iter()
47
+
48
+ def _get_leaves(self) -> Sequence[Any]:
40
49
  """Get a list of leaves."""
41
50
  return self.tree.leaf_nodes()
42
51
 
@@ -18,8 +18,9 @@ class Ete4DataProvider(TreeDataProvider):
18
18
 
19
19
  preorder = partialmethod(_traverse, order="preorder")
20
20
  postorder = partialmethod(_traverse, order="postorder")
21
+ levelorder = partialmethod(_traverse, order="levelorder")
21
22
 
22
- def get_leaves(self) -> Sequence[Any]:
23
+ def _get_leaves(self) -> Sequence[Any]:
23
24
  return self.tree.leaves()
24
25
 
25
26
  @staticmethod
@@ -64,7 +64,17 @@ class SimpleTreeDataProvider(TreeDataProvider):
64
64
 
65
65
  yield from _recur(self.tree)
66
66
 
67
- def get_leaves(self) -> Sequence[Any]:
67
+ def levelorder(self) -> Iterable[dict[dict | str, Any]]:
68
+ from collections import deque
69
+
70
+ queue = deque([self.get_root()])
71
+ while queue:
72
+ node = queue.popleft()
73
+ for child in self.get_children(node):
74
+ queue.append(child)
75
+ yield node
76
+
77
+ def _get_leaves(self) -> Sequence[Any]:
68
78
  def _recur(node):
69
79
  if len(node.children) == 0:
70
80
  yield node
@@ -16,7 +16,10 @@ class SkbioDataProvider(TreeDataProvider):
16
16
  def postorder(self) -> Sequence[Any]:
17
17
  return self.tree.postorder()
18
18
 
19
- def get_leaves(self) -> Sequence[Any]:
19
+ def levelorder(self) -> Sequence[Any]:
20
+ return self.tree.levelorder()
21
+
22
+ def _get_leaves(self) -> Sequence[Any]:
20
23
  return self.tree.tips()
21
24
 
22
25
  @staticmethod
@@ -12,6 +12,7 @@ from typing import (
12
12
  Any,
13
13
  Iterable,
14
14
  )
15
+
15
16
  # NOTE: __init__ in Protocols has had a difficult gestation
16
17
  # https://github.com/python/cpython/issues/88970
17
18
  if sys.version_info < (3, 11):
@@ -156,8 +157,32 @@ class TreeDataProvider(Protocol):
156
157
  return root_attr
157
158
  return self.tree.get_root()
158
159
 
159
- def get_leaves(self) -> Sequence[Any]:
160
- """Get the tree leaves/tips in a provider-specific data structure.
160
+ def get_subtree(self, node: TreeType):
161
+ """Get the subtree rooted at the given node.
162
+
163
+ Parameters:
164
+ node: The node to get the subtree from.
165
+ Returns:
166
+ The subtree rooted at the given node.
167
+ """
168
+ return self.__class__(node)
169
+
170
+ def get_leaves(self, node: Optional[TreeType] = None) -> Sequence[Any]:
171
+ """Get the leaves of the entire tree or a subtree.
172
+
173
+ Parameters:
174
+ node: The node to get the leaves from. If None, get from the entire
175
+ tree.
176
+ Returns:
177
+ The leaves or tips of the tree or node-anchored subtree.
178
+ """
179
+ if node is None:
180
+ return self._get_leaves()
181
+ else:
182
+ return self.get_subtree(node)._get_leaves()
183
+
184
+ def _get_leaves(self) -> Sequence[Any]:
185
+ """Get the whole tree leaves/tips in a provider-specific data structure.
161
186
 
162
187
  Returns:
163
188
  The leaves or tips of the tree.
@@ -235,8 +260,6 @@ class TreeDataProvider(Protocol):
235
260
  NOTE: individual providers may implement more efficient versions of
236
261
  this function if desired.
237
262
  """
238
- provider = self.__class__
239
-
240
263
  # Find leaves of the selected nodes
241
264
  leaves = set()
242
265
  for node in nodes:
@@ -244,7 +267,7 @@ class TreeDataProvider(Protocol):
244
267
  if len(self.get_children(node)) == 0:
245
268
  leaves.add(node)
246
269
  else:
247
- leaves |= set(provider(node).get_leaves())
270
+ leaves |= set(self.get_leaves(node))
248
271
 
249
272
  # Look for nodes with the same set of leaves, starting from the bottom
250
273
  # and stopping at the first (i.e. lowest) hit.
@@ -253,7 +276,7 @@ class TreeDataProvider(Protocol):
253
276
  if len(self.get_children(node)) == 0:
254
277
  leaves_node = {node}
255
278
  else:
256
- leaves_node = set(provider(node).get_leaves())
279
+ leaves_node = set(self.get_leaves(node))
257
280
  if leaves <= leaves_node:
258
281
  root = node
259
282
  break
@@ -285,9 +308,26 @@ class TreeDataProvider(Protocol):
285
308
  orientation = "right"
286
309
  elif layout == "vertical":
287
310
  orientation = "descending"
288
- elif layout == "radial":
311
+ elif layout in ("radial", "equalangle", "daylight"):
289
312
  orientation = "clockwise"
290
313
 
314
+ # Validate orientation
315
+ valid = (layout == "horizontal") and (orientation in ("right", "left"))
316
+ valid |= (layout == "vertical") and (orientation in ("ascending", "descending"))
317
+ valid |= (layout == "radial") and (
318
+ orientation in ("clockwise", "counterclockwise", "left", "right")
319
+ )
320
+ valid |= (layout == "equalangle") and (
321
+ orientation in ("clockwise", "counterclockwise", "left", "right")
322
+ )
323
+ valid |= (layout == "daylight") and (
324
+ orientation in ("clockwise", "counterclockwise", "left", "right")
325
+ )
326
+ if not valid:
327
+ raise ValueError(
328
+ f"Orientation '{orientation}' is not valid for layout '{layout}'.",
329
+ )
330
+
291
331
  tree_data = {
292
332
  "root": self.get_root(),
293
333
  "rooted": self.is_rooted(),
@@ -304,8 +344,10 @@ class TreeDataProvider(Protocol):
304
344
  root=tree_data["root"],
305
345
  preorder_fun=self.preorder,
306
346
  postorder_fun=self.postorder,
347
+ levelorder_fun=self.levelorder,
307
348
  children_fun=self.get_children,
308
349
  branch_length_fun=self.get_branch_length_default_to_one,
350
+ leaves_fun=self.get_leaves,
309
351
  **layout_style,
310
352
  )
311
353
  if layout in ("radial",):
@@ -0,0 +1,9 @@
1
+ """
2
+ Layout functions.
3
+ """
4
+
5
+ from .tree import compute_tree_layout
6
+
7
+ __all__ = [
8
+ "compute_tree_layout",
9
+ ]
@@ -0,0 +1,72 @@
1
+ """
2
+ Tree layout algorithms.
3
+ """
4
+
5
+ from typing import (
6
+ Any,
7
+ )
8
+ from collections.abc import (
9
+ Hashable,
10
+ Callable,
11
+ )
12
+
13
+ from .rooted import (
14
+ _horizontal_tree_layout,
15
+ _vertical_tree_layout,
16
+ _radial_tree_layout,
17
+ )
18
+ from .unrooted import (
19
+ _equalangle_tree_layout,
20
+ _daylight_tree_layout,
21
+ )
22
+
23
+
24
+ def compute_tree_layout(
25
+ layout: str,
26
+ orientation: str,
27
+ root: Any,
28
+ preorder_fun: Callable,
29
+ postorder_fun: Callable,
30
+ levelorder_fun: Callable,
31
+ children_fun: Callable,
32
+ branch_length_fun: Callable,
33
+ leaves_fun: Callable,
34
+ **kwargs,
35
+ ) -> dict[Hashable, list[float]]:
36
+ """Compute the layout for a tree.
37
+
38
+ Parameters:
39
+ layout: The name of the layout, e.g. "horizontal", "vertical", or "radial".
40
+ orientation: The orientation of the layout, e.g. "right", "left", "descending",
41
+ "ascending", "clockwise", "anticlockwise".
42
+
43
+ Returns:
44
+ A layout dictionary with node positions.
45
+ """
46
+ kwargs["root"] = root
47
+ kwargs["preorder_fun"] = preorder_fun
48
+ kwargs["postorder_fun"] = postorder_fun
49
+ kwargs["levelorder_fun"] = levelorder_fun
50
+ kwargs["children_fun"] = children_fun
51
+ kwargs["orientation"] = orientation
52
+ kwargs["branch_length_fun"] = branch_length_fun
53
+ kwargs["leaves_fun"] = leaves_fun
54
+
55
+ # Angular or not, the vertex layout is unchanged. Since we do not
56
+ # currently compute an edge layout here, we can ignore the option.
57
+ kwargs.pop("angular", None)
58
+
59
+ if layout == "radial":
60
+ layout_dict = _radial_tree_layout(**kwargs)
61
+ elif layout == "horizontal":
62
+ layout_dict = _horizontal_tree_layout(**kwargs)
63
+ elif layout == "vertical":
64
+ layout_dict = _vertical_tree_layout(**kwargs)
65
+ elif layout == "equalangle":
66
+ layout_dict = _equalangle_tree_layout(**kwargs)
67
+ elif layout == "daylight":
68
+ layout_dict = _daylight_tree_layout(**kwargs)
69
+ else:
70
+ raise ValueError(f"Tree layout not available: {layout}")
71
+
72
+ return layout_dict