2

I've been plotting a dataframe using the following code within a Jupyter Notebook: For comparision with older data only available on paper in the scale 0.005mm=1cm, I need to export and print the graph in the same scale: 0.005mm in the figure (both x and y-axis) have to be 1cm in the figure.

Is there any way how I can define a custom scale? For information, the x-range and y-range are not fixed, they will vary depending on the data I am loading into the dataframe.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns            
import matplotlib.ticker as ticker


df = pd.DataFrame(np.array([[1.7, 0], [1.75, -0.012], [1.8, 0]]),
                   columns=['pos', 'val'])      
            
# Plot results
sns.set()
plt.figure(figsize=(20,30))

plt.plot(df['pos'], df['val'])

ax = plt.axes()
ax.set_aspect('equal')

plt.xlabel('Position [mm]')
plt.ylabel('Höhe [mm]')

ax.xaxis.set_major_locator(ticker.MultipleLocator(0.005))
ax.yaxis.set_major_locator(ticker.MultipleLocator(0.005))

plt.show()

Result

Zephyr
  • 11,891
  • 53
  • 45
  • 80
user10679526
  • 109
  • 7
  • 2
    I imagine that to check against an old paper graph you want to print your plot, won't you? In this case you have to ① analyze your data before plotting and decide the size of the figure, in inches, to accomodate the Axes and ② knowing the Figure dpi, you can explicitly set the transformation between canvas coordinates, in pixels, and data coordinates (see the Transformation Tutorial in the Matplotlib documentation). If you succeed, I suggest that you post your solution as an answer because it's an interesting problem, OTOH if you are having problems ping me, I'll try to help more… – gboffi Jul 21 '20 at 10:20
  • @gboffi: I tried working on the problem as you suggested. I defined x,y-limits and calculated the axis length in inch by (xmax-xmin)/0.005/2.54. I also read the transformation tutorial, but I do not get how converting data coordinates into pixel coordinates can help me with my problem. Could you go a little bit more into detail on what you exactly would do with with that? – user10679526 Jul 21 '20 at 11:39
  • I have tried for a while to walk my way in `matplotlib.transforms` module but it's a labyrinth of mirrors… I'm sorry but for the moment I will give up. To answer your question you need a real Matplotlib developer, so I'd suggest to try to raise [an issue on Matplotlib's github](https://github.com/matplotlib/matplotlib/issues) – gboffi Jul 21 '20 at 14:37
  • I've changed my mind re transforms and I've posted what I think it's a good, working answer. Could you please have a look and tell me if it is, in some way, unsatisfactory? – gboffi Jul 28 '20 at 13:28
  • Great solution. I had to change dpi=118 to dpi=100, but now the scale is exactly as I want it to be. Thanks a lot! – user10679526 Jul 31 '20 at 08:17
  • You have the possibility to vote my answer (click the "▲" triangle on the left of it) or even to accept it (click the “✓” check mark on the left of my answer) – gboffi Jul 31 '20 at 11:44

2 Answers2

1

In a comment I suggested to use matplotlib.transforms — well I was wrong, the way to go is to shamelessly steal from Matplotlib's Demo Fixed Size Axes

enter image description here

(the figure was resized by StackOverflow to fit in the post, but you can check that the proportions are correct)

import matplotlib.pyplot as plt

from mpl_toolkits.axes_grid1 import Divider, Size
from mpl_toolkits.axes_grid1.mpl_axes import Axes

cm = lambda d: d/2.54

x, y = [1700.0, 1725.0, 1750.0], [0.0, -12.0, 0.0] # μm
dx, dy = 50.0, 12.0

# take margins into account
xmin, xmax = min(x)-dx*0.05, max(x)+dx*0.05
ymin, ymax = min(y)-dy*0.05, max(y)+dy*0.05
dx, dy = xmax-xmin, ymax-ymin

# 5 μm data == 1 cm plot
scale = 5/1
xlen, ylen = dx/scale, dy/scale

# Now we know the extents of our data and the axes dimension,
# so we can set the Figure dimensions, taking borders into account
left, right = 2, 1
bot, top = 1.5, 1.5
fig = plt.figure(
    figsize=(cm(left+xlen+right), cm(bot+ylen+top)),
    dpi=118)

# change bg color to show so that one can measure the figure
# and the axes when pasted into SO and do their math…
fig.set_facecolor('xkcd:grey teal')

##########  Below is stolen from Matplotlib Fixed Size Axes
########## (please don't ask me…)
# Origin and size of the x axis and y axis
h = [Size.Fixed(cm(left)), Size.Fixed(cm(xlen))]
v = [Size.Fixed(cm(bot)), Size.Fixed(cm(ylen))]
divider = Divider(fig, (0.0, 0.0, 1., 1.), h, v, aspect=False)
# NB: Axes is from mpl_toolkits.axes_grid1.mpl_axes
ax = Axes(fig, divider.get_position())
ax.set_axes_locator(divider.new_locator(nx=1, ny=1))
fig.add_axes(ax)
#########  Above is stolen from Matplotlib Fixed Size Axes Demo 

plt.plot(x,y)
plt.grid()
ax.set(xlim=(xmin, xmax), ylim=(ymin, ymax), yticks=range(-12,1,3),
       xlabel='X/μm', ylabel='Y/μm',
       title='X vs Y, 1 cm on plot equals 5 μm')
fig.suptitle('Figure dimensions: w = %.2f cm, h = %.2f cm.'%(
    left+xlen+right, bot+ylen+top))

fig.savefig('Figure_1.png',
            # https://stackoverflow.com/a/4805178/2749397, Joe Kington's
            facecolor=fig.get_facecolor(), edgecolor='none')
gboffi
  • 22,939
  • 8
  • 54
  • 85
0

1 inch = 2.54 cm, so 254/0.005 = 50800 dpi

plt.figure(figsize=(20,30), dpi=50800)
r-beginners
  • 31,170
  • 3
  • 14
  • 32
  • Tried replacing "plt.figure(figsize=(20,30))" with "plt.figure(figsize=(20,30), dpi=50800)" and got "ValueError: Image size of 1016000x1524000 pixels is too large. It must be less than 2^16 in each direction." – mike Jul 21 '20 at 09:08
  • 1
    I'll repeat myself with a triad of errors, but how do you respond with dpi=5080, or dpi=508, and then try to enlarge it at 1000% when printed? – r-beginners Jul 21 '20 at 09:16
  • Still get ValueError with 5080, but not with 508. – mike Jul 21 '20 at 09:30
  • I'm going to print at DPI=508, and then I'll be able to respond with an enlarged copy of what I printed. – r-beginners Jul 21 '20 at 09:32