Skip to content

Commit 2287b27

Browse files
authored
Merge pull request #4958 from rl-utility-man/master
Add Pictogram Bar Chart Tested and Improvements
2 parents 3ab4eb4 + 58a7511 commit 2287b27

File tree

1 file changed

+102
-0
lines changed

1 file changed

+102
-0
lines changed

doc/python/bar-charts.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,108 @@ fig.update_layout(
589589
)
590590
```
591591

592+
### Using a scatterplot to wrap long bars into multiple columns
593+
594+
This bar-style pictogram allows readers to focus on the relative sizes of smaller entities by wrapping the bar for largest entries into multiple columns. You could make it even more of a pictogram by using fontawesome to replace the square markers we use below with icons like mortar boards for students.
595+
596+
```python
597+
import plotly.graph_objects as go
598+
import pandas as pd
599+
def pictogram_bar(data, title, icon_size, max_icons_per_column=10, units_per_icon=1, unit_description="", inter_group_spacing=.8,icon_vertical_spacing=0.005):
600+
601+
fig = go.Figure()
602+
x_start = 1
603+
tick_locations = []
604+
#loop through each group and create a trace with its icons
605+
for i, (category, value) in enumerate(data.items()):
606+
# compute the number of icons to use to represent this category. Depending on your use case, you might replace round with floor or ceiling.
607+
icon_count = round(value / units_per_icon)
608+
# compute the number of columns in which to arrange the icons for this category
609+
# using a double negative sign to convert a floor(division) operation into a ceiling(division) operation
610+
num_columns = -(-icon_count // max_icons_per_column)
611+
612+
#create and populate lists of icon coordinates
613+
x_coordinates, y_coordinates = [], []
614+
for col in range(num_columns):
615+
# the number of icons in this column is the lesser of the column height or
616+
# the number of icons remaining to place
617+
column_icons = min(max_icons_per_column, icon_count - col * max_icons_per_column)
618+
619+
# Create a one item list containing the x-coordinate of this column.
620+
# Then add column_icons copies of that coordinate to the list of icon x coordinates using list multiplication.
621+
# Normalizing the width of each within-category column to 1 simplifies the code.
622+
# We can adjust the visible space between columns by adjusting the total width below.
623+
x_coordinates.extend([x_start + col] * column_icons)
624+
# Create a list of sequentially increasing y-coordinates for icons.
625+
y_coordinates.extend([y + icon_vertical_spacing * y for y in range(1, column_icons + 1)])
626+
# Add scatter plot for the category
627+
fig.add_trace(go.Scatter(
628+
x=x_coordinates,
629+
y=y_coordinates,
630+
mode='markers',
631+
marker=dict(size=icon_size, symbol="square", color= i),
632+
name=category,
633+
# Suppress the x and y coordinates in the hover text, since they are irrelevant implementation details.
634+
hoverinfo="text",
635+
text=[f"{category}: {value}" for _ in range(len(x_coordinates))]
636+
))
637+
638+
# Add an annotation above the center of each category showing its value
639+
fig.add_trace(go.Scatter(
640+
x=[x_start + (num_columns - 1) / 2], # Compute the location of the center
641+
y=[max_icons_per_column* (1+icon_vertical_spacing) + 1.15],
642+
mode="text",
643+
text=[f"{value}"],
644+
textfont=dict(size=14, color="black"),
645+
showlegend=False
646+
))
647+
# Track locations where we will put the text labeling each category
648+
tick_locations.append(x_start + (num_columns - 1) / 2)
649+
#compute the left edge of the next category
650+
x_start += num_columns + inter_group_spacing
651+
652+
fig.update_layout(
653+
title=title,
654+
xaxis=dict(
655+
tickvals=tick_locations,
656+
# Label ecah category
657+
ticktext=list(data.keys()),
658+
tickangle=-45,
659+
showgrid=False,
660+
title="Categories"
661+
),
662+
yaxis=dict(
663+
title=f"Each icon represents {units_per_icon:,g} {unit_description}",
664+
# The y-axis goes above the top icon to make room for the annotations.
665+
# We set tick values so the axis labeling does not go above the top icon.
666+
# If you choose a value of max_icons_per_column that is not a multiple of 5, consider changing this.
667+
tickvals=list(range(0,max_icons_per_column+1,5)),
668+
showgrid=False,
669+
zeroline=False,
670+
),
671+
# We have already got all the labeling we need so we suppress the legend.
672+
showlegend=False,
673+
height=700,
674+
# The x-coordinates scale to fill available space, so adjusting the width of the image is a good way to adjust spacing between columns.
675+
width=(len(data) * 150 + 50)
676+
)
677+
fig.show()
678+
679+
df = pd.DataFrame({
680+
'School': ["Haverford College", "University of Mary Washington", "Brown University", "Arizona State University"],
681+
'Enrollment': [1421, 3611, 7226, 65174]
682+
})
683+
684+
pictogram_bar(
685+
data={row['School']: row['Enrollment'] for _, row in df.iterrows()},
686+
title="Undergraduate Enrollment at Participating Schools",
687+
units_per_icon=1000,
688+
unit_description = "students",
689+
icon_size=27,
690+
icon_vertical_spacing=0.05
691+
)
692+
```
693+
592694
### Customizing Individual Bar Base
593695

594696
```python

0 commit comments

Comments
 (0)