-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Add improved modification of QuiverKey properties #18794
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
``QuiverKey`` properties are now modifiable | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
The `.QuiverKey` object returned by `.pyplot.quiverkey` and `.axes.Axes.quiverkey` | ||
formerly saved various properties as attributes during initialization. However, | ||
modifying these attributes may or may not have had an effect on the final result. | ||
|
||
Now all such properties have getters and setters, and may be modified after creation: | ||
|
||
- `.QuiverKey.X` -> `.QuiverKey.get_x` / `.QuiverKey.set_x` / | ||
`.QuiverKey.get_position` / `.QuiverKey.set_position` | ||
- `.QuiverKey.Y` -> `.QuiverKey.get_y` / `.QuiverKey.set_y` / | ||
`.QuiverKey.get_position` / `.QuiverKey.set_position` | ||
- `.QuiverKey.label` -> `.QuiverKey.get_label_text` / `.QuiverKey.set_label_text` | ||
- `.QuiverKey.labelcolor` -> `.QuiverKey.get_label_color` / `.QuiverKey.set_label_color` | ||
- `.QuiverKey.labelpos` -> `.QuiverKey.get_label_pos` / `.QuiverKey.set_label_pos` | ||
- `.QuiverKey.labelsep` is now read-only as it used a different unit (pixels) | ||
than the constructor (inches), and was automatically overwritten; | ||
`.QuiverKey.get_labelsep` and `.QuiverKey.set_labelsep` have been added which | ||
use inches |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
``QuiverKey`` internal Artists | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
Access to the following `.quiver.QuiverKey` internal Artists is now deprecated. | ||
You may instead use `.quiver.QuiverKey`-level methods to modify these Artists. | ||
|
||
- ``QuiverKey.text`` | ||
- ``QuiverKey.vector`` | ||
- ``QuiverKey.verts`` | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
``QuiverKey`` properties are now modifiable | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
The `.QuiverKey` object returned by `.pyplot.quiverkey` and `.axes.Axes.quiverkey` | ||
formerly saved various properties as attributes during initialization. However, | ||
modifying these attributes may or may not have had an effect on the final result. | ||
|
||
Now all such properties have getters and setters, and may be modified after creation: | ||
|
||
- `.QuiverKey.X` -> `.QuiverKey.get_x` / `.QuiverKey.set_x` / | ||
`.QuiverKey.get_position` / `.QuiverKey.set_position` | ||
- `.QuiverKey.Y` -> `.QuiverKey.get_y` / `.QuiverKey.set_y` / | ||
`.QuiverKey.get_position` / `.QuiverKey.set_position` | ||
- `.QuiverKey.label` -> `.QuiverKey.get_label_text` / `.QuiverKey.set_label_text` | ||
- `.QuiverKey.labelcolor` -> `.QuiverKey.get_label_color` / `.QuiverKey.set_label_color` | ||
- `.QuiverKey.labelpos` -> `.QuiverKey.get_label_pos` / `.QuiverKey.set_label_pos` | ||
- `.QuiverKey.labelsep` is now read-only as it used a different unit (pixels) | ||
than the constructor (inches), and was automatically overwritten; | ||
`.QuiverKey.get_labelsep` and `.QuiverKey.set_labelsep` have been added which | ||
use inches |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -238,6 +238,10 @@ | |
valign = {'N': 'bottom', 'S': 'top', 'E': 'center', 'W': 'center'} | ||
pivot = {'N': 'middle', 'S': 'middle', 'E': 'tip', 'W': 'tail'} | ||
|
||
text = _api.deprecate_privatize_attribute('3.9', alternative='QuiverKey methods') | ||
vector = _api.deprecate_privatize_attribute('3.9') | ||
verts = _api.deprecate_privatize_attribute('3.9') | ||
|
||
def __init__(self, Q, X, Y, U, label, | ||
*, angle=0, coordinates='axes', color=None, labelsep=0.1, | ||
labelpos='N', labelcolor=None, fontproperties=None, **kwargs): | ||
|
@@ -290,73 +294,179 @@ | |
""" | ||
super().__init__() | ||
self.Q = Q | ||
self.X = X | ||
self.Y = Y | ||
self.U = U | ||
self.angle = angle | ||
self.coord = coordinates | ||
self.color = color | ||
self.label = label | ||
self._labelsep_inches = labelsep | ||
|
||
self.labelpos = labelpos | ||
self.labelcolor = labelcolor | ||
_api.check_in_list(['N', 'S', 'E', 'W'], labelpos=labelpos) | ||
self._labelpos = labelpos | ||
self.fontproperties = fontproperties or dict() | ||
self.kw = kwargs | ||
self.text = mtext.Text( | ||
text=label, | ||
horizontalalignment=self.halign[self.labelpos], | ||
verticalalignment=self.valign[self.labelpos], | ||
fontproperties=self.fontproperties) | ||
if self.labelcolor is not None: | ||
self.text.set_color(self.labelcolor) | ||
self._text = mtext.Text( | ||
x=X, y=Y, text=label, | ||
horizontalalignment=self.halign[self._labelpos], | ||
verticalalignment=self.valign[self._labelpos], | ||
fontproperties=self.fontproperties, | ||
color=labelcolor) | ||
self._dpi_at_last_init = None | ||
self.zorder = Q.zorder + 0.1 | ||
|
||
def get_x(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps we can do without {get,set}_{x,y} and just stick to get_position (as for Text)? the backcompat X and Y would be kept for backcompat but can use straight lambdas ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree. We only have get_x on Rectangle and FancyBboxPatch. Let's keep the API size down and only provide get/set_position. |
||
"""Return the *x* position of the QuiverKey.""" | ||
return self._text.get_position()[0] | ||
|
||
def set_x(self, x): | ||
""" | ||
Set the *x* position of the QuiverKey. | ||
|
||
Parameters | ||
---------- | ||
x : float | ||
The *x* location of the key. | ||
""" | ||
self._text.set_x(x) | ||
self.stale = True | ||
|
||
X = property(get_x, set_x) | ||
|
||
def get_y(self): | ||
"""Return the *y* position of the QuiverKey.""" | ||
return self._text.get_position()[1] | ||
|
||
def set_y(self, y): | ||
""" | ||
Set the *y* position of the QuiverKey. | ||
|
||
Parameters | ||
---------- | ||
y : float | ||
The *y* location of the key. | ||
""" | ||
self._text.set_y(y) | ||
self.stale = True | ||
|
||
Y = property(get_y, set_y) | ||
|
||
def get_position(self): | ||
"""Return the (x, y) position of the QuiverKey.""" | ||
return self._text.get_position() | ||
|
||
def set_position(self, xy): | ||
""" | ||
Set the position of the QuiverKey. | ||
|
||
Parameters | ||
---------- | ||
xy : (float, float) | ||
The (*x*, *y*) position of the QuiverKey. | ||
""" | ||
self._text.set_position(xy) | ||
self.stale = True | ||
|
||
def get_label_text(self): | ||
"""Return the label string.""" | ||
return self._text.get_text() | ||
|
||
def set_label_text(self, text): | ||
"""Set the label string.""" | ||
self._text.set_text(text) | ||
self.stale = True | ||
|
||
label = property(get_label_text, set_label_text, doc="The label string.") | ||
|
||
def get_label_color(self): | ||
"""Return the label color.""" | ||
return self._text.get_color() | ||
|
||
def set_label_color(self, labelcolor): | ||
"""Set the label color.""" | ||
self._text.set_color(labelcolor) | ||
self.stale = True | ||
|
||
labelcolor = property(get_label_color, set_label_color, doc="The label color.") | ||
|
||
def get_label_pos(self): | ||
"""Return the label position.""" | ||
return self._labelpos | ||
|
||
def set_label_pos(self, labelpos): | ||
""" | ||
Set the label position. | ||
|
||
Parameters | ||
---------- | ||
labelpos : {'N', 'S', 'E', 'W'} | ||
Position the label above, below, to the right, to the left of the | ||
arrow, respectively. | ||
""" | ||
_api.check_in_list(['N', 'S', 'E', 'W'], labelpos=labelpos) | ||
self._labelpos = labelpos | ||
self._text.set_horizontalalignment(self.halign[labelpos]) | ||
self._text.set_verticalalignment(self.valign[labelpos]) | ||
self._update_text_transform() | ||
self._initialized = False | ||
self.stale = True | ||
|
||
labelpos = property(get_label_pos, set_label_pos, doc="The label position.") | ||
|
||
def get_labelsep(self): | ||
"""Return the distance between the arrow and label in inches.""" | ||
return self._labelsep_inches | ||
|
||
def set_labelsep(self, labelsep): | ||
"""Set the distance between the arrow and label in inches.""" | ||
self._labelsep_inches = labelsep | ||
self._update_text_transform() | ||
self.stale = True | ||
|
||
@property | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider deprecating this, given the potential source of confusion? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree. |
||
def labelsep(self): | ||
"""Return the distance between the arrow and label in pixels.""" | ||
return self._labelsep_inches * self.Q.axes.figure.dpi | ||
|
||
def _init(self): | ||
if True: # self._dpi_at_last_init != self.axes.figure.dpi | ||
if self.Q._dpi_at_last_init != self.Q.axes.figure.dpi: | ||
self.Q._init() | ||
self._set_transform() | ||
with cbook._setattr_cm(self.Q, pivot=self.pivot[self.labelpos], | ||
with cbook._setattr_cm(self.Q, pivot=self.pivot[self._labelpos], | ||
# Hack: save and restore the Umask | ||
Umask=ma.nomask): | ||
u = self.U * np.cos(np.radians(self.angle)) | ||
v = self.U * np.sin(np.radians(self.angle)) | ||
self.verts = self.Q._make_verts([[0., 0.]], | ||
np.array([u]), np.array([v]), 'uv') | ||
self._verts = self.Q._make_verts( | ||
[[0., 0.]], np.array([u]), np.array([v]), 'uv') | ||
kwargs = self.Q.polykw | ||
kwargs.update(self.kw) | ||
self.vector = mcollections.PolyCollection( | ||
self.verts, | ||
self._vector = mcollections.PolyCollection( | ||
self._verts, | ||
offsets=[(self.X, self.Y)], | ||
offset_transform=self.get_transform(), | ||
**kwargs) | ||
if self.color is not None: | ||
self.vector.set_color(self.color) | ||
self.vector.set_transform(self.Q.get_transform()) | ||
self.vector.set_figure(self.get_figure()) | ||
self._vector.set_color(self.color) | ||
self._vector.set_transform(self.Q.get_transform()) | ||
self._vector.set_figure(self.get_figure()) | ||
self._dpi_at_last_init = self.Q.axes.figure.dpi | ||
|
||
def _text_shift(self): | ||
return { | ||
"N": (0, +self.labelsep), | ||
"S": (0, -self.labelsep), | ||
"E": (+self.labelsep, 0), | ||
"W": (-self.labelsep, 0), | ||
}[self.labelpos] | ||
def _update_text_transform(self): | ||
x, y = { | ||
"N": (0, +self._labelsep_inches), | ||
"S": (0, -self._labelsep_inches), | ||
"E": (+self._labelsep_inches, 0), | ||
"W": (-self._labelsep_inches, 0), | ||
}[self._labelpos] | ||
self._text.set_transform( | ||
transforms.offset_copy(self.get_transform(), self.figure, x=x, y=y)) | ||
|
||
@martist.allow_rasterization | ||
def draw(self, renderer): | ||
self._init() | ||
self.vector.draw(renderer) | ||
pos = self.get_transform().transform((self.X, self.Y)) | ||
self.text.set_position(pos + self._text_shift()) | ||
self.text.draw(renderer) | ||
self._vector.draw(renderer) | ||
self._update_text_transform() | ||
self._text.draw(renderer) | ||
self.stale = False | ||
|
||
def _set_transform(self): | ||
|
@@ -369,15 +479,14 @@ | |
|
||
def set_figure(self, fig): | ||
super().set_figure(fig) | ||
self.text.set_figure(fig) | ||
self._text.set_figure(fig) | ||
|
||
def contains(self, mouseevent): | ||
if self._different_canvas(mouseevent): | ||
return False, {} | ||
# Maybe the dictionary should allow one to | ||
# distinguish between a text hit and a vector hit. | ||
if (self.text.contains(mouseevent)[0] or | ||
self.vector.contains(mouseevent)[0]): | ||
if self._text.contains(mouseevent)[0] or self._vector.contains(mouseevent)[0]: | ||
return True, {} | ||
return False, {} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps someone may want e.g. to set usetex or math_fontfamily or whatnot on the Text; do you really want to deprecate access to it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keeping text public is the pragmatic thing to do.