Change plotly choropeth font and legend size - python

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.

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:

Use a custom icon for displaying (gender) in plotly's library in python

I was wondering if there's a way to have a custom icon for plotly's pie chart instead of the usual pie division
As of now I'm displaying the gender information using a pie chart which looks as below:
and im looking for something like this :
this is the pie chart code
fig = px.pie(df5, values='count', names='gender')
fig.show()
and this is the gender df
gender
count
Female
666
male
1889
‎
Your desired graphic is nice, but it's far too specific to exist natively in plotly. As #Hamzah mentioned, if you only need one chart, you would probably save time by manually creating the image yourself.
However, if for some reason you really want to use plotly (or you need to scale up the number of charts), I'll lay out the general idea. Hopefully someone else will find this useful in the future as well.
The first thing to do is get an image with a dark background where the man and woman are transparent (I used the wizard tool in preview to crop them out of the original image):
You can add this image to an empty plot at specific x and y coordinates (see here), and then keep track of the left, right, top, and bottom x- and y-axis coordinates of the man and woman figures themselves. For convenience, I placed the image such that it spans from (0,0) to (3,1) in plotly figure coordinates.
Then I used plotly shapes (see here) to add blue rectangles with heights corresponding to the gender values in your df5, and placed these rectangles below the layer of the image so that it shows through the transparent man and woman. Then I added used plotly annotations to add text below the man and woman. Here is the result:
I want to stress that this is a very hacky solution and not really how plotly is intended to be used and there is a some hard-coding involved in determining the top, bottom, left, and right coordinates where the man and woman images start and end in this picture – but I could see this having a use case if someone needed to generate a large number of images or html files with the same logos with fill heights corresponding to percentages.
import pandas as pd
import plotly.graph_objects as go
from PIL import Image
df5 = pd.DataFrame({
"gender": ["Female","male"],
"count": [666, 1889]
})
logo_height = 1
df5["height"] = logo_height * df5["count"] / df5["count"].sum()
fig = go.Figure()
man_woman_transparent_logo = Image.open("man_woman_transparent_logo.png")
fig.add_layout_image(
dict(
source=man_woman_transparent_logo,
xref="x",
yref="y",
x=0,
y=1,
sizex=3,
sizey=1,
sizing="stretch",
opacity=1,
layer="below")
)
image_top = 0.86
image_bottom = 0.14
image_height = image_top-image_bottom
man_image_left = 0.3
man_image_right = 1.14
woman_image_left = 1.76
woman_image_right = 2.72
female_height_ratio = df5.loc[df5['gender'] == 'Female', ['height']].values[0][0]
male_height_ratio = df5.loc[df5['gender'] == 'male', ['height']].values[0][0]
## add blue fill for man
fig.add_shape(type="rect",
x0=man_image_left, y0=image_bottom,
x1=man_image_right, y1=image_bottom + image_height*male_height_ratio,
line=dict(width=0),
fillcolor="LightSkyBlue",
layer="below"
)
## add blue fill for woman
fig.add_shape(type="rect",
x0=woman_image_left, y0=image_bottom,
x1=woman_image_right, y1=image_bottom + image_height*female_height_ratio,
line=dict(width=0),
fillcolor="LightSkyBlue",
layer="below"
)
## add text for man
fig.add_annotation(
text=f"{male_height_ratio*100:,.2f}% Male",
font=dict(color="white", size=20),
xref="x", yref="y",
x=(man_image_left+man_image_right)/2,
y=image_bottom-0.05,
showarrow=False
)
## add text for woman
fig.add_annotation(
text=f"{female_height_ratio*100:,.2f}% Female",
font=dict(color="white", size=20),
xref="x", yref="y",
x=(woman_image_left+woman_image_right)/2,
y=image_bottom-0.05,
showarrow=False
)
fig.update_layout(
xaxis=dict(showgrid=False, range=[0,3]),
yaxis=dict(showgrid=False, range=[0,1]),
)
fig.update_xaxes(visible=False)
fig.update_yaxes(visible=False)
fig.show()

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()

Is there a way to always show all markers in a plotly scattermapbox, regardless of manual zooming?

