From: John Hunter <jdhunter@ac...>  20040130 21:37:37

I've spent the last couple of days refactoring the matplotlib backends, fixing bugs and adding some functionality. Here's a synopsis of what's new. I encourage everyone to try it out so complaints and bugs can be handled before the major release. ** Note there are some API changes so please read about this below ** ** Note, GD users, GD rendering is significantly improved in my opinion. However, some of new functionality requires a recent version of gd and a patch of the latest gdmodule, see below ** What's new in matplotlib 0.50e GD supports clipping and antialiased line drawing. The line object has a new 'antialiased' property, that if true, the backend will render the line antialiased if supported. **You will need to upgrade to gd2.0.15 or later and gdmodule0.51. You will also need to replace _gdmodule.c with the code as described at http://matplotlib.sourceforge.net/backends.html#GD. wild and wonderful bar charts You can provide an optional argument 'bottom' to the bar command to determine where the bottom of each bar is, default 0 for all. This enables stacked bar plots and candelstick plots  examples/bar_stacked.py. Thanks to David Moore and John Gill for suggestions and code. Bugfixes (by backend) * All : the yticks on the right hand side were placed incorrectly, now fixed * All : ticklabels now make a more intelligent choice about how many significant digits to display * GD : An int truncation bug was causing the dotted lines to disappear * GD and GTK : Fixed line width to scale with DPI * GD : Fixed small text layout bug * GD : Fixed the constant for GD which maps pixels per inch  this should give better agreement with other backends witht he relative sizes of objects * GTK : Dash spacing was not properly scaling with DPI Figure backend refactored The figure functionality was split into a backend independent component Figure and a backend dependent component FigureCanvasBase. This completes the transition to a totally abstract figure interface and improves the ability the switch backends. See the file http://matplotlib.sourceforge.net/API_CHANGES that comes with the src distro for information on migrating applications to the new API. All the backend specific examples have been updated to the new API. Enjoy, John Hunter 
From: matthew arnison <maffew@ca...>  20040202 04:04:20

Hi, I am happily using matplotlib0.50e. I tried eps output and it worked very nicely. The problem with plot lines not being clipped by a manual axis in the PS backend also seems to have been fixed. ... I have some feedback on the default tick behaviour. matplotlib seems to pick a number of ticks, and then divides through to get the tick values. This results in some ugly long tick labels, making it hard to quickly gauge the range between two points on a graph. E.g. if the y range of a plot is 1.927 to 1.948, then matplotlib puts ticks at (1.927, 1.931, 1.935, ..., 1.948) I think it would be better (and closer to the plotting behaviour of other software) if matplotlib picked ticks that were "round", even if that means the endpoints of the axes are slightly outside the range of the data. So the ticks for the example above would become: (1.925, 1.930, 1.935, ..., 1.950) I guess this would be more complicated to implement than the current algorithm, but it would make life easier when interpreting graphs from matplotlib! ... Another slight niggle. If I set the axis range manually, then if a data point is exactly equal to the end of the axis range then it won't be plotted. Making the axis range slightly longer is clumsy. This also violates the principle of least surprise, because automatic axis ranges do not have this behaviour. A simple way to see the problem is to compare the output of the two plots below: >>> xvals = arange(0.0, 1.0, 0.1) >>> plot(xvals, [sin(x) for x in xvals]) [<matplotlib.lines.Line2D instance at 0x93fa844>] >>> show() >>> plot(xvals, [sin(x) for x in xvals]) [<matplotlib.lines.Line2D instance at 0x9249d2c>] >>> autoaxis = axis() >>> autoaxis [0.0, 0.90000000000000002, 0.0, 0.80000000000000004] >>> axis(autoaxis) >>> show() Presumably the logic for picking the datapoints to plot should use <= not <. Cheers, Matthew. 
From: matthew arnison <maffew@ca...>  20040203 03:04:59

