Wrong rectangle layout in matplotlib

I make a plot with bars, and I try to find their absolute location (in pixels) on the chart for further processing later. It seems to me that this should be understood from the matplotlib transform information on the axis instance. In particular, I use ax.transData to go from the data coordinates (where I know the positions of the boxes) to display the coordinates. Here is the code:

 import matplotlib.pyplot as plt x = range(10) y = range(1, 11) fig = plt.figure() ax = fig.add_subplot(111) bars = ax.bar(x, y, width=.5, label="foo") ax.monkey_rectangles = bars ax.legend() def get_useful_info(fig): for ax in fig.get_axes(): for rect in ax.monkey_rectangles: corners = rect.get_bbox().corners()[::3] pos = ax.transData.transform(corners) left = pos[0,0] width = pos[1,0] - pos[0,0] bottom = pos[0,1] height = pos[1,1] - pos[0,1] yield left, width, bottom, height fig.savefig('foo.png') for l, w, b, h in get_useful_info(fig): print l, w, b, h 

The following is printed:

 80.0 24.8 48.0 38.4 129.6 24.8 48.0 76.8 179.2 24.8 48.0 115.2 228.8 24.8 48.0 153.6 278.4 24.8 48.0 192.0 328.0 24.8 48.0 230.4 377.6 24.8 48.0 268.8 427.2 24.8 48.0 307.2 476.8 24.8 48.0 345.6 526.4 24.8 48.0 384.0 

So matplotlib thinks my boxes are 24.8 units (the pixels that I guess) are wide. This is great, except when I'm really doing box width measurement, I get something larger than 32 pixels wide. What is the discrepancy here?

+4
source share
2 answers

As usual, disclosure occurs when sending a question ... The problem is that the units are not pixels , they are points. When this digit is initialized, it is configured using the default dots per inch ( fig.set_dpi() and fig.get_dpi() allows you to change / query the current resolution of the figure). Of course, everything is not so simple: when you save the picture, the dpi changes depending on your rc settings for a specific backend. For me with the default backend and png output, this is 100 dpi. However, one thing that is invariant here is the size of the figure fig.get_size_inches() . So, if I scale my results, it seems like we are a little more consistent ...

 def get_useful_info(fig): dpi = fig.get_dpi() pngdpi = 100 scale_x = pngdpi / dpi scale_y = pngdpi / dpi for ax in fig.get_axes(): for rect in ax.monkey_rectangles: corners = rect.get_bbox().corners()[::3] pos = ax.transData.transform(corners) left = pos[0,0] * scale_x width = (pos[1,0] - pos[0,0]) * scale_x bottom = pos[0,1] * scale_y height = (pos[1,1] - pos[0,1]) * scale_y yield left, width, bottom, height 

I have a feeling that this is not a complete solution. In the end, we are still working at points. I'm not quite sure how a dot is converted to a pixel for png images ... I hope that the display engine (in my particular case, a web browser) displays each dot in one pixel and does not care about the image reported size. I believe that only a few experiments will sort this out ...

+3
source

How do you mean web browsers, I assume that you are creating image maps?

 import matplotlib.pyplot as plt x = range(10) y = range(1, 11) fig = plt.figure() ax = fig.add_subplot(111) bars = ax.bar(x, y, width=.5, label="foo") ax.monkey_rectangles = bars ax.legend() def fig_fraction_info(fig): for ax in fig.get_axes(): inv = fig.transFigure.inverted() # transformations are applied left to right my_trans = ax.transData + inv for rect in ax.monkey_rectangles: corners = rect.get_bbox().corners()[::3] pos = my_trans.transform(corners) left = pos[0,0] width = pos[1,0] - pos[0,0] bottom = pos[0,1] height = pos[1,1] - pos[0,1] yield left, width, bottom, height 

I think something like this is what you want. If you take this data and png to the next level of code, you can pinpoint where all this is.

The problems, I think, are due to the fact that matplotlib abstracts the backend from the front end. Thinking about pixel size is not a big deal for vector outputs.

As an alternative:

 dpi = 300 ... fig.set_dpi(dpi) ... fig.savefig(..., dpi=dpi) 
+1
source

Source: https://habr.com/ru/post/1502785/


All Articles