8

I have a number of plots where the x- and y-axes are in centimeter units, and I am already using axis('equal') to ensure proper aspect ratios. I would like to print out those plots so that when I measure distances within my axes, the distances correspond to real-world centimeters.

That is, a line that is 3 units (cm) long in the plot should print out to be 3 cm long. (A more complex example would be drawing a ruler in Matplotlib, and then printing it out for use /as/ a ruler.) I found solutions in matlab and mathematica, but not for Matplotlib. Is there a magic formula to achieve this? I believe it will require a special combination/ordering of: figure(figsize=??), axis('equal'), fig.canvas.draw(), fig.savefig('filename',format="??"), possibly some math with fig.bbox parameters, and perhaps one or more dpi-settings. I have tried many combinations but haven't hit on the right one. And maybe there is a way-easier approach ...

1
  • I think the missing step is forcing the Axes object to be a known proportion of the measured figure size, and then setting the axis limits so that the data-units match the real-world units. Commented Apr 1, 2015 at 22:59

3 Answers 3

4

Consider this example. Where I specify exactly the dimension of my axes in cm. matplotlib works in inches, so I convert to inches. And then I also save it with a particular dpi (128) so that it matches the designed dimensions in my display. This of course varies for every display. I found that by trial and error, even though there might be other methods. Well here the code:

left_margin = 1.   # cm
right_margin = 1.  # cm
figure_width = 10. # cm
figure_height = 7. # cm
top_margin = 1.    # cm
bottom_margin = 1. # cm

box_width = left_margin + figure_width + right_margin   # cm
box_height = top_margin + figure_height + bottom_margin # cm

cm2inch = 1/2.54 # inch per cm

# specifying the width and the height of the box in inches
fig = figure(figsize=(box_width*cm2inch,box_height*cm2inch))
ax = fig.add_subplot(111)
ax.plot([1,2,3])

fig.subplots_adjust(left   = left_margin / box_width,
                    bottom = bottom_margin / box_height,
                    right  = 1. - right_margin / box_width,
                    top    = 1. - top_margin   / box_height,
                    )
fig.savefig('ten_x_seven_cm.png', dpi=128)
# dpi = 128 is what works in my display for matching the designed dimensions.
Sign up to request clarification or add additional context in comments.

7 Comments

This is in the display -- does it work with printers? (the original question)
If you open the saved figure and print it in 100% scale, it should match the specified size. For the printer, the dpi will only matter for the resolution of the image.
I just tried that and it didn't; much bigger than scale. Not even isometric -- a y-unit is 3cm on paper, an x-unit is 5cm. This is printed 100% scale, no auto-anything, View Actual Size in the viewer, everything I could think of. (I believe printers are mostly unreliable at this; it's plotters that are accurate.)
What are you using for viewing/printing? I used the mac OS X preview. Then in the printer menu, The Scale is 100% and it did the job. Saving as pdf also reflected the size selected.
OS X Preview, standard desktop printer. Will try GIMP.
|
4

Add this to @pablo reyes' answer, check that the printer is at 100%, and it's pretty close;

ax.set_ylim(0,7)
ax.set_xlim(0,10)

ax.plot([0.5, 1.5],[0.25, 0.25],label='One cm?')
ax.plot([6,6],[1,2], label='One cm?')
ax.legend()

we force the axis to be a size we know, we make its data-transform match the real world, and we can "print a ruler".

5 Comments

Now I understand the confusion. I was more concern about the real dimension of the axes.
Could one write a transReal transform that gets as close as the computer can to "print a ruler"? t I think the Axes would have to get information from the Figure that the Figure doesn't normally pass on, so, maybe not, and certainly a pain.
Thank you all! Pablo's solution mostly worked, after modifying dpi to 72. In adding cphlewis' piece, I ran into some bug (oddity?): my axis([xmin, xmax, ymin, ymax]) command is ignored ... same problem with set_xlim() and set_ylim() ... maybe a weird interaction with axis('equal')? The workaround was to plot everything, let matplotlib pick the x/y limits, try to force them (unsuccessfully), use ax.get_xlim() and ax.get_ylim() to determine what the actual limits are (not what I set them to), calc figure_width and figure_height from that, and continue with Pablo's solution. Thanks again!
I think axis([xo, x1, y0, y1]) is in terms of proportion of the Figure, not the data transform. Did you try ax.set_xlim, etc., instead of defining the axis size originally?
also, you won't need axis('equal') if you define them to be equal when printed -- so I'd take it out, to skip the workaround.
3

Another method using fig.add_axes to set the data limits to match the physical plot was quite accurate. I have included 1 cm grid as well.

import matplotlib.pyplot as plt
import matplotlib as mpl

# This example fits a4 paper with 5mm margin printers

# figure settings ( cm size of charted axes plus margin for tick lables) 
figure_width = 28.7 # cm 
figure_height = 20 # cm
left_right_margin = 1 # cm
top_bottom_margin = 1 # cm

# Don't change
left   = left_right_margin / figure_width # Proportion of width
bottom = top_bottom_margin / figure_height # Proportion of height
width  = 1 - left*2 
height = 1 - bottom*2
cm2inch = 1/2.54 # inch per cm

# specifying the width and the height of the box in inches
fig = plt.figure(figsize=(figure_width*cm2inch,figure_height*cm2inch))
ax = fig.add_axes((left, bottom, width, height)) # proportion of figure

# limits settings (important) 
plt.xlim(0, figure_width * width) # width of limits in data units
plt.ylim(0, figure_height * height) # height of limits in data units

# Ticks settings
ax.xaxis.set_major_locator(mpl.ticker.MultipleLocator(5))
ax.xaxis.set_minor_locator(mpl.ticker.MultipleLocator(1))
ax.yaxis.set_major_locator(mpl.ticker.MultipleLocator(5))
ax.yaxis.set_minor_locator(mpl.ticker.MultipleLocator(1))

# Grid settings
ax.grid(color="gray", which="both", linestyle=':', linewidth=0.5)

# your Plot (consider above limits)
ax.plot([1,2,3,5,6,7,8,9,10,12,13,14,15,17])

# save figure ( printing png file had better resolution, pdf was lighter and better on screen)
plt.show()
fig.savefig('A4_grid_cm.png', dpi=1000)
fig.savefig('tA4_grid_cm.pdf')

Results: Results

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.