Hi again, I'm having more trouble with matplotlib ticks today. I wrote a little demo script that illustrates some of the problems: ... #!/usr/bin/python from matplotlib.matlab import * xx = arange(0.002, 0.0101, 0.001) print xx # an instance of yy = rand(9), so all values are between 0 and 1 yy = [ 5.94692328e04, 1.62328354e01, 7.56822907e01, 2.28180047e02, 3.23820274e01, 3.93120900e01, 6.41332889e01, 1.22474302e02, 5.03485402e01] subplot(211) plot(xx, yy) subplot(212) plot(xx, yy) autoaxis = axis() print autoaxis axis(autoaxis) show() ... * the x axis includes *two* 0.004 and *two* 0.008; this really worried me until I realised it was a cosmetic rounding / significant figures issue, however it's bad enough to be seriously misleading. I think the actual tick values are something like 0.0036 and 0.0044 but are both rounded to 0.004. * the data points lie *between* the x axis ticks, this is a sideeffect of the above * the poor choice of tick positions on the y axis  they should be in round numbers like 0.2, 0.4, etc. The most significant varying figure should be a multiple of 1, 2, or 5. * the tick labels should all have the same number of significant figures, e.g. 0.00, 0.15, 0.30, 0.45, 0.60, ... for the y axis * after manually setting the axis (lower subplot), the last point is not plotted I hope you find this feedback useful. I had a go at fixing it in axis.py, but it's a) fiddly and b) I don't quite understand which part has precendence when the axis changes during a zoom or pan. Getting the ticks right depends on the correct bounds for the axis and the choice of numticks. I noticed you have logic to clean up the bounds (vmin and vmax) but not the ticklocs. Thanks for matplotlib. Cheers, Matthew. 
From: John Hunter <jdhunter@ac...>  20040204 03:12:58

>>>>> "matthew" == matthew arnison <maffew@...> writes: matthew> Hi again, I'm having more trouble with matplotlib ticks matthew> today. I wrote a little demo script that illustrates some matthew> of the problems: Hi Matthew, Thanks for sending me these example scripts  it really helps to have complete examples when working on these problems. I've made a few changes to the tickval formatter. The relevant function is matplotlib.axis.format_tickval and is pretty simple conceptually. Try replacing the default format_tickval with this one. The d variable gives the maxmin distance of the view limits. I use different format strings depending on the size of the distance. def format_tickval(self, x): 'Format the number x as a string' d = self.viewlim.interval() if self._scale == 'log': # only label the decades fx = self.transData.func(x) isdecade = abs(fxint(fx))<1e10 if self._ticklabelStrings is None and not isdecade: return '' #if the number is not too big and it's an int, format it as an #int if abs(x)<1e4 and x==int(x): return '%d' % x # if the value is just a fraction off an int, use the int if abs(xint(x))<0.0001*d: return '%d' % int(x) # use exponential formatting for really big or small numbers, # else use float if d < 1e2 : fmt = '%1.2e' elif d < 1e1 : fmt = '%1.2f' elif d > 1e5 : fmt = '%1.3e' elif d > 10 : fmt = '%1.1f' elif d > 1 : fmt = '%1.2f' else : fmt = '%1.3f' s = fmt % x #print d, fmt, x, s # strip trailing zeros, remove '+', and handle exponential formatting m = self._zerorgx.match(s) if m: s = m.group(1) if m.group(2) is not None: s += m.group(2) s = s.replace('+', '') return s And then feed it some more of your sadistic examples <wink>. If you don't like what you see, try tweaking the formats and the distance values until you get sensible results. Or feel free to provide more comments and send more examples. JDH 
From: John Hunter <jdhunter@ac...>  20040204 03:05:23

>>>>> "matthew" == matthew arnison <maffew@...> writes: matthew> Hi, I am happily using matplotlib0.50e. I tried eps matthew> output and it worked very nicely. The problem with plot matthew> lines not being clipped by a manual axis in the PS matthew> backend also seems to have been fixed. Good to hear .. matthew> I have some feedback on the default tick matthew> behaviour. matplotlib seems to pick a number of ticks, matthew> and then divides through to get the tick values. This matthew> results in some ugly long tick labels, making it hard to matthew> quickly gauge the range between two points on a graph. matthew> E.g. if the y range of a plot is 1.927 to 1.948, then matthew> matplotlib puts ticks at (1.927, 1.931, 1.935, ..., matthew> 1.948) I agree this is an important issue. It's also a difficult one. If matplotlib just had to make a good choice for the axis limits and tick values for a single plot, it wouldn't be too hard. What becomes harder is to do this in the presence of interactivity. Once you allow the user to pan and zoom, you have some other considerations. For example, if the tick locations or the number of ticks/grids on the axis move while you pan or zoom, that is visually disturbing. The easiest way to optimize the tick locations is to have some flexibility in choosing the number of ticks, but after the initial plot, this number is set for the rest of the interactive session which makes it harder. So enough excuses! I agree that the current implementation is suboptimal and will give it some more thought. Out of curiosity: do the undersirable tick locs appear more frequently for you on an initial plot or after interacting with the plot. matthew> Another slight niggle. If I set the axis range manually, matthew> then if a data point is exactly equal to the end of the matthew> axis range then it won't be plotted. This is a consequence of the way python and Numeric do ranges, and doesn't really have anything to do with matplotlib. eg, the Numeric function arange >>> from Numeric import * >>> arange(0.0, 1.0, 0.2) array([ 0. , 0.2, 0.4, 0.6, 0.8]) >>> range(5) [0, 1, 2, 3, 4] Ie, ignore the end point is the default behavior of python. JDH 
From: matthew arnison <maffew@ca...>  20040204 05:26:39

