Search code examples
matplotlibscale

How to use pyplot to show data with no scale on xaxis and allow to pan (shift) via mouse


So in the current matplotlib there is a pan button and zoom button and the plot show all data initially and scales it to fit in the visible window. To see the details, I have to zoom in and then pan.

What I want to do is to show the data in a detailed view as default and then I can pan it as needed.

For example, instead of this: https://th.bing.com/th/id/OIP.tMPfzJY6o0TY2Jupl-mPLAHaGA?rs=1&pid=ImgDetMain

I want this (by default):

enter image description here

Then I can pan it as needed.

I don't want to truncate the data because I need all data but just view them in parts. Any easier way out of this without manipulating the data as I pan it?


Solution

  • With Matplotlib you could use a Span Selector like in this example, to show your entire data on the top subplot, while showing the "zoomed" part of the data on the bottom subplot:

    import matplotlib.pyplot as plt
    import numpy as np
    import mplfinance as mpf
    import yfinance as yf
    import pandas as pd
    
    from matplotlib.widgets import SpanSelector
    
    # get market data
    df = yf.download("SPY", start="2024-02-04", end="2024-03-04", interval="1h")
    
    # x data range
    timestamps = list(df.index)
    short_timestamps = [str(t)[0:16] for t in timestamps]
    x = range(len(timestamps))
    
    fig, (ax1, ax2) = plt.subplots(2)
    
    # diagram to select a region from
    mpf.plot(df,
             ax=ax1,
             type="candle"
        )
    
    ax1.set_title('Press left mouse button and drag '
                  'to select a region in the top graph')
    
    # zoomed region
    mpf.plot(df,
             ax=ax2,
             type="candle"
        )
    
    # set initial range
    ax2.set_xlim(x[short_timestamps.index('2024-02-05 09:30')], x[short_timestamps.index('2024-02-06 09:30')])
    
    def onselect(xmin, xmax):
        indmin, indmax = np.searchsorted(x, (xmin, xmax))
        indmax = min(len(x) - 1, indmax)
        region_x = x[indmin:indmax]
    
        if len(region_x) >= 2:
            # plot the original diagram but with a different x range
            ax2.clear()
            mpf.plot(df,
                     ax=ax2,
                     type="candle"
                )
            ax2.set_xlim(region_x[0], region_x[-1])
            ax2.set_title('Zoomed')
            fig.canvas.draw_idle()
    
    span = SpanSelector(
        ax1,
        onselect,
        "horizontal",
        useblit=True,
        interactive=True,
        drag_from_anywhere=True
    )
    
    fig.tight_layout()
    plt.show()
    

    In this way you can select the desired range on the top subplot:

    enter image description here

    But Plotly allows you to do something similar much easier, like in these examples:

    import plotly.graph_objects as go
    import pandas as pd
    
    df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv')
    
    fig = go.Figure(data=[go.Candlestick(x=df['Date'],
                    open=df['AAPL.Open'],
                    high=df['AAPL.High'],
                    low=df['AAPL.Low'],
                    close=df['AAPL.Close'])])
    
    # limit the x range to a required interval
    fig.update_xaxes(range=['2016-01-01', '2016-02-01'])
    fig.show()
    

    In this case, you can see your entire data and select some part of it on the bottom subplot (called rangeslider), while seeing the selected part on the top subplot:

    enter image description here