matplotlib questions

We have some data that we'd like to plot and I'd like to know

    > if matplotlib (MPL) supports this directly (I don't think it
    > does) or where we should start looking to implement this
    > capability.

All the core functionality is there already -- you just have to plug
the pieces together.

    > We want to plot time intervals in a similar way to a
    > horizontal bar graph. In this case, the data is when a
    > spacecraft is in view of a ground station. So for a list of
    > ground stations (the y axis), we have lists of start and stop
    > times that represent the view periods. So we need to:

    > - show the list of ground stations (i.e. arbitrary labels)
    > along the Y axis like you would on a bar chart.

    > - draw sets of rectangles at the right y location with the
    > length determined by the x data (in user controllable line
    > styles, colors, and fills).

This is mostly available in barh, which by default has the left side
of the bar at 0 but optionally allows you to specify the left as an
array. So you can use barh to plot intervals with the left side
specfied by the 'left' arg and the bar width specified by the 'x' arg.
The return value is a list of Rectangles, which you can customize (set
the facecolor, edgecolor, edgewidth, transparency, etc). See
track1.py, attached below for an example.

Note that you can use any mpl function with dates, as long as you set
the tick formatters and locators correctly; this is also illustrated
in the example code.

    > - optionally label the start and stop points of each interval
    > with the X value for that point (in this case a date/time).
    > Ideally, the user would have the option of specifying a
    > location relative to the bar for each end point like this
    > (none, high, mid, low):

    > High High |-----------| Mid | BAR | Mid |-----------| Low Low

This requires you to compute the bar locations for the x, y, width,
height coords of the rectangles. You can then add text with
text(x,y,s) if you have computed y to be the bottom, center or top of
the bar. track1.py also illustrates this; but see track2.py for
examples of setting the horizontal and vertical alignment of the text,
which should vary with High, Low or Mid for the best looking graphs.

    > I don't think I can currently do this in MPL so I'd like to
    > here ideas from John and anyone else on which classes I
    > should start looking at any suggestions on how this should
    > work.

For real work, it helps to create custom classes to manage some of
these layout details. I did this in track2.py, which creates a custom
class derived from Rectangle that contains two Text instances
labelstart and labelstop. This design makes more sense, since the
View object knows it's left, bottom, width, height etc so it makes it
easier to do layout. The example shows how to use mpl connections to
toggle labels on and off.

In any case, the two mpl classes you want to study are
matplotlib.patches.Rectangle and matplotlib.text.Text, and you may
want to take a close look at matplotlib.axes.Axes.barh method.

Sometimes it's easier to write an example than it is to explain how to
use everything together. If you can think of a nice way to wrap some
of the functionality into reusable pieces, that would be great.

JDH

···

####################
# Begin track1.py #
####################

from matplotlib.dates import date2num
from matplotlib.dates import HourLocator, DateFormatter
from matplotlib.mlab import rand
from matplotlib.numerix import arange
from datetime import datetime
from pylab import figure, show

N = 7
labels = ['S%d'%i for i in range(N)]
colors = ('red', 'green', 'purple', 'brown', 'yellow', 'black', 'blue')

t0 = date2num(datetime.now())
start = t0 + rand(N) # add random days in seconds
locator = HourLocator(arange(0,24,2)) # ticks every 2 hours
formatter = DateFormatter('%H') # hour

ylocs = arange(N) # ylocs of the bar, arbitrary

duration = rand(N)*0.2 # random durations in fraction of day

fig = figure()
ax = fig.add_subplot(111)
ax.xaxis.set_major_formatter(formatter)
ax.xaxis.set_major_locator(locator)

height = 0.5 # the height of the bar, arbitrary
bars = ax.barh(duration, ylocs, height=height, left=start)
ax.set_yticks(ylocs)
ax.set_yticklabels(labels)

for bar, color in zip(bars, colors):
    bar.set_facecolor(color)

# define some location arrays for labeling
left = start
right = left + duration
top = ylocs + height/2.
bottom = ylocs - height/2.
center = ylocs

# label the 4th bar on top right
ind = 3
note = 'hi mom'
x = right[ind]
y = top[ind]
ax.text(x,y,note)
#ax.set_xlim((min(left), max(right)+2))
ax.grid(True)
ax.set_title('Time windows when craft visible by station')
ax.set_xlabel('Time (h)')

