@@ -593,53 +593,62 @@ fig.update_layout(
593
593
594
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
595
596
- ```
596
+ ``` python
597
597
import plotly.graph_objects as go
598
598
import pandas as pd
599
-
600
- def pictogram_bar(data, title, icon_size, max_height=10, units_per_icon=1, column_spacing=.75,icon_spacing=0.005):
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 ):
601
600
602
601
fig = go.Figure()
603
602
x_start = 1
604
603
tick_locations = []
605
-
604
+ # loop through each group and create a trace with its icons
606
605
for i, (category, value) in enumerate (data.items()):
606
+ # compute the number of icons represent this category
607
607
icon_count = round (value / units_per_icon)
608
- num_columns = -(-icon_count // max_height) # Ceiling division
609
-
608
+ # compute the number of columns in which to arrange this category
609
+ # every category gets at least one column; we use integer division
610
+ # to compute the number of additional columns
611
+ num_columns = (icon_count // max_icons_per_column)+ 1
612
+
613
+ # create lists of coordinates and populate them
610
614
x_coordinates, y_coordinates = [], []
611
615
for col in range (num_columns):
612
- column_icons = min(max_height, icon_count - col * max_height)
616
+ # the number of icons in this column is the lesser of the column height or
617
+ # the number of icons remaining to place
618
+ column_icons = min (max_icons_per_column, icon_count - col * max_icons_per_column)
619
+
620
+ # create a list element containing the x-coordinate of this column;
621
+ # add column_icons copies of that coordinate to the list of icon x coordinates
622
+ # normalizing the width of each within group column to 1 simplifies the code
623
+ # we can adjust the visible space between columns by adjusting the total width below
613
624
x_coordinates.extend([x_start + col] * column_icons)
614
- y_coordinates.extend([y + icon_spacing * y for y in range(1, column_icons + 1)])
615
-
616
-
625
+ # create a list of sequentially increasing y-coordinates for icons
626
+ y_coordinates.extend([y + icon_vertical_spacing * y for y in range (1 , column_icons + 1 )])
617
627
# Add scatter plot for the category
618
628
fig.add_trace(go.Scatter(
619
629
x = x_coordinates,
620
630
y = y_coordinates,
621
631
mode = ' markers' ,
622
632
marker = dict (size = icon_size, symbol = " square" , color = i),
623
633
name = category,
634
+ # suppress the x and y coordinates in the hover text, since they are meaningless to readers
624
635
hoverinfo = " text" ,
625
636
text = [f " { category} : { value} " for _ in range (len (x_coordinates))]
626
637
))
627
638
628
-
629
- # Add value annotations above the section
639
+ # add an annotation above the center of each section showing its value
630
640
fig.add_trace(go.Scatter(
631
- x=[x_start + (num_columns - 1) / 2],
632
- y=[max_height + 1.2],
641
+ x = [x_start + (num_columns - 1 ) / 2 ], # center
642
+ y = [max_icons_per_column + 1.2 ],
633
643
mode = " text" ,
634
644
text = [f " { value} " ],
635
645
textfont = dict (size = 14 , color = " black" ),
636
646
showlegend = False
637
647
))
638
-
639
- # Track tick locations
648
+ # Track locations where we will put the text for each category
640
649
tick_locations.append(x_start + (num_columns - 1 ) / 2 )
641
- x_start += num_columns + column_spacing
642
-
650
+ # compute the left edge of the next category
651
+ x_start += num_columns + inter_group_spacing
643
652
# Update layout
644
653
fig.update_layout(
645
654
title = title,
@@ -651,18 +660,18 @@ def pictogram_bar(data, title, icon_size, max_height=10, units_per_icon=1, colum
651
660
title = " Categories"
652
661
),
653
662
yaxis = dict (
654
- title=f"Units (1 icon = {units_per_icon})",
663
+ title = f " Units (1 icon = { units_per_icon:,g } { unit_description } ) " ,
655
664
showgrid = False ,
656
665
zeroline = False ,
657
666
),
658
- showlegend=False,
659
- height=600,
660
- width=(len(data) * 200 + 200)
667
+ # we've got all the labeling we need without a legend
668
+ showlegend = False ,
669
+ height = 700 ,
670
+ # the x-coordinates get scales to fill available space, so adjusting the width is a good way to adjust spacing between columns
671
+ width = (len (data) * 150 + 50 )
661
672
)
662
-
663
673
fig.show()
664
674
665
-
666
675
df = pd.DataFrame({
667
676
' School' : [" Haverford College" , " University of Mary Washington" , " Brown University" , " Arizona State University" ],
668
677
' Enrollment' : [1421 , 3611 , 7226 , 65174 ]
@@ -672,8 +681,9 @@ pictogram_bar(
672
681
data = {row[' School' ]: row[' Enrollment' ] for _, row in df.iterrows()},
673
682
title = " Undergraduate Enrollment at Participating Schools" ,
674
683
units_per_icon = 1000 ,
684
+ unit_description = " students" ,
675
685
icon_size = 27 ,
676
- icon_spacing =0.05
686
+ icon_vertical_spacing = 0.05
677
687
)
678
688
```
679
689
0 commit comments