neqtor
neqtor

Reputation: 177

Using Python 2.7 and matplotlib, how do I create a 2D Line using two different styles?

Working on a personal project that draws two lines, each dashed (ls='--') for the first two x-axis markings, then it is a solid line...thinking about writing a tutorial since I've found no information on this. Anyhow, the trick I'm stumped at, is to figure how many points are used to make the line for the first two x-axis markings, so I can properly turn off the solid line up to that point. I'm using the Line.set_dashes() method to turn off the solid line, and I'm making an individual (non-connected) copy and setting the linestyle to dash. This causes the lines to be drawn on top of each other, and the solid to take precedence when ON. However, the Line.set_dashes() takes "points" as arguments. I figured out where, but as you see, the second line has different angles, thus length, so this point is further along the line. Maybe there's a better way to set the line to two styles?

Here is an example plot --> https://flic.kr/p/rin6Z5

r = getPostData(wall)

if len(newTimes) < LIMIT:
    LIMIT = len(newTimes)
yLim = int(round(max(r['Likes'].max(), r['Shares'].max()) * 1.2))
xLim = LIMIT
L1A = plt.Line2D(range(LIMIT), r['Likes'], color='b', ls='--')
L1B = plt.Line2D(range(LIMIT), r['Likes'], label='Likes', color='b')
L2A = plt.Line2D(range(LIMIT), r['Shares'], color='r', ls='--')
L2B = plt.Line2D(range(LIMIT), r['Shares'], label='Shares', color='r')
LNull = plt.Line2D(range(LIMIT), r['Shares'], ls='--', label='Recent Data\n(Early collection)', color='k')
dashes = [1,84,7000,1]
dashesNull=[1,7000]
fig = plt.figure()
ax = fig.add_subplot(111, ylim=(0,yLim), xlim=(0,xLim))
ax.add_line(L1A)
ax.add_line(L1B)
ax.add_line(L2A)
ax.add_line(L2B)
ax.add_line(LNull)
ax.legend(bbox_to_anchor=(1.5,1))
L1B.set_dashes(dashes)
L2B.set_dashes(dashes)
LNull.set_dashes(dashesNull)

Upvotes: 0

Views: 611

Answers (2)

neqtor
neqtor

Reputation: 177

enter image description here

i thought so too, anyhow, the resulting code is now...

def splitLine(ax, x, y, splitNum, style1, style2):
    '''Creates a two styled line given;
    ax = an axis
    x  = an array of x coordinates for 2D Line
    y  = an array of y coordinates for 2D Line
    splitNum = index number to split Line by x tick
    style1 = dictionary for left part of Line
    style2 = dictionary for right part of Line
    '''
    split = x[splitNum]
    low_mask = x <= split
    upper_mask = x >= split

    lower, = ax.plot(x[low_mask], y[low_mask], **style1)
    upper, = ax.plot(x[upper_mask], y[upper_mask], **style2)

    return lower, upper


r = getPostData(wall)



earlyLike  = {'color': 'r', 'lw': 1, 'ls': '--'}
agedLike   = {'color': 'r', 'lw': 2, 'ls': '-', 'label': 'Likes'}
earlyShare = {'color': 'b', 'lw': 1, 'ls': '--'}
agedShare  = {'color': 'b', 'lw': 2, 'ls': '-', 'label': 'Shares'}

fig, ax = plt.subplots()

splitLine(ax, np.array(range(LIMIT)), np.array(r['Likes']), 1, earlyLike, agedLike)
splitLine(ax, np.array(range(LIMIT)), np.array(r['Shares']), 1, earlyShare, agedShare)

Upvotes: 0

tacaswell
tacaswell

Reputation: 87356

I would write your self a helper function, something like:

import numpy as np
import matplotlib.pyplot as plt

def split_plot(ax, x, y, low, high, inner_style, outer_style):
    """
    Split styling of line based on the x-value

    Parameters
    ----------
    x, y : ndarray
        Data, must be same length

    low, high : float
        The low and high threshold values, points for `low < x < high` are
        styled using `inner_style` and points for `x < low or x > high` are
        styled using `outer_style`

    inner_style, outer_style : dict
        Dictionary of styles that can be passed to `ax.plot`

    Returns
    -------
    lower, mid, upper : Line2D
        The artists for the lower, midddle, and upper ranges

    vline_low, vline_hi : Line2D
        Vertical lines at the thresholds

    hspan : Patch
        Patch over middle region
    """

    low_mask = x < low
    high_mask = x > high
    mid_mask = ~np.logical_or(low_mask, high_mask)

    low_mask[1:] |= low_mask[:-1]
    high_mask[:-1] |= high_mask[1:]

    lower, = ax.plot(x[low_mask], y[low_mask], **outer_style)
    mid, = ax.plot(x[mid_mask], y[mid_mask], **inner_style)
    upper, = ax.plot(x[high_mask], y[high_mask], **outer_style)

    # add vertical lines
    vline_low = ax.axvline(low, color='k', ls='--')
    vline_high = ax.axvline(high,  color='k', ls='--')

    hspan = ax.axvspan(low, high, color='b', alpha=.25)

    return lower, mid, upper, vline_low, vline_high, hspan

Which can obviously be generalized to take 3 line style dictionaries and style information for the vertical lines and the span. You use it like:

inner_style = {'color': 'r', 'lw': 5, 'ls':'--'}
outer_style = {'color': 'r', 'lw': 1, 'ls':'-'}

x = np.linspace(0, 2*np.pi, 1024)
y = np.sin(x)

low = np.pi / 2
high = 3*np.pi / 2

fig, ax = plt.subplots()

lower, mid, upper, vl_low, vl_high, hsp = split_plot(ax, x, y, low, high, inner_style, outer_style)
plt.show()

enter image description here

Upvotes: 2

Related Questions