On Tue, 3 Feb 2004, John Hunter wrote: > So enough excuses! I agree that the current implementation is > suboptimal and will give it some more thought. Out of curiosity: do > the undersirable tick locs appear more frequently for you on an > initial plot or after interacting with the plot. I haven't been using the pan and zoom stuff very much, the issues I described this week are all from initial plots. If I had been doing more zooming then I would have noticed your very good point about jumping ticks being distracting. It's fiddly stuff, but getting it right helps a lot when interpreting results from plots! > This is a consequence of the way python and Numeric do ranges, and > doesn't really have anything to do with matplotlib. eg, the Numeric > function arange > > >>> from Numeric import * > >>> arange(0.0, 1.0, 0.2) > array([ 0. , 0.2, 0.4, 0.6, 0.8]) > > >>> range(5) > [0, 1, 2, 3, 4] > > Ie, ignore the end point is the default behavior of python. I guessed as much. But I think in this case the python behaviour needs to be overridden. Python range logic is generally about integers, arange stretches it, and using this [) style range for plots overstretches the principle beyond usefulness. There are also a couple of contradictions in matplotlib's behaviour: * the axis and tick ranges are inclusive, but the data point range is exclusive of the higher end point only * the auto data range (axis()) is inclusive, but setting it manually is exclusive of the higher end point only Some of this may be alleviated by chooing better tick points. But I think it would also be helpful to make whichever behaviour is chosen more consistent. Cheers, Matthew. 
From: John Hunter <jdhunter@ac...>  20040205 04:34:13

>>>>> "matthew" == matthew arnison <maffew@...> writes: matthew> I haven't been using the pan and zoom stuff very much, matthew> the issues I described this week are all from initial matthew> plots. If I had been doing more zooming then I would have matthew> noticed your very good point about jumping ticks being matthew> distracting. It's fiddly stuff, but getting it right matthew> helps a lot when interpreting results from plots! OK, I think my approach will be to optimize the number of ticks, tick locations and view limits on the initial plot and then fix num ticks for interactive mode (pan/zoom). Ie, the initial guess should be good, but with interaction, you're on your own. I'll send you some code when I get this figured out. >> Ie, ignore the end point is the default behavior of python. matthew> I guessed as much. But I think in this case the python matthew> behaviour needs to be overridden. Python range logic is matthew> generally about integers, arange stretches it, and using matthew> this [) style range for plots overstretches the matthew> principle beyond usefulness. There are also a couple of matthew> contradictions in matplotlib's behaviour: Yes, but I can defend myself! From the first line of the homepage matplotlib is a pure python 2D plotting library with a Matlab syntax Ie, matplotlib does contain inherent contradictions because it is both matlablike and pythonlike. matlab has FORTRAN style indexing (starts with 1) and python has C style indexing (starts with 0). So the first matplotlib figure starts with figure(1). When using the matlab interface matplotlib.matlab, I strive for matlab compatibility. Thus, when you set the axis limits with axis([0, 2, 1, 1]) or set(gca(), 'xlim', [0, 2]) I do it like matlab does, ie, endpoints inclusive. However, I plead innocence in the case of t = arange(0.0, 2.0, 0.1) s = sin(2*pi*t) plot(t, s) If the arrays t and s passed to the plot function do not have the point at t=2.0 defined, "plot" can't guess them. I plot the points you give me. Note that matplotlib *does* provide the matlab function "linspace", which returns an evenly sampled array, endpoints included. So if you want matlablike behavior you should use matlablike array functions (linspace) rather Numeric python array functions (arange) to define your arrays. t = linspace(0.0, 2.0, 20) s = sin(2*pi*t) plot(t, s) In a nutshell, with the matlab interface I try to be consistent with matlab, but there are inconsistencies which arise by virtue of the fact that it's natural to use python functions (thank god!, that's why we're all here). I definitely appreciate the criticism, so feel free to keep at it. JDH 