Search code examples
plotlyplotly-pythoncandlestick-chart

Plotly vertical range is not automatically adjusted


The code below makes candlestick plots with a range slider. If I make the slider narrow, I want to zoom on the vertical scale. How is this done? I expect some kind of setting for it, but I can not find it. At the moment the result can look like the screenshot; obviously not optimal. A large part of the vertical scale remains unused. How to fix that?

enter image description here


import sys
import pandas as pd
import plotly.graph_objects as go
from datetime import datetime

from Downloader import CDownloader
# import matplotlib.dates as mdates # Styling dates

class CGraphs:
    def Candlestick(self, aSymbolName:str):
        #  Warning, this function reads from disk, so it is slow.
        print(sys._getframe().f_code.co_name, ": Started. aSymbolName ", aSymbolName)
        
        downloader : CDownloader = CDownloader()
        
        df_ohlc : pd.DataFrame = downloader.GetHistoricalData(aSymbolName)
        print("df_ohlc", df_ohlc)
        
        graph_candlestick = go.Figure()
        
        candle = go.Candlestick(x     = df_ohlc['Date'],
                                open  = df_ohlc['Open'],
                                high  = df_ohlc['High'],
                                low   = df_ohlc['Low'],
                                close = df_ohlc['Close'],
                                name  = "Candlestick " + aSymbolName)
        
        graph_candlestick.add_trace(candle)
        graph_candlestick.update_xaxes(title="Date", rangeslider_visible=True)
        graph_candlestick.update_yaxes(title="Price", autorange=True)     
        
        graph_candlestick.update_layout(
                title               = aSymbolName,
                height              = 600,
                width               = 900, 
                showlegend          = True)
        
        graph_candlestick.update_layout(xaxis_rangebreaks = [ dict(bounds=["sat", "mon"]) ])
        
        graph_candlestick.show()        
        print(sys._getframe().f_code.co_name, ": Finished. aSymbolName ", aSymbolName)
         
graphs:CGraphs = CGraphs()

graphs.Candlestick("MSFT")


Solution

  • This feature isn't available in plotly-python, and it's currently an open issue for the Plotly team.

    I think you could build this functionality out in plotly-dash since this library supports callbacks. For example, using a server-side implementation (with a lot of help from @kkollsg's answers on this forum):

    import dash
    from dash import Output, Input, State, dcc, html
    import plotly.graph_objs as go
    import numpy as np
    import pandas as pd
    import datetime
    
    class CGraphs:
        def makeCandlestick(self, aSymbolName:str):
            #  Warning, this function reads from disk, so it is slow.
            # print(sys._getframe().f_code.co_name, ": Started. aSymbolName ", aSymbolName)
            
            # downloader : CDownloader = CDownloader()
            
            # df_ohlc : pd.DataFrame = downloader.GetHistoricalData(aSymbolName)
            ## load some similar stock data
    
            df_ohlc = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv')
            df_ohlc.rename(columns=dict(zip(['AAPL.Open', 'AAPL.High', 'AAPL.Low', 'AAPL.Close'],['Open','High','Low','Close'])), inplace=True)
    
            # print("loading data")
            # print("df_ohlc", df_ohlc)
            
            graph_candlestick = go.Figure()
            
            candle = go.Candlestick(x     = df_ohlc['Date'],
                                    open  = df_ohlc['Open'],
                                    high  = df_ohlc['High'],
                                    low   = df_ohlc['Low'],
                                    close = df_ohlc['Close'],
                                    name  = "Candlestick " + aSymbolName)
            
            graph_candlestick.add_trace(candle)
            graph_candlestick.update_xaxes(title="Date", rangeslider_visible=True)
            graph_candlestick.update_yaxes(title="Price", autorange=True)
            
            graph_candlestick.update_layout(
                    title               = aSymbolName,
                    height              = 600,
                    width               = 900, 
                    showlegend          = True)
            
            graph_candlestick.update_layout(xaxis_rangebreaks = [ dict(bounds=["sat", "mon"]) ])
        
            app = dash.Dash()
    
            app.layout = html.Div(
                html.Div([
                    dcc.Graph(id='graph_candlestick',figure=graph_candlestick)
                ])
            )
    
            #Server side implementation (slow)
            @app.callback(
            Output('graph_candlestick','figure'),
            [Input('graph_candlestick','relayoutData')],[State('graph_candlestick', 'figure')]
            )
            def update_result(relOut,Fig):
                
                if relOut == None:
                    return Fig
                
                ## if you don't use the rangeslider to adjust the plot, then relOut.keys() won't include the key xaxis.range
                elif "xaxis.range" not in relOut.keys():
                    newLayout = go.Layout(
                        title=aSymbolName,
                        height=600,
                        width=800,
                        showlegend=True,
                        yaxis=dict(autorange=True),
                        template="plotly"
                    )
                    
                    Fig['layout']=newLayout
                    return Fig
    
                else:
                    ymin = df_ohlc.loc[df_ohlc['Date'].between(relOut['xaxis.range'][0], relOut['xaxis.range'][1]),'Low'].min()
                    ymax = df_ohlc.loc[df_ohlc['Date'].between(relOut['xaxis.range'][0], relOut['xaxis.range'][1]),'High'].max()
    
                    newLayout = go.Layout(
                        title=aSymbolName,
                        height=600,
                        width=800,
                        showlegend=True,
                        xaxis=dict(
                            rangeslider_visible=True,
                            range=relOut['xaxis.range']
                        ),
                        yaxis=dict(range=[ymin,ymax]),
                        template="plotly"
                    )
                    
                    Fig['layout']=newLayout
                    return Fig
    
            app.run_server(debug=True)
             
    graphs:CGraphs = CGraphs()
    graphs.makeCandlestick("MSFT")
    

    enter image description here