From: John H. <jdh...@ac...> - 2005-03-11 22:44:37
|
>>>>> "Ted" == Ted Drain <ted...@jp...> writes: Ted> We have some data that we'd like to plot and I'd like to know Ted> if matplotlib (MPL) supports this directly (I don't think it Ted> does) or where we should start looking to implement this Ted> capability. All the core functionality is there already -- you just have to plug the pieces together. Ted> We want to plot time intervals in a similar way to a Ted> horizontal bar graph. In this case, the data is when a Ted> spacecraft is in view of a ground station. So for a list of Ted> ground stations (the y axis), we have lists of start and stop Ted> times that represent the view periods. So we need to: Ted> - show the list of ground stations (i.e. arbitrary labels) Ted> along the Y axis like you would on a bar chart. Ted> - draw sets of rectangles at the right y location with the Ted> length determined by the x data (in user controllable line Ted> 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. Ted> - optionally label the start and stop points of each interval Ted> with the X value for that point (in this case a date/time). Ted> Ideally, the user would have the option of specifying a Ted> location relative to the bar for each end point like this Ted> (none, high, mid, low): Ted> 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. Ted> I don't think I can currently do this in MPL so I'd like to Ted> here ideas from John and anyone else on which classes I Ted> should start looking at any suggestions on how this should Ted> 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 # #################### |