Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2bf21ab

Browse files
committed
Merge branch 'text-box-widget' of https://github.com/QuadmasterXLII/matplotlib into text-box-widget
2 parents 6eeb109 + 2734051 commit 2bf21ab

File tree

4 files changed

+328
-0
lines changed

4 files changed

+328
-0
lines changed

CHANGELOG

Lines 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

Lines 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

Lines 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/widgets.py

Lines changed: 294 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,299 @@ 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+
for cid, func in six.iteritems(self.change_observers):
808+
func(self.text)
809+
if key == "enter":
810+
self._notify_submit_observers()
811+
812+
def begin_typing(self, x):
813+
self.capturekeystrokes = True
814+
#disable command keys so that the user can type without
815+
#command keys causing figure to be saved, etc
816+
self.reset_params = {}
817+
for key in self.params_to_disable:
818+
self.reset_params[key] = rcParams[key]
819+
rcParams[key] = []
820+
821+
def stop_typing(self):
822+
notifysubmit = False
823+
# because _notify_submit_users might throw an error in the
824+
# user's code, we only want to call it once we've already done
825+
# our cleanup.
826+
if self.capturekeystrokes:
827+
#since the user is no longer typing,
828+
#reactivate the standard command keys
829+
for key in self.params_to_disable:
830+
rcParams[key] = self.reset_params[key]
831+
notifysubmit = True
832+
self.capturekeystrokes = False
833+
self.cursor.set_visible(False)
834+
self.ax.figure.canvas.draw()
835+
if notifysubmit:
836+
self._notify_submit_observers()
837+
838+
def position_cursor(self, x):
839+
#now, we have to figure out where the cursor goes.
840+
#approximate it based on assuming all characters the same length
841+
if len(self.text) == 0:
842+
self.cursor_index = 0
843+
else:
844+
bb = self.text_disp.get_window_extent()
845+
846+
trans = self.ax.transData
847+
inv = self.ax.transData.inverted()
848+
bb = trans.transform(inv.transform(bb))
849+
850+
text_start = bb[0, 0]
851+
text_end = bb[1, 0]
852+
853+
ratio = (x - text_start) / (text_end - text_start)
854+
855+
if ratio < 0:
856+
ratio = 0
857+
if ratio > 1:
858+
ratio = 1
859+
860+
self.cursor_index = int(len(self.text) * ratio)
861+
862+
self._rendercursor()
863+
864+
def _click(self, event):
865+
if self.ignore(event):
866+
return
867+
if event.inaxes != self.ax:
868+
self.stop_typing()
869+
return
870+
if not self.eventson:
871+
return
872+
if event.canvas.mouse_grabber != self.ax:
873+
event.canvas.grab_mouse(self.ax)
874+
if not(self.capturekeystrokes):
875+
self.begin_typing(event.x)
876+
self.position_cursor(event.x)
877+
878+
def _resize(self, event):
879+
self.stop_typing()
880+
881+
def _motion(self, event):
882+
if self.ignore(event):
883+
return
884+
if event.inaxes == self.ax:
885+
c = self.hovercolor
886+
else:
887+
c = self.color
888+
if c != self._lastcolor:
889+
self.ax.set_facecolor(c)
890+
self._lastcolor = c
891+
if self.drawon:
892+
self.ax.figure.canvas.draw()
893+
894+
def on_text_change(self, func):
895+
"""
896+
When the text changes, call this *func* with event.
897+
898+
A connection id is returned which can be used to disconnect.
899+
"""
900+
cid = self.cnt
901+
self.change_observers[cid] = func
902+
self.cnt += 1
903+
return cid
904+
905+
def on_submit(self, func):
906+
"""
907+
When the user hits enter or leaves the submision box, call this
908+
*func* with event.
909+
910+
A connection id is returned which can be used to disconnect.
911+
"""
912+
cid = self.cnt
913+
self.submit_observers[cid] = func
914+
self.cnt += 1
915+
return cid
916+
917+
def disconnect(self, cid):
918+
"""remove the observer with connection id *cid*"""
919+
try:
920+
del self.observers[cid]
921+
except KeyError:
922+
pass
923+
924+
631925
class RadioButtons(AxesWidget):
632926
"""
633927
A GUI neutral radio button.

0 commit comments

Comments
 (0)