Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Is it possible to display multiple plots in a row? #114

Closed
Vassiliev99 opened this issue Apr 30, 2020 · 12 comments
Closed

Is it possible to display multiple plots in a row? #114

Vassiliev99 opened this issue Apr 30, 2020 · 12 comments
Labels
enhancement New feature or request question Further information is requested released code merged into repo AND released to Pypi

Comments

@Vassiliev99
Copy link

Hi! As it was mentioned in #53 it is somehow possible to display multiple plots with 'returnfig' kwarg. I want to display 3 plots in a row or even 3x3. While I am not really familiar with matplotlib can someone explain please how to do it with fig and ax returned from mpf.plot()? Or should I do it another way?

@Vassiliev99 Vassiliev99 added the question Further information is requested label Apr 30, 2020
@DanielGoldfarb
Copy link
Collaborator

returnfig does not give you the ability to control how many subplots you have, nor what their geometry is. You will get back a Figure with either 1 or 2 subplots stacked vertically (with a primary and secondar Axes each). You can do what you want with them at that point, but the number of Axes and their geometry is fixed.

I just today began work on #17 which anticipate will take somewhere between 3 and 5 weeks to complete. You can see a rough outline for my implementation here.

  • The "Simple Method" will allow you to have 3 plots, one on top of the other, in whatever sizes you want, with whatever data you want. It should be as simple to call as the existing mpf.plot() and mpf.make_addplot().
  • The "Matplotlib Method" will allow you to have as many subplots as you desire, in whatever geometry you desire (constrained only by matplotlib itself).

So if you can wait a few weeks you should be in good shape. Alternatively you can spend some time learning more about matplotlib, and use the old api for which there are examples here and documentation here.

@Vassiliev99
Copy link
Author

Thank you for your reply! But what if I will call mpf.plot() 3 times with different data and will get 3 fig objects and 3 axlist objects. Can I than display it in a row? Or it is not how matplotlib works by default and "matplotlib method" will do it?

@DanielGoldfarb
Copy link
Collaborator

But what if I will call mpf.plot() 3 times with different data and will get 3 fig objects and 3 axlist objects.

I honestly don't know (you can try it). I suspect that, given the current mplfinance code, when mpf.plot() attempts to create a new Figure and Axes, it will get back the same Figure and Axes from matplotlib, which, as I understand it, does some kind of "caching" of the Figure. I think this is how matplotlib works, except when the call to figure() specifically includes an "identifier" with which to label the Figure, in which case each identifer will either create a new Figure or get a chached one depending on whether that identifier was used already. And it is definitely the case that mplfinance presently is not specifying an identifier when getting the Figure.

None-the-less, I'm not 100% sure about this matplotlib behavior, so feel free to give it a try and let me know how it works.

If not, #17 will should definitely fulfill your needs.

@char101
Copy link

char101 commented May 4, 2020

To add a new row we can shift the existing axes and then add our own axes

fig, axlist = mpf.plot(..., returnfig=True)

ax1 = axlist[0]

height = 0.2
pos = ax1.get_position()
ax1.set_position([pos.xmin, pos.ymin + height, pos.width, pos.height - height])

ax = fig.add_axes([pos.xmin, pos.ymin, pos.width, height], sharex=ax1)
ax.tick_params(bottom=False, labelbottom=False)
ax.plot(...)

Figure_1

@DanielGoldfarb
Copy link
Collaborator

@char101 Charles - That's awsome! Very creative! Thank you for sharing that! --Daniel

@char101
Copy link

char101 commented May 5, 2020

I have wrapped above code into a function and also added code to apply style to the new axes (since mplfinance.plot resets the style after it returns). Maybe this can be used until https://github.com/matplotlib/mplfinance/wiki/SubPlots is implemented.

