I want to make a pie chart of daylength in my city today via Plotly, using this code:
import typing as t
import plotly.graph_objects as go
def add_circular_labels(fig: go.Figure,
labels: t.List[str]):
fig.add_trace(go.Scatterpolar())
fig.update_layout(
polar = dict(
radialaxis_visible=False,
angularaxis = dict(
type="category",
categoryarray=labels,
direction="clockwise",
showticklabels=True,
ticks="outside",
ticklen=8,
tickcolor="white",
showgrid=False)
)
)
return fig
def main():
values = [0.258, 0.542, 0.201]
labels = ["night-sunrise", "day", "night-sunset"]
sunset, sunrise = "06:11", "19:11"
fig = go.Figure(data=[go.Pie(labels=labels,
values=values,
hole=.7,
direction="clockwise",
sort=False)])
fig.update_layout(template="plotly_dark")
fig.update_traces(marker=dict(colors=["#7D8491", "#DEB841", "#7D8491"]))
fig = add_circular_labels(fig, [f"{i:02d}:00" for i in range(24)])
fig.show()
if __name__ == "__main__":
main()
I want to add two lines to the pie chart, where the grey part meets the golden part, and add labels to them with sunset
and sunrise
, respectively. In short, I would like to see something like this:
How could I achieve that (if that's possible at all)?
I refactored your code so that we use go.Barpolar
to create the sectors, and go.Scatterpolar
to add the text labels for the sectors as well as the lines indicating sunrise
and sunset
.
To help plotly place time strings at the correct location, I wrote a function that converts your time string to a degree measure (for example, plotly needs to know that "06:11"
is 92.75 degrees
clockwise from due north), and I set the parameters of polar chart so that 0 degrees is due north and the direction of positive angles moves clockwise – this ensures that we are consistent with the clock polar coordinate system.
Since the sunrise
and sunset
labels are at the same radial location as the other tickmarks, we can extend the tickvals
and ticktext
arrays to include the angles and text for these additional labels. One thing to note is that because plotly writes text from left to right, the ticktext should look like [f"{sunrise} sunrise", f"sunset {sunset}"]
so that the time string is closer to the sector for both sunrise and sunset.
from datetime import datetime
import typing as t
import plotly.graph_objects as go
def get_clock_theta(time_str: str):
"""
converts time string in the format of '%H:%M' to degrees
get_clock_theta("12:00") returns 180.0
"""
time = datetime.strptime(time_str, '%H:%M')
total_hours = time.hour + time.minute/60
return (360 * total_hours / 24)
def main():
values = [0.258, 0.542, 0.201]
labels = ["night-sunrise", "day", "night-sunset"]
sunrise, sunset = "06:11", "19:11"
colors = ["#7D8491", "#DEB841", "#7D8491"]
start_theta, end_theta = 0.0, 360.0
sunrise_theta = get_clock_theta(sunrise)
sunset_theta = get_clock_theta(sunset)
all_angles = [start_theta, sunrise_theta, sunset_theta, end_theta]
thetas, widths = [], []
for i in range(len(all_angles)-1):
thetas.append((all_angles[i] + all_angles[i+1]) / 2)
widths.append(abs(all_angles[i+1] - all_angles[i]))
r_min, r_max = 0.7, 1.0
fig = go.Figure()
for theta, width, label, color, value in zip(thetas, widths, labels, colors, values):
fig.add_trace(go.Barpolar(
r=[r_max],
theta=[theta],
width=[width],
base=[r_min],
name=label,
marker=dict(color=color),
text=value,
legendgroup=label,
))
fig.add_trace(go.Scatterpolar(
r=[(r_min+r_max)/2],
theta=[theta],
mode='text',
text=f"{value:.1%}",
textfont=dict(color='rgb(50,50,50)'),
showlegend=False,
legendgroup=label,
))
for theta, label in zip(
[sunrise_theta, sunset_theta],
["sunrise","sunset"]
):
fig.add_trace(go.Scatterpolar(
r=[r_min,r_max],
theta=[theta, theta],
mode='lines',
marker=dict(color='white'),
showlegend=False,
))
fig.update_layout(template="plotly_dark")
## set 0 degrees to be due north, and go clockwise
## the default is 0 degrees being east, and going counterclockwise
## add 24 tickvals for each hour spaced evenly around the polar chart
## and also add your labels for sunrise and sunset
fig.update_polars(
angularaxis = dict(
tickvals=[i*360/24 for i in range(24)] + [sunrise_theta, sunset_theta],
ticktext=[f"{i:02d}:00" for i in range(24)] + [f"{sunrise} sunrisee", f"sunset {sunset}"],
tickcolor="white",
ticklen=10,
rotation=90,
direction='clockwise',
gridcolor="rgba(0,0,0,0)"
),
radialaxis = dict(
visible=False,
range=[0,1],
)
)
fig.show()
if __name__ == "__main__":
main()