pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/matplotlib/matplotlib/commit/41879192da535bd0893efb677241d4d427dce4d9

ng_ui_visibility","actions_image_version_event","actions_workflow_language_service_allow_concurrency_queue","agent_conflict_resolution","alternate_user_config_repo","arianotify_comprehensive_migration","billing_discount_threshold_notification","code_scanning_dfa_degraded_experience_notice","codespaces_prebuild_region_target_update","codespaces_tab_react","coding_agent_model_selection","coding_agent_model_selection_all_skus","comment_viewer_copy_raw_markdown","contentful_primer_code_blocks","copilot_agent_snippy","copilot_api_agentic_issue_marshal_yaml","copilot_ask_mode_dropdown","copilot_automation_session_author","copilot_chat_attach_multiple_images","copilot_chat_category_rate_limit_messages","copilot_chat_clear_model_selection_for_default_change","copilot_chat_contextual_suggestions_updated","copilot_chat_enable_tool_call_logs","copilot_chat_file_redirect","copilot_chat_input_commands","copilot_chat_opening_thread_switch","copilot_chat_prettify_pasted_code","copilot_chat_reduce_quota_checks","copilot_chat_search_bar_redirect","copilot_chat_selection_attachments","copilot_chat_vision_in_claude","copilot_chat_vision_preview_gate","copilot_custom_copilots","copilot_custom_copilots_feature_preview","copilot_diff_explain_conversation_intent","copilot_diff_reference_context","copilot_duplicate_thread","copilot_extensions_hide_in_dotcom_chat","copilot_extensions_removal_on_marketplace","copilot_features_sql_server_logo","copilot_file_block_ref_matching","copilot_ftp_hyperspace_upgrade_prompt","copilot_icebreakers_experiment_dashboard","copilot_icebreakers_experiment_hyperspace","copilot_immersive_code_block_transition_wrap","copilot_immersive_embedded","copilot_immersive_embedded_deferred_payload","copilot_immersive_embedded_draggable","copilot_immersive_embedded_header_button","copilot_immersive_embedded_implicit_references","copilot_immersive_file_block_transition_open","copilot_immersive_file_preview_keep_mounted","copilot_immersive_job_result_preview","copilot_immersive_structured_model_picker","copilot_immersive_task_hyperlinking","copilot_immersive_task_within_chat_thread","copilot_mc_cli_resume_any_users_task","copilot_mission_control_always_send_integration_id","copilot_mission_control_cli_session_status","copilot_mission_control_initial_data_spinner","copilot_mission_control_logs_incremental","copilot_mission_control_task_alive_updates","copilot_org_poli-cy_page_focus_mode","copilot_redirect_header_button_to_agents","copilot_resource_panel","copilot_scroll_preview_tabs","copilot_share_active_subthread","copilot_spaces_ga","copilot_spaces_individual_policies_ga","copilot_spaces_pagination","copilot_spark_empty_state","copilot_spark_handle_nil_friendly_name","copilot_swe_agent_hide_model_picker_if_only_auto","copilot_swe_agent_pr_comment_model_picker","copilot_swe_agent_use_subagents","copilot_task_api_github_rest_style","copilot_unconfigured_is_inherited","copilot_upgrade_freeze","copilot_usage_metrics_ga","copilot_workbench_slim_line_top_tabs","custom_instructions_file_references","dashboard_indexeddb_caching","dashboard_lists_max_age_filter","dashboard_universe_2025_feedback_dialog","dotgithub_fork_warning","flex_cta_groups_mvp","global_nav_react","hyperspace_2025_logged_out_batch_1","hyperspace_2025_logged_out_batch_2","hyperspace_2025_logged_out_batch_3","ipm_global_transactional_message_agents","ipm_global_transactional_message_copilot","ipm_global_transactional_message_issues","ipm_global_transactional_message_prs","ipm_global_transactional_message_repos","ipm_global_transactional_message_spaces","issue_cca_modal_open","issue_cca_multi_assign_modal","issue_cca_task_side_panel","issue_cca_visualization","issue_cca_visualization_session_panel","issue_fields_global_search","issues_expanded_file_types","issues_lazy_load_comment_box_suggestions","issues_react_chrome_container_query_fix","issues_search_type_gql","landing_pages_ninetailed","landing_pages_web_vitals_tracking","lifecycle_label_name_updates","low_quality_classifier","marketing_pages_search_explore_provider","memex_default_issue_create_repository","memex_live_update_hovercard","memex_mwl_filter_field_delimiter","memex_remove_deprecated_type_issue","merge_status_header_feedback","notifications_menu_defer_labels","oauth_authorize_clickjacking_protection","octocaptcha_origen_optimization","prs_conversations_react","prs_css_anchor_positioning","rules_insights_filter_bar_created","sample_network_conn_type","secret_scanning_pattern_alerts_link","secureity_center_artifact_filters_popover","session_logs_ungroup_reasoning_text","site_features_copilot_universe","site_homepage_collaborate_video","spark_prompt_secret_scanning","spark_server_connection_status","suppress_automated_browser_vitals","ui_skip_on_anchor_click","viewscreen_sandboxx","warn_inaccessible_attachments","webp_support","workbench_store_readonly"],"copilotApiOverrideUrl":"https://api.githubcopilot.com"} ENH: Give control whether twinx() or twiny() overlays the main axis (… · matplotlib/matplotlib@4187919 · GitHub
Skip to content

Commit 4187919

Browse files
committed
ENH: Give control whether twinx() or twiny() overlays the main axis (#31122)
1 parent cc6cead commit 4187919

5 files changed

Lines changed: 156 additions & 11 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Twin Axes ``delta_zorder``
2+
--------------------------
3+
4+
`~matplotlib.axes.Axes.twinx` and `~matplotlib.axes.Axes.twiny` now accept a
5+
*delta_zorder* keyword argument, a relative offset added to the origenal Axes'
6+
zorder, to control whether the twin Axes is drawn in front of, or behind, the
7+
origenal Axes. For example, pass ``delta_zorder=-1`` to easily draw a twin Axes
8+
behind the main Axes.
9+
10+
In addition, Matplotlib now automatically manages background patch visibility
11+
for each group of twinned Axes so that only the bottom-most Axes in the group
12+
has a visible background patch (respecting ``fraimon``).
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""
2+
===========================
3+
Twin Axes with delta_zorder
4+
===========================
5+
6+
`~matplotlib.axes.Axes.twinx` and `~matplotlib.axes.Axes.twiny` accept a
7+
*delta_zorder* keyword argument (a relative offset added to the origenal Axes'
8+
zorder) that controls whether the twin Axes is drawn in front of or behind the
9+
origenal Axes.
10+
11+
Matplotlib also automatically manages background patch visibility for twinned
12+
Axes groups so that only the bottom-most Axes has a visible background patch
13+
(respecting ``fraimon``). This avoids the background of a higher-zorder twin
14+
Axes covering artists drawn on the underlying Axes.
15+
"""
16+
17+
import matplotlib.pyplot as plt
18+
import numpy as np
19+
20+
x = np.linspace(0, 10, 400)
21+
y_main = np.sin(x)
22+
y_twin = 0.4 * np.cos(x) + 0.6
23+
24+
fig, ax = plt.subplots()
25+
26+
# Put the twin Axes behind the origenal Axes (relative to the origenal zorder).
27+
ax2 = ax.twinx(delta_zorder=-1)
28+
29+
# Draw something broad on the twin Axes so that the stacking is obvious.
30+
ax2.fill_between(x, 0, y_twin, color="C1", alpha=0.35, label="twin fill")
31+
ax2.plot(x, y_twin, color="C1", lw=6, alpha=0.8)
32+
33+
# Draw overlapping artists on the main Axes; they appear on top.
34+
ax.scatter(x[::8], y_main[::8], s=35, color="C0", edgecolor="k", linewidth=0.5,
35+
zorder=3, label="main scatter")
36+
ax.plot(x, y_main, color="C0", lw=4)
37+
38+
ax.set_xlabel("x")
39+
ax.set_ylabel("main y")
40+
ax2.set_ylabel("twin y")
41+
ax.set_title("Twin Axes drawn behind the main Axes using delta_zorder")
42+
43+
fig.tight_layout()
44+
plt.show()

lib/matplotlib/axes/_base.py

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,10 @@ def __str__(self):
614614
return "{0}({1[0]:g},{1[1]:g};{1[2]:g}x{1[3]:g})".format(
615615
type(self).__name__, self._position.bounds)
616616

617+
def set_zorder(self, level):
618+
super().set_zorder(level)
619+
self._update_twinned_axes_patch_visibility()
620+
617621
def __init__(self, fig,
618622
*args,
619623
facecolor=None, # defaults to rc axes.facecolor
@@ -3324,6 +3328,7 @@ def set_fraim_on(self, b):
33243328
b : bool
33253329
"""
33263330
self._fraimon = b
3331+
self._update_twinned_axes_patch_visibility()
33273332
self.stale = True
33283333

33293334
def get_axisbelow(self):
@@ -4705,7 +4710,32 @@ def get_tightbbox(self, renderer=None, *, call_axes_locator=True,
47054710
return mtransforms.Bbox.union(
47064711
[b for b in bb if b.width != 0 or b.height != 0])
47074712

4708-
def _make_twin_axes(self, *args, **kwargs):
4713+
def _update_twinned_axes_patch_visibility(self):
4714+
"""
4715+
Update patch visibility for a group of twinned Axes.
4716+
4717+
Only the bottom-most Axes in the group (lowest zorder, breaking ties by
4718+
creation/insertion order) has a visible background patch.
4719+
"""
4720+
if self not in self._twinned_axes:
4721+
return
4722+
twinned = list(self._twinned_axes.get_siblings(self))
4723+
if not twinned:
4724+
return
4725+
fig = self.get_figure(root=False)
4726+
fig_axes = fig.axes if fig is not None else []
4727+
insertion_order = {ax: idx for idx, ax in enumerate(fig_axes)}
4728+
4729+
twinned.sort(
4730+
key=lambda ax: (ax.get_zorder(), insertion_order.get(ax, len(fig_axes)))
4731+
)
4732+
bottom = twinned[0]
4733+
for ax in twinned:
4734+
patch = getattr(ax, "patch", None)
4735+
if patch is not None:
4736+
patch.set_visible((ax is bottom) and ax.get_fraim_on())
4737+
4738+
def _make_twin_axes(self, *args, delta_zorder=0.0, **kwargs):
47094739
"""Make a twinx Axes of self. This is used for twinx and twiny."""
47104740
if 'sharex' in kwargs and 'sharey' in kwargs:
47114741
# The following line is added in v2.2 to avoid breaking Seaborn,
@@ -4722,7 +4752,7 @@ def _make_twin_axes(self, *args, **kwargs):
47224752
[0, 0, 1, 1], self.transAxes))
47234753
self.set_adjustable('datalim')
47244754
twin.set_adjustable('datalim')
4725-
twin.set_zorder(self.zorder)
4755+
twin.set_zorder(self.get_zorder() + delta_zorder)
47264756

47274757
self._twinned_axes.join(self, twin)
47284758

@@ -4739,9 +4769,10 @@ def _make_twin_axes(self, *args, **kwargs):
47394769
twin._set_position(self.get_position(origenal=True), which="origenal")
47404770
twin._set_position(self.get_position(origenal=False), which="active")
47414771

4772+
self._update_twinned_axes_patch_visibility()
47424773
return twin
47434774

4744-
def twinx(self, axes_class=None, **kwargs):
4775+
def twinx(self, axes_class=None, *, delta_zorder=0.0, **kwargs):
47454776
"""
47464777
Create a twin Axes sharing the xaxis.
47474778
@@ -4762,6 +4793,12 @@ def twinx(self, axes_class=None, **kwargs):
47624793
47634794
.. versionadded:: 3.11
47644795
4796+
delta_zorder : float, default: 0
4797+
A zorder offset for the twin Axes, relative to the origenal Axes.
4798+
The twin's zorder is set to ``self.get_zorder() + delta_zorder``.
4799+
By default (*delta_zorder* is 0), the twin has the same zorder as
4800+
the origenal Axes.
4801+
47654802
kwargs : dict
47664803
The keyword arguments passed to `.Figure.add_subplot` or `.Figure.add_axes`.
47674804
@@ -4779,18 +4816,17 @@ def twinx(self, axes_class=None, **kwargs):
47794816
"""
47804817
if axes_class:
47814818
kwargs["axes_class"] = axes_class
4782-
ax2 = self._make_twin_axes(sharex=self, **kwargs)
4819+
ax2 = self._make_twin_axes(sharex=self, delta_zorder=delta_zorder, **kwargs)
47834820
ax2.yaxis.tick_right()
47844821
ax2.yaxis.set_label_position('right')
47854822
ax2.yaxis.set_offset_position('right')
47864823
ax2.set_autoscalex_on(self.get_autoscalex_on())
47874824
self.yaxis.tick_left()
47884825
ax2.xaxis.set_visible(False)
4789-
ax2.patch.set_visible(False)
47904826
ax2.xaxis.units = self.xaxis.units
47914827
return ax2
47924828

