iplotx 0.10.0__tar.gz → 0.11.1__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-0.10.0 → iplotx-0.11.1}/PKG-INFO +1 -1
- {iplotx-0.10.0 → iplotx-0.11.1}/docs/source/sg_execution_times.rst +5 -5
- iplotx-0.11.1/gallery/basic/plot_3d.py +59 -0
- iplotx-0.11.1/iplotx/art3d/edge/__init__.py +147 -0
- iplotx-0.11.1/iplotx/art3d/edge/arrow.py +115 -0
- iplotx-0.10.0/iplotx/edge/geometry3d.py → iplotx-0.11.1/iplotx/art3d/edge/geometry.py +6 -37
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/art3d/vertex.py +20 -2
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/edge/__init__.py +20 -46
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/edge/arrow.py +27 -4
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/edge/geometry.py +6 -29
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/groups.py +1 -1
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/label.py +10 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/network.py +2 -4
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/version.py +1 -1
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/vertex.py +10 -2
- iplotx-0.11.1/tests/baseline_images/test_igraph_3d/directed.png +0 -0
- iplotx-0.11.1/tests/baseline_images/test_igraph_3d/vertex_labels.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/test_igraph_3d.py +30 -0
- iplotx-0.10.0/gallery/basic/plot_3d.py +0 -29
- iplotx-0.10.0/iplotx/art3d/edge.py +0 -65
- iplotx-0.10.0/tests/test_3d_mock.py +0 -87
- {iplotx-0.10.0 → iplotx-0.11.1}/.github/workflows/publish.yml +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/.github/workflows/test.yml +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/.gitignore +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/.pre-commit-config.yaml +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/.readthedocs.yaml +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/LICENSE +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/MANIFEST.in +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/README.md +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/assets/pylint.svg +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/docs/Makefile +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/docs/make.bat +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/docs/source/_static/banner.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/docs/source/_static/graph_basic.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/docs/source/_templates/layout.html +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/docs/source/api/artists.md +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/docs/source/api/complete_style_specification.md +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/docs/source/api/plotting.md +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/docs/source/api/providers.md +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/docs/source/api/style.md +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/docs/source/api.md +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/docs/source/conf.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/docs/source/images/sphx_glr_plot_basic_001.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/docs/source/images/thumb/sphx_glr_plot_basic_thumb.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/docs/source/index.md +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/docs/source/providers.md +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/docs/source/style.md +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/GALLERY_HEADER.rst +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/basic/GALLERY_HEADER.rst +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/basic/plot_basic.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/basic/plot_big_curves.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/basic/plot_dag.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/basic/plot_directed.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/basic/plot_grouping.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/basic/plot_house.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/basic/plot_loops.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/basic/plot_simple_path.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/biology/GALLERY_HEADER.rst +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/biology/data/80201010000000001.mst +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/biology/data/GN-tree.json +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/biology/data/breast_cancer_string_interactions_short.tsv +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/biology/data/breast_cancer_string_network_coordinates.tsv +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/biology/data/cell_cycle_arrest_string_interactions_short.tsv +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/biology/data/cell_cycle_arrest_string_network_coordinates.tsv +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/biology/data/fevo-08-588430_DataSheet1_S1.csv +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/biology/plot_animal_phylogeny.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/biology/plot_antibody_clone.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/biology/plot_breast_cancer_ppi.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/biology/plot_cell_cycle_arrest.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/biology/plot_food_network.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/biology/plot_foraging_table.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/biology/plot_pollinators.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/biology/plot_ppi.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/biology/plot_tca_cycle.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/network_science/GALLERY_HEADER.rst +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/network_science/data/chess_masters_WCC.pgn.bz2 +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/network_science/data/knuth_miles.txt.gz +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/network_science/plot_arrowlawn.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/network_science/plot_chess_masters.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/network_science/plot_cliques.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/network_science/plot_cluster_layout.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/network_science/plot_company_structure.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/network_science/plot_complex.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/network_science/plot_financial_network.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/network_science/plot_knuth_miles.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/network_science/plot_labels_and_colors.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/network_science/plot_max_bipartite_matching.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/network_science/plot_minimum_spanning_trees.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/network_science/plot_multipartite_layout.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/network_science/plot_parallel_igraph_networkx.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/network_science/plot_redblack.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/network_science/plot_shortest_path.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/network_science/plot_simple_networkx.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/network_science/plot_social_network_circles.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/network_science/plot_traveling_salesman.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/network_science/plot_with_colorbar.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/other/GALLERY_HEADER.rst +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/other/plot_animation.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/other/plot_edit_artists.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/other/plot_feedbacks.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/other/plot_graph.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/other/plot_mouse_hover.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/other/plot_train.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/style/GALLERY_HEADER.rst +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/style/plot_arrows.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/style/plot_edgepadding.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/style/plot_elements.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/style/plot_four_grids.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/style/plot_halfarrows.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/style/plot_multistyle.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/style/plot_ports.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/style/plot_style.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/style/plot_tension.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/style/plot_vertexmarkers.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/style/plot_voronoi.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/style/plot_waypoints.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/tree/GALLERY_HEADER.rst +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/tree/data/tree-with-support.json +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/tree/plot_angular_waypoints.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/tree/plot_biopython_tree.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/tree/plot_cladeedges.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/tree/plot_cogent3_layouts.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/tree/plot_cogent3_tree.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/tree/plot_elements_tree.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/tree/plot_ete4.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/tree/plot_leafedges.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/tree/plot_leafedges_and_cascades.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/tree/plot_skbio_tree.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/tree/plot_split_edges.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/tree/plot_style_tree.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/tree/plot_support.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/tree/plot_tree_node_background.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/tree/plot_tree_style_clades.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/tree/plot_trees_of_trees.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/zero_dependency/GALLERY_HEADER.rst +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/zero_dependency/plot_simplenetworkdataprovider.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/gallery/zero_dependency/plot_simpletreedataprovider.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/__init__.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/artists.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/cascades.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/edge/leaf.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/edge/ports.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/ingest/__init__.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/ingest/heuristics.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/ingest/providers/network/igraph.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/ingest/providers/network/networkx.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/ingest/providers/network/simple.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/ingest/providers/tree/biopython.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/ingest/providers/tree/cogent3.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/ingest/providers/tree/ete4.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/ingest/providers/tree/simple.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/ingest/providers/tree/skbio.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/ingest/typing.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/layout.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/plotting.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/style/__init__.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/style/leaf_info.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/style/library.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/tree.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/typing.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/utils/geometry.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/utils/internal.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/utils/matplotlib.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/iplotx/utils/style.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/pyproject.toml +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/scripts/copy_github_release_into_version.sh +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/scripts/make_banner.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/scripts/update_pylint_badge.sh +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_biopython/cascades.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_biopython/directed_child.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_biopython/leaf_labels.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_biopython/leaf_labels_hmargin.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_biopython/leafedges.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_biopython/show_support.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_biopython/tree_basic.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_biopython/tree_radial.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_cogent3/leaf_labels.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_cogent3/leaf_labels_hmargin.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_cogent3/tree_basic.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_cogent3/tree_radial.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_ete4/leaf_labels.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_ete4/leaf_labels_hmargin.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_ete4/split_edges.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_ete4/tree_basic.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_ete4/tree_radial.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_igraph/clustering_directed.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_igraph/clustering_directed_large.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_igraph/graph_basic.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_igraph/graph_directed.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_igraph/graph_directed_curved_loops.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_igraph/graph_edit_children.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_igraph/graph_labels.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_igraph/graph_layout_attribute.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_igraph/graph_null.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_igraph/graph_squares_directed.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_igraph/graph_vertexsize.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_igraph/graph_with_curved_edges.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_igraph/igraph_layout_object.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_igraph/multigraph_with_curved_edges_undirected.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_igraph_3d/undirected.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_networkx/cluster-layout.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_networkx/complex.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_networkx/complex_rotatelabels.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_networkx/directed_graph.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_networkx/directed_graph_with_colorbar.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_networkx/empty_graph.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_networkx/flat_style.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_networkx/house_with_colors.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_networkx/labels_and_colors.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_networkx/shortest_path.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_networkx/simple_graph.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_simple_network_provider/graph_basic.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_simple_network_provider/graph_directed.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_simple_network_provider/graph_labels.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_skbio/leaf_labels.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_skbio/leaf_labels_hmargin.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_skbio/tree_basic.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/baseline_images/test_skbio/tree_radial.png +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/test_arrows.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/test_biopython.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/test_cascades.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/test_cogent3.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/test_edge.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/test_edge_geometry.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/test_ete4.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/test_geometry.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/test_heuristics.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/test_igraph.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/test_ingest_protocols.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/test_matplotlib_utils.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/test_network_hotload.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/test_networkx.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/test_ports.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/test_simple_network_provider.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/test_simple_tree_provider.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/test_skbio.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/test_style.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/test_vertex.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/tests/utils.py +0 -0
- {iplotx-0.10.0 → iplotx-0.11.1}/uv.lock +0 -0
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
Computation times
|
|
8
8
|
=================
|
|
9
|
-
**00:00.
|
|
9
|
+
**00:00.095** total execution time for 73 files **from all galleries**:
|
|
10
10
|
|
|
11
11
|
.. container::
|
|
12
12
|
|
|
@@ -32,8 +32,11 @@ Computation times
|
|
|
32
32
|
* - Example
|
|
33
33
|
- Time
|
|
34
34
|
- Mem (MB)
|
|
35
|
+
* - :ref:`sphx_glr_gallery_basic_plot_grouping.py` (``../../gallery/basic/plot_grouping.py``)
|
|
36
|
+
- 00:00.095
|
|
37
|
+
- 0.0
|
|
35
38
|
* - :ref:`sphx_glr_gallery_basic_plot_3d.py` (``../../gallery/basic/plot_3d.py``)
|
|
36
|
-
- 00:00.
|
|
39
|
+
- 00:00.000
|
|
37
40
|
- 0.0
|
|
38
41
|
* - :ref:`sphx_glr_gallery_basic_plot_basic.py` (``../../gallery/basic/plot_basic.py``)
|
|
39
42
|
- 00:00.000
|
|
@@ -47,9 +50,6 @@ Computation times
|
|
|
47
50
|
* - :ref:`sphx_glr_gallery_basic_plot_directed.py` (``../../gallery/basic/plot_directed.py``)
|
|
48
51
|
- 00:00.000
|
|
49
52
|
- 0.0
|
|
50
|
-
* - :ref:`sphx_glr_gallery_basic_plot_grouping.py` (``../../gallery/basic/plot_grouping.py``)
|
|
51
|
-
- 00:00.000
|
|
52
|
-
- 0.0
|
|
53
53
|
* - :ref:`sphx_glr_gallery_basic_plot_house.py` (``../../gallery/basic/plot_house.py``)
|
|
54
54
|
- 00:00.000
|
|
55
55
|
- 0.0
|
|
@@ -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
|
|
11
|
+
from ...typing import (
|
|
13
12
|
Pair,
|
|
14
13
|
)
|
|
15
14
|
|
|
16
15
|
|
|
17
|
-
def
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
171
|
+
self._update_before_draw()
|
|
149
172
|
super().draw(renderer)
|
|
150
173
|
|
|
151
174
|
|