Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Align matplotlib scatter marker left and or right

I am using the matplotlib scatterplot function to create the appearance of handles on vertical lines to delineate certain parts of a graph. However, in order to make them look correct, I need to be able to align the scatter plot marker to the left (for the left line / delineator) and / or right (for the right line / delineator).

Here's an example:

#create the figure
fig = plt.figure(facecolor = '#f3f3f3', figsize = (11.5, 6))
ax = plt. ax = plt.subplot2grid((1, 1), (0,0))

#make some random data
index = pandas.DatetimeIndex(start = '01/01/2000', freq  = 'b', periods = 100)
rand_levels = pandas.DataFrame( numpy.random.randn(100, 4)/252., index = index, columns = ['a', 'b', 'c', 'd'])
rand_levels = 100*numpy.exp(rand_levels.cumsum(axis = 0))
ax.stackplot(rand_levels.index, rand_levels.transpose())

#create the place holder for the vertical lines
d1, d2 = index[25], index[50]

#draw the lines
ymin, ymax = ax.get_ylim()
ax.vlines([index[25], index[50]], ymin = ymin, ymax = ymax, color = '#353535', lw = 2)

#draw the markers
ax.scatter(d1, ymax, clip_on = False, color = '#353535', marker = '>', s = 200, zorder = 3)
ax.scatter(d2, ymax, clip_on = False, color = '#353535', marker = '<', s = 200, zorder = 3)

#reset the limits
ax.set_ylim(ymin, ymax)
ax.set_xlim(rand_levels.index[0], rand_levels.index[-1])
plt.show()

The code above gives me almost the graph I'm looking for, like this:

focus_timeline

However, I'd like the leftmost marker (">") to be "aligned left" (i.e. shifted slightly to the right) so that the line is continued to the back of the marker Likewise, I'd like the rightmost marker ("<") to be "aligned right" (i.e. slightly shifted to the left). Like this:

desired_fig

Any guidance or suggestions on how to accomplish this in a flexible manner?

NOTE: In practice, my DataFrame index is pandas.Datetime not integers as I've provided for this simple example.

like image 703
benjaminmgross Avatar asked Nov 01 '14 06:11

benjaminmgross


People also ask

How do I align Y axis labels in MatPlotLib?

The correct way to force alignment of the labels is to use the function fig. align_labels() before showing the figure. If you need more fine grained control, you may also use the functions Figure. align_xlabels() or Figure.

How do I align text in MatPlotLib?

MatPlotLib with PythonPlace legend using legend() method and initialize a method. Iterate the legend. get_texts() method to set the horizontal alignment. To display the figure, use show() method.

What is BBOX in MatPlotLib?

BboxTransformTo is a transformation that linearly transforms points from the unit bounding box to a given Bbox. In your case, the transform itself is based upon a TransformedBBox which again has a Bbox upon which it is based and a transform - for this nested instance an Affine2D transform.


2 Answers

I liked this question and was not satisfied with my first answer. In particular, it seemed unnecessarily cumbersome to create figure specific objects (mark_align_*) in order to align markers. What I eventually found was the functionality to specify a marker by verts (a list of 2-element floats, or an Nx2 array, that specifies the marker vertices relative to the target plot-point at (0, 0)). To utilize this functionality for this purpose I wrote this function,

from matplotlib import markers
from matplotlib.path import Path

def align_marker(marker, halign='center', valign='middle',):
    """
    create markers with specified alignment.

    Parameters
    ----------

    marker : a valid marker specification.
      See mpl.markers

    halign : string, float {'left', 'center', 'right'}
      Specifies the horizontal alignment of the marker. *float* values
      specify the alignment in units of the markersize/2 (0 is 'center',
      -1 is 'right', 1 is 'left').

    valign : string, float {'top', 'middle', 'bottom'}
      Specifies the vertical alignment of the marker. *float* values
      specify the alignment in units of the markersize/2 (0 is 'middle',
      -1 is 'top', 1 is 'bottom').

    Returns
    -------

    marker_array : numpy.ndarray
      A Nx2 array that specifies the marker path relative to the
      plot target point at (0, 0).

    Notes
    -----
    The mark_array can be passed directly to ax.plot and ax.scatter, e.g.::

        ax.plot(1, 1, marker=align_marker('>', 'left'))

    """

    if isinstance(halign, (str, unicode)):
        halign = {'right': -1.,
                  'middle': 0.,
                  'center': 0.,
                  'left': 1.,
                  }[halign]

    if isinstance(valign, (str, unicode)):
        valign = {'top': -1.,
                  'middle': 0.,
                  'center': 0.,
                  'bottom': 1.,
                  }[valign]

    # Define the base marker
    bm = markers.MarkerStyle(marker)

    # Get the marker path and apply the marker transform to get the
    # actual marker vertices (they should all be in a unit-square
    # centered at (0, 0))
    m_arr = bm.get_path().transformed(bm.get_transform()).vertices

    # Shift the marker vertices for the specified alignment.
    m_arr[:, 0] += halign / 2
    m_arr[:, 1] += valign / 2

    return Path(m_arr, bm.get_path().codes)

Using this function, the desired markers can be plotted as,

ax.plot(d1, 1, marker=align_marker('>', halign='left'), ms=20,
        clip_on=False, color='k', transform=ax.get_xaxis_transform())
ax.plot(d2, 1, marker=align_marker('<', halign='right'), ms=20,
        clip_on=False, color='k', transform=ax.get_xaxis_transform())

or using ax.scatter,

ax.scatter(d1, 1, 200, marker=align_marker('>', halign='left'),
           clip_on=False, color='k', transform=ax.get_xaxis_transform())
ax.scatter(d2, 1, 200, marker=align_marker('<', halign='right'),
           clip_on=False, color='k', transform=ax.get_xaxis_transform())

In both of these examples I have specified transform=ax.get_xaxis_transform() so that the vertical position of the markers is in axes coordinates (1 is the top of the axes), this has nothing to do with the marker alignment.

The obvious advantage of this solution compared to my previous one is that it does not require knowledge of the markersize, plotting function (ax.plot vs. ax.scatter), or axes (for the transform). Instead, one simply specifes a marker and its alignment!

Cheers!

like image 128
farenorth Avatar answered Oct 23 '22 04:10

farenorth


I found a simple solution to this problem. Matplotlib have built-in markers with different alignments: lines_bars_and_markers example code: marker_reference.py enter image description here

Simply change the '>' marker to 9 and the '<' marker to 8:

#draw the markers
ax.scatter(d1, ymax, clip_on=False, color='#353535', marker=9, s=200, zorder=3)
ax.scatter(d2, ymax, clip_on=False, color='#353535', marker=8, s=200, zorder=3)
like image 6
Liran Funaro Avatar answered Oct 23 '22 04:10

Liran Funaro