I am trying to generate several maps with different content based on a dataframe.
So far, I have managed to display the information I needed on the interactive maps.
However, as I need to include the generated maps as figures in a report, I need to find a way to show all the markers in the figures. Problem is: some markers only are shown when I manually zoom in the area.
Is there a way to always make the markers visible?
Here is the code:
import plotly.graph_objects as go
token = open("token.mapbox_token").read() # you need your own token
df_select = df_map.loc[df_map['Budget'] == 0.9]
fig= go.Figure(go.Scattermapbox(lat=df_select.Latitude, lon=df_select.Longitude,
mode='markers', marker=go.scattermapbox.Marker(
size=df_select.Warehouse_Size*5, color = df_select.Warehouse_Size,
colorscale = ['white','red','orange','green','blue','purple'],
showscale = False)))
fig = fig.add_trace(go.Choroplethmapbox(geojson=br_geo, locations=df_select.State,
featureidkey="properties.UF_05",
z=df_select.Top10,
colorscale=["white","pink"], showscale=False,
zmin = 0,
zmax=1,
marker_opacity=0.5, marker_line_width=1
))
df_prio = df_select.loc[df_select['Prioritisated'] == 1]
fig= fig.add_trace(go.Scattermapbox(lat=df_prio.Latitude, lon=df_prio.Longitude+1,
mode='markers',
marker=go.scattermapbox.Marker(symbol = "campsite", size = 10)))
fig.update_layout(height=850,width = 870,
mapbox_style = "mapbox://styles/rafaelaveloli/ckollp2dg21dd19pmgm3vyebu",
mapbox_zoom=3.4, mapbox_center = {"lat": -14.5 ,"lon": -52},
mapbox_accesstoken = token, showlegend= False)
fig.show()
This is the result I get:
And this is one of the hidden markers that are only visible when zooming in:
How can I make it visible in the first figure, without changing the figure zoom and dimensions?
Passing allowoverlap=True to go.scattermapbox.Marker() seems to resolve the issue (link to relevant docs).

How to place images using image_url in Bokeh

I have a chart that uses datetime for the x-axis and dollars for the y-axis in Bokeh. I want to place a logo in the upper left corner of the plot area. Bokeh documentations seems especially cryptic on placing images. This code works:
from bokeh.plotting import figure, show
#p = figure(x_range=(0,1200), y_range=(0,600))
p = figure(plot_width=1200, plot_height=600,
sizing_mode = 'scale_width',
toolbar_location='above',
x_axis_label='date',
x_axis_type='datetime',
y_axis_label='value',
)
p.image_url(x=0, y=1, url=["Shrewd_Lines_200.png"], anchor='bottom_left')
show(p)
But when I place this into my main chart, where the data is in datetime, I can not get an image to appear. Here are the key excerpts from the code within the primary chart:
plot = figure(plot_width=1200, plot_height=600,
sizing_mode = 'scale_width',
toolbar_location='above',
tools=tools,
title=plot_dict['chart_title'],
x_axis_label='date',
x_axis_type='datetime',
y_axis_label='value',
)
plot.x_range.end=plot_dict['end_data'] + extend_time
if plot_dict['start_chart'] == 'auto':
plot.x_range.start=plot_dict['start_user_data']
else:
plot.x_range.start = plot_dict['start_chart']
plot.y_range.start=0
plot.y_range.end= extend_y * plot_dict['max_value']
plot.left[0].formatter.use_scientific = False
plot.title.text_font_size = "16pt"
I have tried various approaches to plot the image such as:
plot.image_url(x=0, y=0, url=["Shrewd_Lines_200.png"], anchor='bottom_left')
plot.image_url(x=plot_dict['start_user_data'], y=10000000, url=["Shrewd_Lines_200.png"], anchor='bottom_left')
I have several labels in the chart that work quite nicely. Is there a way to specify image location and size using screen units, in the same manner as you specify locations for labels?
Thought I would post how I got this working in order to move forward. I used the following for my Bokeh plot that places my logo with some generic math to convert data space to screen space. It does this without using numpy arrays or ColumnDataSource (neither of which are bad, but trying to keep simple):
from bokeh.plotting import figure, show
# chart size and ranges need defined for dataspace location
# chart size
chart_width = 900
chart_height = 600
aspect_ratio = chart_width/chart_height
# limits of data ranges
x1 = 300
x2 = 1200
y1 = 0
y2 = 600
plot = figure(
plot_width=chart_width,
plot_height=chart_height,
x_range=(x1, x2),
y_range=(y1, y2),
sizing_mode = 'stretch_both',
x_axis_label='date',
x_axis_type='datetime',
y_axis_label='value')
plot.image_url(url=['my_image.png'], x=(.01*(x2-x1))+x1, y=(.98*(y2-y1))+y1,
w=.35*(x2-x1)/aspect_ratio, h=.1*(y2-y1), anchor="top_left")
show(plot)
Note the x_axis_type can be any type with this schema, datetime was just the issue I was dealing with.

Categories

Resources