iplotx 0.10.0__tar.gz → 0.11.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 (240) hide show
  1. {iplotx-0.10.0 → iplotx-0.11.0}/PKG-INFO +1 -1
  2. {iplotx-0.10.0 → iplotx-0.11.0}/docs/source/sg_execution_times.rst +2 -2
  3. iplotx-0.11.0/gallery/basic/plot_3d.py +59 -0
  4. iplotx-0.11.0/iplotx/art3d/edge/__init__.py +147 -0
  5. iplotx-0.11.0/iplotx/art3d/edge/arrow.py +115 -0
  6. iplotx-0.10.0/iplotx/edge/geometry3d.py → iplotx-0.11.0/iplotx/art3d/edge/geometry.py +6 -37
  7. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/art3d/vertex.py +20 -2
  8. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/edge/__init__.py +20 -46
  9. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/edge/arrow.py +27 -4
  10. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/edge/geometry.py +6 -29
  11. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/label.py +10 -0
  12. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/network.py +2 -4
  13. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/version.py +1 -1
  14. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/vertex.py +10 -2
  15. iplotx-0.11.0/tests/baseline_images/test_igraph_3d/directed.png +0 -0
  16. iplotx-0.11.0/tests/baseline_images/test_igraph_3d/vertex_labels.png +0 -0
  17. {iplotx-0.10.0 → iplotx-0.11.0}/tests/test_igraph_3d.py +30 -0
  18. iplotx-0.10.0/gallery/basic/plot_3d.py +0 -29
  19. iplotx-0.10.0/iplotx/art3d/edge.py +0 -65
  20. iplotx-0.10.0/tests/test_3d_mock.py +0 -87
  21. {iplotx-0.10.0 → iplotx-0.11.0}/.github/workflows/publish.yml +0 -0
  22. {iplotx-0.10.0 → iplotx-0.11.0}/.github/workflows/test.yml +0 -0
  23. {iplotx-0.10.0 → iplotx-0.11.0}/.gitignore +0 -0
  24. {iplotx-0.10.0 → iplotx-0.11.0}/.pre-commit-config.yaml +0 -0
  25. {iplotx-0.10.0 → iplotx-0.11.0}/.readthedocs.yaml +0 -0
  26. {iplotx-0.10.0 → iplotx-0.11.0}/LICENSE +0 -0
  27. {iplotx-0.10.0 → iplotx-0.11.0}/MANIFEST.in +0 -0
  28. {iplotx-0.10.0 → iplotx-0.11.0}/README.md +0 -0
  29. {iplotx-0.10.0 → iplotx-0.11.0}/assets/pylint.svg +0 -0
  30. {iplotx-0.10.0 → iplotx-0.11.0}/docs/Makefile +0 -0
  31. {iplotx-0.10.0 → iplotx-0.11.0}/docs/make.bat +0 -0
  32. {iplotx-0.10.0 → iplotx-0.11.0}/docs/source/_static/banner.png +0 -0
  33. {iplotx-0.10.0 → iplotx-0.11.0}/docs/source/_static/graph_basic.png +0 -0
  34. {iplotx-0.10.0 → iplotx-0.11.0}/docs/source/_templates/layout.html +0 -0
  35. {iplotx-0.10.0 → iplotx-0.11.0}/docs/source/api/artists.md +0 -0
  36. {iplotx-0.10.0 → iplotx-0.11.0}/docs/source/api/complete_style_specification.md +0 -0
  37. {iplotx-0.10.0 → iplotx-0.11.0}/docs/source/api/plotting.md +0 -0
  38. {iplotx-0.10.0 → iplotx-0.11.0}/docs/source/api/providers.md +0 -0
  39. {iplotx-0.10.0 → iplotx-0.11.0}/docs/source/api/style.md +0 -0
  40. {iplotx-0.10.0 → iplotx-0.11.0}/docs/source/api.md +0 -0
  41. {iplotx-0.10.0 → iplotx-0.11.0}/docs/source/conf.py +0 -0
  42. {iplotx-0.10.0 → iplotx-0.11.0}/docs/source/images/sphx_glr_plot_basic_001.png +0 -0
  43. {iplotx-0.10.0 → iplotx-0.11.0}/docs/source/images/thumb/sphx_glr_plot_basic_thumb.png +0 -0
  44. {iplotx-0.10.0 → iplotx-0.11.0}/docs/source/index.md +0 -0
  45. {iplotx-0.10.0 → iplotx-0.11.0}/docs/source/providers.md +0 -0
  46. {iplotx-0.10.0 → iplotx-0.11.0}/docs/source/style.md +0 -0
  47. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/GALLERY_HEADER.rst +0 -0
  48. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/basic/GALLERY_HEADER.rst +0 -0
  49. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/basic/plot_basic.py +0 -0
  50. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/basic/plot_big_curves.py +0 -0
  51. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/basic/plot_dag.py +0 -0
  52. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/basic/plot_directed.py +0 -0
  53. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/basic/plot_grouping.py +0 -0
  54. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/basic/plot_house.py +0 -0
  55. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/basic/plot_loops.py +0 -0
  56. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/basic/plot_simple_path.py +0 -0
  57. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/biology/GALLERY_HEADER.rst +0 -0
  58. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/biology/data/80201010000000001.mst +0 -0
  59. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/biology/data/GN-tree.json +0 -0
  60. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/biology/data/breast_cancer_string_interactions_short.tsv +0 -0
  61. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/biology/data/breast_cancer_string_network_coordinates.tsv +0 -0
  62. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/biology/data/cell_cycle_arrest_string_interactions_short.tsv +0 -0
  63. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/biology/data/cell_cycle_arrest_string_network_coordinates.tsv +0 -0
  64. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/biology/data/fevo-08-588430_DataSheet1_S1.csv +0 -0
  65. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/biology/plot_animal_phylogeny.py +0 -0
  66. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/biology/plot_antibody_clone.py +0 -0
  67. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/biology/plot_breast_cancer_ppi.py +0 -0
  68. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/biology/plot_cell_cycle_arrest.py +0 -0
  69. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/biology/plot_food_network.py +0 -0
  70. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/biology/plot_foraging_table.py +0 -0
  71. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/biology/plot_pollinators.py +0 -0
  72. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/biology/plot_ppi.py +0 -0
  73. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/biology/plot_tca_cycle.py +0 -0
  74. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/network_science/GALLERY_HEADER.rst +0 -0
  75. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/network_science/data/chess_masters_WCC.pgn.bz2 +0 -0
  76. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/network_science/data/knuth_miles.txt.gz +0 -0
  77. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/network_science/plot_arrowlawn.py +0 -0
  78. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/network_science/plot_chess_masters.py +0 -0
  79. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/network_science/plot_cliques.py +0 -0
  80. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/network_science/plot_cluster_layout.py +0 -0
  81. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/network_science/plot_company_structure.py +0 -0
  82. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/network_science/plot_complex.py +0 -0
  83. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/network_science/plot_financial_network.py +0 -0
  84. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/network_science/plot_knuth_miles.py +0 -0
  85. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/network_science/plot_labels_and_colors.py +0 -0
  86. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/network_science/plot_max_bipartite_matching.py +0 -0
  87. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/network_science/plot_minimum_spanning_trees.py +0 -0
  88. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/network_science/plot_multipartite_layout.py +0 -0
  89. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/network_science/plot_parallel_igraph_networkx.py +0 -0
  90. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/network_science/plot_redblack.py +0 -0
  91. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/network_science/plot_shortest_path.py +0 -0
  92. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/network_science/plot_simple_networkx.py +0 -0
  93. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/network_science/plot_social_network_circles.py +0 -0
  94. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/network_science/plot_traveling_salesman.py +0 -0
  95. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/network_science/plot_with_colorbar.py +0 -0
  96. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/other/GALLERY_HEADER.rst +0 -0
  97. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/other/plot_animation.py +0 -0
  98. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/other/plot_edit_artists.py +0 -0
  99. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/other/plot_feedbacks.py +0 -0
  100. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/other/plot_graph.py +0 -0
  101. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/other/plot_mouse_hover.py +0 -0
  102. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/other/plot_train.py +0 -0
  103. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/style/GALLERY_HEADER.rst +0 -0
  104. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/style/plot_arrows.py +0 -0
  105. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/style/plot_edgepadding.py +0 -0
  106. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/style/plot_elements.py +0 -0
  107. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/style/plot_four_grids.py +0 -0
  108. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/style/plot_halfarrows.py +0 -0
  109. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/style/plot_multistyle.py +0 -0
  110. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/style/plot_ports.py +0 -0
  111. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/style/plot_style.py +0 -0
  112. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/style/plot_tension.py +0 -0
  113. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/style/plot_vertexmarkers.py +0 -0
  114. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/style/plot_voronoi.py +0 -0
  115. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/style/plot_waypoints.py +0 -0
  116. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/tree/GALLERY_HEADER.rst +0 -0
  117. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/tree/data/tree-with-support.json +0 -0
  118. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/tree/plot_angular_waypoints.py +0 -0
  119. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/tree/plot_biopython_tree.py +0 -0
  120. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/tree/plot_cladeedges.py +0 -0
  121. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/tree/plot_cogent3_layouts.py +0 -0
  122. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/tree/plot_cogent3_tree.py +0 -0
  123. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/tree/plot_elements_tree.py +0 -0
  124. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/tree/plot_ete4.py +0 -0
  125. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/tree/plot_leafedges.py +0 -0
  126. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/tree/plot_leafedges_and_cascades.py +0 -0
  127. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/tree/plot_skbio_tree.py +0 -0
  128. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/tree/plot_split_edges.py +0 -0
  129. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/tree/plot_style_tree.py +0 -0
  130. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/tree/plot_support.py +0 -0
  131. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/tree/plot_tree_node_background.py +0 -0
  132. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/tree/plot_tree_style_clades.py +0 -0
  133. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/tree/plot_trees_of_trees.py +0 -0
  134. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/zero_dependency/GALLERY_HEADER.rst +0 -0
  135. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/zero_dependency/plot_simplenetworkdataprovider.py +0 -0
  136. {iplotx-0.10.0 → iplotx-0.11.0}/gallery/zero_dependency/plot_simpletreedataprovider.py +0 -0
  137. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/__init__.py +0 -0
  138. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/artists.py +0 -0
  139. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/cascades.py +0 -0
  140. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/edge/leaf.py +0 -0
  141. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/edge/ports.py +0 -0
  142. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/groups.py +0 -0
  143. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/ingest/__init__.py +0 -0
  144. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/ingest/heuristics.py +0 -0
  145. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/ingest/providers/network/igraph.py +0 -0
  146. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/ingest/providers/network/networkx.py +0 -0
  147. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/ingest/providers/network/simple.py +0 -0
  148. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/ingest/providers/tree/biopython.py +0 -0
  149. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/ingest/providers/tree/cogent3.py +0 -0
  150. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/ingest/providers/tree/ete4.py +0 -0
  151. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/ingest/providers/tree/simple.py +0 -0
  152. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/ingest/providers/tree/skbio.py +0 -0
  153. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/ingest/typing.py +0 -0
  154. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/layout.py +0 -0
  155. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/plotting.py +0 -0
  156. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/style/__init__.py +0 -0
  157. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/style/leaf_info.py +0 -0
  158. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/style/library.py +0 -0
  159. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/tree.py +0 -0
  160. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/typing.py +0 -0
  161. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/utils/geometry.py +0 -0
  162. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/utils/internal.py +0 -0
  163. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/utils/matplotlib.py +0 -0
  164. {iplotx-0.10.0 → iplotx-0.11.0}/iplotx/utils/style.py +0 -0
  165. {iplotx-0.10.0 → iplotx-0.11.0}/pyproject.toml +0 -0
  166. {iplotx-0.10.0 → iplotx-0.11.0}/scripts/copy_github_release_into_version.sh +0 -0
  167. {iplotx-0.10.0 → iplotx-0.11.0}/scripts/make_banner.py +0 -0
  168. {iplotx-0.10.0 → iplotx-0.11.0}/scripts/update_pylint_badge.sh +0 -0
  169. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_biopython/cascades.png +0 -0
  170. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_biopython/directed_child.png +0 -0
  171. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_biopython/leaf_labels.png +0 -0
  172. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_biopython/leaf_labels_hmargin.png +0 -0
  173. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_biopython/leafedges.png +0 -0
  174. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_biopython/show_support.png +0 -0
  175. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_biopython/tree_basic.png +0 -0
  176. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_biopython/tree_radial.png +0 -0
  177. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_cogent3/leaf_labels.png +0 -0
  178. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_cogent3/leaf_labels_hmargin.png +0 -0
  179. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_cogent3/tree_basic.png +0 -0
  180. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_cogent3/tree_radial.png +0 -0
  181. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_ete4/leaf_labels.png +0 -0
  182. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_ete4/leaf_labels_hmargin.png +0 -0
  183. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_ete4/split_edges.png +0 -0
  184. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_ete4/tree_basic.png +0 -0
  185. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_ete4/tree_radial.png +0 -0
  186. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_igraph/clustering_directed.png +0 -0
  187. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_igraph/clustering_directed_large.png +0 -0
  188. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_igraph/graph_basic.png +0 -0
  189. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_igraph/graph_directed.png +0 -0
  190. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_igraph/graph_directed_curved_loops.png +0 -0
  191. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_igraph/graph_edit_children.png +0 -0
  192. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_igraph/graph_labels.png +0 -0
  193. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_igraph/graph_layout_attribute.png +0 -0
  194. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_igraph/graph_null.png +0 -0
  195. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_igraph/graph_squares_directed.png +0 -0
  196. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_igraph/graph_vertexsize.png +0 -0
  197. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_igraph/graph_with_curved_edges.png +0 -0
  198. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_igraph/igraph_layout_object.png +0 -0
  199. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_igraph/multigraph_with_curved_edges_undirected.png +0 -0
  200. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_igraph_3d/undirected.png +0 -0
  201. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_networkx/cluster-layout.png +0 -0
  202. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_networkx/complex.png +0 -0
  203. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_networkx/complex_rotatelabels.png +0 -0
  204. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_networkx/directed_graph.png +0 -0
  205. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_networkx/directed_graph_with_colorbar.png +0 -0
  206. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_networkx/empty_graph.png +0 -0
  207. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_networkx/flat_style.png +0 -0
  208. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_networkx/house_with_colors.png +0 -0
  209. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_networkx/labels_and_colors.png +0 -0
  210. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_networkx/shortest_path.png +0 -0
  211. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_networkx/simple_graph.png +0 -0
  212. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_simple_network_provider/graph_basic.png +0 -0
  213. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_simple_network_provider/graph_directed.png +0 -0
  214. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_simple_network_provider/graph_labels.png +0 -0
  215. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_skbio/leaf_labels.png +0 -0
  216. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_skbio/leaf_labels_hmargin.png +0 -0
  217. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_skbio/tree_basic.png +0 -0
  218. {iplotx-0.10.0 → iplotx-0.11.0}/tests/baseline_images/test_skbio/tree_radial.png +0 -0
  219. {iplotx-0.10.0 → iplotx-0.11.0}/tests/test_arrows.py +0 -0
  220. {iplotx-0.10.0 → iplotx-0.11.0}/tests/test_biopython.py +0 -0
  221. {iplotx-0.10.0 → iplotx-0.11.0}/tests/test_cascades.py +0 -0
  222. {iplotx-0.10.0 → iplotx-0.11.0}/tests/test_cogent3.py +0 -0
  223. {iplotx-0.10.0 → iplotx-0.11.0}/tests/test_edge.py +0 -0
  224. {iplotx-0.10.0 → iplotx-0.11.0}/tests/test_edge_geometry.py +0 -0
  225. {iplotx-0.10.0 → iplotx-0.11.0}/tests/test_ete4.py +0 -0
  226. {iplotx-0.10.0 → iplotx-0.11.0}/tests/test_geometry.py +0 -0
  227. {iplotx-0.10.0 → iplotx-0.11.0}/tests/test_heuristics.py +0 -0
  228. {iplotx-0.10.0 → iplotx-0.11.0}/tests/test_igraph.py +0 -0
  229. {iplotx-0.10.0 → iplotx-0.11.0}/tests/test_ingest_protocols.py +0 -0
  230. {iplotx-0.10.0 → iplotx-0.11.0}/tests/test_matplotlib_utils.py +0 -0
  231. {iplotx-0.10.0 → iplotx-0.11.0}/tests/test_network_hotload.py +0 -0
  232. {iplotx-0.10.0 → iplotx-0.11.0}/tests/test_networkx.py +0 -0
  233. {iplotx-0.10.0 → iplotx-0.11.0}/tests/test_ports.py +0 -0
  234. {iplotx-0.10.0 → iplotx-0.11.0}/tests/test_simple_network_provider.py +0 -0
  235. {iplotx-0.10.0 → iplotx-0.11.0}/tests/test_simple_tree_provider.py +0 -0
  236. {iplotx-0.10.0 → iplotx-0.11.0}/tests/test_skbio.py +0 -0
  237. {iplotx-0.10.0 → iplotx-0.11.0}/tests/test_style.py +0 -0
  238. {iplotx-0.10.0 → iplotx-0.11.0}/tests/test_vertex.py +0 -0
  239. {iplotx-0.10.0 → iplotx-0.11.0}/tests/utils.py +0 -0
  240. {iplotx-0.10.0 → iplotx-0.11.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iplotx
3
- Version: 0.10.0
3
+ Version: 0.11.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
@@ -6,7 +6,7 @@
6
6
 
7
7
  Computation times
8
8
  =================
9
- **00:00.060** total execution time for 73 files **from all galleries**:
9
+ **00:00.114** total execution time for 73 files **from all galleries**:
10
10
 
11
11
  .. container::
12
12
 
@@ -33,7 +33,7 @@ Computation times
33
33
  - Time
34
34
  - Mem (MB)
35
35
  * - :ref:`sphx_glr_gallery_basic_plot_3d.py` (``../../gallery/basic/plot_3d.py``)
36
- - 00:00.060
36
+ - 00:00.114
37
37
  - 0.0
38
38
  * - :ref:`sphx_glr_gallery_basic_plot_basic.py` (``../../gallery/basic/plot_basic.py``)
39
39
  - 00:00.000
@@ -0,0 +1,59 @@
1
+ """
2
+ 3D layouts
3
+ ==========
4
+
5
+ This example shows how to visualise graphs or networks in 3D using `iplotx`. Of course, a 3D layout is needed
6
+ for this. Here, we use the Fruchterman-Reingold layout algorithm from ``igraph`` to generate a 3D layout.
7
+
8
+ .. note::
9
+ 3D visualisation is most useful when used **interactively**, so you can rotate and pan the plot to inspect
10
+ it from different angles. Matplotlib supports this both in Jupyter notebooks and in IPython via
11
+ multiple interactive backends (e.g., TkAgg, Qt5Agg, etc.). These plots can also be saved as static
12
+ images (the ones you see below were generated this way), however these static images can be quite
13
+ difficult to interpret.
14
+ """
15
+
16
+ import igraph as ig
17
+ import iplotx as ipx
18
+
19
+ # Make the graph
20
+ g = ig.Graph.Erdos_Renyi(30, m=50)
21
+
22
+ # Make a 3D layout
23
+ layout = g.layout_fruchterman_reingold_3d()
24
+
25
+ # Visualise the graph in 3D
26
+ ipx.network(
27
+ g,
28
+ layout,
29
+ vertex_alpha=0.7,
30
+ edge_alpha=0.4,
31
+ figsize=(8, 8),
32
+ )
33
+
34
+ # %%
35
+ # Below is a variation using arrows and vertex labels:
36
+
37
+ import igraph as ig
38
+ import iplotx as ipx
39
+
40
+ # Make the graph
41
+ g = ig.Graph.Erdos_Renyi(30, m=50, directed=True)
42
+
43
+ # Make a 3D layout
44
+ layout = g.layout_fruchterman_reingold_3d()
45
+
46
+ # Visualise the graph in 3D
47
+ ipx.network(
48
+ g,
49
+ layout,
50
+ vertex_alpha=0.3,
51
+ edge_alpha=0.5,
52
+ vertex_labels=True,
53
+ figsize=(8, 8),
54
+ )
55
+
56
+ # %%
57
+ # .. warning::
58
+ # 3D visualisation does not support all features of 2D visualisation yet. Curved edges, waypoints, and edge
59
+ # labels are currently unsupported. PRs are welcome!
@@ -0,0 +1,147 @@
1
+ """
2
+ Module containing code to manipulate edge visualisations in 3D, especially the Edge3DCollection class.
3
+ """
4
+
5
+ from mpl_toolkits.mplot3d import Axes3D
6
+ from mpl_toolkits.mplot3d.art3d import (
7
+ Line3DCollection,
8
+ )
9
+
10
+ from ...utils.matplotlib import (
11
+ _forwarder,
12
+ )
13
+ from ...edge import (
14
+ EdgeCollection,
15
+ )
16
+ from .arrow import (
17
+ arrow_collection_2d_to_3d,
18
+ )
19
+ from .geometry import (
20
+ _compute_edge_segments as _compute_single_edge_segments,
21
+ )
22
+
23
+
24
+ @_forwarder(
25
+ (
26
+ "set_clip_path",
27
+ "set_clip_box",
28
+ "set_snap",
29
+ "set_sketch_params",
30
+ "set_animated",
31
+ "set_picker",
32
+ )
33
+ )
34
+ class Edge3DCollection(Line3DCollection):
35
+ """Collection of vertex patches for plotting."""
36
+
37
+ def get_children(self) -> tuple:
38
+ children = []
39
+ if hasattr(self, "_subedges"):
40
+ children.append(self._subedges)
41
+ if hasattr(self, "_arrows"):
42
+ children.append(self._arrows)
43
+ if hasattr(self, "_label_collection"):
44
+ children.append(self._label_collection)
45
+ return tuple(children)
46
+
47
+ def set_figure(self, fig) -> None:
48
+ super().set_figure(fig)
49
+ for child in self.get_children():
50
+ child.set_figure(fig)
51
+
52
+ @property
53
+ def axes(self):
54
+ return Line3DCollection.axes.__get__(self)
55
+
56
+ @axes.setter
57
+ def axes(self, new_axes):
58
+ Line3DCollection.axes.__set__(self, new_axes)
59
+ for child in self.get_children():
60
+ child.axes = new_axes
61
+
62
+ _get_adjacent_vertices_info = EdgeCollection._get_adjacent_vertices_info
63
+
64
+ def _compute_edge_segments(self):
65
+ """Compute the edge segments for all edges."""
66
+ vinfo = self._get_adjacent_vertices_info()
67
+
68
+ segments3d = []
69
+ for vcoord_data in vinfo["offsets"]:
70
+ segment = _compute_single_edge_segments(
71
+ vcoord_data,
72
+ )
73
+ segments3d.append(segment)
74
+ self.set_segments(segments3d)
75
+
76
+ def _update_before_draw(self) -> None:
77
+ """Update the collection before drawing."""
78
+ if isinstance(self.axes, Axes3D) and hasattr(self, "do_3d_projection"):
79
+ self.do_3d_projection()
80
+
81
+ # TODO: Here's where we would shorten the edges to fit the vertex
82
+ # projections from 3D onto 2D, if we wanted to do that. Because edges
83
+ # in 3D are chains of segments rathen than splines, the shortening
84
+ # needs to be done in a different way to how it's done in 2D.
85
+
86
+ def draw(self, renderer) -> None:
87
+ """Draw the collection of vertices in 3D.
88
+
89
+ Parameters:
90
+ renderer: The renderer to use for drawing.
91
+ """
92
+ # Prepare the collection for drawing
93
+ self._update_before_draw()
94
+
95
+ # Render the Line3DCollection
96
+ # NOTE: we are NOT calling EdgeCollection.draw here
97
+ super().draw(renderer)
98
+
99
+ # This sets the labels offsets
100
+ # TODO: implement labels in 3D (one could copy the function from 2D,
101
+ # but would also need to promote the 2D labels into 3D labels similarly to
102
+ # how it's done for 3D vertices).
103
+ # self._update_labels()
104
+
105
+ # Now attempt to draw the arrows
106
+ for child in self.get_children():
107
+ child.draw(renderer)
108
+
109
+
110
+ def edge_collection_2d_to_3d(
111
+ col: EdgeCollection,
112
+ zdir: str = "z",
113
+ axlim_clip: bool = False,
114
+ ):
115
+ """Convert a 2D EdgeCollection to a 3D Edge3DCollection.
116
+
117
+ Parameters:
118
+ col: The 2D EdgeCollection to convert.
119
+ zs: The z coordinate(s) to use for the 3D vertices.
120
+ zdir: The axis to use as the z axis (default is "z").
121
+ depthshade: Whether to apply depth shading (default is True).
122
+ axlim_clip: Whether to clip the vertices to the axes limits (default is False).
123
+ """
124
+ if not isinstance(col, EdgeCollection):
125
+ raise TypeError("vertices must be a VertexCollection")
126
+
127
+ # NOTE: after this line, none of the EdgeCollection methods will work
128
+ # It's become a static drawer now. It uses segments instead of paths.
129
+ col.__class__ = Edge3DCollection
130
+ col._compute_edge_segments()
131
+
132
+ col._axlim_clip = axlim_clip
133
+
134
+ # Convert the arrow collection if present
135
+ if hasattr(col, "_arrows"):
136
+ segments3d = col._segments3d
137
+
138
+ # Fix the x and y to the center of the target vertex (for now)
139
+ col._arrows._offsets[:] = [segment[-1][:2] for segment in segments3d]
140
+ zs = [segment[-1][2] for segment in segments3d]
141
+ arrow_collection_2d_to_3d(
142
+ col._arrows,
143
+ zs=zs,
144
+ zdir=zdir,
145
+ depthshade=False,
146
+ axlim_clip=axlim_clip,
147
+ )
@@ -0,0 +1,115 @@
1
+ """
2
+ Module containing code to manipulate arrow visualisations in 3D, especially the EdgeArrow3DCollection class.
3
+ """
4
+
5
+ from typing import (
6
+ Sequence,
7
+ )
8
+ from math import atan2, cos, sin
9
+ import numpy as np
10
+ from matplotlib import (
11
+ cbook,
12
+ )
13
+ from mpl_toolkits.mplot3d import Axes3D
14
+ from mpl_toolkits.mplot3d.art3d import (
15
+ Path3DCollection,
16
+ )
17
+
18
+ from ...utils.matplotlib import (
19
+ _forwarder,
20
+ )
21
+ from ...edge.arrow import (
22
+ EdgeArrowCollection,
23
+ )
24
+
25
+
26
+ @_forwarder(
27
+ (
28
+ "set_clip_path",
29
+ "set_clip_box",
30
+ "set_snap",
31
+ "set_sketch_params",
32
+ "set_animated",
33
+ "set_picker",
34
+ )
35
+ )
36
+ class EdgeArrow3DCollection(EdgeArrowCollection, Path3DCollection):
37
+ """Collection of vertex patches for plotting."""
38
+
39
+ def _update_before_draw(self) -> None:
40
+ """Update the collection before drawing."""
41
+ if (
42
+ isinstance(self.axes, Axes3D)
43
+ and hasattr(self, "do_3d_projection")
44
+ and (self.axes.M is not None)
45
+ ):
46
+ self.do_3d_projection()
47
+
48
+ # The original EdgeArrowCollection method for
49
+ # _update_before_draw cannot be used because it
50
+ # relies on paths, whereas edges are now a
51
+ # Line3DCollection which uses segments.
52
+ self.set_sizes(self._sizes, self.get_figure(root=True).dpi)
53
+
54
+ if (not hasattr(self, "_z_markers_idx")) or (
55
+ not isinstance(self._z_markers_idx, np.ndarray)
56
+ ):
57
+ return
58
+
59
+ trans = self.get_offset_transform().transform
60
+
61
+ # The do_3d_projection method above reorders the
62
+ # arrow offsets in some way, so we might have to figure out
63
+ # what edge index corres
64
+ for i, ie in enumerate(self._z_markers_idx):
65
+ segments_2d = self._edge_collection.get_segments()[ie]
66
+
67
+ # We could reset the 3d projection here, might be a way to
68
+ # skip the function call above.
69
+ v2 = trans(segments_2d[-1])
70
+ v1 = trans(segments_2d[-2])
71
+ dv = v2 - v1
72
+ theta = atan2(*(dv[::-1]))
73
+ theta_old = self._angles[i]
74
+ dtheta = theta - theta_old
75
+ mrot = np.array([[cos(dtheta), sin(dtheta)], [-sin(dtheta), cos(dtheta)]])
76
+
77
+ apath = self._paths[i]
78
+ apath.vertices = apath.vertices @ mrot
79
+ self._angles[i] = theta
80
+
81
+ def draw(self, renderer) -> None:
82
+ """Draw the collection of vertices in 3D.
83
+
84
+ Parameters:
85
+ renderer: The renderer to use for drawing.
86
+ """
87
+ with self._use_zordered_offset():
88
+ with cbook._setattr_cm(self, _in_draw=True):
89
+ EdgeArrowCollection.draw(self, renderer)
90
+
91
+
92
+ def arrow_collection_2d_to_3d(
93
+ col: EdgeArrowCollection,
94
+ zs: np.ndarray | float | Sequence[float] = 0,
95
+ zdir: str = "z",
96
+ depthshade: bool = True,
97
+ axlim_clip: bool = False,
98
+ ):
99
+ """Convert a 2D EdgeArrowCollection to a 3D EdgeArrow3DCollection.
100
+
101
+ Parameters:
102
+ col: The 2D EdgeArrowCollection to convert.
103
+ zs: The z coordinate(s) to use for the 3D vertices.
104
+ zdir: The axis to use as the z axis (default is "z").
105
+ depthshade: Whether to apply depth shading (default is True).
106
+ axlim_clip: Whether to clip the vertices to the axes limits (default is False).
107
+ """
108
+ if not isinstance(col, EdgeArrowCollection):
109
+ raise TypeError("vertices must be a EdgeArrowCollection")
110
+
111
+ col.__class__ = EdgeArrow3DCollection
112
+ col._offset_zordered = None
113
+ col._depthshade = depthshade
114
+ col._in_draw = False
115
+ col.set_3d_properties(zs, zdir, axlim_clip)
@@ -7,19 +7,14 @@ from typing import (
7
7
  Sequence,
8
8
  )
9
9
  import numpy as np
10
- import matplotlib as mpl
11
10
 
12
- from ..typing import (
11
+ from ...typing import (
13
12
  Pair,
14
13
  )
15
14
 
16
15
 
17
- def _compute_edge_path_straight(
16
+ def _compute_edge_segments_straight(
18
17
  vcoord_data,
19
- vpath_fig,
20
- vsize_fig,
21
- trans,
22
- trans_inv,
23
18
  layout_coordinate_system: str = "cartesian",
24
19
  shrink: float = 0,
25
20
  **kwargs,
@@ -45,37 +40,11 @@ def _compute_edge_path_straight(
45
40
  f"Layout coordinate system not supported for straight edges in 3D: {layout_coordinate_system}.",
46
41
  )
47
42
 
48
- vcoord_data_cart = vcoord_data
43
+ segments = [vcoord_data[0], vcoord_data[1]]
44
+ return segments
49
45
 
50
- # Coordinates in figure (default) coords
51
- vcoord_fig = trans(vcoord_data_cart)
52
46
 
53
- points = []
54
-
55
- # Angles of the straight line
56
- # FIXME: In 2D, this is only used to make space for loops
57
- # let's ignore for now
58
- # theta = atan2(*((vcoord_fig[1] - vcoord_fig[0])[::-1]))
59
- theta = 0
60
-
61
- # TODO: Shorten at starting vertex (?)
62
- vs = vcoord_fig[0]
63
- points.append(vs)
64
-
65
- # TODO: Shorten at end vertex (?)
66
- ve = vcoord_fig[1]
67
- points.append(ve)
68
-
69
- codes = ["MOVETO", "LINETO"]
70
- path = mpl.path.Path(
71
- points,
72
- codes=[getattr(mpl.path.Path, x) for x in codes],
73
- )
74
- path.vertices = trans_inv(path.vertices)
75
- return path, (theta, theta + np.pi)
76
-
77
-
78
- def _compute_edge_path_3d(
47
+ def _compute_edge_segments(
79
48
  *args,
80
49
  tension: float = 0,
81
50
  waypoints: str | tuple[float, float] | Sequence[tuple[float, float]] | np.ndarray = "none",
@@ -98,7 +67,7 @@ def _compute_edge_path_3d(
98
67
  # )
99
68
 
100
69
  if np.isscalar(tension) and (tension == 0):
101
- return _compute_edge_path_straight(
70
+ return _compute_edge_segments_straight(
102
71
  *args,
103
72
  layout_coordinate_system=layout_coordinate_system,
104
73
  **kwargs,
@@ -9,7 +9,11 @@ import numpy as np
9
9
  from matplotlib import (
10
10
  cbook,
11
11
  )
12
- from mpl_toolkits.mplot3d.art3d import Path3DCollection
12
+ from mpl_toolkits.mplot3d import Axes3D
13
+ from mpl_toolkits.mplot3d.art3d import (
14
+ Path3DCollection,
15
+ text_2d_to_3d,
16
+ )
13
17
 
14
18
  from ..utils.matplotlib import (
15
19
  _forwarder,
@@ -32,6 +36,14 @@ from ..vertex import (
32
36
  class Vertex3DCollection(VertexCollection, Path3DCollection):
33
37
  """Collection of vertex patches for plotting."""
34
38
 
39
+ def _update_before_draw(self) -> None:
40
+ """Update the collection before drawing."""
41
+ # Set the sizes according to the current figure dpi
42
+ VertexCollection._update_before_draw(self)
43
+
44
+ if isinstance(self.axes, Axes3D) and hasattr(self, "do_3d_projection"):
45
+ self.do_3d_projection()
46
+
35
47
  def draw(self, renderer) -> None:
36
48
  """Draw the collection of vertices in 3D.
37
49
 
@@ -56,7 +68,7 @@ def vertex_collection_2d_to_3d(
56
68
  col: The 2D VertexCollection to convert.
57
69
  zs: The z coordinate(s) to use for the 3D vertices.
58
70
  zdir: The axis to use as the z axis (default is "z").
59
- depthshade: Whether to apply depth shading (default is True).
71
+ depthshade: Whether to aply depth shading (default is True).
60
72
  axlim_clip: Whether to clip the vertices to the axes limits (default is False).
61
73
  """
62
74
  if not isinstance(col, VertexCollection):
@@ -67,3 +79,9 @@ def vertex_collection_2d_to_3d(
67
79
  col._depthshade = depthshade
68
80
  col._in_draw = False
69
81
  col.set_3d_properties(zs, zdir, axlim_clip)
82
+
83
+ # Labels if present
84
+ if col.get_labels() is not None:
85
+ for z, art in zip(zs, col.get_labels()._labelartists):
86
+ # zdir=None means the text is always horizontal facing the camera
87
+ text_2d_to_3d(art, z, zdir=None, axlim_clip=axlim_clip)
@@ -9,7 +9,7 @@ from typing import (
9
9
  Optional,
10
10
  Any,
11
11
  )
12
- from math import atan2, cos, pi, sin
12
+ from math import pi
13
13
  from collections import defaultdict
14
14
  import numpy as np
15
15
  import pandas as pd
@@ -181,17 +181,23 @@ class EdgeCollection(mpl.collections.PatchCollection):
181
181
 
182
182
  def set_figure(self, fig) -> None:
183
183
  super().set_figure(fig)
184
- self._update_paths()
184
+ self._update_before_draw()
185
185
  # NOTE: This sets the correct offsets in the arrows,
186
186
  # but not the correct sizes (see below)
187
- self._update_children()
187
+ self._update_labels()
188
188
  for child in self.get_children():
189
189
  # NOTE: This sets the sizes with correct dpi scaling in the arrows
190
190
  child.set_figure(fig)
191
191
 
192
- def _update_children(self):
193
- self._update_arrows()
194
- self._update_labels()
192
+ @property
193
+ def axes(self):
194
+ return mpl.artist.Artist.axes.__get__(self)
195
+
196
+ @axes.setter
197
+ def axes(self, new_axes):
198
+ mpl.artist.Artist.axes.__set__(self, new_axes)
199
+ for child in self.get_children():
200
+ child.axes = new_axes
195
201
 
196
202
  def set_transform(self, transform: mpl.transforms.Transform) -> None:
197
203
  """Set the transform for the edges and their children."""
@@ -303,7 +309,7 @@ class EdgeCollection(mpl.collections.PatchCollection):
303
309
  "sizes": vsizes,
304
310
  }
305
311
 
306
- def _update_paths(self, transform=None):
312
+ def _update_before_draw(self, transform=None):
307
313
  """Compute paths for the edges.
308
314
 
309
315
  Loops split the largest wedge left open by other
@@ -512,42 +518,6 @@ class EdgeCollection(mpl.collections.PatchCollection):
512
518
  if not style.get("rotate", True):
513
519
  self._label_collection.set_rotations(rotations)
514
520
 
515
- def _update_arrows(
516
- self,
517
- ) -> None:
518
- """Extract the start and/or end angles of the paths to compute arrows.
519
-
520
- Parameters:
521
- which: Which end of the edge to put an arrow on. Currently only "end" is accepted.
522
-
523
- NOTE: This function does *not* update the arrow sizes/_transforms to the correct dpi
524
- scaling. That's ok since the correct dpi scaling is set whenever there is a different
525
- figure (before first draw) and whenever a draw is called.
526
- """
527
- if not hasattr(self, "_arrows"):
528
- return
529
-
530
- transform = self.get_transform()
531
- trans = transform.transform
532
-
533
- for i, epath in enumerate(self.get_paths()):
534
- # Offset the arrow to point to the end of the edge
535
- self._arrows._offsets[i] = epath.vertices[-1]
536
-
537
- # Rotate the arrow to point in the direction of the edge
538
- apath = self._arrows._paths[i]
539
- # NOTE: because the tip of the arrow is at (0, 0) in patch space,
540
- # in theory it will rotate around that point already
541
- v2 = trans(epath.vertices[-1])
542
- v1 = trans(epath.vertices[-2])
543
- dv = v2 - v1
544
- theta = atan2(*(dv[::-1]))
545
- theta_old = self._arrows._angles[i]
546
- dtheta = theta - theta_old
547
- mrot = np.array([[cos(dtheta), sin(dtheta)], [-sin(dtheta), cos(dtheta)]])
548
- apath.vertices = apath.vertices @ mrot
549
- self._arrows._angles[i] = theta
550
-
551
521
  @_stale_wrapper
552
522
  def draw(self, renderer):
553
523
  # Visibility affects the children too
@@ -555,11 +525,15 @@ class EdgeCollection(mpl.collections.PatchCollection):
555
525
  return
556
526
 
557
527
  # This includes the subedges if present
558
- self._update_paths()
559
- # This sets the arrow offsets
560
- self._update_children()
528
+ self._update_before_draw()
561
529
 
530
+ # Now you can draw the edges
562
531
  super().draw(renderer)
532
+
533
+ # This sets the labels offsets
534
+ self._update_labels()
535
+
536
+ # Now you can draw arrows and labels
563
537
  for child in self.get_children():
564
538
  child.draw(renderer)
565
539
 
@@ -4,6 +4,7 @@ Module for edge arrows in iplotx.
4
4
 
5
5
  from typing import Never, Optional
6
6
 
7
+ from math import atan2, cos, sin
7
8
  import numpy as np
8
9
  import matplotlib as mpl
9
10
  from matplotlib.patches import PathPatch
@@ -91,9 +92,7 @@ class EdgeArrowCollection(mpl.collections.PatchCollection):
91
92
  def set_figure(self, fig) -> None:
92
93
  """Set the figure for this artist and all children."""
93
94
  super().set_figure(fig)
94
- self.set_sizes(self._sizes, self.get_figure(root=True).dpi)
95
- for child in self.get_children():
96
- child.set_figure(fig)
95
+ self._update_before_draw()
97
96
 
98
97
  def get_offset_transform(self):
99
98
  """Get offset transform for the edge arrows. This sets the tip of each arrow."""
@@ -126,6 +125,30 @@ class EdgeArrowCollection(mpl.collections.PatchCollection):
126
125
 
127
126
  return patches, sizes
128
127
 
128
+ def _update_before_draw(self) -> None:
129
+ """Update the arrow paths and directions before drawing, based on the edge collection."""
130
+ self.set_sizes(self._sizes, self.get_figure(root=True).dpi)
131
+
132
+ trans = self.get_offset_transform().transform
133
+
134
+ for i, epath in enumerate(self._edge_collection.get_paths()):
135
+ # Offset the arrow to point to the end of the edge
136
+ self._offsets[i] = epath.vertices[-1]
137
+
138
+ # Rotate the arrow to point in the direction of the edge
139
+ apath = self._paths[i]
140
+ # NOTE: because the tip of the arrow is at (0, 0) in patch space,
141
+ # in theory it will rotate around that point already
142
+ v2 = trans(epath.vertices[-1])
143
+ v1 = trans(epath.vertices[-2])
144
+ dv = v2 - v1
145
+ theta = atan2(*(dv[::-1]))
146
+ theta_old = self._angles[i]
147
+ dtheta = theta - theta_old
148
+ mrot = np.array([[cos(dtheta), sin(dtheta)], [-sin(dtheta), cos(dtheta)]])
149
+ apath.vertices = apath.vertices @ mrot
150
+ self._angles[i] = theta
151
+
129
152
  def set_array(self, A: np.ndarray) -> Never:
130
153
  """Set the array for cmap/norm coloring, but keep the facecolors as set (usually 'none')."""
131
154
  raise ValueError("Setting an array for arrows directly is not supported.")
@@ -145,7 +168,7 @@ class EdgeArrowCollection(mpl.collections.PatchCollection):
145
168
 
146
169
  @mpl.artist.allow_rasterization
147
170
  def draw(self, renderer):
148
- self.set_sizes(self._sizes, self.get_figure(root=True).dpi)
171
+ self._update_before_draw()
149
172
  super().draw(renderer)
150
173
 
151
174