mirror of
https://github.com/Estom/notes.git
synced 2026-02-11 06:15:45 +08:00
matplotlib & pandas
This commit is contained in:
265
Python/matplotlab/advanced/path_tutorial.md
Normal file
265
Python/matplotlab/advanced/path_tutorial.md
Normal file
@@ -0,0 +1,265 @@
|
||||
---
|
||||
sidebarDepth: 3
|
||||
sidebar: auto
|
||||
---
|
||||
|
||||
# Path Tutorial
|
||||
|
||||
Defining paths in your Matplotlib visualization.
|
||||
|
||||
The object underlying all of the ``matplotlib.patch`` objects is
|
||||
the [``Path``](https://matplotlib.orgapi/path_api.html#matplotlib.path.Path), which supports the standard set of
|
||||
moveto, lineto, curveto commands to draw simple and compound outlines
|
||||
consisting of line segments and splines. The ``Path`` is instantiated
|
||||
with a (N,2) array of (x,y) vertices, and a N-length array of path
|
||||
codes. For example to draw the unit rectangle from (0,0) to (1,1), we
|
||||
could use this code
|
||||
|
||||
``` python
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.path import Path
|
||||
import matplotlib.patches as patches
|
||||
|
||||
verts = [
|
||||
(0., 0.), # left, bottom
|
||||
(0., 1.), # left, top
|
||||
(1., 1.), # right, top
|
||||
(1., 0.), # right, bottom
|
||||
(0., 0.), # ignored
|
||||
]
|
||||
|
||||
codes = [
|
||||
Path.MOVETO,
|
||||
Path.LINETO,
|
||||
Path.LINETO,
|
||||
Path.LINETO,
|
||||
Path.CLOSEPOLY,
|
||||
]
|
||||
|
||||
path = Path(verts, codes)
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
patch = patches.PathPatch(path, facecolor='orange', lw=2)
|
||||
ax.add_patch(patch)
|
||||
ax.set_xlim(-2, 2)
|
||||
ax.set_ylim(-2, 2)
|
||||
plt.show()
|
||||
```
|
||||
|
||||

|
||||
|
||||
The following path codes are recognized
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Code
|
||||
Vertices
|
||||
Description
|
||||
|
||||
|
||||
|
||||
STOP
|
||||
1 (ignored)
|
||||
A marker for the end of the entire path (currently not required and ignored)
|
||||
|
||||
MOVETO
|
||||
1
|
||||
Pick up the pen and move to the given vertex.
|
||||
|
||||
LINETO
|
||||
1
|
||||
Draw a line from the current position to the given vertex.
|
||||
|
||||
CURVE3
|
||||
2 (1 control point, 1 endpoint)
|
||||
Draw a quadratic Bézier curve from the current position, with the given control point, to the given end point.
|
||||
|
||||
CURVE4
|
||||
3 (2 control points, 1 endpoint)
|
||||
Draw a cubic Bézier curve from the current position, with the given control points, to the given end point.
|
||||
|
||||
CLOSEPOLY
|
||||
1 (point itself is ignored)
|
||||
Draw a line segment to the start point of the current polyline.
|
||||
|
||||
|
||||
|
||||
|
||||
## Bézier example
|
||||
|
||||
Some of the path components require multiple vertices to specify them:
|
||||
for example CURVE 3 is a [bézier](https://en.wikipedia.org/wiki/B%C3%A9zier_curve) curve with one
|
||||
control point and one end point, and CURVE4 has three vertices for the
|
||||
two control points and the end point. The example below shows a
|
||||
CURVE4 Bézier spline -- the bézier curve will be contained in the
|
||||
convex hull of the start point, the two control points, and the end
|
||||
point
|
||||
|
||||
``` python
|
||||
verts = [
|
||||
(0., 0.), # P0
|
||||
(0.2, 1.), # P1
|
||||
(1., 0.8), # P2
|
||||
(0.8, 0.), # P3
|
||||
]
|
||||
|
||||
codes = [
|
||||
Path.MOVETO,
|
||||
Path.CURVE4,
|
||||
Path.CURVE4,
|
||||
Path.CURVE4,
|
||||
]
|
||||
|
||||
path = Path(verts, codes)
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
patch = patches.PathPatch(path, facecolor='none', lw=2)
|
||||
ax.add_patch(patch)
|
||||
|
||||
xs, ys = zip(*verts)
|
||||
ax.plot(xs, ys, 'x--', lw=2, color='black', ms=10)
|
||||
|
||||
ax.text(-0.05, -0.05, 'P0')
|
||||
ax.text(0.15, 1.05, 'P1')
|
||||
ax.text(1.05, 0.85, 'P2')
|
||||
ax.text(0.85, -0.05, 'P3')
|
||||
|
||||
ax.set_xlim(-0.1, 1.1)
|
||||
ax.set_ylim(-0.1, 1.1)
|
||||
plt.show()
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Compound paths
|
||||
|
||||
All of the simple patch primitives in matplotlib, Rectangle, Circle,
|
||||
Polygon, etc, are implemented with simple path. Plotting functions
|
||||
like [``hist()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.hist.html#matplotlib.axes.Axes.hist) and
|
||||
[``bar()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.bar.html#matplotlib.axes.Axes.bar), which create a number of
|
||||
primitives, e.g., a bunch of Rectangles, can usually be implemented more
|
||||
efficiently using a compound path. The reason ``bar`` creates a list
|
||||
of rectangles and not a compound path is largely historical: the
|
||||
[``Path``](https://matplotlib.orgapi/path_api.html#matplotlib.path.Path) code is comparatively new and ``bar``
|
||||
predates it. While we could change it now, it would break old code,
|
||||
so here we will cover how to create compound paths, replacing the
|
||||
functionality in bar, in case you need to do so in your own code for
|
||||
efficiency reasons, e.g., you are creating an animated bar plot.
|
||||
|
||||
We will make the histogram chart by creating a series of rectangles
|
||||
for each histogram bar: the rectangle width is the bin width and the
|
||||
rectangle height is the number of datapoints in that bin. First we'll
|
||||
create some random normally distributed data and compute the
|
||||
histogram. Because numpy returns the bin edges and not centers, the
|
||||
length of ``bins`` is 1 greater than the length of ``n`` in the
|
||||
example below:
|
||||
|
||||
``` python
|
||||
# histogram our data with numpy
|
||||
data = np.random.randn(1000)
|
||||
n, bins = np.histogram(data, 100)
|
||||
```
|
||||
|
||||
We'll now extract the corners of the rectangles. Each of the
|
||||
``left``, ``bottom``, etc, arrays below is ``len(n)``, where ``n`` is
|
||||
the array of counts for each histogram bar:
|
||||
|
||||
``` python
|
||||
# get the corners of the rectangles for the histogram
|
||||
left = np.array(bins[:-1])
|
||||
right = np.array(bins[1:])
|
||||
bottom = np.zeros(len(left))
|
||||
top = bottom + n
|
||||
```
|
||||
|
||||
Now we have to construct our compound path, which will consist of a
|
||||
series of ``MOVETO``, ``LINETO`` and ``CLOSEPOLY`` for each rectangle.
|
||||
For each rectangle, we need 5 vertices: 1 for the ``MOVETO``, 3 for
|
||||
the ``LINETO``, and 1 for the ``CLOSEPOLY``. As indicated in the
|
||||
table above, the vertex for the closepoly is ignored but we still need
|
||||
it to keep the codes aligned with the vertices:
|
||||
|
||||
``` python
|
||||
nverts = nrects*(1+3+1)
|
||||
verts = np.zeros((nverts, 2))
|
||||
codes = np.ones(nverts, int) * path.Path.LINETO
|
||||
codes[0::5] = path.Path.MOVETO
|
||||
codes[4::5] = path.Path.CLOSEPOLY
|
||||
verts[0::5,0] = left
|
||||
verts[0::5,1] = bottom
|
||||
verts[1::5,0] = left
|
||||
verts[1::5,1] = top
|
||||
verts[2::5,0] = right
|
||||
verts[2::5,1] = top
|
||||
verts[3::5,0] = right
|
||||
verts[3::5,1] = bottom
|
||||
```
|
||||
|
||||
All that remains is to create the path, attach it to a
|
||||
``PathPatch``, and add it to our axes:
|
||||
|
||||
``` python
|
||||
barpath = path.Path(verts, codes)
|
||||
patch = patches.PathPatch(barpath, facecolor='green',
|
||||
edgecolor='yellow', alpha=0.5)
|
||||
ax.add_patch(patch)
|
||||
```
|
||||
|
||||
``` python
|
||||
import numpy as np
|
||||
import matplotlib.patches as patches
|
||||
import matplotlib.path as path
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
# Fixing random state for reproducibility
|
||||
np.random.seed(19680801)
|
||||
|
||||
# histogram our data with numpy
|
||||
data = np.random.randn(1000)
|
||||
n, bins = np.histogram(data, 100)
|
||||
|
||||
# get the corners of the rectangles for the histogram
|
||||
left = np.array(bins[:-1])
|
||||
right = np.array(bins[1:])
|
||||
bottom = np.zeros(len(left))
|
||||
top = bottom + n
|
||||
nrects = len(left)
|
||||
|
||||
nverts = nrects*(1+3+1)
|
||||
verts = np.zeros((nverts, 2))
|
||||
codes = np.ones(nverts, int) * path.Path.LINETO
|
||||
codes[0::5] = path.Path.MOVETO
|
||||
codes[4::5] = path.Path.CLOSEPOLY
|
||||
verts[0::5, 0] = left
|
||||
verts[0::5, 1] = bottom
|
||||
verts[1::5, 0] = left
|
||||
verts[1::5, 1] = top
|
||||
verts[2::5, 0] = right
|
||||
verts[2::5, 1] = top
|
||||
verts[3::5, 0] = right
|
||||
verts[3::5, 1] = bottom
|
||||
|
||||
barpath = path.Path(verts, codes)
|
||||
patch = patches.PathPatch(barpath, facecolor='green',
|
||||
edgecolor='yellow', alpha=0.5)
|
||||
ax.add_patch(patch)
|
||||
|
||||
ax.set_xlim(left[0], right[-1])
|
||||
ax.set_ylim(bottom.min(), top.max())
|
||||
|
||||
plt.show()
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Download
|
||||
|
||||
- [Download Python source code: path_tutorial.py](https://matplotlib.org/_downloads/ec90dd07bc241d860eb972db796c96bc/path_tutorial.py)
|
||||
- [Download Jupyter notebook: path_tutorial.ipynb](https://matplotlib.org/_downloads/da8cacf827800cc7398495a527da865d/path_tutorial.ipynb)
|
||||
|
||||
122
Python/matplotlab/advanced/patheffects_guide.md
Normal file
122
Python/matplotlab/advanced/patheffects_guide.md
Normal file
@@ -0,0 +1,122 @@
|
||||
---
|
||||
sidebarDepth: 3
|
||||
sidebar: auto
|
||||
---
|
||||
|
||||
# Path effects guide
|
||||
|
||||
Defining paths that objects follow on a canvas.
|
||||
|
||||
Matplotlib's [``patheffects``](#module-matplotlib.patheffects) module provides functionality to
|
||||
apply a multiple draw stage to any Artist which can be rendered via a
|
||||
[``Path``](https://matplotlib.orgapi/path_api.html#matplotlib.path.Path).
|
||||
|
||||
Artists which can have a path effect applied to them include [``Patch``](https://matplotlib.orgapi/_as_gen/matplotlib.patches.Patch.html#matplotlib.patches.Patch),
|
||||
[``Line2D``](https://matplotlib.orgapi/_as_gen/matplotlib.lines.Line2D.html#matplotlib.lines.Line2D), [``Collection``](https://matplotlib.orgapi/collections_api.html#matplotlib.collections.Collection) and even
|
||||
[``Text``](https://matplotlib.orgapi/text_api.html#matplotlib.text.Text). Each artist's path effects can be controlled via the
|
||||
``set_path_effects`` method ([``set_path_effects``](https://matplotlib.orgapi/_as_gen/matplotlib.artist.Artist.set_path_effects.html#matplotlib.artist.Artist.set_path_effects)), which takes
|
||||
an iterable of [``AbstractPathEffect``](https://matplotlib.orgapi/patheffects_api.html#matplotlib.patheffects.AbstractPathEffect) instances.
|
||||
|
||||
The simplest path effect is the [``Normal``](https://matplotlib.orgapi/patheffects_api.html#matplotlib.patheffects.Normal) effect, which simply
|
||||
draws the artist without any effect:
|
||||
|
||||
``` python
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.patheffects as path_effects
|
||||
|
||||
fig = plt.figure(figsize=(5, 1.5))
|
||||
text = fig.text(0.5, 0.5, 'Hello path effects world!\nThis is the normal '
|
||||
'path effect.\nPretty dull, huh?',
|
||||
ha='center', va='center', size=20)
|
||||
text.set_path_effects([path_effects.Normal()])
|
||||
plt.show()
|
||||
```
|
||||
|
||||

|
||||
|
||||
Whilst the plot doesn't look any different to what you would expect without any path
|
||||
effects, the drawing of the text now been changed to use the path effects
|
||||
framework, opening up the possibilities for more interesting examples.
|
||||
|
||||
## Adding a shadow
|
||||
|
||||
A far more interesting path effect than [``Normal``](https://matplotlib.orgapi/patheffects_api.html#matplotlib.patheffects.Normal) is the
|
||||
drop-shadow, which we can apply to any of our path based artists. The classes
|
||||
[``SimplePatchShadow``](https://matplotlib.orgapi/patheffects_api.html#matplotlib.patheffects.SimplePatchShadow) and
|
||||
[``SimpleLineShadow``](https://matplotlib.orgapi/patheffects_api.html#matplotlib.patheffects.SimpleLineShadow) do precisely this by drawing either a filled
|
||||
patch or a line patch below the original artist:
|
||||
|
||||
``` python
|
||||
import matplotlib.patheffects as path_effects
|
||||
|
||||
text = plt.text(0.5, 0.5, 'Hello path effects world!',
|
||||
path_effects=[path_effects.withSimplePatchShadow()])
|
||||
|
||||
plt.plot([0, 3, 2, 5], linewidth=5, color='blue',
|
||||
path_effects=[path_effects.SimpleLineShadow(),
|
||||
path_effects.Normal()])
|
||||
plt.show()
|
||||
```
|
||||
|
||||

|
||||
|
||||
Notice the two approaches to setting the path effects in this example. The
|
||||
first uses the ``with*`` classes to include the desired functionality automatically
|
||||
followed with the "normal" effect, whereas the latter explicitly defines the two path
|
||||
effects to draw.
|
||||
|
||||
## Making an artist stand out
|
||||
|
||||
One nice way of making artists visually stand out is to draw an outline in a bold
|
||||
color below the actual artist. The [``Stroke``](https://matplotlib.orgapi/patheffects_api.html#matplotlib.patheffects.Stroke) path effect
|
||||
makes this a relatively simple task:
|
||||
|
||||
``` python
|
||||
fig = plt.figure(figsize=(7, 1))
|
||||
text = fig.text(0.5, 0.5, 'This text stands out because of\n'
|
||||
'its black border.', color='white',
|
||||
ha='center', va='center', size=30)
|
||||
text.set_path_effects([path_effects.Stroke(linewidth=3, foreground='black'),
|
||||
path_effects.Normal()])
|
||||
plt.show()
|
||||
```
|
||||
|
||||

|
||||
|
||||
It is important to note that this effect only works because we have drawn the text
|
||||
path twice; once with a thick black line, and then once with the original text
|
||||
path on top.
|
||||
|
||||
You may have noticed that the keywords to [``Stroke``](https://matplotlib.orgapi/patheffects_api.html#matplotlib.patheffects.Stroke) and
|
||||
[``SimplePatchShadow``](https://matplotlib.orgapi/patheffects_api.html#matplotlib.patheffects.SimplePatchShadow) and [``SimpleLineShadow``](https://matplotlib.orgapi/patheffects_api.html#matplotlib.patheffects.SimpleLineShadow) are not the usual Artist
|
||||
keywords (such as ``facecolor`` and ``edgecolor`` etc.). This is because with these
|
||||
path effects we are operating at lower level of matplotlib. In fact, the keywords
|
||||
which are accepted are those for a [``matplotlib.backend_bases.GraphicsContextBase``](https://matplotlib.orgapi/backend_bases_api.html#matplotlib.backend_bases.GraphicsContextBase)
|
||||
instance, which have been designed for making it easy to create new backends - and not
|
||||
for its user interface.
|
||||
|
||||
## Greater control of the path effect artist
|
||||
|
||||
As already mentioned, some of the path effects operate at a lower level than most users
|
||||
will be used to, meaning that setting keywords such as ``facecolor`` and ``edgecolor``
|
||||
raise an AttributeError. Luckily there is a generic [``PathPatchEffect``](https://matplotlib.orgapi/patheffects_api.html#matplotlib.patheffects.PathPatchEffect) path effect
|
||||
which creates a [``PathPatch``](https://matplotlib.orgapi/_as_gen/matplotlib.patches.PathPatch.html#matplotlib.patches.PathPatch) class with the original path.
|
||||
The keywords to this effect are identical to those of [``PathPatch``](https://matplotlib.orgapi/_as_gen/matplotlib.patches.PathPatch.html#matplotlib.patches.PathPatch):
|
||||
|
||||
``` python
|
||||
fig = plt.figure(figsize=(8, 1))
|
||||
t = fig.text(0.02, 0.5, 'Hatch shadow', fontsize=75, weight=1000, va='center')
|
||||
t.set_path_effects([path_effects.PathPatchEffect(offset=(4, -4), hatch='xxxx',
|
||||
facecolor='gray'),
|
||||
path_effects.PathPatchEffect(edgecolor='white', linewidth=1.1,
|
||||
facecolor='black')])
|
||||
plt.show()
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Download
|
||||
|
||||
- [Download Python source code: patheffects_guide.py](https://matplotlib.org/_downloads/b0857128f7eceadab81240baf9185710/patheffects_guide.py)
|
||||
- [Download Jupyter notebook: patheffects_guide.ipynb](https://matplotlib.org/_downloads/d678b58ce777643e611577a5aafc6f8d/patheffects_guide.ipynb)
|
||||
|
||||
615
Python/matplotlab/advanced/transforms_tutorial.md
Normal file
615
Python/matplotlab/advanced/transforms_tutorial.md
Normal file
@@ -0,0 +1,615 @@
|
||||
---
|
||||
sidebarDepth: 3
|
||||
sidebar: auto
|
||||
---
|
||||
|
||||
# Transformations Tutorial
|
||||
|
||||
Like any graphics packages, Matplotlib is built on top of a
|
||||
transformation framework to easily move between coordinate systems,
|
||||
the userland ``data`` coordinate system, the ``axes`` coordinate system,
|
||||
the ``figure`` coordinate system, and the ``display`` coordinate system.
|
||||
In 95% of your plotting, you won't need to think about this, as it
|
||||
happens under the hood, but as you push the limits of custom figure
|
||||
generation, it helps to have an understanding of these objects so you
|
||||
can reuse the existing transformations Matplotlib makes available to
|
||||
you, or create your own (see [``matplotlib.transforms``](https://matplotlib.orgapi/transformations.html#module-matplotlib.transforms)). The table
|
||||
below summarizes the some useful coordinate systems, the transformation
|
||||
object you should use to work in that coordinate system, and the
|
||||
description of that system. In the ``Transformation Object`` column,
|
||||
``ax`` is a [``Axes``](https://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes) instance, and ``fig`` is a
|
||||
[``Figure``](https://matplotlib.orgapi/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure) instance.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Coordinates
|
||||
Transformation object
|
||||
Description
|
||||
|
||||
|
||||
|
||||
"data"
|
||||
ax.transData
|
||||
The coordinate system for the data,
|
||||
controlled by xlim and ylim.
|
||||
|
||||
"axes"
|
||||
ax.trans[Axes](https://matplotlib.org/../api/axes_api.html#matplotlib.axes.Axes)
|
||||
The coordinate system of the
|
||||
Axes; (0, 0)
|
||||
is bottom left of the axes, and
|
||||
(1, 1) is top right of the axes.
|
||||
|
||||
"figure"
|
||||
fig.trans[[Figure](https://matplotlib.org/../api/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure)](https://matplotlib.org/../api/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure)
|
||||
The coordinate system of the
|
||||
Figure; (0, 0) is bottom left
|
||||
of the figure, and (1, 1) is top
|
||||
right of the figure.
|
||||
|
||||
"figure-inches"
|
||||
fig.dpi_scale_trans
|
||||
The coordinate system of the
|
||||
Figure in inches; (0, 0) is
|
||||
bottom left of the figure, and
|
||||
(width, height) is the top right
|
||||
of the figure in inches.
|
||||
|
||||
"display"
|
||||
None, or
|
||||
IdentityTransform()
|
||||
The pixel coordinate system of the
|
||||
display window; (0, 0) is bottom
|
||||
left of the window, and (width,
|
||||
height) is top right of the
|
||||
display window in pixels.
|
||||
|
||||
"xaxis",
|
||||
"yaxis"
|
||||
ax.get_xaxis_transform(),
|
||||
ax.get_yaxis_transform()
|
||||
Blended coordinate systems; use
|
||||
data coordinates on one of the axis
|
||||
and axes coordinates on the other.
|
||||
|
||||
|
||||
|
||||
|
||||
All of the transformation objects in the table above take inputs in
|
||||
their coordinate system, and transform the input to the ``display``
|
||||
coordinate system. That is why the ``display`` coordinate system has
|
||||
``None`` for the ``Transformation Object`` column -- it already is in
|
||||
display coordinates. The transformations also know how to invert
|
||||
themselves, to go from ``display`` back to the native coordinate system.
|
||||
This is particularly useful when processing events from the user
|
||||
interface, which typically occur in display space, and you want to
|
||||
know where the mouse click or key-press occurred in your data
|
||||
coordinate system.
|
||||
|
||||
Note that specifying objects in ``display`` coordinates will change their
|
||||
location if the ``dpi`` of the figure changes. This can cause confusion when
|
||||
printing or changing screen resolution, because the object can change location
|
||||
and size. Therefore it is most common
|
||||
for artists placed in an axes or figure to have their transform set to
|
||||
something *other* than the [``IdentityTransform()``](https://matplotlib.orgapi/transformations.html#matplotlib.transforms.IdentityTransform); the default when
|
||||
an artist is placed on an axes using ``add_artist`` is for the
|
||||
transform to be ``ax.transData``.
|
||||
|
||||
## Data coordinates
|
||||
|
||||
Let's start with the most commonly used coordinate, the ``data``
|
||||
coordinate system. Whenever you add data to the axes, Matplotlib
|
||||
updates the datalimits, most commonly updated with the
|
||||
[``set_xlim()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.set_xlim.html#matplotlib.axes.Axes.set_xlim) and
|
||||
[``set_ylim()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.set_ylim.html#matplotlib.axes.Axes.set_ylim) methods. For example, in the
|
||||
figure below, the data limits stretch from 0 to 10 on the x-axis, and
|
||||
-1 to 1 on the y-axis.
|
||||
|
||||
``` python
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.patches as mpatches
|
||||
|
||||
x = np.arange(0, 10, 0.005)
|
||||
y = np.exp(-x/2.) * np.sin(2*np.pi*x)
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
ax.plot(x, y)
|
||||
ax.set_xlim(0, 10)
|
||||
ax.set_ylim(-1, 1)
|
||||
|
||||
plt.show()
|
||||
```
|
||||
|
||||

|
||||
|
||||
You can use the ``ax.transData`` instance to transform from your
|
||||
``data`` to your ``display`` coordinate system, either a single point or a
|
||||
sequence of points as shown below:
|
||||
|
||||
``` python
|
||||
In [14]: type(ax.transData)
|
||||
Out[14]: <class 'matplotlib.transforms.CompositeGenericTransform'>
|
||||
|
||||
In [15]: ax.transData.transform((5, 0))
|
||||
Out[15]: array([ 335.175, 247. ])
|
||||
|
||||
In [16]: ax.transData.transform([(5, 0), (1, 2)])
|
||||
Out[16]:
|
||||
array([[ 335.175, 247. ],
|
||||
[ 132.435, 642.2 ]])
|
||||
```
|
||||
|
||||
You can use the [``inverted()``](https://matplotlib.orgapi/transformations.html#matplotlib.transforms.Transform.inverted)
|
||||
method to create a transform which will take you from display to data
|
||||
coordinates:
|
||||
|
||||
``` python
|
||||
In [41]: inv = ax.transData.inverted()
|
||||
|
||||
In [42]: type(inv)
|
||||
Out[42]: <class 'matplotlib.transforms.CompositeGenericTransform'>
|
||||
|
||||
In [43]: inv.transform((335.175, 247.))
|
||||
Out[43]: array([ 5., 0.])
|
||||
```
|
||||
|
||||
If your are typing along with this tutorial, the exact values of the
|
||||
display coordinates may differ if you have a different window size or
|
||||
dpi setting. Likewise, in the figure below, the display labeled
|
||||
points are probably not the same as in the ipython session because the
|
||||
documentation figure size defaults are different.
|
||||
|
||||
``` python
|
||||
x = np.arange(0, 10, 0.005)
|
||||
y = np.exp(-x/2.) * np.sin(2*np.pi*x)
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
ax.plot(x, y)
|
||||
ax.set_xlim(0, 10)
|
||||
ax.set_ylim(-1, 1)
|
||||
|
||||
xdata, ydata = 5, 0
|
||||
xdisplay, ydisplay = ax.transData.transform_point((xdata, ydata))
|
||||
|
||||
bbox = dict(boxstyle="round", fc="0.8")
|
||||
arrowprops = dict(
|
||||
arrowstyle="->",
|
||||
connectionstyle="angle,angleA=0,angleB=90,rad=10")
|
||||
|
||||
offset = 72
|
||||
ax.annotate('data = (%.1f, %.1f)' % (xdata, ydata),
|
||||
(xdata, ydata), xytext=(-2*offset, offset), textcoords='offset points',
|
||||
bbox=bbox, arrowprops=arrowprops)
|
||||
|
||||
disp = ax.annotate('display = (%.1f, %.1f)' % (xdisplay, ydisplay),
|
||||
(xdisplay, ydisplay), xytext=(0.5*offset, -offset),
|
||||
xycoords='figure pixels',
|
||||
textcoords='offset points',
|
||||
bbox=bbox, arrowprops=arrowprops)
|
||||
|
||||
plt.show()
|
||||
```
|
||||
|
||||

|
||||
|
||||
::: tip Note
|
||||
|
||||
If you run the source code in the example above in a GUI backend,
|
||||
you may also find that the two arrows for the ``data`` and ``display``
|
||||
annotations do not point to exactly the same point. This is because
|
||||
the display point was computed before the figure was displayed, and
|
||||
the GUI backend may slightly resize the figure when it is created.
|
||||
The effect is more pronounced if you resize the figure yourself.
|
||||
This is one good reason why you rarely want to work in display
|
||||
space, but you can connect to the ``'on_draw'``
|
||||
[``Event``](https://matplotlib.orgapi/backend_bases_api.html#matplotlib.backend_bases.Event) to update figure
|
||||
coordinates on figure draws; see [Event handling and picking](https://matplotlib.orgusers/event_handling.html#event-handling-tutorial).
|
||||
|
||||
:::
|
||||
|
||||
When you change the x or y limits of your axes, the data limits are
|
||||
updated so the transformation yields a new display point. Note that
|
||||
when we just change the ylim, only the y-display coordinate is
|
||||
altered, and when we change the xlim too, both are altered. More on
|
||||
this later when we talk about the
|
||||
[``Bbox``](https://matplotlib.orgapi/transformations.html#matplotlib.transforms.Bbox).
|
||||
|
||||
``` python
|
||||
In [54]: ax.transData.transform((5, 0))
|
||||
Out[54]: array([ 335.175, 247. ])
|
||||
|
||||
In [55]: ax.set_ylim(-1, 2)
|
||||
Out[55]: (-1, 2)
|
||||
|
||||
In [56]: ax.transData.transform((5, 0))
|
||||
Out[56]: array([ 335.175 , 181.13333333])
|
||||
|
||||
In [57]: ax.set_xlim(10, 20)
|
||||
Out[57]: (10, 20)
|
||||
|
||||
In [58]: ax.transData.transform((5, 0))
|
||||
Out[58]: array([-171.675 , 181.13333333])
|
||||
```
|
||||
|
||||
## Axes coordinates
|
||||
|
||||
After the ``data`` coordinate system, ``axes`` is probably the second most
|
||||
useful coordinate system. Here the point (0, 0) is the bottom left of
|
||||
your axes or subplot, (0.5, 0.5) is the center, and (1.0, 1.0) is the
|
||||
top right. You can also refer to points outside the range, so (-0.1,
|
||||
1.1) is to the left and above your axes. This coordinate system is
|
||||
extremely useful when placing text in your axes, because you often
|
||||
want a text bubble in a fixed, location, e.g., the upper left of the axes
|
||||
pane, and have that location remain fixed when you pan or zoom. Here
|
||||
is a simple example that creates four panels and labels them 'A', 'B',
|
||||
'C', 'D' as you often see in journals.
|
||||
|
||||
``` python
|
||||
fig = plt.figure()
|
||||
for i, label in enumerate(('A', 'B', 'C', 'D')):
|
||||
ax = fig.add_subplot(2, 2, i+1)
|
||||
ax.text(0.05, 0.95, label, transform=ax.transAxes,
|
||||
fontsize=16, fontweight='bold', va='top')
|
||||
|
||||
plt.show()
|
||||
```
|
||||
|
||||

|
||||
|
||||
You can also make lines or patches in the axes coordinate system, but
|
||||
this is less useful in my experience than using ``ax.transAxes`` for
|
||||
placing text. Nonetheless, here is a silly example which plots some
|
||||
random dots in ``data`` space, and overlays a semi-transparent
|
||||
[``Circle``](https://matplotlib.orgapi/_as_gen/matplotlib.patches.Circle.html#matplotlib.patches.Circle) centered in the middle of the axes
|
||||
with a radius one quarter of the axes -- if your axes does not
|
||||
preserve aspect ratio (see [``set_aspect()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.set_aspect.html#matplotlib.axes.Axes.set_aspect)),
|
||||
this will look like an ellipse. Use the pan/zoom tool to move around,
|
||||
or manually change the data xlim and ylim, and you will see the data
|
||||
move, but the circle will remain fixed because it is not in ``data``
|
||||
coordinates and will always remain at the center of the axes.
|
||||
|
||||
``` python
|
||||
fig, ax = plt.subplots()
|
||||
x, y = 10*np.random.rand(2, 1000)
|
||||
ax.plot(x, y, 'go', alpha=0.2) # plot some data in data coordinates
|
||||
|
||||
circ = mpatches.Circle((0.5, 0.5), 0.25, transform=ax.transAxes,
|
||||
facecolor='blue', alpha=0.75)
|
||||
ax.add_patch(circ)
|
||||
plt.show()
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Blended transformations
|
||||
|
||||
Drawing in ``blended`` coordinate spaces which mix ``axes`` with ``data``
|
||||
coordinates is extremely useful, for example to create a horizontal
|
||||
span which highlights some region of the y-data but spans across the
|
||||
x-axis regardless of the data limits, pan or zoom level, etc. In fact
|
||||
these blended lines and spans are so useful, we have built in
|
||||
functions to make them easy to plot (see
|
||||
[``axhline()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.axhline.html#matplotlib.axes.Axes.axhline),
|
||||
[``axvline()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.axvline.html#matplotlib.axes.Axes.axvline),
|
||||
[``axhspan()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.axhspan.html#matplotlib.axes.Axes.axhspan),
|
||||
[``axvspan()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.axvspan.html#matplotlib.axes.Axes.axvspan)) but for didactic purposes we
|
||||
will implement the horizontal span here using a blended
|
||||
transformation. This trick only works for separable transformations,
|
||||
like you see in normal Cartesian coordinate systems, but not on
|
||||
inseparable transformations like the
|
||||
[``PolarTransform``](https://matplotlib.orgapi/projections_api.html#matplotlib.projections.polar.PolarAxes.PolarTransform).
|
||||
|
||||
``` python
|
||||
import matplotlib.transforms as transforms
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
x = np.random.randn(1000)
|
||||
|
||||
ax.hist(x, 30)
|
||||
ax.set_title(r'$\sigma=1 \/ \dots \/ \sigma=2$', fontsize=16)
|
||||
|
||||
# the x coords of this transformation are data, and the
|
||||
# y coord are axes
|
||||
trans = transforms.blended_transform_factory(
|
||||
ax.transData, ax.transAxes)
|
||||
|
||||
# highlight the 1..2 stddev region with a span.
|
||||
# We want x to be in data coordinates and y to
|
||||
# span from 0..1 in axes coords
|
||||
rect = mpatches.Rectangle((1, 0), width=1, height=1,
|
||||
transform=trans, color='yellow',
|
||||
alpha=0.5)
|
||||
|
||||
ax.add_patch(rect)
|
||||
|
||||
plt.show()
|
||||
```
|
||||
|
||||

|
||||
|
||||
::: tip Note
|
||||
|
||||
The blended transformations where x is in data coords and y in axes
|
||||
coordinates is so useful that we have helper methods to return the
|
||||
versions mpl uses internally for drawing ticks, ticklabels, etc.
|
||||
The methods are [``matplotlib.axes.Axes.get_xaxis_transform()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.get_xaxis_transform.html#matplotlib.axes.Axes.get_xaxis_transform) and
|
||||
[``matplotlib.axes.Axes.get_yaxis_transform()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.get_yaxis_transform.html#matplotlib.axes.Axes.get_yaxis_transform). So in the example
|
||||
above, the call to
|
||||
[``blended_transform_factory()``](https://matplotlib.orgapi/transformations.html#matplotlib.transforms.blended_transform_factory) can be
|
||||
replaced by ``get_xaxis_transform``:
|
||||
|
||||
``` python
|
||||
trans = ax.get_xaxis_transform()
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Plotting in physical units
|
||||
|
||||
Sometimes we want an object to be a certain physical size on the plot.
|
||||
Here we draw the same circle as above, but in physical units. If done
|
||||
interactively, you can see that changing the size of the figure does
|
||||
not change the offset of the circle from the lower-left corner,
|
||||
does not change its size, and the circle remains a circle regardless of
|
||||
the aspect ratio of the axes.
|
||||
|
||||
``` python
|
||||
fig, ax = plt.subplots(figsize=(5, 4))
|
||||
x, y = 10*np.random.rand(2, 1000)
|
||||
ax.plot(x, y*10., 'go', alpha=0.2) # plot some data in data coordinates
|
||||
# add a circle in fixed-units
|
||||
circ = mpatches.Circle((2.5, 2), 1.0, transform=fig.dpi_scale_trans,
|
||||
facecolor='blue', alpha=0.75)
|
||||
ax.add_patch(circ)
|
||||
plt.show()
|
||||
```
|
||||
|
||||

|
||||
|
||||
If we change the figure size, the circle does not change its absolute
|
||||
position and is cropped.
|
||||
|
||||
``` python
|
||||
fig, ax = plt.subplots(figsize=(7, 2))
|
||||
x, y = 10*np.random.rand(2, 1000)
|
||||
ax.plot(x, y*10., 'go', alpha=0.2) # plot some data in data coordinates
|
||||
# add a circle in fixed-units
|
||||
circ = mpatches.Circle((2.5, 2), 1.0, transform=fig.dpi_scale_trans,
|
||||
facecolor='blue', alpha=0.75)
|
||||
ax.add_patch(circ)
|
||||
plt.show()
|
||||
```
|
||||
|
||||

|
||||
|
||||
Another use is putting a patch with a set physical dimension around a
|
||||
data point on the axes. Here we add together two transforms. The
|
||||
first sets the scaling of how large the ellipse should be and the second
|
||||
sets its position. The ellipse is then placed at the origin, and then
|
||||
we use the helper transform [``ScaledTranslation``](https://matplotlib.orgapi/transformations.html#matplotlib.transforms.ScaledTranslation)
|
||||
to move it
|
||||
to the right place in the ``ax.transData`` coordinate system.
|
||||
This helper is instantiated with:
|
||||
|
||||
``` python
|
||||
trans = ScaledTranslation(xt, yt, scale_trans)
|
||||
```
|
||||
|
||||
where ``xt`` and ``yt`` are the translation offsets, and ``scale_trans`` is
|
||||
a transformation which scales ``xt`` and ``yt`` at transformation time
|
||||
before applying the offsets.
|
||||
|
||||
Note the use of the plus operator on the transforms below.
|
||||
This code says: first apply the scale transformation ``fig.dpi_scale_trans``
|
||||
to make the ellipse the proper size, but still centered at (0, 0),
|
||||
and then translate the data to ``xdata[0]`` and ``ydata[0]`` in data space.
|
||||
|
||||
In interactive use, the ellipse stays the same size even if the
|
||||
axes limits are changed via zoom.
|
||||
|
||||
``` python
|
||||
fig, ax = plt.subplots()
|
||||
xdata, ydata = (0.2, 0.7), (0.5, 0.5)
|
||||
ax.plot(xdata, ydata, "o")
|
||||
ax.set_xlim((0, 1))
|
||||
|
||||
trans = (fig.dpi_scale_trans +
|
||||
transforms.ScaledTranslation(xdata[0], ydata[0], ax.transData))
|
||||
|
||||
# plot an ellipse around the point that is 150 x 130 points in diameter...
|
||||
circle = mpatches.Ellipse((0, 0), 150/72, 130/72, angle=40,
|
||||
fill=None, transform=trans)
|
||||
ax.add_patch(circle)
|
||||
plt.show()
|
||||
```
|
||||
|
||||

|
||||
|
||||
::: tip Note
|
||||
|
||||
The order of transformation matters. Here the ellipse
|
||||
is given the right dimensions in display space *first* and then moved
|
||||
in data space to the correct spot.
|
||||
If we had done the ``ScaledTranslation`` first, then
|
||||
``xdata[0]`` and ``ydata[0]`` would
|
||||
first be transformed to ``display`` coordinates (``[ 358.4 475.2]`` on
|
||||
a 200-dpi monitor) and then those coordinates
|
||||
would be scaled by ``fig.dpi_scale_trans`` pushing the center of
|
||||
the ellipse well off the screen (i.e. ``[ 71680. 95040.]``).
|
||||
|
||||
:::
|
||||
|
||||
## Using offset transforms to create a shadow effect
|
||||
|
||||
Another use of [``ScaledTranslation``](https://matplotlib.orgapi/transformations.html#matplotlib.transforms.ScaledTranslation) is to create
|
||||
a new transformation that is
|
||||
offset from another transformation, e.g., to place one object shifted a
|
||||
bit relative to another object. Typically you want the shift to be in
|
||||
some physical dimension, like points or inches rather than in data
|
||||
coordinates, so that the shift effect is constant at different zoom
|
||||
levels and dpi settings.
|
||||
|
||||
One use for an offset is to create a shadow effect, where you draw one
|
||||
object identical to the first just to the right of it, and just below
|
||||
it, adjusting the zorder to make sure the shadow is drawn first and
|
||||
then the object it is shadowing above it.
|
||||
|
||||
Here we apply the transforms in the *opposite* order to the use of
|
||||
[``ScaledTranslation``](https://matplotlib.orgapi/transformations.html#matplotlib.transforms.ScaledTranslation) above. The plot is
|
||||
first made in data units (``ax.transData``) and then shifted by
|
||||
``dx`` and ``dy`` points using ``fig.dpi_scale_trans``. (In typography,
|
||||
a`point <[https://en.wikipedia.org/wiki/Point_%28typography%29](https://en.wikipedia.org/wiki/Point_%28typography%29)>`_ is
|
||||
1/72 inches, and by specifying your offsets in points, your figure
|
||||
will look the same regardless of the dpi resolution it is saved in.)
|
||||
|
||||
``` python
|
||||
fig, ax = plt.subplots()
|
||||
|
||||
# make a simple sine wave
|
||||
x = np.arange(0., 2., 0.01)
|
||||
y = np.sin(2*np.pi*x)
|
||||
line, = ax.plot(x, y, lw=3, color='blue')
|
||||
|
||||
# shift the object over 2 points, and down 2 points
|
||||
dx, dy = 2/72., -2/72.
|
||||
offset = transforms.ScaledTranslation(dx, dy, fig.dpi_scale_trans)
|
||||
shadow_transform = ax.transData + offset
|
||||
|
||||
# now plot the same data with our offset transform;
|
||||
# use the zorder to make sure we are below the line
|
||||
ax.plot(x, y, lw=3, color='gray',
|
||||
transform=shadow_transform,
|
||||
zorder=0.5*line.get_zorder())
|
||||
|
||||
ax.set_title('creating a shadow effect with an offset transform')
|
||||
plt.show()
|
||||
```
|
||||
|
||||

|
||||
|
||||
::: tip Note
|
||||
|
||||
The dpi and inches offset is a
|
||||
common-enough use case that we have a special helper function to
|
||||
create it in [``matplotlib.transforms.offset_copy()``](https://matplotlib.orgapi/transformations.html#matplotlib.transforms.offset_copy), which returns
|
||||
a new transform with an added offset. So above we could have done:
|
||||
|
||||
``` python
|
||||
shadow_transform = transforms.offset_copy(ax.transData,
|
||||
fig=fig, dx, dy, units='inches')
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## The transformation pipeline
|
||||
|
||||
The ``ax.transData`` transform we have been working with in this
|
||||
tutorial is a composite of three different transformations that
|
||||
comprise the transformation pipeline from ``data`` -> ``display``
|
||||
coordinates. Michael Droettboom implemented the transformations
|
||||
framework, taking care to provide a clean API that segregated the
|
||||
nonlinear projections and scales that happen in polar and logarithmic
|
||||
plots, from the linear affine transformations that happen when you pan
|
||||
and zoom. There is an efficiency here, because you can pan and zoom
|
||||
in your axes which affects the affine transformation, but you may not
|
||||
need to compute the potentially expensive nonlinear scales or
|
||||
projections on simple navigation events. It is also possible to
|
||||
multiply affine transformation matrices together, and then apply them
|
||||
to coordinates in one step. This is not true of all possible
|
||||
transformations.
|
||||
|
||||
Here is how the ``ax.transData`` instance is defined in the basic
|
||||
separable axis [``Axes``](https://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes) class:
|
||||
|
||||
``` python
|
||||
self.transData = self.transScale + (self.transLimits + self.transAxes)
|
||||
```
|
||||
|
||||
We've been introduced to the ``transAxes`` instance above in
|
||||
[Axes coordinates](#axes-coords), which maps the (0, 0), (1, 1) corners of the
|
||||
axes or subplot bounding box to ``display`` space, so let's look at
|
||||
these other two pieces.
|
||||
|
||||
``self.transLimits`` is the transformation that takes you from
|
||||
``data`` to ``axes`` coordinates; i.e., it maps your view xlim and ylim
|
||||
to the unit space of the axes (and ``transAxes`` then takes that unit
|
||||
space to display space). We can see this in action here
|
||||
|
||||
``` python
|
||||
In [80]: ax = subplot(111)
|
||||
|
||||
In [81]: ax.set_xlim(0, 10)
|
||||
Out[81]: (0, 10)
|
||||
|
||||
In [82]: ax.set_ylim(-1, 1)
|
||||
Out[82]: (-1, 1)
|
||||
|
||||
In [84]: ax.transLimits.transform((0, -1))
|
||||
Out[84]: array([ 0., 0.])
|
||||
|
||||
In [85]: ax.transLimits.transform((10, -1))
|
||||
Out[85]: array([ 1., 0.])
|
||||
|
||||
In [86]: ax.transLimits.transform((10, 1))
|
||||
Out[86]: array([ 1., 1.])
|
||||
|
||||
In [87]: ax.transLimits.transform((5, 0))
|
||||
Out[87]: array([ 0.5, 0.5])
|
||||
```
|
||||
|
||||
and we can use this same inverted transformation to go from the unit
|
||||
``axes`` coordinates back to ``data`` coordinates.
|
||||
|
||||
``` python
|
||||
In [90]: inv.transform((0.25, 0.25))
|
||||
Out[90]: array([ 2.5, -0.5])
|
||||
```
|
||||
|
||||
The final piece is the ``self.transScale`` attribute, which is
|
||||
responsible for the optional non-linear scaling of the data, e.g., for
|
||||
logarithmic axes. When an Axes is initially setup, this is just set to
|
||||
the identity transform, since the basic Matplotlib axes has linear
|
||||
scale, but when you call a logarithmic scaling function like
|
||||
[``semilogx()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.semilogx.html#matplotlib.axes.Axes.semilogx) or explicitly set the scale to
|
||||
logarithmic with [``set_xscale()``](https://matplotlib.orgapi/_as_gen/matplotlib.axes.Axes.set_xscale.html#matplotlib.axes.Axes.set_xscale), then the
|
||||
``ax.transScale`` attribute is set to handle the nonlinear projection.
|
||||
The scales transforms are properties of the respective ``xaxis`` and
|
||||
``yaxis`` [``Axis``](https://matplotlib.orgapi/axis_api.html#matplotlib.axis.Axis) instances. For example, when
|
||||
you call ``ax.set_xscale('log')``, the xaxis updates its scale to a
|
||||
[``matplotlib.scale.LogScale``](https://matplotlib.orgapi/scale_api.html#matplotlib.scale.LogScale) instance.
|
||||
|
||||
For non-separable axes the PolarAxes, there is one more piece to
|
||||
consider, the projection transformation. The ``transData``
|
||||
[``matplotlib.projections.polar.PolarAxes``](https://matplotlib.orgapi/projections_api.html#matplotlib.projections.polar.PolarAxes) is similar to that for
|
||||
the typical separable matplotlib Axes, with one additional piece
|
||||
``transProjection``:
|
||||
|
||||
``` python
|
||||
self.transData = self.transScale + self.transProjection + \
|
||||
(self.transProjectionAffine + self.transAxes)
|
||||
```
|
||||
|
||||
``transProjection`` handles the projection from the space,
|
||||
e.g., latitude and longitude for map data, or radius and theta for polar
|
||||
data, to a separable Cartesian coordinate system. There are several
|
||||
projection examples in the ``matplotlib.projections`` package, and the
|
||||
best way to learn more is to open the source for those packages and
|
||||
see how to make your own, since Matplotlib supports extensible axes
|
||||
and projections. Michael Droettboom has provided a nice tutorial
|
||||
example of creating a Hammer projection axes; see
|
||||
[Custom projection](https://matplotlib.orggallery/misc/custom_projection.html).
|
||||
|
||||
**Total running time of the script:** ( 0 minutes 1.328 seconds)
|
||||
|
||||
## Download
|
||||
|
||||
- [Download Python source code: transforms_tutorial.py](https://matplotlib.org/_downloads/1d1cf62db33a4554c487470c01670fe5/transforms_tutorial.py)
|
||||
- [Download Jupyter notebook: transforms_tutorial.ipynb](https://matplotlib.org/_downloads/b6ea9be45c260fbed02d8e2d9b2e4549/transforms_tutorial.ipynb)
|
||||
|
||||
Reference in New Issue
Block a user