@@ -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