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

Skip to content

Text box widget, take over of PR5375 #6988

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Sep 3, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@

2015-11-16 Levels passed to contour(f) and tricontour(f) must be in increasing
order.

2015-10-21 Added TextBox widget


2015-10-21 Added get_ticks_direction()

2015-02-27 Added the rcParam 'image.composite_image' to permit users
Expand Down
7 changes: 7 additions & 0 deletions doc/users/whats_new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,13 @@ Some parameters have been added, others have been improved.
Widgets
-------

Added TextBox Widget
````````````````````

Added a widget that allows text entry by reading key events when it is active.
Text caret in text box is visible when it is active, can be moved using arrow keys and mouse


Active state of Selectors
`````````````````````````

Expand Down
23 changes: 23 additions & 0 deletions examples/widgets/textbox.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import TextBox
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.2)
t = np.arange(-2.0, 2.0, 0.001)
s = t ** 2
initial_text = "t ** 2"
l, = plt.plot(t, s, lw=2)


def submit(text):
ydata = eval(text)
l.set_ydata(ydata)
ax.set_ylim(np.min(ydata), np.max(ydata))
plt.draw()

axbox = plt.axes([0.1, 0.05, 0.8, 0.075])
text_box = TextBox(axbox, 'Evaluate', initial=initial_text)
text_box.on_submit(submit)

plt.show()
4 changes: 2 additions & 2 deletions lib/matplotlib/backend_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -2606,14 +2606,14 @@ def _get_uniform_gridstate(ticks):
# keys in list 'all' enables all axes (default key 'a'),
# otherwise if key is a number only enable this particular axes
# if it was the axes, where the event was raised
if not (event.key in all):
if not (event.key in all_keys):
n = int(event.key) - 1
for i, a in enumerate(canvas.figure.get_axes()):
# consider axes, in which the event was raised
# FIXME: Why only this axes?
if event.x is not None and event.y is not None \
and a.in_axes(event):
if event.key in all:
if event.key in all_keys:
a.set_navigate(True)
else:
a.set_navigate(i == n)
Expand Down
309 changes: 309 additions & 0 deletions lib/matplotlib/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from six.moves import zip

import numpy as np
from matplotlib import rcParams

from .mlab import dist
from .patches import Circle, Rectangle, Ellipse
Expand Down Expand Up @@ -628,6 +629,314 @@ def disconnect(self, cid):
pass


class TextBox(AxesWidget):
"""
A GUI neutral text input box.

For the text box to remain responsive you must keep a reference to it.

The following attributes are accessible:

*ax*
The :class:`matplotlib.axes.Axes` the button renders into.

*label*
A :class:`matplotlib.text.Text` instance.

*color*
The color of the text box when not hovering.

*hovercolor*
The color of the text box when hovering.

Call :meth:`on_text_change` to be updated whenever the text changes.

Call :meth:`on_submit` to be updated whenever the user hits enter or
leaves the text entry field.
"""

def __init__(self, ax, label, initial='',
color='.95', hovercolor='1', label_pad=.01):
"""
Parameters
----------
ax : matplotlib.axes.Axes
The :class:`matplotlib.axes.Axes` instance the button
will be placed into.

label : str
Label for this text box. Accepts string.

initial : str
Initial value in the text box

color : color
The color of the box

hovercolor : color
The color of the box when the mouse is over it

label_pad : float
the distance between the label and the right side of the textbox
"""
AxesWidget.__init__(self, ax)

self.DIST_FROM_LEFT = .05

self.params_to_disable = []
for key in rcParams.keys():
if u'keymap' in key:
self.params_to_disable += [key]

self.text = initial
self.label = ax.text(-label_pad, 0.5, label,
verticalalignment='center',
horizontalalignment='right',
transform=ax.transAxes)
self.text_disp = self._make_text_disp(self.text)

self.cnt = 0
self.change_observers = {}
self.submit_observers = {}

# If these lines are removed, the cursor won't appear the first
# time the box is clicked:
self.ax.set_xlim(0, 1)
self.ax.set_ylim(0, 1)

self.cursor_index = 0

# Because this is initialized, _render_cursor
# can assume that cursor exists.
self.cursor = self.ax.vlines(0, 0, 0)
self.cursor.set_visible(False)

self.connect_event('button_press_event', self._click)
self.connect_event('button_release_event', self._release)
self.connect_event('motion_notify_event', self._motion)
self.connect_event('key_press_event', self._keypress)
self.connect_event('resize_event', self._resize)
ax.set_navigate(False)
ax.set_facecolor(color)
ax.set_xticks([])
ax.set_yticks([])
self.color = color
self.hovercolor = hovercolor

self._lastcolor = color

self.capturekeystrokes = False

def _make_text_disp(self, string):
return self.ax.text(self.DIST_FROM_LEFT, 0.5, string,
verticalalignment='center',
horizontalalignment='left',
transform=self.ax.transAxes)

def _rendercursor(self):
# this is a hack to figure out where the cursor should go.
# we draw the text up to where the cursor should go, measure
# and save its dimensions, draw the real text, then put the cursor
# at the saved dimensions

widthtext = self.text[:self.cursor_index]
no_text = False
if(widthtext == "" or widthtext == " " or widthtext == " "):
no_text = widthtext == ""
widthtext = ","

wt_disp = self._make_text_disp(widthtext)

self.ax.figure.canvas.draw()
bb = wt_disp.get_window_extent()
inv = self.ax.transData.inverted()
bb = inv.transform(bb)
wt_disp.set_visible(False)
if no_text:
bb[1, 0] = bb[0, 0]
# hack done
self.cursor.set_visible(False)

self.cursor = self.ax.vlines(bb[1, 0], bb[0, 1], bb[1, 1])
self.ax.figure.canvas.draw()

def _notify_submit_observers(self):
for cid, func in six.iteritems(self.submit_observers):
func(self.text)

def _release(self, event):
if self.ignore(event):
return
if event.canvas.mouse_grabber != self.ax:
return
event.canvas.release_mouse(self.ax)

def _keypress(self, event):
if self.ignore(event):
return
if self.capturekeystrokes:
key = event.key

if(len(key) == 1):
self.text = (self.text[:self.cursor_index] + key +
self.text[self.cursor_index:])
self.cursor_index += 1
elif key == "right":
if self.cursor_index != len(self.text):
self.cursor_index += 1
elif key == "left":
if self.cursor_index != 0:
self.cursor_index -= 1
elif key == "home":
self.cursor_index = 0
elif key == "end":
self.cursor_index = len(self.text)
elif(key == "backspace"):
if self.cursor_index != 0:
self.text = (self.text[:self.cursor_index - 1] +
self.text[self.cursor_index:])
self.cursor_index -= 1
elif(key == "delete"):
if self.cursor_index != len(self.text):
self.text = (self.text[:self.cursor_index] +
self.text[self.cursor_index + 1:])

self.text_disp.remove()
self.text_disp = self._make_text_disp(self.text)
self._rendercursor()
self._notify_change_observers()
if key == "enter":
self._notify_submit_observers()

def set_val(self, val):
newval = str(val)
if self.text == newval:
return
self.text = newval
self.text_disp.remove()
self.text_disp = self._make_text_disp(self.text)
self._rendercursor()
self._notify_change_observers()
self._notify_submit_observers()

def _notify_change_observers(self):
for cid, func in six.iteritems(self.change_observers):
func(self.text)

def begin_typing(self, x):
self.capturekeystrokes = True
# disable command keys so that the user can type without
# command keys causing figure to be saved, etc
self.reset_params = {}
for key in self.params_to_disable:
self.reset_params[key] = rcParams[key]
rcParams[key] = []

def stop_typing(self):
notifysubmit = False
# because _notify_submit_users might throw an error in the
# user's code, we only want to call it once we've already done
# our cleanup.
if self.capturekeystrokes:
# since the user is no longer typing,
# reactivate the standard command keys
for key in self.params_to_disable:
rcParams[key] = self.reset_params[key]
notifysubmit = True
self.capturekeystrokes = False
self.cursor.set_visible(False)
self.ax.figure.canvas.draw()
if notifysubmit:
self._notify_submit_observers()

def position_cursor(self, x):
# now, we have to figure out where the cursor goes.
# approximate it based on assuming all characters the same length
if len(self.text) == 0:
self.cursor_index = 0
else:
bb = self.text_disp.get_window_extent()

trans = self.ax.transData
inv = self.ax.transData.inverted()
bb = trans.transform(inv.transform(bb))

text_start = bb[0, 0]
text_end = bb[1, 0]

ratio = (x - text_start) / (text_end - text_start)

if ratio < 0:
ratio = 0
if ratio > 1:
ratio = 1

self.cursor_index = int(len(self.text) * ratio)

self._rendercursor()

def _click(self, event):
if self.ignore(event):
return
if event.inaxes != self.ax:
self.capturekeystrokes = False
self.stop_typing()
return
if not self.eventson:
return
if event.canvas.mouse_grabber != self.ax:
event.canvas.grab_mouse(self.ax)
if not(self.capturekeystrokes):
self.begin_typing(event.x)
self.position_cursor(event.x)

def _resize(self, event):
self.stop_typing()

def _motion(self, event):
if self.ignore(event):
return
if event.inaxes == self.ax:
c = self.hovercolor
else:
c = self.color
if c != self._lastcolor:
self.ax.set_facecolor(c)
self._lastcolor = c
if self.drawon:
self.ax.figure.canvas.draw()

def on_text_change(self, func):
"""
When the text changes, call this *func* with event.

A connection id is returned which can be used to disconnect.
"""
cid = self.cnt
self.change_observers[cid] = func
self.cnt += 1
return cid

def on_submit(self, func):
"""
When the user hits enter or leaves the submision box, call this
*func* with event.

A connection id is returned which can be used to disconnect.
"""
cid = self.cnt
self.submit_observers[cid] = func
self.cnt += 1
return cid

def disconnect(self, cid):
"""remove the observer with connection id *cid*"""
try:
del self.observers[cid]
except KeyError:
pass


class RadioButtons(AxesWidget):
"""
A GUI neutral radio button.
Expand Down