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

Appearance settings

Commit 5f553bc

Browse filesBrowse files
authored
Merge pull request #6988 from fariza/text-box-widget
ENH: Text box widget, take over of PR5375
2 parents 09a78d5 + bde1040 commit 5f553bc
Copy full SHA for 5f553bc

File tree

Expand file treeCollapse file tree

5 files changed

+345
-2
lines changed
Filter options
Expand file treeCollapse file tree

5 files changed

+345
-2
lines changed

‎CHANGELOG

Copy file name to clipboardExpand all lines: CHANGELOG
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
12
2015-11-16 Levels passed to contour(f) and tricontour(f) must be in increasing
23
order.
34

5+
2015-10-21 Added TextBox widget
6+
7+
48
2015-10-21 Added get_ticks_direction()
59

610
2015-02-27 Added the rcParam 'image.composite_image' to permit users

‎doc/users/whats_new.rst

Copy file name to clipboardExpand all lines: doc/users/whats_new.rst
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,13 @@ Some parameters have been added, others have been improved.
244244
Widgets
245245
-------
246246

247+
Added TextBox Widget
248+
````````````````````
249+
250+
Added a widget that allows text entry by reading key events when it is active.
251+
Text caret in text box is visible when it is active, can be moved using arrow keys and mouse
252+
253+
247254
Active state of Selectors
248255
`````````````````````````
249256

‎examples/widgets/textbox.py

Copy file name to clipboard
+23Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
import numpy as np
3+
import matplotlib.pyplot as plt
4+
from matplotlib.widgets import TextBox
5+
fig, ax = plt.subplots()
6+
plt.subplots_adjust(bottom=0.2)
7+
t = np.arange(-2.0, 2.0, 0.001)
8+
s = t ** 2
9+
initial_text = "t ** 2"
10+
l, = plt.plot(t, s, lw=2)
11+
12+
13+
def submit(text):
14+
ydata = eval(text)
15+
l.set_ydata(ydata)
16+
ax.set_ylim(np.min(ydata), np.max(ydata))
17+
plt.draw()
18+
19+
axbox = plt.axes([0.1, 0.05, 0.8, 0.075])
20+
text_box = TextBox(axbox, 'Evaluate', initial=initial_text)
21+
text_box.on_submit(submit)
22+
23+
plt.show()

‎lib/matplotlib/backend_bases.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backend_bases.py
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2606,14 +2606,14 @@ def _get_uniform_gridstate(ticks):
26062606
# keys in list 'all' enables all axes (default key 'a'),
26072607
# otherwise if key is a number only enable this particular axes
26082608
# if it was the axes, where the event was raised
2609-
if not (event.key in all):
2609+
if not (event.key in all_keys):
26102610
n = int(event.key) - 1
26112611
for i, a in enumerate(canvas.figure.get_axes()):
26122612
# consider axes, in which the event was raised
26132613
# FIXME: Why only this axes?
26142614
if event.x is not None and event.y is not None \
26152615
and a.in_axes(event):
2616-
if event.key in all:
2616+
if event.key in all_keys:
26172617
a.set_navigate(True)
26182618
else:
26192619
a.set_navigate(i == n)

‎lib/matplotlib/widgets.py

Copy file name to clipboardExpand all lines: lib/matplotlib/widgets.py
+309Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from six.moves import zip
1818

1919
import numpy as np
20+
from matplotlib import rcParams
2021

2122
from .mlab import dist
2223
from .patches import Circle, Rectangle, Ellipse
@@ -628,6 +629,314 @@ def disconnect(self, cid):
628629
pass
629630

630631

632+
class TextBox(AxesWidget):
633+
"""
634+
A GUI neutral text input box.
635+
636+
For the text box to remain responsive you must keep a reference to it.
637+
638+
The following attributes are accessible:
639+
640+
*ax*
641+
The :class:`matplotlib.axes.Axes` the button renders into.
642+
643+
*label*
644+
A :class:`matplotlib.text.Text` instance.
645+
646+
*color*
647+
The color of the text box when not hovering.
648+
649+
*hovercolor*
650+
The color of the text box when hovering.
651+
652+
Call :meth:`on_text_change` to be updated whenever the text changes.
653+
654+
Call :meth:`on_submit` to be updated whenever the user hits enter or
655+
leaves the text entry field.
656+
"""
657+
658+
def __init__(self, ax, label, initial='',
659+
color='.95', hovercolor='1', label_pad=.01):
660+
"""
661+
Parameters
662+
----------
663+
ax : matplotlib.axes.Axes
664+
The :class:`matplotlib.axes.Axes` instance the button
665+
will be placed into.
666+
667+
label : str
668+
Label for this text box. Accepts string.
669+
670+
initial : str
671+
Initial value in the text box
672+
673+
color : color
674+
The color of the box
675+
676+
hovercolor : color
677+
The color of the box when the mouse is over it
678+
679+
label_pad : float
680+
the distance between the label and the right side of the textbox
681+
"""
682+
AxesWidget.__init__(self, ax)
683+
684+
self.DIST_FROM_LEFT = .05
685+
686+
self.params_to_disable = []
687+
for key in rcParams.keys():
688+
if u'keymap' in key:
689+
self.params_to_disable += [key]
690+
691+
self.text = initial
692+
self.label = ax.text(-label_pad, 0.5, label,
693+
verticalalignment='center',
694+
horizontalalignment='right',
695+
transform=ax.transAxes)
696+
self.text_disp = self._make_text_disp(self.text)
697+
698+
self.cnt = 0
699+
self.change_observers = {}
700+
self.submit_observers = {}
701+
702+
# If these lines are removed, the cursor won't appear the first
703+
# time the box is clicked:
704+
self.ax.set_xlim(0, 1)
705+
self.ax.set_ylim(0, 1)
706+
707+
self.cursor_index = 0
708+
709+
# Because this is initialized, _render_cursor
710+
# can assume that cursor exists.
711+
self.cursor = self.ax.vlines(0, 0, 0)
712+
self.cursor.set_visible(False)
713+
714+
self.connect_event('button_press_event', self._click)
715+
self.connect_event('button_release_event', self._release)
716+
self.connect_event('motion_notify_event', self._motion)
717+
self.connect_event('key_press_event', self._keypress)
718+
self.connect_event('resize_event', self._resize)
719+
ax.set_navigate(False)
720+
ax.set_facecolor(color)
721+
ax.set_xticks([])
722+
ax.set_yticks([])
723+
self.color = color
724+
self.hovercolor = hovercolor
725+
726+
self._lastcolor = color
727+
728+
self.capturekeystrokes = False
729+
730+
def _make_text_disp(self, string):
731+
return self.ax.text(self.DIST_FROM_LEFT, 0.5, string,
732+
verticalalignment='center',
733+
horizontalalignment='left',
734+
transform=self.ax.transAxes)
735+
736+
def _rendercursor(self):
737+
# this is a hack to figure out where the cursor should go.
738+
# we draw the text up to where the cursor should go, measure
739+
# and save its dimensions, draw the real text, then put the cursor
740+
# at the saved dimensions
741+
742+
widthtext = self.text[:self.cursor_index]
743+
no_text = False
744+
if(widthtext == "" or widthtext == " " or widthtext == " "):
745+
no_text = widthtext == ""
746+
widthtext = ","
747+
748+
wt_disp = self._make_text_disp(widthtext)
749+
750+
self.ax.figure.canvas.draw()
751+
bb = wt_disp.get_window_extent()
752+
inv = self.ax.transData.inverted()
753+
bb = inv.transform(bb)
754+
wt_disp.set_visible(False)
755+
if no_text:
756+
bb[1, 0] = bb[0, 0]
757+
# hack done
758+
self.cursor.set_visible(False)
759+
760+
self.cursor = self.ax.vlines(bb[1, 0], bb[0, 1], bb[1, 1])
761+
self.ax.figure.canvas.draw()
762+
763+
def _notify_submit_observers(self):
764+
for cid, func in six.iteritems(self.submit_observers):
765+
func(self.text)
766+
767+
def _release(self, event):
768+
if self.ignore(event):
769+
return
770+
if event.canvas.mouse_grabber != self.ax:
771+
return
772+
event.canvas.release_mouse(self.ax)
773+
774+
def _keypress(self, event):
775+
if self.ignore(event):
776+
return
777+
if self.capturekeystrokes:
778+
key = event.key
779+
780+
if(len(key) == 1):
781+
self.text = (self.text[:self.cursor_index] + key +
782+
self.text[self.cursor_index:])
783+
self.cursor_index += 1
784+
elif key == "right":
785+
if self.cursor_index != len(self.text):
786+
self.cursor_index += 1
787+
elif key == "left":
788+
if self.cursor_index != 0:
789+
self.cursor_index -= 1
790+
elif key == "home":
791+
self.cursor_index = 0
792+
elif key == "end":
793+
self.cursor_index = len(self.text)
794+
elif(key == "backspace"):
795+
if self.cursor_index != 0:
796+
self.text = (self.text[:self.cursor_index - 1] +
797+
self.text[self.cursor_index:])
798+
self.cursor_index -= 1
799+
elif(key == "delete"):
800+
if self.cursor_index != len(self.text):
801+
self.text = (self.text[:self.cursor_index] +
802+
self.text[self.cursor_index + 1:])
803+
804+
self.text_disp.remove()
805+
self.text_disp = self._make_text_disp(self.text)
806+
self._rendercursor()
807+
self._notify_change_observers()
808+
if key == "enter":
809+
self._notify_submit_observers()
810+
811+
def set_val(self, val):
812+
newval = str(val)
813+
if self.text == newval:
814+
return
815+
self.text = newval
816+
self.text_disp.remove()
817+
self.text_disp = self._make_text_disp(self.text)
818+
self._rendercursor()
819+
self._notify_change_observers()
820+
self._notify_submit_observers()
821+
822+
def _notify_change_observers(self):
823+
for cid, func in six.iteritems(self.change_observers):
824+
func(self.text)
825+
826+
def begin_typing(self, x):
827+
self.capturekeystrokes = True
828+
# disable command keys so that the user can type without
829+
# command keys causing figure to be saved, etc
830+
self.reset_params = {}
831+
for key in self.params_to_disable:
832+
self.reset_params[key] = rcParams[key]
833+
rcParams[key] = []
834+
835+
def stop_typing(self):
836+
notifysubmit = False
837+
# because _notify_submit_users might throw an error in the
838+
# user's code, we only want to call it once we've already done
839+
# our cleanup.
840+
if self.capturekeystrokes:
841+
# since the user is no longer typing,
842+
# reactivate the standard command keys
843+
for key in self.params_to_disable:
844+
rcParams[key] = self.reset_params[key]
845+
notifysubmit = True
846+
self.capturekeystrokes = False
847+
self.cursor.set_visible(False)
848+
self.ax.figure.canvas.draw()
849+
if notifysubmit:
850+
self._notify_submit_observers()
851+
852+
def position_cursor(self, x):
853+
# now, we have to figure out where the cursor goes.
854+
# approximate it based on assuming all characters the same length
855+
if len(self.text) == 0:
856+
self.cursor_index = 0
857+
else:
858+
bb = self.text_disp.get_window_extent()
859+
860+
trans = self.ax.transData
861+
inv = self.ax.transData.inverted()
862+
bb = trans.transform(inv.transform(bb))
863+
864+
text_start = bb[0, 0]
865+
text_end = bb[1, 0]
866+
867+
ratio = (x - text_start) / (text_end - text_start)
868+
869+
if ratio < 0:
870+
ratio = 0
871+
if ratio > 1:
872+
ratio = 1
873+
874+
self.cursor_index = int(len(self.text) * ratio)
875+
876+
self._rendercursor()
877+
878+
def _click(self, event):
879+
if self.ignore(event):
880+
return
881+
if event.inaxes != self.ax:
882+
self.capturekeystrokes = False
883+
self.stop_typing()
884+
return
885+
if not self.eventson:
886+
return
887+
if event.canvas.mouse_grabber != self.ax:
888+
event.canvas.grab_mouse(self.ax)
889+
if not(self.capturekeystrokes):
890+
self.begin_typing(event.x)
891+
self.position_cursor(event.x)
892+
893+
def _resize(self, event):
894+
self.stop_typing()
895+
896+
def _motion(self, event):
897+
if self.ignore(event):
898+
return
899+
if event.inaxes == self.ax:
900+
c = self.hovercolor
901+
else:
902+
c = self.color
903+
if c != self._lastcolor:
904+
self.ax.set_facecolor(c)
905+
self._lastcolor = c
906+
if self.drawon:
907+
self.ax.figure.canvas.draw()
908+
909+
def on_text_change(self, func):
910+
"""
911+
When the text changes, call this *func* with event.
912+
913+
A connection id is returned which can be used to disconnect.
914+
"""
915+
cid = self.cnt
916+
self.change_observers[cid] = func
917+
self.cnt += 1
918+
return cid
919+
920+
def on_submit(self, func):
921+
"""
922+
When the user hits enter or leaves the submision box, call this
923+
*func* with event.
924+
925+
A connection id is returned which can be used to disconnect.
926+
"""
927+
cid = self.cnt
928+
self.submit_observers[cid] = func
929+
self.cnt += 1
930+
return cid
931+
932+
def disconnect(self, cid):
933+
"""remove the observer with connection id *cid*"""
934+
try:
935+
del self.observers[cid]
936+
except KeyError:
937+
pass
938+
939+
631940
class RadioButtons(AxesWidget):
632941
"""
633942
A GUI neutral radio button.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.