@mpf.plotting.with_rc_context
def add_axes(fig, axlist, n=1, height=0.2, style=None, position='top'):
    """Adds one or more axes
    params:
        - n: number of axes to add
        - height: total height of the lower axes including the volume axes
        - style: the same value passed to mplfinance plot function
        - position: 'top' or 'bottom' of the volume axes if there is a volume axes
    returns:
        - ax if n == 1
        - [ax1, ax2, ...] if n > 1
    """
    from mplfinance import _styles

    # apply style
    if isinstance(style, str):
        style = _styles._get_mpfstyle(style)
    if isinstance(style, dict):
        _styles._apply_mpfstyle(style)

    has_volume = len(axlist) > 2

    # shift chart axes
    ax1 = axlist[0]
    pos1 = ax1.get_position()
    if not has_volume:
        ax1.set_position([pos1.xmin, pos1.ymin + height, pos1.width, pos1.height - height])
    else:
        ax2 = axlist[2]
        pos2 = ax2.get_position()
        if pos2.height != height:
            d = height - pos2.height
            ax1.set_position([pos1.xmin, pos1.ymin + d, pos1.width, pos1.height - d])

    # shift volume axes
    pos1 = ax1.get_position()
    if has_volume:
        ax_height = height / (n + 1)
        if position == 'top':
            ax_y = pos1.ymin
            ax2.set_position([pos2.xmin, pos2.ymin, pos2.width, ax_height])
        elif position == 'bottom':
            ax_y = pos1.ymin - ax_height
            ax2.set_position([pos2.xmin, ax_y, pos2.width, ax_height])
    else:
        ax_height = height / n
        ax_y = pos1.ymin

    # add new axes
    axs = []
    for _ in range(n):
        ax_y -= ax_height
        ax = fig.add_axes([pos1.xmin, ax_y, pos1.width, ax_height])
        ax.set_xlim(*ax1.get_xlim())
        ax.tick_params(bottom=False, labelbottom=False)
        axs.append(ax)

    return axs[0] if n == 1 else axs

Example: adding 2 axes on top of volume axes

fig, axlist = mpf.plot(...style=style, returnfig=True)
ax1, ax2 = add_axes(fig, axlist, height=0.3, n=2, style=style)
ax1.plot(...)
ax2.plot(...)

2020-05-05 12_19_43-Figure 1

Edit 1: added ax.set_xlim(*ax1.get_xlim()) so that the xticks are aligned

@twopirllc
Copy link

@DanielGoldfarb,

Excellent work with mplfinance! A serious contender among the various plotting modules/libraries out there trying to support financial charting. I had this same issue as I require multiple subplots both above and below the main price chart. For example:

sample

@char101
Nice work adding additional subplots with the function. Definitely a time saver! Is it possible to plot above the price chart as well?

Thanks,
KJ

@DanielGoldfarb
Copy link
Collaborator

@twopirllc
Kevin,
There is an enhancement currently in my fork that allows up to three charts (called "panels") and you can control the order (put additional panels above or below the main panel), and you can control the relative size of each panel with the panel_ratio kwarg. From your image however it looks like you've got six charts there. I will look into whether I can increase the number of panels without overly complicating the code. @char101 Charle's function shows some awesome creativity as workaround until this enhancement is complete. If you get a chance to test with the code in my fork, let me know. Otherwise I am hoping to release it by the end of this week.

All the best. --Daniel

@DanielGoldfarb
Copy link
Collaborator

@twopirllc ... actually you've given me an idea how I can might generalize the code in my fork to have N panels stacked up, and to specify which one is the main panel (so it can have charts both above and below it). Will play with this today and see if I can get it working without to much complexity.

@twopirllc
Copy link

@DanielGoldfarb,

I am in no rush. Quality takes time. That chart has 7 panels including price. Hopefully heights of each panel are adjustable.

Thanks for your time and expertise,
KJ

@char101
Copy link

char101 commented May 18, 2020

Currently the layout and the plot type is rather tightly coupled. It would be nice to have an abstraction between the layout and the plot type, probably in OOP style.

fig = mpf.add_figure(ohlc, style, ...)
ax = fig.add_axes(...)
ax.add_plot(mpf.plot.candle(...), ...)
ax.add_plot(mpf.plot.mav(...), ...)
ax.add_plot(mpf.plot.mav(...), ...)
ax2 = fig.add_axes(...).add_plot(mpf.plot.volume(...), ...)

def my_custom_plot(ax, data):
    pass
ax3 = fig.add_axes(...).add_plot(my_custom_plot(...), ...)

This isn't practical in something like jupyter notebook but is useful when using it from python program so that we can easily mix and match multiple plot types and to customize the layout.

For quick typing we can use the chain style

mpf.add_figure(...).add_axes(plot=candle(...)).add_axes(plot=volume(...))

@DanielGoldfarb
Copy link
Collaborator

External Axes Mode is now available.

@DanielGoldfarb DanielGoldfarb added enhancement New feature or request released code merged into repo AND released to Pypi labels Aug 9, 2020
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
enhancement New feature or request question Further information is requested released code merged into repo AND released to Pypi
Projects
None yet
Development

No branches or pull requests

4 participants