Python Dash graph legend covering x-axis labels - python

My Python web app has a Plotly Dash "Graph" whose legend covers the x-axis labels. I've tried adjusting the following elements, with no success and no visible changes at all:
legend style 'margin-top'
margin 'b'
padding 'b'
Here's the code:
import dash
import dash_core_components as dcc
import dash_html_components as html
graph = dcc.Graph(
figure = {
'data': data,
'layout': dict(
hovermode = "closest",
height = 400, # 500 is a bit too big on a smartphone
legend = dict(
font=dict(color='#7f7f7f'),
orientation="h", # Looks much better horizontal than vertical
style={'margin-top': 100},
),
font = {
'family': 'Segoe UI',
'color': "#7f7f7f"
},
# Added more margin on the left side to fix the cutoff True/False labels on the booleans
margin = dict(l=40, r=25, b=10, t=10),
padding = dict(l=0, r=0, b=10, t=0),
)
}
)
Here's what it looks like, showing the legend overlapping the x-axis labels:

I found the solution here in the documentation.
y
Parent: layout.legend
Type: number between or equal to -2 and 3
Sets the y position (in normalized coordinates) of the legend. Defaults to "1" for
vertical legends, defaults to "-0.1" for horizontal legends on graphs w/o range sliders and defaults to "1.1" for horizontal legends on graph with one or multiple range sliders.
It defaults to -0.1 so I set it to -0.15, which is a little bit lower, to give the x-axis labels some more room.
import dash
import dash_core_components as dcc
import dash_html_components as html
graph = dcc.Graph(
figure = {
'data': data,
'layout': dict(
hovermode = "closest",
height = 400, # 500 is a bit too big on a smartphone
legend = dict(
font=dict(color='#7f7f7f'),
orientation="h", # Looks much better horizontal than vertical
y=-0.15
),
)
}
)
Result:

Related

How to make Plotly Pie charts the same size always

I'm generating different Pie charts that have legends of different lengths. The problem is that when the legend is long, the Pie chart is smaller, I'd like to make the Pie chart always the same size.
This is my code:
pie_chart = go.Pie(labels=labels, values=y)
fig = go.Figure(data=[pie_chart])
fig.update_layout(legend={'font': {'size': 17}})
io_bytes = fig.to_image(format="png", scale=2.5, width=900, height=500)
These are the results:
Big pie chart, short legend:
Small pie chart, long legend:
Given that you've forced your image to a particular size, a long label is going to force the graph to get smaller and smaller. You might be expecting the labels to word-wrap, but they don't. You could attempt to implement some sort of word-wrapping capability to your labels, which might suit your needs.
import plotly.graph_objects as go
labels = ['This is a super long label name that would shrink your chart because there is not enough room','Hydrogen','Carbon_Dioxide','Nitrogen']
values = [4500, 2500, 1053, 500]
fig = go.Figure(data=[go.Pie(labels=labels, values=values)])
fig.update_layout(legend={'font': {'size': 17}, })
fig.update_layout(
width=900,
height=500
)
fig.show()
Adding the html break tag every n words to turn label into a multi-line label.
import numpy as np
new_labels = []
max_label_length = 6
for label in labels:
l = label.split()
if len(l)>5:
l = np.array_split(l, int(len(l)/max_label_length))
l = ['<br>'.join(' '.join(x for x in w) for w in l)]
new_labels.append(l)
fig = go.Figure(data=[go.Pie(labels=new_labels, values=values)])
fig.update_layout(legend={'font': {'size': 17}, })
fig.update_layout(
width=900,
height=500
)
fig.show()
As per the documentation, the plot will normally resize to accommodate the legend. But you can use specific anchor points to help adjust where the legend sits. And thus restrict how it impacts the chart.
Example code:
import plotly.graph_objects as go
from IPython.display import Image
labels = ['This is a very very very long label to illustrate the point, that you can have very long labels','This is just another label']
y = [62, 38]
pie_chart = go.Pie(labels=labels, values=y)
fig = go.Figure(data=[pie_chart])
fig.update_layout(legend=dict(
font = dict(size=17),
orientation="v",
yanchor="bottom",
y=1.1,
xanchor="right",
x=1
))
io_bytes = fig.to_image(format="png", scale=2.5, width=900, height=500)
Image(io_bytes)
Output:
And one with short labels:

plotly express | change tick units on y axis

I want to add units to my yaxis of my bar chart.
Im using plotly.express for that but didnt found a working solution inside the documentation.
text_auto() and fig.update_layout() are not working for me right now.
(Tried that thread without success -> Changing Text Inside Plotly Express Bar Charts)
Im not using panda data format right now, rather a own dictionary i feed plotly.
Please bear with me as im still new to analysing data with plotly.
import json
import requests
from operator import itemgetter
import plotly.express as px
#hyperlinks = xaxis with description and link to the game
#times = yaxis total playtime (<- where i want to use "xx.xh")
#titles = simple hover text
df = {
"x" : hyperlinks,
"y" : times,
"titles" : titles,
}
fig = px.bar(
df,
x="x",
y="y",
hover_data=["titles"],
color="y",
color_continuous_scale="Plotly3_r",
title=f"Top 30 games with most playtime",
text_auto=".h",
labels={"y" : "entire playtime of steam games"},
)
fig.update_layout(
yaxis={
"tickformat" : '.h'
}
)
fig.show()
fig.write_html("My_most_played_games.html")
I have generated some random values for the example.
Since recently you can have access to figure parameters of plotly using fig.full_figure_for_development() from there you can extract element to check where plotly added ticks and regenerate them adding to them any string you want
import plotly.express as px
import numpy as np
#hyperlinks = xaxis with description and link to the game
#times = yaxis total playtime (<- where i want to use "xx.xh")
#titles = simple hover text
df = {
"x" : ['black desert', 'arma 3', 'borderland 2', 'Cyberpunk'],
"y" : [420, 350, 310, 180],
"titles" : ['black desert', 'arma 3', 'borderland 2', 'Cyberpunk'],
}
fig = px.bar(
df,
x="x",
y="y",
hover_data=["titles"],
color="y",
color_continuous_scale="Plotly3_r",
title=f"Top 30 games with most playtime",
text_auto=".h",
labels={"y" : "entire playtime of steam games"},
)
# Important part to recover infor from the figure
full_fig = fig.full_figure_for_development() # recover data from figure
range_vl = full_fig.layout.yaxis.range # get range of y axis
distance_tick = full_fig.layout.yaxis.dtick # get distance between ticks
number_ticks = range_vl[1]//full_fig.layout.yaxis.dtick + 1 # calculate number of ticks of your figure
tick_vals = [range_vl[0]+distance_tick*num for num in range(int(number_ticks))] # generate your ticks
tick_text = [f"{val} h" for val in tick_vals] #generate text for your ticks
fig.update_layout(
# set tick mode to array, tickvals to your vals calculated and tick text to the text genrated
yaxis={"tickmode":"array","tickvals":tick_vals, "ticktext": tick_text}
)
fig.show()

Change plotly choropeth font and legend size

I have the following code to generate a county level map of vote change between 2016 and 2020.
from urllib.request import urlopen
import json
with urlopen('https://raw.githubusercontent.com/plotly/datasets/master/geojson-counties-fips.json') as response:
counties = json.load(response)
import plotly.express as px
fig = px.choropleth_mapbox(df, geojson=counties, locations='fips', color='vote_change',
color_continuous_scale="magma",
range_color=(-25, 35),
mapbox_style="carto-positron",
zoom=2, center = {"lat": 37.0902, "lon": -95.7129},
opacity=0.75,
title='Figure 2: Change in Turnout from 2016 to 2020',
labels={'total_votes_2016':'TEST'}
)
fig.update_layout(margin={"r":0,"t":40,"l":0,"b":0})
)
fig.show()
fig.write_image("../figures/vote_change_map.png", width = 450, height = 250)
The code renders this resulting png.
I would like to make the title text size 8 and potentially make the legend more narrow so that it does not take up as much space. Does anyone know the way to do this?
First of all, let's start with changing title font size = 8. Then we will solve the issue related to legend size. For changing font-size = 8 Kindly refer to the Updated Code stated below:-
# Import all the Libraries
from urllib.request import urlopen
import plotly.express as px
import json
# Open JSON File Using 'urlopen' Module of 'json' library and used 'json.load()' JSON Loader to load JSON Data
with urlopen('https://raw.githubusercontent.com/plotly/datasets/master/geojson-counties-fips.json') as response:
counties = json.load(response)
# Used Mapbox choropleth map, each row of 'data_frame (df)' is represented by a colored region on a Mapbox map.
fig = px.choropleth_mapbox(df, geojson=counties, locations='fips', color='vote_change',
color_continuous_scale="magma",
range_color=(-25, 35),
mapbox_style="carto-positron",
zoom=2, center = {"lat": 37.0902, "lon": -95.7129},
opacity=0.75,
labels={'total_votes_2016':'TEST'}
)
# Updated Layout for 'Title with Font Size 8' and 'Narrower Legend'
fig.update_layout(
title='Figure 2: Change in Turnout from 2016 to 2020',
margin=dict(l=0, r=0, t=40, b=0),
font=dict(size=8),
)
# Show Plotted Figure
fig.show()
# Store Image of Generated Plot
fig.write_image("../figures/vote_change_map.png", width = 450, height = 250)
I have used the same code provided by you. Now, We can move towards legends size.
So, According to me, you can't change legend size. You got bigger legend size due to defined Image Size. Current Image Size is squashing all layout.
There are 2 Solution which may help you:-
NOTE:- All the parameters related to position and size are expected. You can fill it up according to your requirements.
(1.) Plot Legend in horizontal Format:-
If you don't want to Change Image Size. Then you can try to plot legend in horizontal format. For doing this task reference code was given below:-
fig.update_layout(
# customize legend orientation & position
legend=dict(
title=None, orientation = 'h', y=1, yanchor="bottom", x=0.5, xanchor="center"
)
)
NOTE:- If you want to learn more about orientation, x, xanchor or more operation related to plotly legends then you can refer: Official Plotly Documentation
(2.) Change Image Size:-
If you want to change Image Size then you can refer code given below:-
# Added 'width=600' and 'height=400' in Current Code for Ploting Chroleopath Mapbox
fig = px.choropleth_mapbox(df, geojson=counties, locations='fips', color='vote_change',
width=600, height=400,
color_continuous_scale="magma",
range_color=(-25, 35),
mapbox_style="carto-positron",
zoom=2, center = {"lat": 37.0902, "lon": -95.7129},
opacity=0.75,
labels={'total_votes_2016':'TEST'}
)
and if it looks perfect then you can store it using the same size Using:-
# Store Image of Generated PLot
fig.write_image("../figures/vote_change_map.png", width = 600, height = 400)
Hope this Solution will be helpful to you.

How do I resize my Plotly bar height and show only bar’s edge (in subplot)?

this is my first foray into Plotly. I love the ease of use compared to matplotlib and bokeh. However I'm stuck on some basic questions on how to beautify my plot. First, this is the code below (its fully functional, just copy and paste!):
import plotly.express as px
from plotly.subplots import make_subplots
import plotly as py
import pandas as pd
from plotly import tools
d = {'Mkt_cd': ['Mkt1','Mkt2','Mkt3','Mkt4','Mkt5','Mkt1','Mkt2','Mkt3','Mkt4','Mkt5'],
'Category': ['Apple','Orange','Grape','Mango','Orange','Mango','Apple','Grape','Apple','Orange'],
'CategoryKey': ['Mkt1Apple','Mkt2Orange','Mkt3Grape','Mkt4Mango','Mkt5Orange','Mkt1Mango','Mkt2Apple','Mkt3Grape','Mkt4Apple','Mkt5Orange'],
'Current': [15,9,20,10,20,8,10,21,18,14],
'Goal': [50,35,21,44,20,24,14,29,28,19]
}
dataset = pd.DataFrame(d)
grouped = dataset.groupby('Category', as_index=False).sum()
data = grouped.to_dict(orient='list')
v_cat = grouped['Category'].tolist()
v_current = grouped['Current']
v_goal = grouped['Goal']
fig1 = px.bar(dataset, x = v_current, y = v_cat, orientation = 'h',
color_discrete_sequence = ["#ff0000"],height=10)
fig2 = px.bar(dataset, x = v_goal, y = v_cat, orientation = 'h',height=15)
trace1 = fig1['data'][0]
trace2 = fig2['data'][0]
fig = make_subplots(rows = 1, cols = 1, shared_xaxes=True, shared_yaxes=True)
fig.add_trace(trace2, 1, 1)
fig.add_trace(trace1, 1, 1)
fig.update_layout(barmode = 'overlay')
fig.show()
Here is the Output:
Question1: how do I make the width of v_current (shown in red bar) smaller? As in, it should be smaller in height since this is a horizontal bar. I added the height as 10 for trace1 and 15 for trace2, but they are still showing at the same heights.
Question2: Is there a way to make the v_goal (shown in blue bar) only show it's right edge, instead of a filled out bar? Something like this:
If you noticed, I also added a line under each of the category. Is there a quick way to add this as well? Not a deal breaker, just a bonus. Other things I'm trying to do is add animation, etc but that's for some other time!
Thanks in advance for answering!
Running plotly.express wil return a plotly.graph_objs._figure.Figure object. The same goes for plotly.graph_objects running go.Figure() together with, for example, go.Bar(). So after building a figure using plotly express, you can add lines or traces through references directly to the figure, like:
fig['data'][0].width = 0.4
Which is exactly what you need to set the width of your bars. And you can easily use this in combination with plotly express:
Code 1
fig = px.bar(grouped, y='Category', x = ['Current'],
orientation = 'h', barmode='overlay', opacity = 1,
color_discrete_sequence = px.colors.qualitative.Plotly[1:])
fig['data'][0].width = 0.4
Plot 1
In order to get the bars or shapes to indicate the goal levels, you can use the approach described by DerekO, or you can use:
for i, g in enumerate(grouped.Goal):
fig.add_shape(type="rect",
x0=g+1, y0=grouped.Category[i], x1=g, y1=grouped.Category[i],
line=dict(color='#636EFA', width = 28))
Complete code:
import plotly.express as px
from plotly.subplots import make_subplots
import plotly as py
import pandas as pd
from plotly import tools
d = {'Mkt_cd': ['Mkt1','Mkt2','Mkt3','Mkt4','Mkt5','Mkt1','Mkt2','Mkt3','Mkt4','Mkt5'],
'Category': ['Apple','Orange','Grape','Mango','Orange','Mango','Apple','Grape','Apple','Orange'],
'CategoryKey': ['Mkt1Apple','Mkt2Orange','Mkt3Grape','Mkt4Mango','Mkt5Orange','Mkt1Mango','Mkt2Apple','Mkt3Grape','Mkt4Apple','Mkt5Orange'],
'Current': [15,9,20,10,20,8,10,21,18,14],
'Goal': [50,35,21,44,20,24,14,29,28,19]
}
dataset = pd.DataFrame(d)
grouped = dataset.groupby('Category', as_index=False).sum()
fig = px.bar(grouped, y='Category', x = ['Current'],
orientation = 'h', barmode='overlay', opacity = 1,
color_discrete_sequence = px.colors.qualitative.Plotly[1:])
fig['data'][0].width = 0.4
fig['data'][0].marker.line.width = 0
for i, g in enumerate(grouped.Goal):
fig.add_shape(type="rect",
x0=g+1, y0=grouped.Category[i], x1=g, y1=grouped.Category[i],
line=dict(color='#636EFA', width = 28))
f = fig.full_figure_for_development(warn=False)
fig.show()
You can use Plotly Express and then directly access the figure object as #vestland described, but personally I prefer to use graph_objects to make all of the changes in one place.
I'll also point out that since you are stacking bars in one chart, you don't need subplots. You can create a graph_object with fig = go.Figure() and add traces to get stacked bars, similar to what you already did.
For question 1, if you are using go.Bar(), you can pass a width parameter. However, this is in units of the position axis, and since your y-axis is categorical, width=1 will fill the entire category, so I have chosen width=0.25 for the red bar, and width=0.3 (slightly larger) for the blue bar since that seems like it was your intention.
For question 2, the only thing that comes to mind is a hack. Split the bars into two sections (one with height = original height - 1), and set its opacity to 0 so that it is transparent. Then place down bars of height 1 on top of the transparent bars.
If you don't want the traces to show up in the legend, you can set this individually for each bar by passing showlegend=False to fig.add_trace, or hide the legend entirely by passing showlegend=False to the fig.update_layout method.
import plotly.express as px
import plotly.graph_objects as go
# from plotly.subplots import make_subplots
import plotly as py
import pandas as pd
from plotly import tools
d = {'Mkt_cd': ['Mkt1','Mkt2','Mkt3','Mkt4','Mkt5','Mkt1','Mkt2','Mkt3','Mkt4','Mkt5'],
'Category': ['Apple','Orange','Grape','Mango','Orange','Mango','Apple','Grape','Apple','Orange'],
'CategoryKey': ['Mkt1Apple','Mkt2Orange','Mkt3Grape','Mkt4Mango','Mkt5Orange','Mkt1Mango','Mkt2Apple','Mkt3Grape','Mkt4Apple','Mkt5Orange'],
'Current': [15,9,20,10,20,8,10,21,18,14],
'Goal': [50,35,21,44,20,24,14,29,28,19]
}
dataset = pd.DataFrame(d)
grouped = dataset.groupby('Category', as_index=False).sum()
data = grouped.to_dict(orient='list')
v_cat = grouped['Category'].tolist()
v_current = grouped['Current']
v_goal = grouped['Goal']
fig = go.Figure()
## you have a categorical plot and the units for width are in position axis units
## therefore width = 1 will take up the entire allotted space
## a width value of less than 1 will be the fraction of the allotted space
fig.add_trace(go.Bar(
x=v_current,
y=v_cat,
marker_color="#ff0000",
orientation='h',
width=0.25
))
## you can show the right edge of the bar by splitting it into two bars
## with the majority of the bar being transparent (opacity set to 0)
fig.add_trace(go.Bar(
x=v_goal-1,
y=v_cat,
marker_color="#ffffff",
opacity=0,
orientation='h',
width=0.30,
))
fig.add_trace(go.Bar(
x=[1]*len(v_cat),
y=v_cat,
marker_color="#1f77b4",
orientation='h',
width=0.30,
))
fig.update_layout(barmode='relative')
fig.show()

Deal with overlapping in multiple x-axes in plotly python

I am trying to create a plot using plotly with multiple axes. And for this, I am using the following code:
#Plotly libraries and options for graphic logic
from plotly.io import to_html
import plotly.io as pio
pio.renderers.default='browser'
import plotly.graph_objects as go
#Generic libraries
import pandas as pd
import numpy as np
from datetime import datetime
input_df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv')
threshold =2.8
name_yaxis="Gap"
input_df["AAPL.High"] = (input_df["AAPL.High"]-min(input_df["AAPL.High"]))*(threshold)/(max(input_df["AAPL.High"])-min(input_df["AAPL.High"]))+np.random.uniform(0.3,0.4,1)
ID_TAIL = "ID_1"
fig = go.Figure()
fig.add_trace(go.Scatter(x=input_df['Date'], y=input_df['AAPL.High'],
mode='lines+markers',
marker_size=12,
line = dict(color="#C4C4C4"),
marker=dict(color=( (0 < input_df['AAPL.High']) & (input_df['AAPL.High'] < threshold)).astype('int'),
colorscale=[[0, '#A51890'], [1, '#3BBFFE']]
),
showlegend=False,
xaxis="x1",
name = ""
)
)
my_x = [ID_TAIL + "_" +format(i, '04d') + "_0" for i in range(1,input_df.shape[0])]
fig.add_trace(go.Scatter(x=my_x, y=input_df['AAPL.High'],
mode='lines+markers',
marker_size=12,
line = dict(color="#C4C4C4"),
marker=dict(color=( (0 < input_df['AAPL.High']) & (input_df['AAPL.High'] < threshold)).astype('int'),
colorscale=[[0, '#A51890'], [1, '#3BBFFE']]
),
showlegend=False,
xaxis="x2",
name = ""
)
)
#== Add title boxes ==#
# Add title legend for box status
fig.add_annotation( text="<b>Health status<b>", xref="paper", yref="paper",
x=1.02, xanchor="left",
y=0.9, yanchor="bottom", # Same y as legend below
showarrow=False,
font = dict(family = "Roboto", size = 10))
#== End ==#
My problem is that as you can see in the following image, the ticks are overlapping:
So, my question is, how to create space between them?
Thanks in advance.
Here's a quick fix. Pop this line at the bottom of your code, and it will move xaxis2 to the top of the graph:
fig.update_layout({'xaxis2': {'side': 'top', 'tickangle': 45, 'nticks': 50}})
Output:
Shifting the secondary xaxis to the top will look like this.
Another Option:
Another approach would be to concatenate the axis titles into a single string, and display the concatenated string on the x-axis. This SO answer demonstrates this logic.
You can reduce the number of ticks by adding the following line
fig.update_layout(xaxis={'nticks': 8, 'tickangle': 90}, xaxis2={'nticks': 8, 'tickangle': 90})
Depending on the size of the plot, ticks may still overlap. In that case, you can either further reduce the tick number or hardcode the tick positions:
tickvalsX = ['2015-07', '2016-01', '2016-07', '2017-01']
tickvalsY = ['ID_1_0001_0', 'ID_1_00100_0', 'ID_1_0200_0', 'ID_1_0300_0', 'ID_1_0400_0', 'ID_1_0500_0']
fig.update_layout(xaxis={'tickmode': 'array', 'tickangle': 90, 'tickvals': tickvalsX}, xaxis2={'tickmode': 'array', 'tickangle': 90, 'tickvals': tickvalsY})
Further style elements of the axis you can find in the Plotly reference.

Categories

Resources