Hi. A couple of questions about `scatter`: Q1 ==== The bounding box `axes.dataLim` increases in size when calling scatter(x, y) compared to plot(x, y) (for the same x, y data, of course). I think this change is due to the size of the markers being added to the data limits (not sure if this is true). Is there an easy way to get the same data limits I would have for a call to `plot`? ==== Is there a way to get the data from the axes of a scatter plot? Initially I thought I could get it with: >>> for collection in ax.collections: >>> for path in collection._paths: >>> print path.vertices But this seems to be the path that draws the scatter markers. Any ideas? Frame Class ========== Finally, if anyone is interested, I'm playing around with a Frame class for `axes.frame`. This class adds customizable axes frames similar to the topic of this thread: In this older thread, the SAGE axes frames were criticized for not being flexible enough. I've tried to make this class as general as possible (within my ability:). As an example of the flexibility of this Frame class, I've added some Tufte-style frames similar to: To the developers on this thread: If there's anything I could do to make the attached Frame class more flexible (and more suitable for possible inclusion into MPL), I'd be happy to get some feedback. Current Limitations: ================ * the frame can only be placed on the borders of the axes (mainly because I don't know how to move the tickers anywhere else). * RangeFrame only works with linear data (I'm not sure how to use the `axes.transScale` to properly transform the data) * RangeFrame and DashDotFrame don't work properly with `scatter` (because of the questions in this post). The frame class itself isn't too long, but I got a little carried away adding in extra crap. Sorry for the long, rambling email.;) -Tony |
#!/usr/bin/env python """ Frame classes for customizing frame borders that surround the plot axes. """ import numpy as np from numpy.random import rand
import matplotlib.axes as maxes import matplotlib.pyplot as plt import matplotlib.ticker as ticker import matplotlib.artist as martist import matplotlib.collections as col import matplotlib.projections as projections class Frame(martist.Artist): """Draw frame along the edges of the axes patch. Frame position can be controlled upon initialization or by setting `frame_position` property with a list of positions ['left', 'right', 'top', 'bottom' | 'all'] """ _position_list = ('left', 'right', 'top', 'bottom') def __init__(self, axes, positions=('left', 'bottom'), **kwargs): """ `positions` is a list of strings of frame positions to plot. ['left', 'right', 'top', 'bottom' | 'all'] """ super(Frame, self).__init__() # TODO: allow more keyword configuration self.axes = axes rc = plt.rcParams self.color = kwargs.pop('color', rc['axes.edgecolor']) self.linewidth = kwargs.pop('linewidth', rc['axes.linewidth']) self.linestyle = kwargs.pop('linestyle', 'solid') self.frame_position = positions def get_data(self): """Convenience method returns tuple of (x, y) data in `self.axes`""" x, y = [], [] ax = self.axes for artist in (ax.lines, ax.patches): if not artist == []: x.append(np.concatenate([a.get_xdata() for a in artist])) y.append(np.concatenate([a.get_ydata() for a in artist])) # TODO: get scatter data from ax.collections return (np.concatenate(x), np.concatenate(y)) def _set_frame_position(self, positions): """Set positions where frame will be drawn. `positions` is a list of strings of frame positions to plot. ['left', 'right', 'top', 'bottom' | 'all'] """ self._frame_on = self._frame_dict_from(positions) def _get_frame_position(self): return [p for p in self._position_list if self._frame_on[p]] # xposition tuples turn on frame for (bottom, top) _xposition_pairs = {(True, False): 'bottom', (False, True): 'top', (True, True): 'both', (False, False): 'none'} def _get_xposition(self, frame_on=None): """Returns position that matches `XAxis.set_ticks_position` inputs. `frame_on` is a dict that matches frame positions with bools. """ if frame_on is None: frame_on = self._frame_on return self._xposition_pairs[(frame_on['bottom'], frame_on['top'])] # yposition tuples turn on frame for (left, right) _yposition_pairs = {(True, False): 'left', (False, True): 'right', (True, True): 'both', (False, False): 'none'} def _get_yposition(self, frame_on=None): """Returns position that matches `YAxis.set_ticks_position` inputs. `frame_on` is a dict that matches frame positions with bools. """ if frame_on is None: frame_on = self._frame_on return self._yposition_pairs[(frame_on['left'], frame_on['right'])] def _frame_dict_from(self, positions): """Parse `positions` and return xposition, yposition tuple `positions` is a list of strings of frame positions to plot. ['left', 'right', 'top', 'bottom' | 'all'] """ frame_dict = dict.fromkeys(self._position_list, False) if 'all' in positions: frame_dict = dict.fromkeys(self._position_list, True) else: for position in positions: frame_dict[position] = True return frame_dict def _set_ticks(self): """Overide this method to customize tick positioning.""" # Draw ticks on axes only where a frame is drawn self.axes.xaxis.set_ticks_position(self._get_xposition()) self.axes.yaxis.set_ticks_position(self._get_yposition()) _frame_lines = dict(bottom=[(0., 0.), (1., 0.)], top=[(0., 1.), (1., 1.)], left=[(0., 0.), (0., 1.)], right=[(1., 0.), (1., 1.)]) def _make_frame(self): """Get axis frame specified by `self._frame_on`.""" lines = [self._frame_lines[p] for p in self._position_list if self._frame_on[p]] frame_lines = col.LineCollection(segments=lines, linewidths=[self.linewidth], colors=[self.color]) frame_lines.set_transform(self.axes.transAxes) return frame_lines def draw(self, renderer): if not self.get_visible(): return self._set_ticks() frame = self._make_frame() frame.draw(renderer) frame_position = property(_get_frame_position, _set_frame_position) class RangeFrame(Frame): """Range frame from Edward Tufte's book VDQI p.131. The x-axis frame begins and ends at the minimum and maximum x-values of the data, resprectively. Similarly, for the y-axis. """ def __init__(self, *args, **kwargs): super(RangeFrame, self).__init__(*args, **kwargs) self.show_extrema_lines = kwargs.pop('extrema', False) def _make_frame(self): """Get frame that extends to the limits of the data""" t = self.axes.transLimits (xminf, yminf), (xmaxf, ymaxf) = t.transform(self.axes.dataLim) frame_lines = dict(bottom=[(xminf, 0.), (xmaxf, 0.)], top=[(xminf, 1.), (xmaxf, 1.)], left=[(0., yminf), (0., ymaxf)], right=[(1., yminf), (1., ymaxf)]) lines = [frame_lines[p] for p in self._position_list if self._frame_on[p]] linestyles = [self.linestyle] * len(lines) colors = [self.color] * len(lines) if self.show_extrema_lines: extrema_lines = self._get_extrema_lines() linestyles += ['dotted'] * len(extrema_lines) colors += ['gray'] * len(extrema_lines) lines += extrema_lines range_lines = col.LineCollection(segments=lines, linestyles=linestyles, linewidths=[self.linewidth], colors=colors) range_lines.set_transform(self.axes.transAxes) return range_lines def _get_extrema_lines(self): """Get lines that go vertically from x-axis to data with min and max x values, and horizontally from y_axis to data with min and max y values. This method is mainly intended for debuging. """ x, y = self.get_data() xy = np.array([x, y]).transpose() xy = self.axes.transLimits.transform(xy) x = xy[:, 0] y = xy[:, 1] lines = [] for extremum in (x.min(), x.max()): x0 = float(x[x == extremum]) y0 = float(y[x == extremum]) lines.append([(x0, 0), (x0, y0)]) for extremum in (y.min(), y.max()): x0 = float(x[y == extremum]) y0 = float(y[y == extremum]) lines.append([(0, y0), (x0, y0)]) return lines class DashDotFrame(Frame): """Dash dot plot frame Edward Tufte's book VDQI p.133. x and y coordinates of data are projected on the x- and y-axis as minor ticks. By default, no frame lines are plotted, only ticks. However, you can override this default, by specifying the `positions` keyword on init. """ def __init__(self, axes, tick_positions=('left', 'bottom'), positions=(), majorsize=5, minorsize=3, *args, **kwargs): """ `tick positions` is a list of strings of tick frame positions to plot. ['left', 'right', 'top', 'bottom' | 'all'] """ super(DashDotFrame, self).__init__(axes, positions=positions, *args, **kwargs) self.tframe_position = tick_positions self.majorsize = majorsize self.minorsize = minorsize def _set_tframe_position(self, positions): """Set positions where tick frame will be drawn. `positions` is a list of strings of frame positions to plot. ['left', 'right', 'top', 'bottom' | 'all'] """ self._tick_frame_on = self._frame_dict_from(positions) def _get_tframe_position(self): return [p for p in self._position_list if self._tick_frame_on[p]] def _set_ticks(self): """Project x, y data as minor ticks on the x- and y-axes.""" x, y = self.get_data() xaxis = self.axes.xaxis yaxis = self.axes.yaxis # add minor ticks for every data point xaxis.set_minor_locator(ticker.FixedLocator(x)) yaxis.set_minor_locator(ticker.FixedLocator(y)) xticks_position = self._get_xposition(self._tick_frame_on) yticks_position = self._get_yposition(self._tick_frame_on) xaxis.set_ticks_position(xticks_position) yaxis.set_ticks_position(yticks_position) # TODO: major and minor ticks on opposite sides of the axis majorlines = (xaxis.get_majorticklines() + yaxis.get_majorticklines()) minorlines = (xaxis.get_minorticklines() + yaxis.get_minorticklines()) for line in majorlines: line.set_markersize(self.majorsize) for line in minorlines: line.set_markersize(self.minorsize) tframe_position = property(_get_tframe_position, _set_tframe_position) class FrameAxes(maxes.Axes): """Override Axes so that `frame` attribute is a custom FrameArtist """ name = 'frameaxes' def cla(self): """Override default Axes.frame and clear patch edgecolor""" if not hasattr(self, '_frame'): self._frame = Frame(self) super(FrameAxes, self).cla() self.patch.set_edgecolor('none') # retrieve `_frame` because Axes.cla() overwrites `frame` self.frame = self._frame def set_frame(self, frame_artist, **kwargs): """Set artist to draw axes frame. `frame_artist` should be subclass of `FrameArtist`. """ # save frame artist as `_frame` because Axes.cla() overwrites `frame` self.frame = self._frame = frame_artist(self, **kwargs) def get_frame(self): return self.frame projections.register_projection(FrameAxes) def plot_test(): """Plot comparison of some custom frames""" plt.rcParams['xtick.direction'] = 'out' plt.rcParams['ytick.direction'] = 'out' frames_test = (Frame, Frame, RangeFrame, DashDotFrame) positions_test = (['all'], None, None, None) names = ('normal frame', 'L frame', 'range frame', 'dash-dot frame') sub_pos = (221, 222, 223, 224) x = rand(10) y = rand(10) test = zip(frames_test, positions_test, names, sub_pos) for frametype, positions, name, sub in test: ax = plt.subplot(sub, projection='frameaxes') ax.set_frame(frametype) if frametype is RangeFrame: ax.frame.show_extrema_lines = True if frametype is DashDotFrame: ax.frame.tframe_position = ['top', 'right'] if positions is not None: ax.frame.frame_position = positions ax.plot(x, y, 'o') if False: # debug scatter print 'Before Scatter:\n', ax.dataLim ax.scatter(x, y) print 'After Scatter:\n', ax.dataLim ax.set_ylabel(name) ax.axis([0, 1, 0, 1]) fig = plt.gcf() # remove background color to make the frames stand out fig.set_facecolor('w') fig.subplots_adjust(hspace=0.5, wspace=0.5) plt.show() if __name__ == '__main__': plot_test()
------------------------------------------------------------------------- Check out the new SourceForge.net Marketplace. It's the best place to buy or sell services for just about anything Open Source. http://sourceforge.net/services/buy/index.php
_______________________________________________ Matplotlib-users mailing list Matplotlib-users@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/matplotlib-users