Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitb660bc5

Browse files
doronbeharclaude
andcommitted
RadioButtons: Add 2D grid labels layout support
The RadioButtons widget now supports arranging buttons in a 2D grid bypassing a list of lists of strings as the labels parameter. Each innerlist represents a row in the grid.Key features:- Active index and index_selected remain as single integers for the flattened array (reading left-to-right, top-to-bottom)- Column positions are automatically calculated based on the maximum text width in each column for optimal spacing- Text offset is now consistent between 1D and 2D layouts (0.10)- Includes new example: galleries/examples/widgets/radio_buttons_grid.pyCloses#13374🤖 Generated with [Claude Code](https://claude.com/claude-code)Co-Authored-By: Claude <noreply@anthropic.com>
1 parent4fcf990 commitb660bc5

File tree

4 files changed

+220
-14
lines changed

4 files changed

+220
-14
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
RadioButtons widget supports 2D grid layout
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
The `.widgets.RadioButtons` widget now supports arranging buttons in a 2D grid
5+
layout. Pass a list of lists of strings as the *labels* parameter to arrange
6+
buttons in a grid where each inner list represents a row.
7+
8+
The *active* parameter and the `.widgets.RadioButtons.index_selected` attribute
9+
continue to use a single integer index into the flattened array, reading
10+
left-to-right, top-to-bottom. The column positions are automatically calculated
11+
based on the maximum text width in each column, ensuring optimal spacing.
12+
13+
See:doc:`/gallery/widgets/radio_buttons_grid` for a complete example.
14+
15+
..plot::
16+
:include-source: true
17+
:alt: A sine wave plot with a 3x3 grid of radio buttons for selecting line color.
18+
19+
import matplotlib.pyplot as plt
20+
import numpy as np
21+
from matplotlib.widgets import RadioButtons
22+
23+
t = np.arange(0.0, 2.0, 0.01)
24+
s = np.sin(2*np.pi*t)
25+
26+
fig, (ax_plot, ax_buttons) = plt.subplots(1, 2, figsize=(8, 4),
27+
width_ratios=[3, 1])
28+
29+
line, = ax_plot.plot(t, s, lw=2, color='red')
30+
ax_plot.set_xlabel('Time (s)')
31+
ax_plot.set_ylabel('Amplitude')
32+
33+
ax_buttons.set_facecolor('lightgray')
34+
ax_buttons.set_title('Line Color', fontsize=12, pad=10)
35+
36+
colors = [
37+
['red', 'orange', 'yellow'],
38+
['green', 'blue', 'purple'],
39+
['brown', 'pink', 'gray'],
40+
]
41+
42+
radio = RadioButtons(ax_buttons, colors, active=0)
43+
44+
def color_func(label):
45+
line.set_color(label)
46+
fig.canvas.draw()
47+
48+
radio.on_clicked(color_func)
49+
plt.show()
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"""
2+
==================
3+
Radio Buttons Grid
4+
==================
5+
6+
Using radio buttons in a 2D grid layout.
7+
8+
Radio buttons can be arranged in a 2D grid by passing a list of lists of
9+
strings as the *labels* parameter. This is useful when you have multiple
10+
related options that are best displayed in a grid format rather than a
11+
vertical list.
12+
13+
In this example, we create a color picker using a 2D grid of radio buttons
14+
to select the line color of a plot.
15+
"""
16+
17+
importmatplotlib.pyplotasplt
18+
importnumpyasnp
19+
20+
frommatplotlib.widgetsimportRadioButtons
21+
22+
# Generate sample data
23+
t=np.arange(0.0,2.0,0.01)
24+
s=np.sin(2*np.pi*t)
25+
26+
fig, (ax_plot,ax_buttons)=plt.subplots(
27+
1,2,
28+
figsize=(8,4),
29+
width_ratios=[4,1],
30+
)
31+
32+
# Create initial plot
33+
line,=ax_plot.plot(t,s,lw=2,color='red')
34+
ax_plot.set_xlabel('Time (s)')
35+
ax_plot.set_ylabel('Amplitude')
36+
ax_plot.set_title('Sine Wave - Click a color!')
37+
ax_plot.grid(True,alpha=0.3)
38+
39+
# Configure the radio buttons axes
40+
ax_buttons.set_facecolor('lightgray')
41+
ax_buttons.set_title('Line Color',fontsize=12,pad=10)
42+
43+
# Create a 2D grid of color options (3 rows x 2 columns)
44+
colors= [
45+
['red','yellow'],
46+
['green','purple'],
47+
['brown','gray'],
48+
]
49+
50+
radio=RadioButtons(ax_buttons,colors,active=0)
51+
52+
53+
defcolor_func(label):
54+
"""Update the line color based on selected button."""
55+
line.set_color(label)
56+
fig.canvas.draw()
57+
58+
59+
radio.on_clicked(color_func)
60+
61+
plt.show()
62+
63+
# %%
64+
#
65+
# .. admonition:: References
66+
#
67+
# The use of the following functions, methods, classes and modules is shown
68+
# in this example:
69+
#
70+
# - `matplotlib.widgets.RadioButtons`

