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: zunzun@...709...
[http://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: zunzun@…709…
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')
AutoscaledText.py (12.5 KB)