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.
- {iplotx-1.2.1 → iplotx-1.4.0}/PKG-INFO +11 -4
- {iplotx-1.2.1 → iplotx-1.4.0}/README.md +10 -3
- {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/api/complete_style_specification.md +8 -3
- {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/code_of_conduct.rst +1 -1
- {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/sg_execution_times.rst +9 -3
- iplotx-1.4.0/gallery/basic/plot_arcs.py +42 -0
- iplotx-1.4.0/gallery/tree/plot_equalangle.py +35 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/edge/__init__.py +7 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/edge/geometry.py +113 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/providers/tree/biopython.py +2 -1
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/providers/tree/cogent3.py +4 -1
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/providers/tree/dendropy.py +10 -1
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/providers/tree/ete4.py +2 -1
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/providers/tree/simple.py +11 -1
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/providers/tree/skbio.py +4 -1
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/typing.py +49 -7
- iplotx-1.4.0/iplotx/layout/__init__.py +9 -0
- iplotx-1.4.0/iplotx/layout/tree/__init__.py +72 -0
- iplotx-1.2.1/iplotx/layout.py → iplotx-1.4.0/iplotx/layout/tree/rooted.py +3 -45
- iplotx-1.4.0/iplotx/layout/tree/unrooted.py +444 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/style/leaf_info.py +1 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/tree/__init__.py +5 -2
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/version.py +1 -1
- iplotx-1.4.0/tests/baseline_images/test_biopython/leafedges.png +0 -0
- iplotx-1.4.0/tests/baseline_images/test_biopython/tree_radial.png +0 -0
- iplotx-1.4.0/tests/baseline_images/test_cogent3/tree_radial.png +0 -0
- iplotx-1.4.0/tests/baseline_images/test_ete4/equalangle_layout.png +0 -0
- iplotx-1.4.0/tests/baseline_images/test_ete4/tree_radial.png +0 -0
- iplotx-1.4.0/tests/baseline_images/test_igraph/arcs.png +0 -0
- iplotx-1.4.0/tests/baseline_images/test_igraph/large_arcs.png +0 -0
- iplotx-1.4.0/tests/baseline_images/test_skbio/tree_radial.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_ete4.py +22 -4
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_igraph.py +29 -0
- iplotx-1.2.1/tests/baseline_images/test_biopython/leafedges.png +0 -0
- iplotx-1.2.1/tests/baseline_images/test_biopython/tree_radial.png +0 -0
- iplotx-1.2.1/tests/baseline_images/test_cogent3/tree_radial.png +0 -0
- iplotx-1.2.1/tests/baseline_images/test_ete4/tree_radial.png +0 -0
- iplotx-1.2.1/tests/baseline_images/test_skbio/tree_radial.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/.github/workflows/publish.yml +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/.github/workflows/test.yml +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/.gitignore +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/.pre-commit-config.yaml +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/.readthedocs.yaml +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/LICENSE +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/MANIFEST.in +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/assets/pylint.svg +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/docs/Makefile +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/docs/make.bat +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/_static/banner.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/_static/custom-icons.js +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/_static/custom.css +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/_static/graph_basic.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/_templates/layout.html +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/api/artists.md +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/api/plotting.md +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/api/providers.md +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/api/style.md +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/api.md +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/conf.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/images/sphx_glr_plot_basic_001.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/images/thumb/sphx_glr_plot_basic_thumb.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/index.md +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/installing.md +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/providers.md +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/docs/source/style.md +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/GALLERY_HEADER.rst +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/basic/GALLERY_HEADER.rst +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/basic/plot_3d.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/basic/plot_basic.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/basic/plot_big_curves.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/basic/plot_dag.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/basic/plot_directed.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/basic/plot_grouping.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/basic/plot_house.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/basic/plot_loops.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/basic/plot_simple_path.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/GALLERY_HEADER.rst +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/data/80201010000000001.mst +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/data/GN-tree.json +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/data/breast_cancer_string_interactions_short.tsv +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/data/breast_cancer_string_network_coordinates.tsv +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/data/cell_cycle_arrest_string_interactions_short.tsv +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/data/cell_cycle_arrest_string_network_coordinates.tsv +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/data/fevo-08-588430_DataSheet1_S1.csv +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/plot_animal_phylogeny.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/plot_antibody_clone.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/plot_breast_cancer_ppi.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/plot_cell_cycle_arrest.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/plot_food_network.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/plot_foraging_table.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/plot_pollinators.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/plot_ppi.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/biology/plot_tca_cycle.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/GALLERY_HEADER.rst +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/data/chess_masters_WCC.pgn.bz2 +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/data/knuth_miles.txt.gz +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_arrowlawn.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_chess_masters.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_cliques.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_cluster_layout.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_company_structure.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_complex.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_financial_network.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_knuth_miles.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_labels_and_colors.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_max_bipartite_matching.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_minimum_spanning_trees.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_multipartite_layout.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_parallel_igraph_networkx.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_redblack.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_shortest_path.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_simple_networkx.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_social_network_circles.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_traveling_salesman.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/network_science/plot_with_colorbar.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/other/GALLERY_HEADER.rst +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/other/plot_animation.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/other/plot_edit_artists.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/other/plot_feedbacks.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/other/plot_graph.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/other/plot_mouse_hover.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/other/plot_train.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/GALLERY_HEADER.rst +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_arrows.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_depthshade.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_edgepadding.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_elements.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_four_grids.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_halfarrows.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_multistyle.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_ports.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_style.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_tension.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_vertexmarkers.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_voronoi.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/style/plot_waypoints.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/GALLERY_HEADER.rst +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/data/tree-with-support.json +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_angular_waypoints.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_biopython_tree.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_cladeedges.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_cogent3_layouts.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_cogent3_tree.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_dendropy.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_double_tree.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_elements_tree.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_ete4.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_leafedges.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_leafedges_and_cascades.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_scalebar.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_skbio_tree.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_split_edges.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_style_tree.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_support.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_tree_node_background.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_tree_style_clades.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/tree/plot_trees_of_trees.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/zero_dependency/GALLERY_HEADER.rst +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/zero_dependency/plot_simplenetworkdataprovider.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/gallery/zero_dependency/plot_simpletreedataprovider.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/__init__.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/art3d/edge/__init__.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/art3d/edge/arrow.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/art3d/edge/geometry.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/art3d/vertex.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/artists.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/edge/arrow.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/edge/leaf.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/edge/ports.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/__init__.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/heuristics.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/providers/network/graph_tool.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/providers/network/igraph.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/providers/network/networkx.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/ingest/providers/network/simple.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/label.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/network/__init__.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/network/groups.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/plotting.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/style/__init__.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/style/library.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/tree/cascades.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/tree/scalebar.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/typing.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/utils/geometry.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/utils/internal.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/utils/matplotlib.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/utils/style.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/iplotx/vertex.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/pyproject.toml +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/scripts/copy_github_release_into_version.sh +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/scripts/make_banner.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/scripts/update_pylint_badge.sh +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_biopython/cascades.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_biopython/directed_child.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_biopython/leaf_labels.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_biopython/leaf_labels_hmargin.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_biopython/show_support.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_biopython/tree_basic.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_cogent3/leaf_labels.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_cogent3/leaf_labels_hmargin.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_cogent3/tree_basic.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/cascades.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/directed_child.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/leaf_labels.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/leaf_labels_hmargin.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/leafedges.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/tree_basic.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/tree_radial.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_doubletree/tree_gap.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_doubletree/tree_nogap.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_ete4/leaf_labels.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_ete4/leaf_labels_hmargin.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_ete4/split_edges.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_ete4/tree_basic.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_graph_tool/graph_basic.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_graph_tool/graph_directed.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/clustering_directed.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/clustering_directed_large.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_basic.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_directed.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_directed_curved_loops.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_edit_children.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_labels.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_layout_attribute.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_null.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_squares_directed.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_vertexsize.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_with_curved_edges.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/igraph_layout_object.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph/multigraph_with_curved_edges_undirected.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph_3d/directed.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph_3d/undirected.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_igraph_3d/vertex_labels.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_networkx/cluster-layout.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_networkx/complex.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_networkx/complex_rotatelabels.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_networkx/directed_graph.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_networkx/directed_graph_with_colorbar.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_networkx/empty_graph.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_networkx/flat_style.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_networkx/house_with_colors.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_networkx/labels_and_colors.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_networkx/shortest_path.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_networkx/simple_graph.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_simple_network_provider/graph_basic.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_simple_network_provider/graph_directed.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_simple_network_provider/graph_labels.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_skbio/leaf_labels.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_skbio/leaf_labels_hmargin.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/baseline_images/test_skbio/tree_basic.png +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_arrows.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_biopython.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_cascades.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_cogent3.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_dendropy.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_doubletree.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_edge.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_edge_geometry.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_geometry.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_graph_tool.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_heuristics.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_igraph_3d.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_ingest_protocols.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_matplotlib_utils.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_network_hotload.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_networkx.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_ports.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_simple_network_provider.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_simple_tree_provider.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_skbio.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_style.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/test_vertex.py +0 -0
- {iplotx-1.2.1 → iplotx-1.4.0}/tests/utils.py +0 -0
- {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.
|
|
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
|
-

|
|
40
|
-

|
|
41
|
-

|
|
39
|
+
[](https://github.com/fabilab/iplotx/actions/workflows/test.yml)
|
|
40
|
+
[](https://pypi.org/project/iplotx/)
|
|
41
|
+
[](https://iplotx.readthedocs.io/en/latest/)
|
|
42
42
|
[](https://coveralls.io/github/fabilab/iplotx?branch=main)
|
|
43
43
|

|
|
44
44
|
[](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
|
-

|
|
2
|
-

|
|
3
|
-

|
|
1
|
+
[](https://github.com/fabilab/iplotx/actions/workflows/test.yml)
|
|
2
|
+
[](https://pypi.org/project/iplotx/)
|
|
3
|
+
[](https://iplotx.readthedocs.io/en/latest/)
|
|
4
4
|
[](https://coveralls.io/github/fabilab/iplotx?branch=main)
|
|
5
5
|

|
|
6
6
|
[](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
|
|
106
|
-
# Bezier
|
|
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,
|
|
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.
|
|
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:`
|
|
36
|
-
- 00:00.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
160
|
-
"""Get the
|
|
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(
|
|
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(
|
|
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
|
|
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,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
|