show()

####################
# End track1.py #
####################

####################
# Begin track2.py #
####################

from matplotlib.artist import Artist
from matplotlib.dates import date2num
from matplotlib.dates import HourLocator, DateFormatter
from matplotlib.patches import Rectangle
from matplotlib.mlab import rand
from matplotlib.numerix import arange
from datetime import datetime

import pylab

class View(Rectangle):
    """
    A view of when a craft is visible defined by a start and stop time
    (days as float) with labeling capability.

    A rectangle will be drawn
    """
    def __init__(self, ax, ind, station, start, stop, timefmt='%H:%M',
                 timeloc='top', height=0.5, **kwargs):
        """
        ax is an axes instance -- the class will add required Artists
        to to axes

        ind is the station number for yaxis positions -- the rectangles will be
        drawn with vertical centers at ind with a height height.

        station is a string label
        
        start and stop will be the left and right side of the
        rectangles
        """
        # for rects, x,y is lower left but we want ind to be the
        # center
        Rectangle.__init__(self, (start, ind-height/2), stop-start, height, **kwargs)
        ax.add_patch(self)
        self.ind = ind
        self.station = station
        
        self.timefmt = timefmt
        self.formatter = DateFormatter(timefmt)

        self.labelstart = ax.text(start, 0, self.formatter(start),
                                horizontalalignment='right')
        self.labelstop = ax.text(stop, 0, self.formatter(stop),
                                horizontalalignment='left')

        self.labelstart.set_visible(False)
        self.labelstop.set_visible(False)
        if timeloc is not None:
            if timeloc == 'top':
                y = ind + height/2.
                valign = 'bottom'
            elif timeloc == 'bottom':
                y = ind - height/2.
                valign = 'top'
            elif timeloc == 'center':
                y = ind
                valign = 'center'

            self.labelstart.set_visible(True)
            self.labelstop.set_visible(True)

            self.labelstart.set_y(y)
            self.labelstop.set_y(y)

            self.labelstart.set_verticalalignment(valign)
            self.labelstop.set_verticalalignment(valign)

N = 8
t0 = date2num(datetime.now())
start = t0 + rand(N) # add random days in seconds
locator = HourLocator(arange(0,24,2)) # ticks every 2 hours
formatter = DateFormatter('%H') # hour

duration = rand(N)*0.2 # random durations in fraction of day
stop = start + duration
fig = pylab.figure()
ax = fig.add_subplot(111)
ax.xaxis.set_major_formatter(formatter)
ax.xaxis.set_major_locator(locator)

views = []
for ind, x1, x2 in zip(range(1,N+1), start, stop):
    view = View(ax, ind, 'S%d'%ind, x1, x2, timeloc='center')
    # Now call any Rectangle function on view instance to customize rect,
    # and any Text prop on view.labelstart and view.labelstop
    views.append(view)

yticks = [view.ind for view in views]
ylabels = [view.station for view in views]

ax.set_yticks(yticks)
ax.set_yticklabels(ylabels)

def toggle_labels(event):
    if event.key != 't': return
    toggle_labels.on = not toggle_labels.on
    for view in views:
        view.labelstart.set_visible(toggle_labels.on)
        view.labelstop.set_visible(toggle_labels.on)
    pylab.draw()
toggle_labels.on = True

# use canvas.mpl_connect in API
pylab.connect('key_press_event', toggle_labels)

ax.autoscale_view() # this is normally called by a plot command

ax.set_xlim((min(start)-.1, max(stop)+.1))
ax.set_ylim((0,N+1))
ax.set_title("Press 't' to toggle labels")
ax.set_xlabel('Time (hours)')
ax.grid(True)
pylab.show()

####################
# End track2.py #
####################

Jeez John if you're going to do this much work for a simple information request I may start sending you "questions" about my normal job. (thank you very much!)

Thanks for the code - it's going to save us a ton of time. We're going to take the track2 demo and extend/refine/document it to be a function similar to barh. We'll have to think about the x data input for this function a little bit since it really needs to be something like

x= [ [ (start1,stop1), (start2,stop2), ... ],
      [ (start1,stop1), (start2,stop2), ... ],
      ...
    ]

which might be a little complicated for someone to set up correctly.

Thanks,
Ted

···

At 02:33 PM 3/11/2005, John Hunter wrote:

    > We have some data that we'd like to plot and I'd like to know
    > if matplotlib (MPL) supports this directly (I don't think it
    > does) or where we should start looking to implement this
    > capability.

All the core functionality is there already -- you just have to plug
the pieces together.

    > We want to plot time intervals in a similar way to a
    > horizontal bar graph. In this case, the data is when a
    > spacecraft is in view of a ground station. So for a list of
    > ground stations (the y axis), we have lists of start and stop
    > times that represent the view periods. So we need to:

    > - show the list of ground stations (i.e. arbitrary labels)
    > along the Y axis like you would on a bar chart.

    > - draw sets of rectangles at the right y location with the
    > length determined by the x data (in user controllable line
    > styles, colors, and fills).

This is mostly available in barh, which by default has the left side
of the bar at 0 but optionally allows you to specify the left as an
array. So you can use barh to plot intervals with the left side
specfied by the 'left' arg and the bar width specified by the 'x' arg.
The return value is a list of Rectangles, which you can customize (set
the facecolor, edgecolor, edgewidth, transparency, etc). See
track1.py, attached below for an example.

Note that you can use any mpl function with dates, as long as you set
the tick formatters and locators correctly; this is also illustrated
in the example code.

    > - optionally label the start and stop points of each interval
    > with the X value for that point (in this case a date/time).
    > Ideally, the user would have the option of specifying a
    > location relative to the bar for each end point like this
    > (none, high, mid, low):

    > High High |-----------| Mid | BAR | Mid |-----------| Low Low

This requires you to compute the bar locations for the x, y, width,
height coords of the rectangles. You can then add text with
text(x,y,s) if you have computed y to be the bottom, center or top of
the bar. track1.py also illustrates this; but see track2.py for
examples of setting the horizontal and vertical alignment of the text,
which should vary with High, Low or Mid for the best looking graphs.

    > I don't think I can currently do this in MPL so I'd like to
    > here ideas from John and anyone else on which classes I
    > should start looking at any suggestions on how this should
    > work.

For real work, it helps to create custom classes to manage some of
these layout details. I did this in track2.py, which creates a custom
class derived from Rectangle that contains two Text instances
labelstart and labelstop. This design makes more sense, since the
View object knows it's left, bottom, width, height etc so it makes it
easier to do layout. The example shows how to use mpl connections to
toggle labels on and off.

In any case, the two mpl classes you want to study are
matplotlib.patches.Rectangle and matplotlib.text.Text, and you may
want to take a close look at matplotlib.axes.Axes.barh method.

Sometimes it's easier to write an example than it is to explain how to
use everything together. If you can think of a nice way to wrap some
of the functionality into reusable pieces, that would be great.

JDH

####################
# Begin track1.py #
####################

from matplotlib.dates import date2num
from matplotlib.dates import HourLocator, DateFormatter
from matplotlib.mlab import rand
from matplotlib.numerix import arange
from datetime import datetime
from pylab import figure, show

N = 7
labels = ['S%d'%i for i in range(N)]
colors = ('red', 'green', 'purple', 'brown', 'yellow', 'black', 'blue')

t0 = date2num(datetime.now())
start = t0 + rand(N) # add random days in seconds
locator = HourLocator(arange(0,24,2)) # ticks every 2 hours
formatter = DateFormatter('%H') # hour

ylocs = arange(N) # ylocs of the bar, arbitrary

duration = rand(N)*0.2 # random durations in fraction of day

fig = figure()
ax = fig.add_subplot(111)
ax.xaxis.set_major_formatter(formatter)
ax.xaxis.set_major_locator(locator)

height = 0.5 # the height of the bar, arbitrary
bars = ax.barh(duration, ylocs, height=height, left=start)
ax.set_yticks(ylocs)
ax.set_yticklabels(labels)

for bar, color in zip(bars, colors):
    bar.set_facecolor(color)

# define some location arrays for labeling
left = start
right = left + duration
top = ylocs + height/2.
bottom = ylocs - height/2.
center = ylocs

# label the 4th bar on top right
ind = 3
note = 'hi mom'
x = right[ind]
y = top[ind]
ax.text(x,y,note)
#ax.set_xlim((min(left), max(right)+2))
ax.grid(True)
ax.set_title('Time windows when craft visible by station')
ax.set_xlabel('Time (h)')

