Skip to content

External Axes Mode and SaveFig #301

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

Closed
ftnpang opened this issue Dec 25, 2020 · 13 comments
Closed

External Axes Mode and SaveFig #301

ftnpang opened this issue Dec 25, 2020 · 13 comments
Labels
question Further information is requested

Comments

@ftnpang
Copy link

ftnpang commented Dec 25, 2020

It seems that mpf.plot does not save plot to file when operating in external axes mode (by specifying "ax" argument). Is this a known issue or has been implemented. I am using '0.12.7a0' but no luck.

@ftnpang ftnpang added the question Further information is requested label Dec 25, 2020
@manuelwithbmw
Copy link

It seems that mpf.plot does not save plot to file when operating in external axes mode (by specifying "ax" argument). Is this a known issue or has been implemented. I am using '0.12.7a0' but no luck.

I have no issues with 0.12.7a0 and the below:

fig, axlist = mpf.plot(df, type='candle', ..., ylabel='Price', ylabel_lower='Volume', ... , style=s, ..., returnfig=True)
ax_RSI = axlist[4]
...
fig.savefig('/Users/...Plots/figure_1.png')
plt.show()

@DanielGoldfarb
Copy link
Collaborator

This is by design. When in external axes mode (by specifying "ax" argument) certain kwargs are ignored or disabled. Most of these will give a warning if used in external axes mode, but apparently some were missed when setting up the warnings.

The general rule is that if two conditions are met, then the kwarg will be ignored or disabled. The two conditions are: (1) The kwarg is just as easily implemented by calling a method on the external Figure or Axes, and (2) there is at least one case where, in external axes mode, it would be undesireable for the code to take the action that the kwarg requests.

In this specific case:

  1. The method .savefig() is easily called on the external figure (passing into fig.savefig() arguments that are exactly what you would have otherwise passed in to the kwarg savefig=), and
  2. There are situations, in external axes mode, where it may be undesireable for mpf.plot() to save the figure: For example, in external axes mode, mplfinance does not have direct access to the Figure object. It can only assume the user means the "current figure" which possibly may not be the case. Futhermore the caller may decide to call mpf.plot() more tha once for more than one Axes object before being ready to save the final Figure.

I hope that makes sense. I probably should document all of the kwargs that do not work in external axes mode, along with the reasons why for each kwarg.

Let me know if you have any more questions on this topic.
Thanks. --Daniel

@ftnpang
Copy link
Author

ftnpang commented Dec 25, 2020

Thank you both for the responses during holidays. I gave a quick shot to Manuel's but it seems not working on my side. I've updated to '0.12.7a4' and the scripts read:

file_specs = {'fname': r'.\figure_001.png',
              'dpi': 64,
              'bbox_inches': 'tight',
              'pad_inches': 0.0}

fig, axlist = mpf.plot(df_ohlc,
                       type = 'candle',
                       addplot = ap,
                       ax = ax1,
                       returnfig = True)
fig.savefig(savefig = file_specs)

Where "ap" uses a second axis for volume, and "ax1" is the mean axis. "returnfig" seems not working as expected and thus "fig" is actually None.

Did I miss anything here?

@DanielGoldfarb
Copy link
Collaborator

Not sure, but if I am remember correctly it should work this way:

fname = r'.\figure_001.png'
kwargs = {'dpi': 64,
          'bbox_inches': 'tight',
          'pad_inches': 0.0}

fig, axlist = mpf.plot(df_ohlc,
                       type = 'candle',
                       addplot = ap,
                       ax = ax1,
                       returnfig = True)

fig.savefig(fname,**kwargs)

hth

@manuelwithbmw
Copy link

manuelwithbmw commented Dec 25, 2020

file_specs = {'fname': r'.\figure_001.png',
'dpi': 64,
'bbox_inches': 'tight',
'pad_inches': 0.0}

fig, axlist = mpf.plot(df_ohlc,
type = 'candle',
addplot = ap,
ax = ax1,
returnfig = True)
fig.savefig(savefig = file_specs)