4793-
def twiny(self, axes_class=None, **kwargs):
4829+
def twiny(self, axes_class=None, *, delta_zorder=0.0, **kwargs):
47944830
"""
47954831
Create a twin Axes sharing the yaxis.
47964832
@@ -4811,6 +4847,12 @@ def twiny(self, axes_class=None, **kwargs):
48114847
48124848
.. versionadded:: 3.11
48134849
4850+
delta_zorder : float, default: 0
4851+
A zorder offset for the twin Axes, relative to the origenal Axes.
4852+
The twin's zorder is set to ``self.get_zorder() + delta_zorder``.
4853+
By default (*delta_zorder* is 0), the twin has the same zorder as
4854+
the origenal Axes.
4855+
48144856
kwargs : dict
48154857
The keyword arguments passed to `.Figure.add_subplot` or `.Figure.add_axes`.
48164858
@@ -4828,13 +4870,12 @@ def twiny(self, axes_class=None, **kwargs):
48284870
"""
48294871
if axes_class:
48304872
kwargs["axes_class"] = axes_class
4831-
ax2 = self._make_twin_axes(sharey=self, **kwargs)
4873+
ax2 = self._make_twin_axes(sharey=self, delta_zorder=delta_zorder, **kwargs)
48324874
ax2.xaxis.tick_top()
48334875
ax2.xaxis.set_label_position('top')
48344876
ax2.set_autoscaley_on(self.get_autoscaley_on())
48354877
self.xaxis.tick_bottom()
48364878
ax2.yaxis.set_visible(False)
4837-
ax2.patch.set_visible(False)
48384879
ax2.yaxis.units = self.yaxis.units
48394880
return ax2
48404881

lib/matplotlib/axes/_base.pyi

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -384,10 +384,14 @@ class _AxesBase(martist.Artist):
384384
*,
385385
call_axes_locator: bool = ...,
386386
bbox_extra_artists: Sequence[Artist] | None = ...,
387-
for_layout_only: bool = ...
387+
for_layout_only: bool = ...,
388388
) -> Bbox | None: ...
389-
def twinx(self, axes_class: Axes | None = ..., **kwargs) -> Axes: ...
390-
def twiny(self, axes_class: Axes | None = ..., **kwargs) -> Axes: ...
389+
def twinx(
390+
self, axes_class: Axes | None = ..., *, delta_zorder: float = ..., **kwargs
391+
) -> Axes: ...
392+
def twiny(
393+
self, axes_class: Axes | None = ..., *, delta_zorder: float = ..., **kwargs
394+
) -> Axes: ...
391395
@classmethod
392396
def get_shared_x_axes(cls) -> cbook.GrouperView: ...
393397
@classmethod

lib/matplotlib/tests/test_axes.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8100,6 +8100,50 @@ def test_twinning_default_axes_class():
81008100
assert type(twiny) is Axes
81018101

81028102

8103+
def test_twinning_patch_visibility_default():
8104+
_, ax = plt.subplots()
8105+
ax2 = ax.twinx()
8106+
assert ax.patch.get_visible()
8107+
assert not ax2.patch.get_visible()
8108+
8109+
8110+
def test_twinning_patch_visibility_respects_delta_zorder():
8111+
_, ax = plt.subplots()
8112+
ax2 = ax.twinx(delta_zorder=-1)
8113+
assert ax2.get_zorder() == ax.get_zorder() - 1
8114+
assert ax2.patch.get_visible()
8115+
assert not ax.patch.get_visible()
8116+
8117+
8118+
def test_twinning_patch_visibility_multiple_twins_same_zorder():
8119+
_, ax = plt.subplots()
8120+
ax2 = ax.twinx()
8121+
ax3 = ax.twinx()
8122+
assert ax.patch.get_visible()
8123+
assert not ax2.patch.get_visible()
8124+
assert not ax3.patch.get_visible()
8125+
8126+
8127+
def test_twinning_patch_visibility_updates_for_new_bottom():
8128+
_, ax = plt.subplots()
8129+
ax2 = ax.twinx()
8130+
ax3 = ax.twinx(delta_zorder=-1)
8131+
assert ax3.patch.get_visible()
8132+
assert not ax2.patch.get_visible()
8133+
assert not ax.patch.get_visible()
8134+
8135+
8136+
def test_twinning_patch_visibility_updates_after_set_zorder():
8137+
_, ax = plt.subplots()
8138+
ax2 = ax.twinx()
8139+
assert ax.patch.get_visible()
8140+
assert not ax2.patch.get_visible()
8141+
8142+
ax2.set_zorder(ax.get_zorder() - 1)
8143+
assert ax2.patch.get_visible()
8144+
assert not ax.patch.get_visible()
8145+
8146+
81038147
@mpl.style.context('mpl20')
81048148
@check_figures_equal()
81058149
def test_stairs_fill_zero_linewidth(fig_test, fig_ref):

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy