Search code examples
python3dplotlyinteractive

3d animated line plot with plotly in python


I saw this 3d plot. it was animated and added a new value every day. i have not found an example to recreate it with plotly in python.

enter image description here

the plot should start with the value from the first row (100). The start value should remain (no rolling values). The plot should be animated in such a way that each row value is added one after the other and the x-axis expands. the following data frame contains the values (df_stocks) and Dates to plot. assigning the colors would be a great addition. the more positive the deeper the green, the more negative the darker red.

import yfinance as yf
import pandas as pd

stocks = ["AAPL", "MSFT"]

df_stocks = pd.DataFrame()
for stock in stocks:
    df = yf.download(stock, start="2022-01-01", end="2022-07-01", group_by='ticker')
    df['perct'] = df['Close'].pct_change()
    df_stocks[stock] = df['perct']

df_stocks.iloc[0] = 0
df_stocks += 1
df_stocks = df_stocks.cumprod()*100
df_stocks -= 100 

Solution

  • You can use a list of go.Frame objects as shown in this example. Since you want the line plot to continually extend outward, each frame needs to include data that's one row longer than the previous frame, so we can use a list comprehension like:

    frames = [go.Frame(data=
    
        ## ...extract info from df_stocks.iloc[:i]
    
    for i in range(len(df_stocks))]
    

    To add colors to your lines depending on their value, you can use binning and labels (as in this answer) to create new columns called AAPL_color and MSFT_color that contain the string of the css color (like 'darkorange' or 'green'). Then you can pass the information from these columns using the argument line=dict(color=...) in each go.Scatter3d object.

    import yfinance as yf
    import numpy as np
    import pandas as pd
    import plotly.express as px
    import plotly.graph_objects as go
    
    stocks = ["AAPL", "MSFT"]
    
    df_stocks = pd.DataFrame()
    for stock in stocks:
        df = yf.download(stock, start="2022-01-01", end="2022-07-01", group_by='ticker')
        df['perct'] = df['Close'].pct_change()
        df_stocks[stock] = df['perct']
    
    df_stocks.iloc[0] = 0
    df_stocks += 1
    df_stocks = df_stocks.cumprod()*100
    df_stocks -= 100 
    
    df_min = df_stocks[['AAPL','MSFT']].min().min() - 1
    df_max = df_stocks[['AAPL','MSFT']].max().max() + 1
    
    labels = ['firebrick','darkorange','peachpuff','palegoldenrod','palegreen','green']
    bins = np.linspace(df_min,df_max,len(labels)+1)
    df_stocks['AAPL_color'] = pd.cut(df_stocks['AAPL'], bins=bins, labels=labels).astype(str)
    df_stocks['MSFT_color'] = pd.cut(df_stocks['MSFT'], bins=bins, labels=labels).astype(str)
    
    frames = [go.Frame(
        data=[
            go.Scatter3d(
                y=df_stocks.iloc[:i].index, 
                z=df_stocks.iloc[:i].AAPL.values,
                x=['AAPL']*i,
                name='AAPL',
                mode='lines',
                line=dict(
                    color=df_stocks.iloc[:i].AAPL_color.values, width=3,
                )
            ), 
            go.Scatter3d(
                y=df_stocks.iloc[:i].index, 
                z=df_stocks.iloc[:i].MSFT.values,
                x=['MSFT']*i,
                name='MSFT',
                mode='lines',
                line=dict(
                    color=df_stocks.iloc[:i].MSFT_color.values, width=3,
                )
            )]
        ) 
    for i in range(len(df_stocks))]
    
    fig = go.Figure(
        data=list(frames[1]['data']),
        frames=frames,
        layout=go.Layout(
            # xaxis=dict(range=[0, 5], autorange=False),
            # yaxis=dict(range=[0, 5], autorange=False),
            # zaxis=dict(range=[0, 5], autorange=False),
            template='plotly_dark',
            legend = dict(bgcolor = 'grey'),
            updatemenus=[dict(
                type="buttons",
                font=dict(color='black'),
                buttons=[dict(label="Play",
                              method="animate",
                              args=[None])])]
        ),
    )
    
    fig.show()
    

    enter image description here