To make it simpler: try dropping all the info 'dpi, 'bbox_inches', 'pad_inches' and just pass the absolute path of your file's position into fig.savefig (and I am using forward slash / on my Mac: fig.savefig('/Users/...Plots/figure_1.png') and see if it works like this? Daniel will know more than me for sure...

@ftnpang
Copy link
Author

ftnpang commented Dec 25, 2020

Hmmm, does not seem to be working either. I guess the key issue here is mpf.plot does not return anything and thus fig is actually None:

TypeError: cannot unpack non-iterable NoneType object

For completeness, the scripts that creates the figure and axis are added below (not sure if this makes any difference):

mpf_style = mpf.make_mpf_style(base_mpf_style='charles', rc={})
fig = mpf.figure(figsize = (8,4), facecolor = 'black', style = mpf_style)
ax1 = fig.subplot()
fig, axlist = mpf.plot(df_ohlc, type = 'candle', addplot = ap, ax = ax1, returnfig = True)
fig.savefig(r'D:\Tmp\Figure_001.png')

The figure is successfully plotted in the console though. Puzzled...

@manuelwithbmw
Copy link

Ok, you on Windows as you using backslashes to separate directories in file path, correct?
Silly question, you obviously have Write access to D:\Tmp disk, right?
And in your console you plot it OK with facecolor = 'black', style = mpf_style?

I am not sure but you 'define' fig twice in your code:

  1. with mpf.figure where you put the 'styling' info and
  2. by the means of fig, axlist = mpf.plot ( ... return fig = True)
    maybe this is confusing the library?

I have only 2. in my code and my 'style' s, -defined beforehand- is applied into it (not in mpf.figure) like below:

fig, axlist = mpf.plot(df, type='candle', axtitle=(ticker.ticker), ylabel='Price', ylabel_lower='Volume',
...
mav=(21, 50, 200), volume=True, figscale=0.8, figsize=(12.5,6.5), addplot=apd,
...
scale_width_adjustment=dict(candle=1.25), style=s,
returnfig=True)

Finally I do call fig.savefig('/Users/...Plots/figure_1.png')
[forward slashes as I am on Mac]

@ftnpang
Copy link
Author

ftnpang commented Dec 26, 2020

Yes, I am on Windows and have access to the drive. The issue, I think, is that "mpf.plot" does not return a figure handle when operating in external axis mode. Your scrips are working fine as they are operating on the "panel" mode. I tried that mode originally but realized it's difficult to precisely control the figure size when using "tight_layout" and thus switched to the "external axis mode". I am not sure if this is an intrinsic issue with the "external axis mode" as Daniel indicated and, if so, there is a solution.

@DanielGoldfarb
Copy link
Collaborator

DanielGoldfarb commented Dec 26, 2020

A few things:


First
I appologize that I said

The method .savefig() is easily called on the external figure (passing into fig.savefig() arguments that are exactly what you would have otherwise passed in to the kwarg savefig=),

... exactly is not truly correct, as I corrected myself in my comment here where I indicated that with the savefig kwarg you specify fname in the dict, but when calling matplotlib's .savefig() method the first argument is the filename and the rest of the arguments are whatever else you put into the dict. Hope that makes sense. Let me know if not.


Second
It sounds like you are trying to mix external axes mode with returnfig=True.
These are two opposite and incompatible concepts.

  • The whole point of returnfig=True is that you are want mpf.plot() to create and own the Figure and Axes objects, but you want it to return them to you so that you can do some additional manipulation with them afterwards.
  • The whole point of external axes mode is that you create the Figure and Axes objects outside of mpf.plot() and pass them in. It makes no sense for mpf.plot() to then return what you have created and already have in variables outside of mpf.plot().

Do one or the other, but not both.


Third
As a general rule I recommend that you avoid external axes mode, unless what you are trying to accomplish truly cannot be done any other way. I also recommend generally, whenever possible, avoid returnfig=True, but this is more commonly needed for some things that mplfinance does not yet support.

That said, if all you are trying to accomplish is some specific sizing, and saving the figure, both of these things can be accomplished entirely with various kwargs to mpf.plot() and there should be no reason for you to have to do any direct manipulation of the Figure or Axes objects.

  • Sizing can be controled with kwargs figscale and figratio or alternatively figsize as described here.
  • Saving the figure, as you know, is done with kwarg savefig as described here.
  • The spacing around the plot can be controlled with (as you mentioned) tight_layout, or alternatively by using the undocumented kwarg scale_padding as described here.

Please try these things first (without savefig and without external axes), and/or describe exactly what you want to accomplish, what you want the end-result plot to look like, and I will do my best to provide you with a solution that will keep your code as simple as possible.

Wishing you all the best, and thank you for your interest in mplfinance.

--Daniel

@ftnpang
Copy link
Author

ftnpang commented Dec 27, 2020

Per your suggestions above, @DanielGoldfarb, I played around with these features which I believe is a good way to learn. Now, I can get almost all features I need by using mpf.plot() without "external axes mode". As you suggested, scale_padding is a very useful feature in controlling the figure padding and figsize at the same time. The only thing that seems still missing is how to reduce the additional blank space as shown below.

  1. Setting scale_padding = 0.0, there is still some blank area around the chart:
    image

  2. Setting scale_padding = -0.2 can further reduce the blank but this may not be an universally reliable and safe method as it may clip the data for certain dates.
    image

Is there a more reliable and safer way to reduce the padding without clipping?

Btw, there are a few coloring abnormalities which are highlighted in yellow circle in the second chart.

@DanielGoldfarb
Copy link
Collaborator

DanielGoldfarb commented Dec 28, 2020

Regarding the extra padding, there are a few things you can try:

  1. Use tight_layout in combination with scale_padding. You may, however, have to adjust your scale_padding setting since tight_layout itself does some scaling of the padding.
  2. It appears to me from your images that the extra padding is on the sides, therefore perhaps use xlim=(lowx,highx) to set limits on the x-values.
  3. Instead of a single scale_padding value, use a dict: scale_padding=dict(left=...,right=...,top=...,bottom=...). For example, if scale_padding=-0.2 was working for you for the sides, but was clipping the top and bottom, try scale_padding=dict(left=-0.2,right=-0.2,top=0.0,bottom=0.0) ... maybe that will work.
  4. Looking at you images, it appears also that perhaps you are trying to accomplish what axisoff=True does? For more information see complete discussion under Issue #48

  • Regarding the coloring abnormalities, I would need your data and code, to be able to reproduce it on my system if you want me to look into it. Happy to do so if you can provide a reproducible example.

@ftnpang
Copy link
Author

ftnpang commented Dec 28, 2020

Yes, I used axisoff=True in mpf.plot() for my plots, and they've been working as expected. It seems to me tight_layout is not a good choice for my application as it may clip data for certain dates. I will go with customized scale_padding for now as you suggested, which has been working fine.

@DanielGoldfarb Thanks a lot for your help and appreciate your spending time during holidays! Wish you a happy New Year!

P.S. Strangely, I couldn't reproduce the coloring abnormalities today with the same scripts. I will definitely revert if it happens again in the future.

@ftnpang ftnpang closed this as completed Dec 28, 2020
@DanielGoldfarb
Copy link
Collaborator

@ftnpang Thanks. Happy New Year to you and yours! All the best. --Daniel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants