6

I noticed that plotting different time scales causes the opacity of my overlaid bar chart to fade. How do I correct this? In the first image, I plotted over a range of 2 years and in the second I plotted a 1 year time range. Notice that the former has a significantly faded bar chart, I would expect that these two charts to be the same regardless of range.

Sidenote: I am "hacking" the chart to center on the primary axis, if anyone can help me figure out how to directly set the y-axis range of the secondary axis that would be very helpful as well.

import plotly.graph_objects as go
from plotly.subplots import make_subplots

filtered = df[(df['date'] > '2017-1-24') & (df['date'] <= '2018-1-24')]

fig = make_subplots(specs=[[{"secondary_y": True}]])

fig.add_trace(
    go.Bar(
        x=filtered['date'], 
        y=filtered['divergence'],
        opacity=0.5
    )
)

fig.add_trace(
    go.Scatter(
        x=filtered['date'], 
        y=filtered['price'],
        mode="lines"
    ),
    secondary_y=True
)

fig.update_layout(yaxis_range=[-9, 9])
fig.show()

Opacity lower than expected:

enter image description here

Opacity normal:

enter image description here

vestland
  • 55,229
  • 37
  • 187
  • 305
0xCourtney
  • 83
  • 1
  • 9

3 Answers3

13

Short answer:

This has nothing to do with opacity. For some more details take a look below at the complete answer. To obtain consisteny between a figures with many and few observations, you'll have to set the width of the bar line to zero, and set bargap to zero like in the next code snippet. Using a color like rgba(0,0,250,0) you can also select any opacity you'd like through the last digit.

fig.update_traces(marker_color = 'rgba(0,0,250, 0.5)',
                  marker_line_width = 0,
                  selector=dict(type="bar"))

fig.update_layout(bargap=0,
                  bargroupgap = 0,
                 )

Plot 1a - Few observations

enter image description here

Plot 1b - Many observations

enter image description here

The details:

This has nothing to do with opacity. You're asking plotly to build a bar-plot, and apparently barplots according to plotly must have a space between the bars. So for a few observations you'll get this:

enter image description here

And for many observations, as you have demonstrated, you'll get this:

enter image description here

The color of the bars has not changed, but it seems like it since plolty squeezes in a bit of space for many more observations.

I initially thought this would be amendable through:

fig.update_layout(bargap=0,
                  bargroupgap = 0,
                 )

But no:

enter image description here

In order to increase consistency between smaller and larger selectoins, you'll have to select the same color for the bar fill as for the line color of the bar, like blue.

fig.update_traces(marker_color='blue',
                  marker_line_color='blue',
                  selector=dict(type="bar"))

enter image description here

But there's still a little color difference between the bars if you zoom in:

enter image description here

And this becomes clearer for lighter colors:

enter image description here

But the best solution turned out to be setting marker_line_width = 0 like described at the beginning of the answer.

End result:

enter image description here

Complete code:

import numpy as np
import pandas as pd
import plotly.graph_objects as go
import plotly.express as px
import datetime
from plotly.subplots import make_subplots

pd.set_option('display.max_rows', None)

# data sample
nperiods = 50
np.random.seed(123)
df = pd.DataFrame(np.random.randint(-10, 12, size=(nperiods, 2)),
                  columns=['price', 'divergence'])
datelist = pd.date_range(datetime.datetime(2017, 1, 1).strftime('%Y-%m-%d'),periods=nperiods).tolist()
df['date'] = datelist 
df = df.set_index(['date'])
df.index = pd.to_datetime(df.index)
# df.iloc[0] =1000
# df = df.cumsum().reset_index()
df.reset_index(inplace=True)
df['price'] = df['price'].cumsum()
df['divergence'] = df['divergence'].cumsum()

filtered = df[(df['date'] > '2017-1-24') & (df['date'] <= '2018-1-24')]

fig = make_subplots(specs=[[{"secondary_y": True}]])

fig.add_trace(
    go.Bar(
        x=filtered['date'], 
        y=filtered['divergence'],
        #opacity=0.5
    )
)

fig.add_trace(
    go.Scatter(
        x=filtered['date'], 
        y=filtered['price'],
        mode="lines"
    ),
    secondary_y=True
)


fig.update_traces(marker_color = 'rgba(0,0,250, 0.5)',
                  marker_line_width = 0,
                  selector=dict(type="bar"))

fig.update_layout(bargap=0,
                  bargroupgap = 0,
                 )


fig.show()
vestland
  • 55,229
  • 37
  • 187
  • 305
1

It is not changing opacity, but it is trying to plot large number of bars in given plot area. try zooming in and see the difference. also try changing width of the plot with : fig.update_layout(width=2500)

to change secondary axis range use : fig.update_layout(yaxis2_range=[lower_range,upper_range])

dSH
  • 26
  • 3
0

I was struggleing with the same issue. However in my case i have a date range slider for a bar plot with different classes. Hence different colors of the bars per bar. I was assisted by chatgpt. I know pure chatgpt answers is not allowed. But i want to assist anyone struggleing with the same issue in the future. Also it is not pure chatpt code i am providing here, since it needed to be modified.

Inspired by the approved answer here @vestland. I set the marker_line per class. predefining a color pallett per class.

fig = go.Figure()

color_palette = {
    'WeightTraining': 'blue',
    'Run': 'green',
    'Swim': 'red',
    'Walk': 'orange',
    'Ride': 'purple',
    'RockClimbing': 'pink',
    'NordicSki': 'brown',
    'AlpineSki': 'gray',
    'Workout': 'cyan',
    'Hike': 'lime',
    'VirtualRide': 'magenta',
    'Crossfit': 'yellow',
    'Kayaking': 'teal'
}

for t in df["type"].unique():
    df2 = df[df["type"] == t]
    fig.add_trace(go.Bar(x=df2["date"], y=df2["area"], name=t, marker_color=color_palette[t], marker_line_color=color_palette[t]))


fig.update_layout(
    xaxis=dict(
        rangeselector=dict(
            buttons=list([
                dict(count=1,
                     label="1m",
                     step="month",
                     stepmode="backward"),
                dict(count=6,
                     label="6m",
                     step="month",
                     stepmode="backward"),
                dict(count=1,
                     label="YTD",
                     step="year",
                     stepmode="todate"),
                dict(count=1,
                     label="1y",
                     step="year",
                     stepmode="backward"),
                dict(step="all")
            ])
        ),
        rangeslider=dict(
            visible=True
        ),
        type="date"
    ),
    yaxis=dict(
       autorange = True,
       fixedrange= False
   )
)

fig.show()

The main takeaway here is the colorpallett dictionary and marker_color=color_palette[t], marker_line_color=color_palette[t] in the for loop through each class in dataframe df.

Hope this helps anyone with this issue in the future.

user31907
  • 43
  • 1
  • 3