show()

####################
# End track1.py #
####################

####################
# Begin track2.py #
####################

from matplotlib.artist import Artist
from matplotlib.dates import date2num
from matplotlib.dates import HourLocator, DateFormatter
from matplotlib.patches import Rectangle
from matplotlib.mlab import rand
from matplotlib.numerix import arange
from datetime import datetime

import pylab

class View(Rectangle):
    """
    A view of when a craft is visible defined by a start and stop time
    (days as float) with labeling capability.

    A rectangle will be drawn
    """
    def __init__(self, ax, ind, station, start, stop, timefmt='%H:%M',
                 timeloc='top', height=0.5, **kwargs):
        """
        ax is an axes instance -- the class will add required Artists
        to to axes

        ind is the station number for yaxis positions -- the rectangles will be
        drawn with vertical centers at ind with a height height.

        station is a string label

        start and stop will be the left and right side of the
        rectangles
        """
        # for rects, x,y is lower left but we want ind to be the
        # center
        Rectangle.__init__(self, (start, ind-height/2), stop-start, height, **kwargs)
        ax.add_patch(self)
        self.ind = ind
        self.station = station

        self.timefmt = timefmt
        self.formatter = DateFormatter(timefmt)

        self.labelstart = ax.text(start, 0, self.formatter(start),
                                horizontalalignment='right')
        self.labelstop = ax.text(stop, 0, self.formatter(stop),
                                horizontalalignment='left')

        self.labelstart.set_visible(False)
        self.labelstop.set_visible(False)
        if timeloc is not None:
            if timeloc == 'top':
                y = ind + height/2.
                valign = 'bottom'
            elif timeloc == 'bottom':
                y = ind - height/2.
                valign = 'top'
            elif timeloc == 'center':
                y = ind
                valign = 'center'

            self.labelstart.set_visible(True)
            self.labelstop.set_visible(True)

            self.labelstart.set_y(y)
            self.labelstop.set_y(y)

            self.labelstart.set_verticalalignment(valign)
            self.labelstop.set_verticalalignment(valign)

N = 8
t0 = date2num(datetime.now())
start = t0 + rand(N) # add random days in seconds
locator = HourLocator(arange(0,24,2)) # ticks every 2 hours
formatter = DateFormatter('%H') # hour

duration = rand(N)*0.2 # random durations in fraction of day
stop = start + duration
fig = pylab.figure()
ax = fig.add_subplot(111)
ax.xaxis.set_major_formatter(formatter)
ax.xaxis.set_major_locator(locator)

views = []
for ind, x1, x2 in zip(range(1,N+1), start, stop):
    view = View(ax, ind, 'S%d'%ind, x1, x2, timeloc='center')
    # Now call any Rectangle function on view instance to customize rect,
    # and any Text prop on view.labelstart and view.labelstop
    views.append(view)

yticks = [view.ind for view in views]
ylabels = [view.station for view in views]

ax.set_yticks(yticks)
ax.set_yticklabels(ylabels)

def toggle_labels(event):
    if event.key != 't': return
    toggle_labels.on = not toggle_labels.on
    for view in views:
        view.labelstart.set_visible(toggle_labels.on)
        view.labelstop.set_visible(toggle_labels.on)
    pylab.draw()
toggle_labels.on = True

# use canvas.mpl_connect in API
pylab.connect('key_press_event', toggle_labels)

ax.autoscale_view() # this is normally called by a plot command

ax.set_xlim((min(start)-.1, max(stop)+.1))
ax.set_ylim((0,N+1))
ax.set_title("Press 't' to toggle labels")
ax.set_xlabel('Time (hours)')
ax.grid(True)
pylab.show()

####################
# End track2.py #
####################

-------------------------------------------------------
SF email is sponsored by - The IT Product Guide
Read honest & candid reviews on hundreds of IT Products from real users.
Discover which products truly live up to the hype. Start reading now.
http://ads.osdn.com/?ad_id=6595&alloc_id=14396&op=click
_______________________________________________
Matplotlib-users mailing list
Matplotlib-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-users

Ted Drain Jet Propulsion Laboratory ted.drain@...369...