‎lib/matplotlib/widgets.py‎

Lines changed: 100 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,7 +1578,8 @@ class RadioButtons(AxesWidget):
15781578
value_selected : str
15791579
The label text of the currently selected button.
15801580
index_selected : int
1581-
The index of the selected button.
1581+
The index of the selected button in the flattened array. For 2D grids,
1582+
this is the index when reading left-to-right, top-to-bottom.
15821583
"""
15831584

15841585
def__init__(self,ax,labels,active=0,activecolor=None,*,
@@ -1590,10 +1591,14 @@ def __init__(self, ax, labels, active=0, activecolor=None, *,
15901591
----------
15911592
ax : `~matplotlib.axes.Axes`
15921593
The Axes to add the buttons to.
1593-
labels : list of str
1594-
The button labels.
1594+
labels : list of str or list of list of str
1595+
The button labels. If a list of strings, buttons are arranged
1596+
vertically. If a list of lists of strings, buttons are arranged
1597+
in a 2D grid where each inner list represents a row.
15951598
active : int
1596-
The index of the initially selected button.
1599+
The index of the initially selected button in the flattened array.
1600+
For 2D grids, this is the index when reading left-to-right,
1601+
top-to-bottom.
15971602
activecolor : :mpltype:`color`
15981603
The color of the selected button. The default is ``'blue'`` if not
15991604
specified here or in *radio_props*.
@@ -1606,8 +1611,8 @@ def __init__(self, ax, labels, active=0, activecolor=None, *,
16061611
label_props : dict of lists, optional
16071612
Dictionary of `.Text` properties to be used for the labels. Each
16081613
dictionary value should be a list of at least a single element. If
1609-
the list is of length M, its values are cycled such that the Nth
1610-
label gets the (N mod M) property.
1614+
theflatlistof labelsis of length M, its values are cycled such
1615+
that the Nthlabel gets the (N mod M) property.
16111616
16121617
.. versionadded:: 3.7
16131618
radio_props : dict, optional
@@ -1627,6 +1632,14 @@ def __init__(self, ax, labels, active=0, activecolor=None, *,
16271632
_api.check_isinstance((dict,None),label_props=label_props,
16281633
radio_props=radio_props)
16291634

1635+
# Check if labels is 2D (list of lists)
1636+
_is_2d=isinstance(labels[0], (list,tuple))
1637+
1638+
if_is_2d:
1639+
flat_labels= [itemforrowinlabelsforiteminrow]
1640+
else:
1641+
flat_labels=list(labels)
1642+
16301643
radio_props=cbook.normalize_kwargs(radio_props,
16311644
collections.PathCollection)
16321645
ifactivecolorisnotNone:
@@ -1640,24 +1653,98 @@ def __init__(self, ax, labels, active=0, activecolor=None, *,
16401653

16411654
self._activecolor=activecolor
16421655
self._initial_active=active
1643-
self.value_selected=labels[active]
1656+
self.value_selected=flat_labels[active]
16441657
self.index_selected=active
16451658

16461659
ax.set_xticks([])
16471660
ax.set_yticks([])
16481661
ax.set_navigate(False)
16491662

1650-
ys=np.linspace(1,0,len(labels)+2)[1:-1]
1651-
16521663
self._useblit=useblitandself.canvas.supports_blit
16531664
self._background=None
16541665

16551666
label_props=_expand_text_props(label_props)
1667+
# Calculate positions based on layout
1668+
text_x_offset=0.10
1669+
1670+
if_is_2d:
1671+
n_rows=len(labels)
1672+
n_cols=max(len(row)forrowinlabels)
1673+
# Y positions with margins
1674+
y_margin=0.05
1675+
y_spacing= (1-2*y_margin)/max(1,n_rows-1)ifn_rows>1else0
1676+
1677+
# Create temporary text objects to measure widths
1678+
flat_label_list= []
1679+
temp_texts= []
1680+
fori,rowinenumerate(labels):
1681+
forj,labelinenumerate(row):
1682+
flat_label_list.append(label)
1683+
forlabel,propsinzip(flat_label_list,label_props):
1684+
temp_texts.append(ax.text(
1685+
0,
1686+
0,
1687+
label,
1688+
transform=ax.transAxes,
1689+
**props,
1690+
))
1691+
# Force a draw to get accurate text measurements
1692+
ax.figure.canvas.draw()
1693+
# Calculate max text width per column (in axes coordinates)
1694+
col_widths= []
1695+
forcol_idxinrange(n_cols):
1696+
col_texts= []
1697+
forrow_idx,rowinenumerate(labels):
1698+
ifcol_idx<len(row):
1699+
col_texts.append(temp_texts[
1700+
sum(len(labels[r])forrinrange(row_idx))+col_idx
1701+
])
1702+
ifcol_texts:
1703+
col_widths.append(
1704+
max(
1705+
text.get_window_extent(
1706+
ax.figure.canvas.get_renderer()
1707+
).width
1708+
fortextincol_texts
1709+
)/ax.bbox.width
1710+
)
1711+
else:
1712+
col_widths.append(0)
1713+
# Remove temporary text objects
1714+
fortextintemp_texts:
1715+
text.remove()
1716+
# Calculate x positions based on text widths
1717+
# TODO: Should these be arguments?
1718+
button_x_margin=0.07# Left margin for first button
1719+
col_spacing=0.07# Space between columns
1720+
1721+
col_x_positions= [button_x_margin]# First column starts at left margin
1722+
forcol_idxinrange(n_cols-1):
1723+
col_x_positions.append(sum([
1724+
col_x_positions[-1],
1725+
text_x_offset,
1726+
col_widths[col_idx],
1727+
col_spacing
1728+
]))
1729+
# Create final positions
1730+
positions= []
1731+
fori,rowinenumerate(labels):
1732+
y=1-y_margin-i*y_spacing
1733+
forj,labelinenumerate(row):
1734+
x=col_x_positions[j]
1735+
positions.append((x,y))
1736+
xs= [pos[0]forposinpositions]
1737+
ys= [pos[1]forposinpositions]
1738+
else:
1739+
ys=np.linspace(1,0,len(flat_labels)+2)[1:-1]
1740+
xs= [0.15]*len(ys)
1741+
flat_label_list=flat_labels
1742+
16561743
self.labels= [
1657-
ax.text(0.25,y,label,transform=ax.transAxes,
1744+
ax.text(x+text_x_offset,y,label,transform=ax.transAxes,
16581745
horizontalalignment="left",verticalalignment="center",
16591746
**props)
1660-
fory,label,propsinzip(ys,labels,label_props)]
1747+
forx,y,label,propsinzip(xs,ys,flat_label_list,label_props)]
16611748
text_size=np.array([text.get_fontsize()fortextinself.labels])/2
16621749

16631750
radio_props= {
@@ -1670,13 +1757,13 @@ def __init__(self, ax, labels, active=0, activecolor=None, *,
16701757
radio_props.setdefault('edgecolor',radio_props.get('color','black'))
16711758
radio_props.setdefault('facecolor',
16721759
radio_props.pop('color',activecolor))
1673-
self._buttons=ax.scatter([.15]*len(ys),ys,**radio_props)
1760+
self._buttons=ax.scatter(xs,ys,**radio_props)
16741761
# The user may have passed custom colours in radio_props, so we need to
16751762
# create the radios, and modify the visibility after getting whatever
16761763
# the user set.
16771764
self._active_colors=self._buttons.get_facecolor()
16781765
iflen(self._active_colors)==1:
1679-
self._active_colors=np.repeat(self._active_colors,len(labels),
1766+
self._active_colors=np.repeat(self._active_colors,len(flat_labels),
16801767
axis=0)
16811768
self._buttons.set_facecolor(
16821769
[activecolorifi==activeelse"none"

‎lib/matplotlib/widgets.pyi‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ class RadioButtons(AxesWidget):
206206
def__init__(
207207
self,
208208
ax:Axes,
209-
labels:Iterable[str],
209+
labels:Iterable[str]|Iterable[Iterable[str]],
210210
active:int= ...,
211211
activecolor:ColorType|None= ...,
212212
*,

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp