Repository navigation
Closed
Labels
Milestone
Description
There is a discrepancy between vector format (pdf, svg, eps) output and image output (png, jpg, etc) for the BboxImage object. I can't attach a pdf, but the this code creates and saves a pdf and png illustrating the BboxImage object translation. Just update the save folder path to run the code.
Thanks and let me know if I should include anything else.
'''
Minimum working example
version 1.3.1
Plots a descriptive flowchart showing error
@Author: wronk
'''
from os import path as op
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.image import BboxImage
from matplotlib.offsetbox import TextArea, AnnotationBbox
from matplotlib.transforms import Bbox
from mpl_toolkits.axes_grid1 import make_axes_locatable
from matplotlib.backends.backend_pdf import PdfPages
###############################################################################
#EDIT SAVE FOLDER TO AUTOSAVE
saveFig = True
save_fname = '/home/wronk/'
mpl.rcParams['mathtext.default'] = 'rm'
#Box properties
bboxProp = dict(boxstyle='round,pad=0.3', fill=False, ec='w', linewidth=8)
#Colors for annotation arrows
senCol = (0.35, 0.35, 0.35)
srcCol = (.8, .216, 0.0)
#Define color map
cm = 'autumn'
colRange = np.atleast_2d(np.arange(256)/256.)
###############################################################################
#Initialize Plot
plt.ion()
plt.close('all')
mpl.rcParams['pdf.fonttype'] = 42
figSize = (12, 6)
ftSize = 32
dpi = 80
rowYVals = [0.085, 0.55, 0.85]
rowXVals = [0, .075, .2, .34, .5, .65, .8, .875]
fig = plt.figure(figsize=figSize, facecolor='white', dpi=dpi)
#fig = plt.figure(figsize=figSize, facecolor='white')
#ax = fig.gca()
ax = plt.subplot(111)
###############################################################################
#Mid level of annotation boxes
#Text for boxes in mathematical font
flowMid = r'$j_{N-1}$'
midBox3 = ax.annotate(flowMid, xy=(0, .5), xycoords='axes fraction',
xytext=(rowXVals[3], rowYVals[1]),
textcoords='axes fraction',
size=ftSize, ha='left', va='center', bbox=bboxProp,
arrowprops=None, color='black')
#######################################
#HEREIN LIES THE PROBLEM
#Add color (gradients) behind the boxes
#Something about the bbox window extent property must get shifted when saving
#as a vector format (pdf, svg, eps) but not common image types. In my experience,
#every bboximage was translated and scaled uniformly.
#I tried changing DPI, image size, subplot parameters as well as sychronizing
#plotting and saving figures in matplotlibrc to no avail. DPI does seem to
#have some sort of effect in terms of how the bboximage gets shifted though.
gradient = BboxImage(midBox3.get_bbox_patch().get_window_extent,
data=np.zeros_like(colRange), cmap=cm, norm=None,
origin=None)
ax.add_artist(gradient)
#######################################
plt.draw()
#Save figure as pdf and png to highlight difference
#Same results when saving from GUI window
if saveFig:
pdfFile = PdfPages(save_fname + 'flowchart_PDF.pdf')
plt.savefig(pdfFile, format='pdf', dpi=fig.dpi)
pdfFile.close()
plt.savefig(save_fname + 'flowchart_PNG.png', dpi=fig.dpi)
plt.show()
(tacaswell edited for code markup)
Activity
tacaswell commentedon Mar 20, 2014
That is bad. This is maybe related to #2889 ?
Can confirm this on master.
If you set the dpi to 100 it more-or-less works correctly, you can see the corners of the red sequare outside of the edges of the annotation box, but it is in the right place.
pelson commentedon Mar 20, 2014
I've not seen the picture, but it is worth noting that the issue there was highlighted by a change which was applied after v1.3.1. Though it could be the same underlying bug, none-the-less.
pelson commentedon Mar 20, 2014
P.S. A screenshot of the problem:
So the red box is in the wrong place for the plot.
pelson commentedon Mar 20, 2014
Interestingly, if you don't specify the correct DPI at savefig for PNG, the red box is also in the wrong place. Something is a little fishy there...
tacaswell commentedon Mar 20, 2014
@pelson The similarity is (I think) related to using
get_extentin one context which creates values based on current screen-space transforms. The values are then saved and get re-used with different screen-space transforms which causes things to be the wrong size/in the wrong place.pelson commentedon Mar 21, 2014
Agreed. The issue can be seen with all backends with a simple modification to @wronk 's code:
The key is that the red box only appears after the figure has been re-drawn. The change in x/y limit is significant here.
tacaswell commentedon May 9, 2014
@pelson @efiring @mdboom I am going to punt this and the related issues to 1.5 (at least).
My understanding of the problem here is that artists are being lined up in screen-space (through
get_window_extent) and then when things get updated underneath the artists the changes don't propagate. I think any fix for this will be a major overhaul.I will create an issue to add a note to
get_window_extentdocs warning that it can lead to this sort of thing.pelson commentedon May 9, 2014
@tacaswell - do you know if #3054 fixes this issue?
tacaswell commentedon May 9, 2014
I don't, but should probably check (I left this comment before I found that patch).
21 remaining items
tacaswell commentedon Apr 9, 2024
This "fixes" the problem
github-actions commentedon Apr 11, 2025
This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!
tacaswell commentedon Apr 13, 2025
Re-evaluating this, I think @leejjoon is completely correct. The source of the problem is that for zorder reasons we have to draw the background before the text, but the text does not know how big it is before has been drawn.
Short of implementing a system to automatically detect when we need to render multiple times there are two fixes:
BytesIOwith the exact same settings as you want to save the output as.layuot='constrained'orlayout='tight'which internally do a dummy render to get all the text sizes so that they can then update the layout.DOC: add warnings about get_window_extent and BboxImage
DOC: add warnings about get_window_extent and BboxImage
timhoffm commentedon Apr 13, 2025
Does this work? I believe not reliably just with a
savefig, because it only replaces the canvas temporarily.If I do:
the
Affine2D().scale(100.0)is the dpi scaling so I won't get mydpi=50into the window extent ?!?leejjoon commentedon Apr 14, 2025
Another approach would be to use a callable object that explicitly update the position of the text and the patch. This does not require rendering the figure twice
Yet another option is to use patheffects. The example code below uses
mpl_visual_contextmodule which I created.tacaswell commentedon Apr 14, 2025
My understanding is that in similar cases (passing
arg.get_window_extentas a callable referring to something later in the draw order, that artist may move/resize itself as part of the draw, and saving to a vector format) this will work reliably. Because vector backends always set the dpi to 72 (because we need all of the "screen" positions that come out to be in inches) so doingdraw_without_renderwithout doing the same won't work. The fist save will get the wrong position for the box, correct the position of the text which is preserved in some state (I'm not entirely sure which state) between draws so in the second draw the box is in the right place and the text recomputes to the same place.@leejjoon's suggestion will work 100% with only one save, but requires the user to know which methods to call to update the correct state.