Thanks for sharing this -- I'm curious about how you've dealt with some of these issues and see if any of them can be brought into the core. Overlapping text has long been something I've wanted to address, but it's difficult to solve and maintain as much flexibility as we currently have.
Running your script, I get this traceback: Traceback (most recent call last): File "AutoscaledText.py", line 236, in <module> 1024, 768, 'UseOffset_ON', 'ScientificNotation_ON') File "AutoscaledText.py", line 171, in HistogramPlot fig, ax = CommonPlottingCode(in_WidthInPixels, in_HeightInPixels, in_DataName, 'Frequency', useOffsetIfNeeded, scientificNotation, False, 0.0, 0.0, 1.0, 1.0) File "AutoscaledText.py", line 75, in CommonPlottingCode x_label._fontproperties._size = x_label._fontproperties._size * heightRatioForTextSize TypeError: can't multiply sequence by non-int of type 'float' > /wonkabar/data1/scraps/AutoscaledText.py(75)CommonPlottingCode() -> x_label._fontproperties._size = x_label._fontproperties._size * heightRatioForTextSize _fontproperties._size can be a CSS size name, such as "medium" or "large", so this line of code won't work. I replaced this with: x_label.set_size(x_label.get_size() * heightRatioForTextSize) y_label.set_size(y_label.get_size() * widthRatioForTextSize) which also has the advantage of avoiding private APIs that may change in the future. This seems to work for me, but I don't know if it matches your results. Mike James Phillips wrote: > All, > > Attached, and below, is public domain code for making > variable-sized plots with autoscaled text that exactly fits the > available visual plot space, useful for web sites where users choose > output files with different sizes. Examples are at the bottom of the > file. > > James R. Phillips > 2548 Vera Cruz Drive > Birmingham, AL 35235 USA > email: zun...@zunzun.com <mailto:zun...@zunzun.com> > http://zunzun.com > > > > # Entered into the public domain 20 March 2009 > # James R. Phillips > # 2548 Vera Cruz Drive > # Birmingham, AL 35235 USA > # email: zun...@zunzun.com <mailto:zun...@zunzun.com> > # http://zunzun.com > > import numpy as np > import math, matplotlib > matplotlib.use('Agg') # must be used prior to the next two statements > import matplotlib.pyplot as plt > import matplotlib.mlab as mlab > > > def DetermineOnOrOffFromString(in_String): > tempString = in_String.split('_')[-1:][0].upper() # allows any > amount of prefacing text > if tempString == 'ON': > return True > return False > > > def DetermineScientificNotationFromString(inData, in_String): > tempString = in_String.split('_')[-1:][0].upper() # allows any > amount of prefacing text > if tempString == 'ON': > return True > elif tempString == 'OFF': > return False > else: # must be AUTO > minVal = np.abs(np.min(inData)) > maxVal = np.abs(np.max(inData)) > deltaVal = np.abs(maxVal - minVal) > > scientificNotation = False > if (maxVal > 100.0) or (minVal < -100.0) or (deltaVal < .05): > scientificNotation = True > return scientificNotation > > > def CommonPlottingCode(in_WidthInPixels, in_HeightInPixels, in_XName, > in_YName, in_UseOffsetIfNeeded, in_X_UseScientificNotationIfNeeded, > in_Y_UseScientificNotationIfNeeded, in_Left, in_Bottom, in_Right, > in_Top): # default to lots of room around graph > > # a litle more room between x axis and tick mark labels, so not > text overlap at the bottom left corner - set this before other calls > matplotlib.rcParams['xtick.major.pad'] = 5+ > (float(in_HeightInPixels) / 100.0) # minimum + some scaled > > fig = plt.figure(figsize=(float(in_WidthInPixels ) / 100.0, > float(in_HeightInPixels ) / 100.0), dpi=100) > fig.subplotpars.update(in_Left, in_Bottom, in_Right, in_Top) > ax = fig.add_subplot(111, frameon=True) > > # white background, almost no border space > fig.set_facecolor('w') > > xFormatter = fig.gca().xaxis.get_major_formatter() > xFormatter._useOffset = in_UseOffsetIfNeeded > xFormatter.set_scientific(in_X_UseScientificNotationIfNeeded) > fig.gca().xaxis.set_major_formatter(xFormatter) > > yFormatter = fig.gca().yaxis.get_major_formatter() > yFormatter._useOffset = in_UseOffsetIfNeeded > yFormatter.set_scientific(in_Y_UseScientificNotationIfNeeded) > fig.gca().yaxis.set_major_formatter(yFormatter) > > # Scale text to imagesize. Text sizes originally determined at > image size of 500 x 400 > widthRatioForTextSize = float(in_WidthInPixels) / 500.0 > heightRatioForTextSize = float(in_HeightInPixels) / 400.0 > for xlabel_i in ax.get_xticklabels(): > xlabel_i.set_fontsize(xlabel_i.get_fontsize() * > heightRatioForTextSize) > xOffsetText = fig.gca().xaxis.get_offset_text() > xOffsetText.set_fontsize(xOffsetText.get_fontsize() * > heightRatioForTextSize * 0.9) > for ylabel_i in ax.get_yticklabels(): > ylabel_i.set_fontsize(ylabel_i.get_fontsize() * > widthRatioForTextSize) > yOffsetText = fig.gca().yaxis.get_offset_text() > yOffsetText.set_fontsize(yOffsetText.get_fontsize() * > heightRatioForTextSize * 0.9) > > x_label = ax.set_xlabel(in_XName) > y_label = ax.set_ylabel(in_YName) > x_label._fontproperties._size = x_label._fontproperties._size * > heightRatioForTextSize > y_label._fontproperties._size = y_label._fontproperties._size * > widthRatioForTextSize > > plt.grid(True) # call this just before returning > > return fig, ax > > > def YieldNewExtentsAndNumberOfMajor_X_TickMarks(fig, ax, > in_WidthInPixels, in_HeightInPixels, in_OffsetUsed): > # draw everything so items can be measured for size > canvas = plt.get_current_fig_manager().canvas > canvas.draw() > > # some preliminary info > xLabelPoints = > ax.set_xlabel(ax.get_xlabel()).get_window_extent().get_points() # [ > [x,y], [x,y] ] > yLabelPoints = > ax.set_ylabel(ax.get_ylabel()).get_window_extent().get_points() # [ > [x,y], [x,y] ], rotated 90 degrees > xTickZeroPoints = > ax.get_xticklabels()[0].get_window_extent().get_points() > yTickZeroPoints = > ax.get_yticklabels()[0].get_window_extent().get_points() > xTickIndexPoints = > ax.get_xticklabels()[len(ax.get_xticklabels())-1].get_window_extent().get_points() > yTickIndexPoints = > ax.get_yticklabels()[len(ax.get_yticklabels())-1].get_window_extent().get_points() > currentPoints = ax.bbox.get_points() > maxLeft = currentPoints[0][0] > maxBottom = currentPoints[0][1] > maxRight = currentPoints[1][0] > maxTop = currentPoints[1][1] > > # find the most left-ward location > if xTickZeroPoints[0][0] < maxLeft: > maxLeft = xTickZeroPoints[0][0] > if yTickZeroPoints[0][0] < maxLeft: > maxLeft = yTickZeroPoints[0][0] > if yTickIndexPoints[0][0] < maxLeft: > maxLeft = yTickIndexPoints[0][0] > if xLabelPoints[0][0] < maxLeft: > maxLeft = xLabelPoints[0][0] > if yLabelPoints[0][0] < maxLeft: # 90 degrees > maxLeft = yLabelPoints[0][0] > > # find the most right-ward location > if xTickIndexPoints[1][0] > maxRight: > maxRight = xTickIndexPoints[1][0] > if xLabelPoints[1][0] > maxRight: > maxRight = xLabelPoints[1][0] > > # find the most bottom-ward location > if xTickZeroPoints[0][1] < maxBottom: > maxBottom = xTickZeroPoints[0][1] > if xLabelPoints[0][1] < maxBottom: > maxBottom = xLabelPoints[0][1] > if yLabelPoints[0][1] < maxBottom: > maxBottom = yLabelPoints[0][1] > > # find the most top-ward location > if yTickIndexPoints[1][1] > maxTop: > maxTop = yTickIndexPoints[1][1] > if True == in_OffsetUsed: # could not find a better way to get this > yp = ax.get_yticklabels()[0].get_window_extent().get_points() > maxTop += yp[1][1] - yp[0][1] > > newLeft = ax.bbox._bbox.get_points()[0][0] - (float(maxLeft) / > float(in_WidthInPixels)) + 0.01 > newBottom = ax.bbox._bbox.get_points()[0][1] - (float(maxBottom) / > float(in_HeightInPixels)) + 0.01 > newRight = ax.bbox._bbox.get_points()[1][0] + (1.0 - > (float(maxRight) / float(in_WidthInPixels))) - 0.01 > newTop = ax.bbox._bbox.get_points()[1][1] + (1.0 - (float(maxTop) > / float(in_HeightInPixels))) - 0.01 > > # now redraw and check number of X tick marks > canvas.draw() > > # Calculate major number of X tick marks based on label size > totalWidth = 0.0 > maxWidth = 0.0 > numberOfMajor_X_TickMarks = len(ax.get_xticklabels()) > for xlabel_i in ax.get_xticklabels(): > w = xlabel_i.get_window_extent().get_points() # the drawn text > bounding box corners as numpy array of [x,y], [x,y] > width = w[1][0] - w[0][0] > totalWidth += width > if width > maxWidth: > maxWidth = width > if totalWidth > (0.95 * ((newRight - newLeft) * > float(in_WidthInPixels))): # 0.95 for some spacing between tick labels > numberOfMajor_X_TickMarks = int(math.floor((0.95 * ((newRight > - newLeft) * float(in_WidthInPixels))) / maxWidth)) > > return (newLeft, newBottom, newRight, newTop, > numberOfMajor_X_TickMarks,) > > > def HistogramPlot(in_DataToPlot, in_FileNameAndPath, in_DataName, > in_FillColor, in_WidthInPixels, in_HeightInPixels, > in_UseOffsetIfNeeded, in_UseScientificNotationIfNeeded): > > # decode ends of strings ('XYZ_ON', 'XYZ_OFF', 'XYZ_AUTO', etc.) > to boolean values > scientificNotation = > DetermineScientificNotationFromString(in_DataToPlot, > in_UseScientificNotationIfNeeded) > useOffsetIfNeeded = DetermineOnOrOffFromString(in_UseOffsetIfNeeded) > > numberOfBins = len(in_DataToPlot) / 2 > if numberOfBins > 25: > numberOfBins = 25 > if numberOfBins < 5: > numberOfBins = 5 > > # first with 0, 0, 1, 1 > fig, ax = CommonPlottingCode(in_WidthInPixels, in_HeightInPixels, > in_DataName, 'Frequency', useOffsetIfNeeded, scientificNotation, > False, 0.0, 0.0, 1.0, 1.0) > > # histogram of data > n, bins, patches = ax.hist(in_DataToPlot, numberOfBins, > facecolor=in_FillColor) > > # some axis space at the top of the graph > ylim = ax.get_ylim() > if ylim[1] == max(n): > ax.set_ylim(0.0, ylim[1] + 1) > > newLeft, newBottom, newRight, newTop, numberOfMajor_X_TickMarks = > YieldNewExtentsAndNumberOfMajor_X_TickMarks(fig, ax, in_WidthInPixels, > in_HeightInPixels, False) > > # now with scaled > fig, ax = CommonPlottingCode(in_WidthInPixels, in_HeightInPixels, > in_DataName, 'Frequency', useOffsetIfNeeded, scientificNotation, > False, newLeft, newBottom, newRight, newTop) > > # histogram of data > n, bins, patches = ax.hist(in_DataToPlot, numberOfBins, > facecolor=in_FillColor) > > # some axis space at the top of the graph > ylim = ax.get_ylim() > if ylim[1] == max(n): > ax.set_ylim(0.0, ylim[1] + 1) > > if len(ax.get_xticklabels()) > numberOfMajor_X_TickMarks: > > ax.xaxis.set_major_locator(matplotlib.ticker.MaxNLocator(numberOfMajor_X_TickMarks)) > > fig.savefig(in_FileNameAndPath, format = 'png', dpi=100) > > > def ScatterPlot(in_DataToPlot, in_FileNameAndPath, in_DataNameX, > in_DataNameY, in_WidthInPixels, in_HeightInPixels, > in_UseOffsetIfNeeded, in_ReverseXY, > in_X_UseScientificNotationIfNeeded, in_Y_UseScientificNotationIfNeeded): > > # decode ends of strings ('XYZ_ON', 'XYZ_OFF', 'XYZ_AUTO', etc.) > to boolean values > scientificNotationX = > DetermineScientificNotationFromString(in_DataToPlot[0], > in_X_UseScientificNotationIfNeeded) > scientificNotationY = > DetermineScientificNotationFromString(in_DataToPlot[1], > in_Y_UseScientificNotationIfNeeded) > useOffsetIfNeeded = DetermineOnOrOffFromString(in_UseOffsetIfNeeded) > reverseXY = DetermineOnOrOffFromString(in_ReverseXY) > > if reverseXY: > fig, ax = CommonPlottingCode(in_WidthInPixels, > in_HeightInPixels, in_DataNameY, in_DataNameX, useOffsetIfNeeded, > scientificNotationX, scientificNotationY, 0.0, 0.0, 1.0, 1.0) > ax.plot(np.array([min(in_DataToPlot[1]), > max(in_DataToPlot[1])]), np.array([min(in_DataToPlot[0]), > max(in_DataToPlot[0])]), 'o') # first ax.plot() is only with extents > newLeft, newBottom, newRight, newTop, > numberOfMajor_X_TickMarks = > YieldNewExtentsAndNumberOfMajor_X_TickMarks(fig, ax, in_WidthInPixels, > in_HeightInPixels, scientificNotationY or useOffsetIfNeeded) > fig, ax = CommonPlottingCode(in_WidthInPixels, > in_HeightInPixels, in_DataNameY, in_DataNameX, useOffsetIfNeeded, > scientificNotationX, scientificNotationY, newLeft, newBottom, > newRight, newTop) > if len(ax.get_xticklabels()) > numberOfMajor_X_TickMarks: > > ax.xaxis.set_major_locator(matplotlib.ticker.MaxNLocator(numberOfMajor_X_TickMarks)) > ax.plot(in_DataToPlot[1], in_DataToPlot[0], 'o') # now that > autoscaling is done, use all data for second ax.plot() > else: > fig, ax = CommonPlottingCode(in_WidthInPixels, > in_HeightInPixels, in_DataNameX, in_DataNameY, useOffsetIfNeeded, > scientificNotationY, scientificNotationX, 0.0, 0.0, 1.0, 1.0) > ax.plot(np.array([min(in_DataToPlot[0]), > max(in_DataToPlot[0])]), np.array([min(in_DataToPlot[1]), > max(in_DataToPlot[1])]), 'o') # first ax.plot() is only with extents > newLeft, newBottom, newRight, newTop, > numberOfMajor_X_TickMarks = > YieldNewExtentsAndNumberOfMajor_X_TickMarks(fig, ax, in_WidthInPixels, > in_HeightInPixels, scientificNotationY or useOffsetIfNeeded) > fig, ax = CommonPlottingCode(in_WidthInPixels, > in_HeightInPixels, in_DataNameX, in_DataNameY, useOffsetIfNeeded, > scientificNotationY, scientificNotationX, newLeft, newBottom, > newRight, newTop) > if len(ax.get_xticklabels()) > numberOfMajor_X_TickMarks: > > ax.xaxis.set_major_locator(matplotlib.ticker.MaxNLocator(numberOfMajor_X_TickMarks)) > ax.plot(in_DataToPlot[0], in_DataToPlot[1], 'o') # now that > autoscaling is done, use all data for second ax.plot() > > fig.savefig(in_FileNameAndPath, format = 'png', dpi=100) > > > if __name__ in ('main', '__main__'): > > testData1D = 12345678901.5 + np.random.randn(100) > testData2D = [testData1D, 1000.0 * testData1D + 1500 + 200.0 * > np.random.randn(100)] > > # note file names > HistogramPlot(testData1D, 'test_histogram_large.png', 'Test Data > Name', 'lightgrey', > 1024, 768, 'UseOffset_ON', 'ScientificNotation_ON') > > HistogramPlot(testData1D, 'test_histogram_small.png', 'Test Data > Name', 'lightgrey', > 320, 240, 'UseOffset_ON', 'ScientificNotation_ON') > > ScatterPlot(testData2D, 'test_scatterplot_small.png', 'Test Data X > Name', 'Test Data Y Name', > 320, 240, 'UseOffset_ON', 'ReverseXY_OFF', > 'ScientificNotation_X_OFF', 'ScientificNotation_Y_OFF') > > ScatterPlot(testData2D, 'test_scatterplot_large.png', 'Test Data X > Name', 'Test Data Y Name', > 1024, 768, 'UseOffset_ON', 'ReverseXY_ON', > 'ScientificNotation_X_OFF', 'ScientificNotation_Y_ON') > > ------------------------------------------------------------------------ > > ------------------------------------------------------------------------------ > Apps built with the Adobe(R) Flex(R) framework and Flex Builder(TM) are > powering Web 2.0 with engaging, cross-platform capabilities. Quickly and > easily build your RIAs with Flex Builder, the Eclipse(TM)based development > software that enables intelligent coding and step-through debugging. > Download the free 60 day trial. http://p.sf.net/sfu/www-adobe-com > ------------------------------------------------------------------------ > > _______________________________________________ > Matplotlib-users mailing list > Matplotlib-users@lists.sourceforge.net > https://lists.sourceforge.net/lists/listinfo/matplotlib-users -- Michael Droettboom Science Software Branch Operations and Engineering Division Space Telescope Science Institute Operated by AURA for NASA ------------------------------------------------------------------------------ Apps built with the Adobe(R) Flex(R) framework and Flex Builder(TM) are powering Web 2.0 with engaging, cross-platform capabilities. Quickly and easily build your RIAs with Flex Builder, the Eclipse(TM)based development software that enables intelligent coding and step-through debugging. Download the free 60 day trial. http://p.sf.net/sfu/www-adobe-com _______________________________________________ Matplotlib-users mailing list Matplotlib-users@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/matplotlib-users