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.
- {iplotx-1.3.0 → iplotx-1.4.0}/PKG-INFO +1 -1
- {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/api/complete_style_specification.md +8 -3
- {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/sg_execution_times.rst +6 -3
- iplotx-1.4.0/gallery/basic/plot_arcs.py +42 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/edge/__init__.py +7 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/edge/geometry.py +113 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/layout/tree/unrooted.py +118 -57
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/style/leaf_info.py +1 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/version.py +1 -1
- 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.3.0 → iplotx-1.4.0}/tests/test_igraph.py +29 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/.github/workflows/publish.yml +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/.github/workflows/test.yml +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/.gitignore +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/.pre-commit-config.yaml +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/.readthedocs.yaml +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/LICENSE +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/MANIFEST.in +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/README.md +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/assets/pylint.svg +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/docs/Makefile +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/docs/make.bat +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/_static/banner.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/_static/custom-icons.js +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/_static/custom.css +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/_static/graph_basic.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/_templates/layout.html +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/api/artists.md +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/api/plotting.md +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/api/providers.md +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/api/style.md +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/api.md +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/code_of_conduct.rst +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/conf.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/images/sphx_glr_plot_basic_001.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/images/thumb/sphx_glr_plot_basic_thumb.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/index.md +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/installing.md +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/providers.md +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/docs/source/style.md +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/GALLERY_HEADER.rst +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/basic/GALLERY_HEADER.rst +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/basic/plot_3d.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/basic/plot_basic.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/basic/plot_big_curves.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/basic/plot_dag.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/basic/plot_directed.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/basic/plot_grouping.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/basic/plot_house.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/basic/plot_loops.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/basic/plot_simple_path.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/GALLERY_HEADER.rst +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/data/80201010000000001.mst +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/data/GN-tree.json +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/data/breast_cancer_string_interactions_short.tsv +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/data/breast_cancer_string_network_coordinates.tsv +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/data/cell_cycle_arrest_string_interactions_short.tsv +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/data/cell_cycle_arrest_string_network_coordinates.tsv +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/data/fevo-08-588430_DataSheet1_S1.csv +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/plot_animal_phylogeny.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/plot_antibody_clone.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/plot_breast_cancer_ppi.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/plot_cell_cycle_arrest.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/plot_food_network.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/plot_foraging_table.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/plot_pollinators.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/plot_ppi.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/biology/plot_tca_cycle.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/GALLERY_HEADER.rst +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/data/chess_masters_WCC.pgn.bz2 +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/data/knuth_miles.txt.gz +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_arrowlawn.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_chess_masters.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_cliques.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_cluster_layout.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_company_structure.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_complex.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_financial_network.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_knuth_miles.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_labels_and_colors.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_max_bipartite_matching.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_minimum_spanning_trees.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_multipartite_layout.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_parallel_igraph_networkx.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_redblack.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_shortest_path.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_simple_networkx.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_social_network_circles.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_traveling_salesman.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/network_science/plot_with_colorbar.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/other/GALLERY_HEADER.rst +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/other/plot_animation.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/other/plot_edit_artists.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/other/plot_feedbacks.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/other/plot_graph.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/other/plot_mouse_hover.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/other/plot_train.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/GALLERY_HEADER.rst +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_arrows.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_depthshade.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_edgepadding.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_elements.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_four_grids.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_halfarrows.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_multistyle.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_ports.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_style.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_tension.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_vertexmarkers.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_voronoi.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/style/plot_waypoints.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/GALLERY_HEADER.rst +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/data/tree-with-support.json +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_angular_waypoints.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_biopython_tree.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_cladeedges.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_cogent3_layouts.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_cogent3_tree.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_dendropy.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_double_tree.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_elements_tree.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_equalangle.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_ete4.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_leafedges.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_leafedges_and_cascades.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_scalebar.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_skbio_tree.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_split_edges.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_style_tree.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_support.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_tree_node_background.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_tree_style_clades.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/tree/plot_trees_of_trees.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/zero_dependency/GALLERY_HEADER.rst +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/zero_dependency/plot_simplenetworkdataprovider.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/gallery/zero_dependency/plot_simpletreedataprovider.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/__init__.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/art3d/edge/__init__.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/art3d/edge/arrow.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/art3d/edge/geometry.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/art3d/vertex.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/artists.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/edge/arrow.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/edge/leaf.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/edge/ports.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/__init__.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/heuristics.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/providers/network/graph_tool.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/providers/network/igraph.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/providers/network/networkx.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/providers/network/simple.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/providers/tree/biopython.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/providers/tree/cogent3.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/providers/tree/dendropy.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/providers/tree/ete4.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/providers/tree/simple.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/providers/tree/skbio.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/ingest/typing.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/label.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/layout/__init__.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/layout/tree/__init__.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/layout/tree/rooted.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/network/__init__.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/network/groups.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/plotting.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/style/__init__.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/style/library.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/tree/__init__.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/tree/cascades.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/tree/scalebar.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/typing.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/utils/geometry.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/utils/internal.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/utils/matplotlib.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/utils/style.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/iplotx/vertex.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/pyproject.toml +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/scripts/copy_github_release_into_version.sh +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/scripts/make_banner.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/scripts/update_pylint_badge.sh +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_biopython/cascades.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_biopython/directed_child.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_biopython/leaf_labels.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_biopython/leaf_labels_hmargin.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_biopython/leafedges.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_biopython/show_support.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_biopython/tree_basic.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_biopython/tree_radial.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_cogent3/leaf_labels.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_cogent3/leaf_labels_hmargin.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_cogent3/tree_basic.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_cogent3/tree_radial.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/cascades.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/directed_child.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/leaf_labels.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/leaf_labels_hmargin.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/leafedges.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/tree_basic.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_dendropy/tree_radial.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_doubletree/tree_gap.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_doubletree/tree_nogap.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_ete4/equalangle_layout.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_ete4/leaf_labels.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_ete4/leaf_labels_hmargin.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_ete4/split_edges.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_ete4/tree_basic.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_ete4/tree_radial.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_graph_tool/graph_basic.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_graph_tool/graph_directed.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/clustering_directed.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/clustering_directed_large.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_basic.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_directed.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_directed_curved_loops.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_edit_children.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_labels.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_layout_attribute.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_null.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_squares_directed.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_vertexsize.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/graph_with_curved_edges.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/igraph_layout_object.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph/multigraph_with_curved_edges_undirected.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph_3d/directed.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph_3d/undirected.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_igraph_3d/vertex_labels.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_networkx/cluster-layout.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_networkx/complex.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_networkx/complex_rotatelabels.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_networkx/directed_graph.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_networkx/directed_graph_with_colorbar.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_networkx/empty_graph.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_networkx/flat_style.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_networkx/house_with_colors.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_networkx/labels_and_colors.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_networkx/shortest_path.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_networkx/simple_graph.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_simple_network_provider/graph_basic.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_simple_network_provider/graph_directed.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_simple_network_provider/graph_labels.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_skbio/leaf_labels.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_skbio/leaf_labels_hmargin.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_skbio/tree_basic.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/baseline_images/test_skbio/tree_radial.png +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_arrows.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_biopython.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_cascades.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_cogent3.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_dendropy.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_doubletree.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_edge.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_edge_geometry.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_ete4.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_geometry.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_graph_tool.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_heuristics.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_igraph_3d.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_ingest_protocols.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_matplotlib_utils.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_network_hotload.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_networkx.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_ports.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_simple_network_provider.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_simple_tree_provider.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_skbio.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_style.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/test_vertex.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/tests/utils.py +0 -0
- {iplotx-1.3.0 → iplotx-1.4.0}/uv.lock +0 -0
|
@@ -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
|
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
#
|
|
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(
|
|
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
|
|
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(
|
|
241
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
272
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
print(daylight)
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
370
|
+
correction,
|
|
314
371
|
recur=True,
|
|
315
372
|
)
|
|
316
|
-
|
|
373
|
+
daylight_corrections_abs += abs(correction)
|
|
374
|
+
|
|
375
|
+
# __import__("ipdb").set_trace()
|
|
317
376
|
|
|
318
377
|
# Caller wants degrees
|
|
319
|
-
|
|
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
|
|
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] =
|
|
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
|
-
|
|
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
|
|
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], [
|
|
443
|
+
rot = np.array([[cos, -sin], [sin, cos]])
|
|
383
444
|
return pivot + (point - pivot) @ rot
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|