@@ -597,82 +597,84 @@ This bar-style pictogram allows readers to focus on the relative sizes of smalle
597597import plotly.graph_objects as go
598598import pandas as pd
599599
600- #TODO: make the results and the code compellingly clear, terse, and well designed; for example, make sure all the variable names are descriptive
601- #TODO: when we're happy, remove print statements
602- #TODO: consider adding the value for each group either above its section or to its title
603-
604- def pictogram_bar(data, title, icon_size, max_height=10, units_per_icon=1,columns_between_units=.5):
605- fig = go.Figure()
600+ def pictogram_bar(data, title, icon_size, max_height=10, units_per_icon=1, column_spacing=.75,icon_spacing=0.005):
606601
607- # Iterate through the data and create a scatter plot for each category
602+ fig = go.Figure()
608603 x_start = 1
609604 tick_locations = []
610- for i, (category, count) in enumerate(data.items()):
611- #convert the real number input to an integer number of icons. Depending on the context, you might want to take floor or a ceiling rather than rouding
612- count = round(count / units_per_icon)
613- num_cols = (count + max_height - 1) // max_height # Ceiling division
614- x_coordinates = []
615- y_coordinates = []
616- for col in range(num_cols):
617- print([x_start+col]*min(max_height, count-col*max_height))
618- x_coordinates += [x_start+col]*min(max_height, count-col*max_height)
619- print(list(range(0, min(max_height, count-col*max_height))))
620- for yc in range(1, min(max_height, count-col*max_height)+1):
621- y_coordinates.append(yc)
622- print(f"{category=}")
623- print(f"{x_coordinates=}")
624- print(f"{y_coordinates=}")
625- # Add dots for this category
605+
606+ for i, (category, value) in enumerate(data.items()):
607+ icon_count = round(value / units_per_icon)
608+ num_columns = -(-icon_count // max_height) # Ceiling division
609+
610+ x_coordinates, y_coordinates = [], []
611+ for col in range(num_columns):
612+ column_icons = min(max_height, icon_count - col * max_height)
613+ 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+
617+ # Add scatter plot for the category
626618 fig.add_trace(go.Scatter(
627- x=x_coordinates, # All dots are in the same x position (category)
619+ x=x_coordinates,
628620 y=y_coordinates,
629621 mode='markers',
630- marker=dict(size=icon_size, symbol="square", color=i),
622+ marker=dict(size=icon_size, symbol="square", color= i),
631623 name=category,
632- #text=[category] * (y_end - y_start), # Hover text
633- hoverinfo="text"
624+ hoverinfo=" text",
625+ text=[f"{category}: {value}" for _ in range(len(x_coordinates))]
634626 ))
635- tick_locations += [x_start+ (col)/2]
636- x_start += col+1+columns_between_units
637- print(f"{tick_locations=}")
638-
639- # Update layout for better visualization
627+
628+
629+ # Add value annotations above the section
630+ fig.add_trace(go.Scatter(
631+ x=[x_start + (num_columns - 1) / 2],
632+ y=[max_height + 1.2],
633+ mode="text",
634+ text=[f"{value}"],
635+ textfont=dict(size=14, color="black"),
636+ showlegend=False
637+ ))
638+
639+ # Track tick locations
640+ tick_locations.append(x_start + (num_columns - 1) / 2)
641+ x_start += num_columns + column_spacing
642+
643+ # Update layout
640644 fig.update_layout(
641645 title=title,
642646 xaxis=dict(
643647 tickvals=tick_locations,
644648 ticktext=list(data.keys()),
645649 tickangle=-45,
646- showgrid=False
650+ showgrid=False,
651+ title="Categories"
647652 ),
648- #TODO: HIDE THE Y-AXIS? OR ENUMERATE IT IN "NATURAL UNITS" -- so count
649653 yaxis=dict(
650- title="Units",
654+ title=f "Units (1 icon = {units_per_icon}) ",
651655 showgrid=False,
652- showline=False,
653- zeroline=False
656+ zeroline=False,
654657 ),
655- #TO DO: SHOW THE LEGEND, BUT JUST FOR ONE TRACE; LABEL IT WITH SOMETHING LIKE "EACH ICON REPRESENTS {units_per_icon} {Y_VARNAME}"
656658 showlegend=False,
657- #setting the width implicitly sets the amount of space between columns within groups and it's desirable to keep those columns close but not too close
658- #TODO: set the width to a value that makes the spacing between columns reasonable; try it as a function of the number of columns of data, number of columns left blank as spacers, the icon size; and the left and right margins
659- # there's no right answer; but some answers will look a lot better than others; I'm guessing that roughly 2-3 times as many px as we fill with icons might be good
660- height=600
659+ height=600,
660+ width=(len(data) * 200 + 200)
661661 )
662662
663- # Show the plot
664663 fig.show()
665664
666- # TODO: CHANGE THIS THROUGHOUT TO A DF NAMED DF.
667665
668- data = {
669- "Haverford College": 1421, #https://www.usnews.com/best-colleges/haverford-college-3274
670- "University of Mary Washington": 3611, #https://www.usnews.com/best-colleges/university-of-mary-washington-3746#:~:text=Overview,campus%20size%20is%20234%20acres.
671- "Brown University": 7226, #https://oir.brown.edu/institutional-data/factbooks/enrollment
672- "Arizona State University": 65174, #https://www.usnews.com/best-colleges/arizona-state-university-1081
673- }
666+ df = pd.DataFrame({
667+ 'School': ["Haverford College", "University of Mary Washington", "Brown University", "Arizona State University"],
668+ 'Enrollment': [1421, 3611, 7226, 65174]
669+ })
674670
675- pictogram_bar(data, title="Undergraduate Enrollment at Participating Schools", units_per_icon=1000, icon_size=27)
671+ pictogram_bar(
672+ data={row['School']: row['Enrollment'] for _, row in df.iterrows()},
673+ title="Undergraduate Enrollment at Participating Schools",
674+ units_per_icon=1000,
675+ icon_size=27,
676+ icon_spacing=0.05
677+ )
676678```
677679
678680### Customizing Individual Bar Base
0 commit comments