@@ -589,6 +589,108 @@ fig.update_layout(
589
589
)
590
590
```
591
591
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
+
592
694
### Customizing Individual Bar Base
593
695
594
696
``` python
0 commit comments