0

I'm working on a graph that ilustrates computer usage each day. I want to have a button that will group dates monthly for last year and set y as AVERAGE (mean) and draw avg line.

My code:

import datetime
import numpy as np
import pandas as pd
import plotly.graph_objects as go

example_data = {"date": ["29/07/2022", "30/07/2022", "31/07/2022", "01/08/2022", "02/08/2022"], 
                "time_spent" : [15840, 21720, 40020, 1200, 4200]}
df = pd.DataFrame(example_data)

df["date"] = pd.to_datetime(df["date"], dayfirst=True)
df['Time spent'] = df['time_spent'].apply(lambda x:str(datetime.timedelta(seconds=x)))
df['Time spent'] = pd.to_datetime(df['Time spent'])

df = df.drop("time_spent", axis=1)

dfall = df.resample("M", on="date").mean().copy()
dfyearly = dfall.tail(12).copy()
dfweekly = df.tail(7).copy()
dfmonthly = df.tail(30).copy()

del df

dfs = {'Week':dfweekly, 'Month': dfmonthly, 'Year' : dfyearly, "All" : dfall}

for dframe in list(dfs.values()):
    dframe['StfTime'] = dframe['Time spent'].apply(lambda x: x.strftime("%H:%M"))

frames = len(dfs) # number of dataframes organized in  dict
columns = len(dfs['Week'].columns) - 1 # number of columns i df, minus 1 for Date
scenarios = [list(s) for s in [e==1 for e in np.eye(frames)]]
visibility = [list(np.repeat(e, columns)) for e in scenarios]
lowest_value = datetime.datetime.combine(datetime.date.today(), datetime.datetime.min.time())
highest_value = dfweekly["Time spent"].max().ceil("H")
buttons = []
fig = go.Figure()

for i, (period, df) in enumerate(dfs.items()):
    print(i)
    for column in df.columns[1:]:
        fig.add_bar(
            name = column,
            x = df['date'],
            y = df[column], 
            customdata=df[['StfTime']], 
            text=df['StfTime'],
            visible=True if period=='Week' else False # 'Week' values are shown from the start
                       )
        
        #Change display data to more friendly format
        fig.update_traces(textfont=dict(size=20), hovertemplate='<b>Time ON</b>: %{customdata[0]}</br>')
        
        #Change range for better scalling
        this_value =df["Time spent"].max().ceil("H")
        if highest_value <= this_value:
            highest_value = this_value
            fig.update_yaxes(range=[lowest_value, highest_value])

        #Add average value indicator
        average_value = df["Time spent"].mean()
        
        fig.add_hline(y=average_value, line_width=3, line_dash="dash", 
                    line_color="green")
                
    # one button per dataframe to trigger the visibility
    # of all columns / traces for each dataframe
    button =  dict(label=period,
                   method = 'restyle',
                   args = ['visible',visibility[i]])
    buttons.append(button)

fig.update_yaxes(dtick=60*60*1000, tickformat='%H:%M')
fig.update_xaxes(type='date', dtick='D1')

fig.update_layout(updatemenus=[dict(type="dropdown",
                                    direction="down",
                                    buttons = buttons)])
fig.show()

EDIT 1.
Thanks to vestland I managed to get semi-working dropdown. The problem is that the line added with add_hline affect all bar charts. I want it to display only on the chart that it had been added for. Also after passing in custom data for nicer display, the space between bars is doubled. Any way to fix above issues?

After edit chart

batreska
  • 53
  • 5
  • You would need a custom button I suppose: https://plotly.com/python/custom-buttons/ – viggnah Aug 04 '22 at 06:15
  • I cant seem to find a way to implement it with custom buttons. – batreska Aug 04 '22 at 08:05
  • @batreska I think [this](https://stackoverflow.com/questions/66236257/plotly-how-to-create-monthly-and-annual-average-dropdown-options) should be exactly what you're looking for. Let me know if it is *not*, and I'll take a look at it. – vestland Aug 05 '22 at 07:03
  • I eddited. You mind taking a look, please? @vestland – batreska Aug 05 '22 at 11:08
  • @batreska When looking at all your shapes created by `add_hline`, there are `8` in total. Is that in fact what you're aiming to do here? And not *one* shape per category `week`, `month` etc? – vestland Aug 07 '22 at 22:10
  • @batreska After a bit of testing, I've found that what you're aiming to do seems very much possible. But tricky, since you're going to have to use `method = 'update'` and then reference the visibilty for your traces *and* shapes, since the shapes are a part of the layout structure of the `go.Fig` object. So I would rather consider adding your averages as their own `traces`, for example in `dframe['avg'] = dfs['Week']['Time spent'].mean()`. – vestland Aug 08 '22 at 08:20

0 Answers0