iplotx 1.3.0__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 (270) hide show
  1. {iplotx-1.3.0 → iplotx-1.4.0}/PKG-INFO +1 -1
  2. {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/api/complete_style_specification.md +8 -3
  3. {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/sg_execution_times.rst +6 -3
  4. iplotx-1.4.0/gallery/basic/plot_arcs.py +42 -0
  5. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/edge/__init__.py +7 -0
  6. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/edge/geometry.py +113 -0
  7. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/layout/tree/unrooted.py +118 -57
  8. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/style/leaf_info.py +1 -0
  9. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/version.py +1 -1
  10. iplotx-1.4.0/tests/baseline_images/test_igraph/arcs.png +0 -0
  11. iplotx-1.4.0/tests/baseline_images/test_igraph/large_arcs.png +0 -0
  12. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_igraph.py +29 -0
  13. {iplotx-1.3.0 → iplotx-1.4.0}/.github/workflows/publish.yml +0 -0
  14. {iplotx-1.3.0 → iplotx-1.4.0}/.github/workflows/test.yml +0 -0
  15. {iplotx-1.3.0 → iplotx-1.4.0}/.gitignore +0 -0
  16. {iplotx-1.3.0 → iplotx-1.4.0}/.pre-commit-config.yaml +0 -0
  17. {iplotx-1.3.0 → iplotx-1.4.0}/.readthedocs.yaml +0 -0
  18. {iplotx-1.3.0 → iplotx-1.4.0}/LICENSE +0 -0
  19. {iplotx-1.3.0 → iplotx-1.4.0}/MANIFEST.in +0 -0
  20. {iplotx-1.3.0 → iplotx-1.4.0}/README.md +0 -0
  21. {iplotx-1.3.0 → iplotx-1.4.0}/assets/pylint.svg +0 -0
  22. {iplotx-1.3.0 → iplotx-1.4.0}/docs/Makefile +0 -0
  23. {iplotx-1.3.0 → iplotx-1.4.0}/docs/make.bat +0 -0
  24. {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/_static/banner.png +0 -0
  25. {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/_static/custom-icons.js +0 -0
  26. {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/_static/custom.css +0 -0
  27. {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/_static/graph_basic.png +0 -0
  28. {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/_templates/layout.html +0 -0
  29. {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/api/artists.md +0 -0
  30. {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/api/plotting.md +0 -0
  31. {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/api/providers.md +0 -0
  32. {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/api/style.md +0 -0
  33. {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/api.md +0 -0
  34. {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/code_of_conduct.rst +0 -0
  35. {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/conf.py +0 -0
  36. {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/images/sphx_glr_plot_basic_001.png +0 -0
  37. {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/images/thumb/sphx_glr_plot_basic_thumb.png +0 -0
  38. {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/index.md +0 -0
  39. {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/installing.md +0 -0
  40. {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/providers.md +0 -0
  41. {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/style.md +0 -0
  42. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/GALLERY_HEADER.rst +0 -0
  43. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/basic/GALLERY_HEADER.rst +0 -0
  44. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/basic/plot_3d.py +0 -0
  45. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/basic/plot_basic.py +0 -0
  46. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/basic/plot_big_curves.py +0 -0
  47. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/basic/plot_dag.py +0 -0
  48. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/basic/plot_directed.py +0 -0
  49. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/basic/plot_grouping.py +0 -0
  50. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/basic/plot_house.py +0 -0
  51. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/basic/plot_loops.py +0 -0
  52. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/basic/plot_simple_path.py +0 -0
  53. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/GALLERY_HEADER.rst +0 -0
  54. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/data/80201010000000001.mst +0 -0
  55. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/data/GN-tree.json +0 -0
  56. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/data/breast_cancer_string_interactions_short.tsv +0 -0
  57. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/data/breast_cancer_string_network_coordinates.tsv +0 -0
  58. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/data/cell_cycle_arrest_string_interactions_short.tsv +0 -0
  59. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/data/cell_cycle_arrest_string_network_coordinates.tsv +0 -0
  60. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/data/fevo-08-588430_DataSheet1_S1.csv +0 -0
  61. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/plot_animal_phylogeny.py +0 -0
  62. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/plot_antibody_clone.py +0 -0
  63. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/plot_breast_cancer_ppi.py +0 -0
  64. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/plot_cell_cycle_arrest.py +0 -0
  65. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/plot_food_network.py +0 -0
  66. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/plot_foraging_table.py +0 -0
  67. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/plot_pollinators.py +0 -0
  68. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/plot_ppi.py +0 -0
  69. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/plot_tca_cycle.py +0 -0
  70. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/GALLERY_HEADER.rst +0 -0
  71. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/data/chess_masters_WCC.pgn.bz2 +0 -0
  72. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/data/knuth_miles.txt.gz +0 -0
  73. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_arrowlawn.py +0 -0
  74. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_chess_masters.py +0 -0
  75. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_cliques.py +0 -0
  76. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_cluster_layout.py +0 -0
  77. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_company_structure.py +0 -0
  78. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_complex.py +0 -0
  79. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_financial_network.py +0 -0
  80. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_knuth_miles.py +0 -0
  81. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_labels_and_colors.py +0 -0
  82. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_max_bipartite_matching.py +0 -0
  83. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_minimum_spanning_trees.py +0 -0
  84. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_multipartite_layout.py +0 -0
  85. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_parallel_igraph_networkx.py +0 -0
  86. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_redblack.py +0 -0
  87. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_shortest_path.py +0 -0
  88. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_simple_networkx.py +0 -0
  89. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_social_network_circles.py +0 -0
  90. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_traveling_salesman.py +0 -0
  91. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_with_colorbar.py +0 -0
  92. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/other/GALLERY_HEADER.rst +0 -0
  93. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/other/plot_animation.py +0 -0
  94. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/other/plot_edit_artists.py +0 -0
  95. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/other/plot_feedbacks.py +0 -0
  96. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/other/plot_graph.py +0 -0
  97. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/other/plot_mouse_hover.py +0 -0
  98. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/other/plot_train.py +0 -0
  99. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/GALLERY_HEADER.rst +0 -0
  100. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_arrows.py +0 -0
  101. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_depthshade.py +0 -0
  102. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_edgepadding.py +0 -0
  103. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_elements.py +0 -0
  104. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_four_grids.py +0 -0
  105. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_halfarrows.py +0 -0
  106. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_multistyle.py +0 -0
  107. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_ports.py +0 -0
  108. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_style.py +0 -0
  109. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_tension.py +0 -0
  110. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_vertexmarkers.py +0 -0
  111. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_voronoi.py +0 -0
  112. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_waypoints.py +0 -0
  113. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/GALLERY_HEADER.rst +0 -0
  114. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/data/tree-with-support.json +0 -0
  115. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_angular_waypoints.py +0 -0
  116. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_biopython_tree.py +0 -0
  117. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_cladeedges.py +0 -0
  118. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_cogent3_layouts.py +0 -0
  119. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_cogent3_tree.py +0 -0
  120. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_dendropy.py +0 -0
  121. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_double_tree.py +0 -0
  122. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_elements_tree.py +0 -0
  123. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_equalangle.py +0 -0
  124. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_ete4.py +0 -0
  125. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_leafedges.py +0 -0
  126. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_leafedges_and_cascades.py +0 -0
  127. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_scalebar.py +0 -0
  128. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_skbio_tree.py +0 -0
  129. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_split_edges.py +0 -0
  130. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_style_tree.py +0 -0
  131. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_support.py +0 -0
  132. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_tree_node_background.py +0 -0
  133. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_tree_style_clades.py +0 -0
  134. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_trees_of_trees.py +0 -0
  135. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/zero_dependency/GALLERY_HEADER.rst +0 -0
  136. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/zero_dependency/plot_simplenetworkdataprovider.py +0 -0
  137. {iplotx-1.3.0 → iplotx-1.4.0}/gallery/zero_dependency/plot_simpletreedataprovider.py +0 -0
  138. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/__init__.py +0 -0
  139. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/art3d/edge/__init__.py +0 -0
  140. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/art3d/edge/arrow.py +0 -0
  141. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/art3d/edge/geometry.py +0 -0
  142. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/art3d/vertex.py +0 -0
  143. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/artists.py +0 -0
  144. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/edge/arrow.py +0 -0
  145. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/edge/leaf.py +0 -0
  146. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/edge/ports.py +0 -0
  147. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/__init__.py +0 -0
  148. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/heuristics.py +0 -0
  149. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/providers/network/graph_tool.py +0 -0
  150. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/providers/network/igraph.py +0 -0
  151. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/providers/network/networkx.py +0 -0
  152. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/providers/network/simple.py +0 -0
  153. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/providers/tree/biopython.py +0 -0
  154. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/providers/tree/cogent3.py +0 -0
  155. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/providers/tree/dendropy.py +0 -0
  156. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/providers/tree/ete4.py +0 -0
  157. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/providers/tree/simple.py +0 -0
  158. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/providers/tree/skbio.py +0 -0
  159. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/typing.py +0 -0
  160. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/label.py +0 -0
  161. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/layout/__init__.py +0 -0
  162. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/layout/tree/__init__.py +0 -0
  163. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/layout/tree/rooted.py +0 -0
  164. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/network/__init__.py +0 -0
  165. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/network/groups.py +0 -0
  166. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/plotting.py +0 -0
  167. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/style/__init__.py +0 -0
  168. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/style/library.py +0 -0
  169. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/tree/__init__.py +0 -0
  170. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/tree/cascades.py +0 -0
  171. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/tree/scalebar.py +0 -0
  172. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/typing.py +0 -0
  173. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/utils/geometry.py +0 -0
  174. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/utils/internal.py +0 -0
  175. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/utils/matplotlib.py +0 -0
  176. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/utils/style.py +0 -0
  177. {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/vertex.py +0 -0
  178. {iplotx-1.3.0 → iplotx-1.4.0}/pyproject.toml +0 -0
  179. {iplotx-1.3.0 → iplotx-1.4.0}/scripts/copy_github_release_into_version.sh +0 -0
  180. {iplotx-1.3.0 → iplotx-1.4.0}/scripts/make_banner.py +0 -0
  181. {iplotx-1.3.0 → iplotx-1.4.0}/scripts/update_pylint_badge.sh +0 -0
  182. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_biopython/cascades.png +0 -0
  183. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_biopython/directed_child.png +0 -0
  184. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_biopython/leaf_labels.png +0 -0
  185. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_biopython/leaf_labels_hmargin.png +0 -0
  186. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_biopython/leafedges.png +0 -0
  187. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_biopython/show_support.png +0 -0
  188. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_biopython/tree_basic.png +0 -0
  189. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_biopython/tree_radial.png +0 -0
  190. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_cogent3/leaf_labels.png +0 -0
  191. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_cogent3/leaf_labels_hmargin.png +0 -0
  192. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_cogent3/tree_basic.png +0 -0
  193. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_cogent3/tree_radial.png +0 -0
  194. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/cascades.png +0 -0
  195. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/directed_child.png +0 -0
  196. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/leaf_labels.png +0 -0
  197. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/leaf_labels_hmargin.png +0 -0
  198. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/leafedges.png +0 -0
  199. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/tree_basic.png +0 -0
  200. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/tree_radial.png +0 -0
  201. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_doubletree/tree_gap.png +0 -0
  202. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_doubletree/tree_nogap.png +0 -0
  203. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_ete4/equalangle_layout.png +0 -0
  204. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_ete4/leaf_labels.png +0 -0
  205. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_ete4/leaf_labels_hmargin.png +0 -0
  206. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_ete4/split_edges.png +0 -0
  207. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_ete4/tree_basic.png +0 -0
  208. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_ete4/tree_radial.png +0 -0
  209. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_graph_tool/graph_basic.png +0 -0
  210. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_graph_tool/graph_directed.png +0 -0
  211. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/clustering_directed.png +0 -0
  212. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/clustering_directed_large.png +0 -0
  213. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_basic.png +0 -0
  214. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_directed.png +0 -0
  215. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_directed_curved_loops.png +0 -0
  216. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_edit_children.png +0 -0
  217. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_labels.png +0 -0
  218. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_layout_attribute.png +0 -0
  219. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_null.png +0 -0
  220. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_squares_directed.png +0 -0
  221. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_vertexsize.png +0 -0
  222. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_with_curved_edges.png +0 -0
  223. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/igraph_layout_object.png +0 -0
  224. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/multigraph_with_curved_edges_undirected.png +0 -0
  225. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph_3d/directed.png +0 -0
  226. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph_3d/undirected.png +0 -0
  227. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph_3d/vertex_labels.png +0 -0
  228. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_networkx/cluster-layout.png +0 -0
  229. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_networkx/complex.png +0 -0
  230. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_networkx/complex_rotatelabels.png +0 -0
  231. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_networkx/directed_graph.png +0 -0
  232. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_networkx/directed_graph_with_colorbar.png +0 -0
  233. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_networkx/empty_graph.png +0 -0
  234. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_networkx/flat_style.png +0 -0
  235. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_networkx/house_with_colors.png +0 -0
  236. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_networkx/labels_and_colors.png +0 -0
  237. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_networkx/shortest_path.png +0 -0
  238. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_networkx/simple_graph.png +0 -0
  239. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_simple_network_provider/graph_basic.png +0 -0
  240. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_simple_network_provider/graph_directed.png +0 -0
  241. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_simple_network_provider/graph_labels.png +0 -0
  242. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_skbio/leaf_labels.png +0 -0
  243. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_skbio/leaf_labels_hmargin.png +0 -0
  244. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_skbio/tree_basic.png +0 -0
  245. {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_skbio/tree_radial.png +0 -0
  246. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_arrows.py +0 -0
  247. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_biopython.py +0 -0
  248. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_cascades.py +0 -0
  249. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_cogent3.py +0 -0
  250. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_dendropy.py +0 -0
  251. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_doubletree.py +0 -0
  252. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_edge.py +0 -0
  253. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_edge_geometry.py +0 -0
  254. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_ete4.py +0 -0
  255. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_geometry.py +0 -0
  256. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_graph_tool.py +0 -0
  257. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_heuristics.py +0 -0
  258. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_igraph_3d.py +0 -0
  259. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_ingest_protocols.py +0 -0
  260. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_matplotlib_utils.py +0 -0
  261. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_network_hotload.py +0 -0
  262. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_networkx.py +0 -0
  263. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_ports.py +0 -0
  264. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_simple_network_provider.py +0 -0
  265. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_simple_tree_provider.py +0 -0
  266. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_skbio.py +0 -0
  267. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_style.py +0 -0
  268. {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_vertex.py +0 -0
  269. {iplotx-1.3.0 → iplotx-1.4.0}/tests/utils.py +0 -0
  270. {iplotx-1.3.0 → 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.3.0
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
@@ -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
  ############################################################################
@@ -6,7 +6,7 @@
6
6
 
7
7
  Computation times
8
8
  =================
9
- **00:00.514** total execution time for 78 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_equalangle.py` (``../../gallery/tree/plot_equalangle.py``)
36
- - 00:00.514
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
@@ -227,6 +227,9 @@ Computation times
227
227
  * - :ref:`sphx_glr_gallery_tree_plot_elements_tree.py` (``../../gallery/tree/plot_elements_tree.py``)
228
228
  - 00:00.000
229
229
  - 0.0
230
+ * - :ref:`sphx_glr_gallery_tree_plot_equalangle.py` (``../../gallery/tree/plot_equalangle.py``)
231
+ - 00:00.000
232
+ - 0.0
230
233
  * - :ref:`sphx_glr_gallery_tree_plot_ete4.py` (``../../gallery/tree/plot_ete4.py``)
231
234
  - 00:00.000
232
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
+ )
@@ -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,
@@ -74,13 +74,15 @@ def _equalangle_tree_layout(
74
74
  for child in children:
75
75
  nleaves_child = props["nleaves"][child]
76
76
  alpha = nleaves_child / nleaves * total_angle
77
+ if orientation in ("left", "counterclockwise"):
78
+ alpha = -alpha
77
79
  beta = start + alpha / 2
78
80
 
79
81
  props["layout"][child] = [
80
82
  cur_x + branch_length_fun(child) * np.cos(np.radians(beta)),
81
83
  cur_y + branch_length_fun(child) * np.sin(np.radians(beta)),
82
84
  ]
83
- props["angle"][child] = -90 - beta * np.sign(beta - 180)
85
+ # props["angle"][child] = -90 - beta * np.sign(beta - 180)
84
86
  props["start"][child] = start
85
87
  props["end"][child] = start + alpha
86
88
  start += alpha
@@ -101,7 +103,8 @@ def _daylight_tree_layout(
101
103
  start: float = 180,
102
104
  span: float = 360,
103
105
  max_iter: int = 5,
104
- dampening: float = 0.3,
106
+ dampening: float = 0.33,
107
+ max_correction: float = 10.0,
105
108
  **kwargs,
106
109
  ) -> dict[Hashable, list[float]]:
107
110
  """Daylight unrooted tree layout.
@@ -154,16 +157,34 @@ def _daylight_tree_layout(
154
157
  ninternal = 0
155
158
  parents = [None] + list(levelorder_fun())
156
159
  for parent in parents:
157
- children = children_fun(parent) if parent is not None else [root]
160
+ if parent is None:
161
+ # If the root has only two children, it's a passthrough node, skip it
162
+ if len(children_fun(root)) < 3:
163
+ continue
164
+ # Else, include it
165
+ children = [root]
166
+ else:
167
+ children = children_fun(parent)
168
+
158
169
  for node in children:
170
+ grandchildren = children_fun(node)
171
+ # Exclude leaves, since they have no children subtrees
172
+ # that can be adjusted. Exclude also passthrough nodes with
173
+ # a single child, because they are rotating rigidly when their
174
+ # parent does so or tells them to do so.
175
+ if len(grandchildren) < 2:
176
+ continue
177
+
159
178
  res = _apply_daylight_single_node(
160
179
  node,
161
180
  parent,
181
+ grandchildren,
162
182
  all_leaves,
163
183
  layout,
164
184
  leaves_fun,
165
185
  children_fun,
166
186
  dampening,
187
+ max_correction,
167
188
  )
168
189
  change_sum += res
169
190
  ninternal += 1
@@ -182,11 +203,13 @@ def _daylight_tree_layout(
182
203
  def _apply_daylight_single_node(
183
204
  node: Any,
184
205
  parent: Any,
206
+ children: list[Any],
185
207
  all_leaves: list[Any],
186
208
  layout: dict[Hashable, np.ndarray],
187
209
  leaves_fun: Callable,
188
210
  children_fun: Callable,
189
211
  dampening: float,
212
+ max_correction: float,
190
213
  ) -> float:
191
214
  """Apply daylight adjustment to a single internal node.
192
215
 
@@ -208,9 +231,9 @@ def _apply_daylight_single_node(
208
231
 
209
232
  print = _print if DEBUG_DAYLIGHT else lambda *a, **k: None
210
233
 
211
- children = children_fun(node)
212
-
213
- # 1. Find boundary leaves for each child and for the parent
234
+ # 1. Find daylight boundary leaves for each subtree. There are always at least two subtrees,
235
+ # the first child and the parent (this function is not called for leaves, and the root hopefully
236
+ # has at least two children).
214
237
  p0 = layout[node]
215
238
  bounds = {}
216
239
 
@@ -224,99 +247,137 @@ def _apply_daylight_single_node(
224
247
 
225
248
  # Check the parent first if there is one
226
249
  if parent is not None:
250
+ leaves_parent_subtree = [leaf for leaf in all_leaves if leaf not in leaves_below]
227
251
  vec1 = layout[parent] - p0
228
252
  print("parent side leaves:")
229
253
  print(parent)
230
- print(f" node to parent vector: {vec1[0]:.2f}, {vec1[1]:.2f}")
254
+ print(
255
+ f" node to parent vector: {vec1[0]:.2f}, {vec1[1]:.2f}, angle: {np.degrees(np.arctan2(vec1[1], vec1[0])):.2f}"
256
+ )
231
257
  lower_angle, upper_angle = 2 * np.pi, -2 * np.pi
232
- for leaf in all_leaves:
233
- # Skip subtree leaves
234
- if leaf in leaves_below:
235
- continue
258
+ for leaf in leaves_parent_subtree:
236
259
  vec2 = layout[leaf] - p0
237
260
  angle = _anticlockwise_angle(vec1, vec2)
238
261
  print(" parent side leaf:")
239
262
  print(leaf)
240
- print(f" node to leaf vector: {vec2[0]:.2f}, {vec2[1]:.2f}")
241
- print(f" angle: {angle:.2f}")
263
+ print(
264
+ f" node to leaf vector: {vec2[0]:.2f}, {vec2[1]:.2f}, angle: {np.degrees(np.arctan2(vec2[1], vec2[0])):.2f}"
265
+ )
266
+ print(f" angle: {np.degrees(angle):.2f}")
242
267
  if angle < lower_angle:
268
+ print("lowering lower angle")
243
269
  lower_angle = angle
244
270
  lower = leaf
271
+ else:
272
+ print("not lowering lower angle")
245
273
  if angle > upper_angle:
274
+ print("raising upper angle")
246
275
  upper_angle = angle
247
276
  upper = leaf
277
+ else:
278
+ print("not raising upper angle")
248
279
  bounds[parent] = (lower, upper, lower_angle, upper_angle)
249
280
 
250
281
  # Repeat the exact same thing for each child rather than the parent
251
282
  print("subtree leaves:")
252
283
  for child in children:
253
284
  vec1 = layout[child] - p0
254
- print(f" node to child vector: {vec1[0]:.2f}, {vec1[1]:.2f}")
285
+ print(
286
+ f" node to child vector: {vec1[0]:.2f}, {vec1[1]:.2f}, angle: {np.degrees(np.arctan2(vec1[1], vec1[0])):.2f}"
287
+ )
255
288
  lower_angle, upper_angle = 2 * np.pi, -2 * np.pi
256
289
 
257
290
  for leaf in leaves_fun(child):
258
291
  vec2 = layout[leaf] - p0
259
292
  angle = _anticlockwise_angle(vec1, vec2)
260
- print(f" node to leaf vector: {vec2[0]:.2f}, {vec2[1]:.2f}")
293
+ print(
294
+ f" node to leaf vector: {vec2[0]:.2f}, {vec2[1]:.2f}, angle: {np.degrees(np.arctan2(vec2[1], vec2[0])):.2f}"
295
+ )
261
296
  print(leaf)
262
- print(f" angle: {angle:.2f}")
297
+ print(f" angle: {np.degrees(angle):.2f}")
263
298
  if angle < lower_angle:
299
+ print("lowering lower angle")
264
300
  lower_angle = angle
265
301
  lower = leaf
302
+ else:
303
+ print("not lowering lower angle")
266
304
  if angle > upper_angle:
305
+ print("raising upper angle")
267
306
  upper_angle = angle
268
307
  upper = leaf
308
+ else:
309
+ print("not raising upper angle")
269
310
  bounds[child] = (lower, upper, lower_angle, upper_angle)
270
311
 
271
- print("final boundary leaves:")
272
- print(bounds)
312
+ for subtree, bound in bounds.items():
313
+ vec1 = layout[bound[0]] - p0
314
+ vec2 = layout[bound[1]] - p0
315
+ angle = _anticlockwise_angle(vec1, vec2)
316
+ print("subtree angles:")
317
+ print(f" lower {np.degrees(np.arctan2(vec1[1], vec1[0])):.2f}")
318
+ print(f" upper {np.degrees(np.arctan2(vec2[1], vec2[0])):.2f}")
319
+ print(f" angle {np.degrees(angle):.2f}")
273
320
 
274
321
  # 2. Compute daylight angles
275
322
  # NOTE: Since Python 3.6, python keys are ordered by insertion order.
276
323
  daylight = {}
277
- daylight_sum = 0.0
278
- # TODO: Mayvbe optimise this by avoiding creating all these lists
279
- prev_leaves = [bound[1] for bound in bounds.values()]
280
- leaves = [bound[0] for bound in bounds.values()]
281
- leaves = leaves[1:] + [leaves[0]] # cycle left
282
- subtrees = children + ([parent] if parent is not None else [])
283
- for subtree, prev_leaf, leaf in zip(subtrees, prev_leaves, leaves):
284
- vec1 = layout[prev_leaf] - p0
285
- vec2 = layout[leaf] - p0
324
+ subtrees = list(bounds.keys())
325
+ subtrees += [subtrees[0]] # Repeat first subtree
326
+
327
+ for i in range(len(subtrees) - 1):
328
+ subtree = subtrees[i + 1]
329
+ old_subtree = subtrees[i]
330
+ lower = bounds[subtree][0]
331
+ old_upper = bounds[old_subtree][1]
332
+ vec1 = layout[old_upper] - p0
333
+ vec2 = layout[lower] - p0
286
334
  angle = _anticlockwise_angle(vec1, vec2)
287
-
288
- daylight_sum += angle
289
- if leaf != parent:
290
- # daylight[leaf] = float(angle)
291
- daylight[subtree] = float(angle)
292
-
293
- print("daylight")
294
- print(daylight)
295
- print(f"daylight sum: {daylight_sum:.2f}")
296
-
297
- # 3. Compute *excess* daylight, and correct it
298
- # NOTE: There seems to be this notion that you rotate a node by the accumulated daylight
299
- # correction (to fill the space on the left) plus its own correction (to fill the space on the right).
300
- # It reads funny and this is within a BFS iteration anyway so it's probably ok, but it's
301
- # curious.
302
- # NOTE: The average adjustment is divided by children + 1 (the parent) bc this is unrooted.
303
- daylight_avg = daylight_sum / (len(children) + 1)
304
- daylight_cum_corr = 0.0
305
- daylight_changes = 0
306
- for leaf in daylight:
307
- daylight_cum_corr += daylight_avg - daylight[leaf]
308
- _rotate_subtree_around_point(
335
+ daylight[subtree] = float(angle)
336
+ print("daylight angle:")
337
+ print(f" previous upper {np.degrees(np.arctan2(vec1[1], vec1[0])):.2f}")
338
+ print(f" new lower {np.degrees(np.arctan2(vec2[1], vec2[0])):.2f}")
339
+ print(f" angle: {np.degrees(angle):.2f}")
340
+
341
+ daylight_avg = sum(daylight.values()) / len(daylight)
342
+ print(f"daylight average angle: {np.degrees(daylight_avg):.2f}")
343
+
344
+ # 3. Compute *excess* daylight and corrections
345
+ daylight_correction = {}
346
+ corr_cum = 0.0
347
+ print("daylight correction:")
348
+ for subtree, angle in daylight.items():
349
+ # Correction is negative of the residue
350
+ corr_cum -= angle - daylight_avg
351
+ daylight_correction[subtree] = corr_cum
352
+ print(f" daylight angle: {np.degrees(angle):.2f}, correction: {np.degrees(corr_cum):.2f}")
353
+
354
+ # NOTE: the last daylight correction must be 0, otherwise we are just rotating the entire tree.
355
+ # In most cases, this will be the parent which cannot rotate anyway (for the same reason).
356
+ # However, when applied to the root node with 3+ children, the nonrotating one will be the first
357
+ # child, which is arbitrary but correct: even in this case, we do not want a merry-go-round.
358
+
359
+ # 4. Correct (the last one is dumb)
360
+ daylight_corrections_abs = 0.0
361
+ for subtree, correction in daylight_correction.items():
362
+ correction *= dampening
363
+ correction = np.clip(correction, np.radians(-max_correction), np.radians(max_correction))
364
+ print(f"Applying correction to subtree {subtree}: {np.degrees(correction):.2f}")
365
+ _rotate_subtree_anticlockwise(
309
366
  leaf,
310
367
  children_fun,
311
368
  layout,
312
369
  p0,
313
- dampening * daylight_cum_corr,
370
+ correction,
314
371
  recur=True,
315
372
  )
316
- daylight_changes += abs(dampening * daylight_cum_corr)
373
+ daylight_corrections_abs += abs(correction)
374
+
375
+ # __import__("ipdb").set_trace()
317
376
 
318
377
  # Caller wants degrees
319
- return np.degrees(daylight_changes / len(leaves))
378
+ # NOTE: The denominator is -1 because the last correction is always zero anyway, so the
379
+ # actually possible corrections are #subtrees - 1.
380
+ return np.degrees(daylight_corrections_abs / (len(daylight_correction) - 1))
320
381
 
321
382
 
322
383
  # see: https://stackoverflow.com/questions/14066933/direct-way-of-computing-the-clockwise-angle-between-two-vectors
@@ -334,7 +395,7 @@ def _anticlockwise_angle(v1, v2):
334
395
  return np.arctan2(determinant, dot)
335
396
 
336
397
 
337
- def _rotate_subtree_around_point(
398
+ def _rotate_subtree_anticlockwise(
338
399
  node,
339
400
  children_fun: Callable,
340
401
  layout: dict[Hashable, list[float]],
@@ -344,7 +405,7 @@ def _rotate_subtree_around_point(
344
405
  ):
345
406
  point = np.asarray(layout[node])
346
407
  pivot = np.asarray(pivot)
347
- layout[node] = _rotate_around_point(
408
+ layout[node] = _rotate_anticlockwise(
348
409
  point,
349
410
  pivot,
350
411
  angle,
@@ -352,7 +413,7 @@ def _rotate_subtree_around_point(
352
413
  if not recur:
353
414
  return
354
415
  for child in children_fun(node):
355
- _rotate_subtree_around_point(
416
+ _rotate_subtree_anticlockwise(
356
417
  child,
357
418
  children_fun,
358
419
  layout,
@@ -361,7 +422,7 @@ def _rotate_subtree_around_point(
361
422
  )
362
423
 
363
424
 
364
- def _rotate_around_point(
425
+ def _rotate_anticlockwise(
365
426
  point,
366
427
  pivot,
367
428
  angle,
@@ -379,5 +440,5 @@ def _rotate_around_point(
379
440
  pivot = np.asarray(pivot)
380
441
  cos = np.cos(angle)
381
442
  sin = np.sin(angle)
382
- rot = np.array([[cos, sin], [-sin, cos]])
443
+ rot = np.array([[cos, -sin], [sin, cos]])
383
444
  return pivot + (point - pivot) @ rot
@@ -41,6 +41,7 @@ nonrotating_leaves = (
41
41
  "deep",
42
42
  "angular",
43
43
  "curved",
44
+ "arc",
44
45
  "capstyle",
45
46
  "depthshade",
46
47
  )
@@ -2,4 +2,4 @@
2
2
  iplotx version information module.
3
3
  """
4
4
 
5
- __version__ = "1.3.0"
5
+ __version__ = "1.4.0"
@@ -204,6 +204,35 @@ class GraphTestRunner(unittest.TestCase):
204
204
  ipx.graph(g, ax=ax)
205
205
  ax.set_aspect(1.0)
206
206
 
207
+ @image_comparison(baseline_images=["arcs"])
208
+ def test_arcs(self):
209
+ g = ig.Graph.Full(4)
210
+ layout = [[0, 0], [1, 0], [2, 0], [3, 0]]
211
+
212
+ fig, ax = plt.subplots()
213
+ ipx.plot(
214
+ g,
215
+ layout=layout,
216
+ ax=ax,
217
+ edge_arc=True,
218
+ edge_tension=-1,
219
+ )
220
+
221
+ @image_comparison(baseline_images=["large_arcs"])
222
+ def test_large_arcs(self):
223
+ g = ig.Graph.Full(4)
224
+ layout = [[0, 0], [1, 0], [2, 0], [3, 0]]
225
+
226
+ fig, ax = plt.subplots()
227
+ ipx.plot(
228
+ g,
229
+ layout=layout,
230
+ ax=ax,
231
+ edge_arc=True,
232
+ edge_tension=-3,
233
+ margins=0.2,
234
+ )
235
+
207
236
 
208
237
  class ClusteringTestRunner(unittest.TestCase):
209
238
  @property
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes