-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
[Bug]: coredump when combining xkcd, FigureCanvasTkAgg and FuncAnimation #24908
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
One minor change: import matplotlib.figure as mfigure
fig = mfigure.Figure() that is unrelated but is a bit more reliable. Additionally, in your animation your are creating a new bar each time (rather than updating the same bar) so the first frame we draw 1 bar, then 2, then 3 ... which is not helping. I think the core of the problem here is related to the units.... attn @ksunden |
Does running |
You're right, sorry. I messed this up when reducing my code to a minimal (non-)working example.
Perhaps. Meanwhile, I figured out the following, narrowing down the issue some more: from datetime import datetime
import matplotlib
import matplotlib.figure
matplotlib.use('svg')
import matplotlib.pyplot as plt
fig = matplotlib.figure.Figure()
axes = fig.add_subplot()
now = datetime.now()
day_start = now.replace(hour=8, minute=45, second=0)
day_end = now.replace(hour=15, minute=10, second=0)
axes.set_title(now.strftime("%A, %-d %B %Y"), fontsize=70)
axes.set_xlim(day_start, day_end)
axes.barh(y=[1], width=now)
plt.savefig('/tmp/plot.svg') # this works just fine, output is about 15K
plt.xkcd()
axes.barh(y=[1], width=now)
fig.savefig('/tmp/plot.svg') # this doesn't crash now, but outputs a 1.5G SVG file! Without the |
Aha, looking at the above plot what might be happening is the |
So this doesn't happen if the width is a |
I'm not sure this is true? I think Moreover, trying to specify a Anyway, this is besides the point here, I guess. |
ah that could be a pointer in the right direction. A workaround I'm using now is the following, which works well: from datetime import datetime, timedelta
import matplotlib
import matplotlib.figure
matplotlib.use('svg')
import matplotlib.pyplot as plt
fig = matplotlib.figure.Figure()
axes = fig.add_subplot()
now = datetime.now()
day_start = now.replace(hour=8, minute=45, second=0)
day_end = now.replace(hour=15, minute=10, second=0)
axes.set_title(now.strftime("%A, %-d %B %Y"), fontsize=70)
axes.set_xlim(day_start, day_end)
axes.set_ylim(0.5, 1.5)
axes.barh(y=[1], width=now)
plt.savefig('/tmp/plot.svg') # this works just fine, output is about 15K
plt.xkcd()
# axes.barh(y=[1], width=now)
# fig.savefig('/tmp/plot-xkcd.svg') # this outputs a 2.9G SVG file!
axes.broken_barh([(day_start, now - day_start)], (0.6, 0.8))
fig.savefig('/tmp/plot-xkcd-brokenbarh.svg') # back to a sensible 65K SVG Somehow XKCD style applied to a (note that here we indeed speak of an actual starting point ( Not sure this helps? |
Ah my bad, I got confused about what So I think this issue boils down to the svg backend not clipping paths outside the bounds of the axes, which is tracked in #9488. As such I'll close this issue - feel free to comment if I've got anything wrong there, and we can re-open (and thanks again for reporting!) |
I actually think it's a bit more complex. Look at the impact of from datetime import datetime
from time import perf_counter
import matplotlib
import matplotlib.figure
matplotlib.use("agg")
import matplotlib.pyplot as plt
tic = perf_counter()
fig = matplotlib.figure.Figure()
axes = fig.add_subplot()
now = datetime.now()
day_start = now.replace(hour=8, minute=45, second=0)
day_end = now.replace(hour=15, minute=10, second=0)
axes.set_title(now.strftime("%A, %-d %B %Y"), fontsize=70)
axes.set_ylim(0.5, 1.5)
plt.xkcd()
axes.barh(y=[1], width=now)
fig.savefig("/tmp/plot-noxlim.png") # this completes after about 0.05 seconds
print(perf_counter() - tic)
axes.set_xlim(day_start, day_end)
axes.barh(y=[1], width=now)
fig.savefig("/tmp/plot-xlim.png") # this takes over 59 seconds
print(perf_counter() - tic) Running it without the So, I'm now thinking that
|
Still confused as to whether this has anything to do with datetime or not. If you concoct an example without datetime, does the same thing happen? |
I still think that @dstansby 's analysis is correct. In the case of non-xkcd we are drawing a rectangle with no fancy effects so the path is very simple (only 5 vertexes a moveto, 3 lineto, and a close) which is fine in all cases because the cost is independent of the view limits. In the case of SVG 4 points (even if way out of range) is still just 4+1 points and in the Agg cases we only have to do 4 path/clip box intersection computations. In the case of XKCD style which puts a screen-space oscillation on the path takes our very simple path and makes it more complex proportional to the size of the length of the path in screen space. If the whole path fits on the screen this is fine, but if you make the view limit a tiny fraction of the total (like 1/18_000 kth) then we have a lot of ripples. This either results in a huge number of points which eventually exhausts the svg output or a very large number of (non-intersecting) intersections to compute in the AGG case. |
Here's a minimal example: import matplotlib.pyplot as plt
plt.xkcd()
fig, ax = plt.subplots()
ax.bar(0, height=1e4)
ax.set_ylim(0, 1)
plt.show() This takes a long time (~seconds) to render the image on my laptop. Profiling to see where time is spent gives: 7.744 <module> test.py:1
├─ 7.278 savefig matplotlib/pyplot.py:993
│ └─ 7.278 Figure.savefig matplotlib/figure.py:3202
│ └─ 7.278 FigureCanvasMac.print_figure matplotlib/backend_bases.py:2237
│ └─ 7.278 <lambda> matplotlib/backend_bases.py:2228
│ └─ 7.278 FigureCanvasMac.print_png matplotlib/backends/backend_agg.py:462
│ └─ 7.278 FigureCanvasMac._print_pil matplotlib/backends/backend_agg.py:452
│ ├─ 6.921 FigureCanvasMac.draw matplotlib/backends/backend_agg.py:392
│ │ └─ 6.920 draw_wrapper matplotlib/artist.py:93
│ │ └─ 6.920 draw_wrapper matplotlib/artist.py:54
│ │ └─ 6.920 Figure.draw matplotlib/figure.py:3102
│ │ └─ 6.920 _draw_list_compositing_images matplotlib/image.py:113
│ │ └─ 6.920 draw_wrapper matplotlib/artist.py:54
│ │ └─ 6.920 Axes.draw matplotlib/axes/_base.py:3001
│ │ └─ 6.919 _draw_list_compositing_images matplotlib/image.py:113
│ │ └─ 6.919 draw_wrapper matplotlib/artist.py:54
│ │ └─ 6.881 Rectangle.draw matplotlib/patches.py:582
│ │ └─ 6.881 Rectangle._draw_paths_with_artist_properties matplotlib/patches.py:533
│ │ └─ 6.881 PathEffectRenderer.draw_path matplotlib/patheffects.py:99
│ │ └─ 6.881 withStroke.draw_path matplotlib/patheffects.py:171
│ │ ├─ 5.756 withStroke.draw_path matplotlib/patheffects.py:207
│ │ │ └─ 5.756 RendererAgg.draw_path matplotlib/backends/backend_agg.py:109
│ │ │ └─ 5.756 RendererAgg.draw_path None
... more lines below that I haven't included which confirms that time is spent in drawing the path. I'm not sure the best way to go here. Someone who knows more about path clipping might have a better idea if this is 'just life', or if there's any space for optimisations here? |
About half of the time seems to be spent on these two lines: matplotlib/src/path_converters.h Lines 1041 to 1042 in 87c9b47
So, yes, it looks like reducing the amount of drawing is really the best way around it. |
I don't immediately see why clipping would not work with the wiggly path (perhaps clip to a slightly larger box so that if a line is "just beyond" the clip its wiggles can still come within the original clipbox; I think the size of the wiggle is bounded anyways). |
I suspect the issue is that we wiggle then clip, not clip then wiggle. So while clipping "works" we spend a lot of time agreeing that a wiggle very far away is indeed very far away. |
If I read template <class PathIterator>
inline void
RendererAgg::draw_path(GCAgg &gc, PathIterator &path, agg::trans_affine &trans, agg::rgba &color)
{
typedef agg::conv_transform<py::PathIterator> transformed_path_t;
typedef PathNanRemover<transformed_path_t> nan_removed_t;
typedef PathClipper<nan_removed_t> clipped_t;
typedef PathSnapper<clipped_t> snapped_t;
typedef PathSimplifier<snapped_t> simplify_t;
typedef agg::conv_curve<simplify_t> curve_t;
typedef Sketch<curve_t> sketch_t;
...
transformed_path_t tpath(path, trans);
nan_removed_t nan_removed(tpath, true, path.has_codes());
clipped_t clipped(nan_removed, clip, width, height);
snapped_t snapped(clipped, gc.snap_mode, path.total_vertices(), snapping_linewidth);
simplify_t simplified(snapped, simplify, path.simplify_threshold());
curve_t curve(simplified);
sketch_t sketch(curve, gc.sketch.scale, gc.sketch.length, gc.sketch.randomness);
_draw_path(sketch, has_clippath, face, gc);
} correctly, we clip first and wiggle (sketch) last. |
hmm, I agree with that reading. Does clipping not do what we think it does? There are some cut-outs is clipping, I think for closed paths(?), that disable it when we can not be sure we can clip it correctly..... |
I modified the pow-computation a bit in #24964, about halving the time spent on that line (from 33% to 16% of the total time). Still, this is a linear decrease so although beneficial will not fix the actual issue. Edit: will be faster on x86, should not be slower on other architectures. Read the comments to see why. |
Uh oh!
There was an error while loading. Please reload this page.
Bug summary
When setting up a simple animation on a TkAgg canvas, everything works as expected, until the XKCD-style is applied.
Code for reproduction
Actual outcome
coredump
Expected outcome
the animation, but in XKCD style
Additional information
the init_func works just fine, the crash happens at the
barh
statementOperating system
Arch
Matplotlib Version
3.6.2
Matplotlib Backend
TkAgg
Python version
Python 3.9.2
Jupyter version
No response
Installation
pip
The text was updated successfully, but these errors were encountered: