-
-
Notifications
You must be signed in to change notification settings - Fork 409
Publication quality bars example #6772
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,207 @@ | ||
| # ============================================================================ | ||
| # Publication-Quality Bar Chart - HoloViews Best Practices Example | ||
| # ============================================================================ | ||
| # Demonstrates: | ||
| # - Data extraction, transformation, and visualization separation | ||
| # - Custom Bokeh themes for consistent styling | ||
| # - Interactive tooltips with formatted data | ||
| # - Text annotations on bars | ||
| # - Professional fonts, grids, and axis formatting | ||
| # - Panel integration for web serving | ||
| # ============================================================================ | ||
|
|
||
| import hvsampledata | ||
| import panel as pn | ||
| from bokeh.models.formatters import NumeralTickFormatter | ||
| from bokeh.themes import Theme | ||
|
|
||
| import holoviews as hv | ||
| from holoviews.plotting.bokeh import ElementPlot | ||
|
|
||
| # ============================================================================ | ||
| # BOKEH THEME SETUP - Define global styling | ||
| # ============================================================================ | ||
|
|
||
| ACCENT_COLOR = '#007ACC' # Professional blue | ||
| FONT_FAMILY = 'Roboto' | ||
|
|
||
| def create_bokeh_theme(font_family=FONT_FAMILY, accent_color=ACCENT_COLOR): | ||
| """Create custom theme with specified font. Default: Roboto""" | ||
| return Theme(json={ | ||
| 'attrs': { | ||
| 'Title': { | ||
| 'text_font': font_family, | ||
| 'text_font_size': '16pt', | ||
| 'text_font_style': 'bold' | ||
| }, | ||
| 'Axis': { | ||
| 'axis_label_text_font': font_family, | ||
| 'axis_label_text_font_size': '12pt', | ||
| 'axis_label_text_font_style': 'bold', | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think the default |
||
| 'major_label_text_font': font_family, | ||
| 'major_label_text_font_size': '10pt', | ||
| 'major_tick_line_color': "black", # Remove tick marks | ||
| 'minor_tick_line_color': None | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the |
||
| }, | ||
| 'Plot': { | ||
| 'background_fill_color': '#fafafa', | ||
| 'border_fill_color': '#fafafa' | ||
| }, | ||
| 'Legend': { | ||
| 'label_text_font': font_family, | ||
| 'label_text_font_size': '10pt' | ||
| }, | ||
| 'Toolbar': { | ||
| "autohide": True, | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wish that |
||
| "logo": None, | ||
| "stylesheets": [ | ||
| f""" | ||
| .bk-OnOffButton.bk-active{{ | ||
| border-color: {accent_color} !important; | ||
| }} | ||
| """ | ||
| ] | ||
| }, | ||
| # Does not work via Theme, so added here for reference purposes until I figure out how to do it | ||
| 'Tooltip': { | ||
| "stylesheets": [f""" | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I could not figure out how to change the tooltip text color. I believe this should work but does not. Maybe a Bokeh Theme issues? |
||
| .bk-tooltip-row-label {{ | ||
| color: {ACCENT_COLOR} !important; | ||
| }}"""] | ||
|
|
||
| } | ||
| } | ||
| }) | ||
|
|
||
| # Apply theme globally - affects all plots | ||
| hv.renderer('bokeh').theme = create_bokeh_theme() | ||
|
|
||
| # ============================================================================ | ||
| # HOLOVIEWS OPTS SETUP - Define global configuration | ||
| # ============================================================================ | ||
|
|
||
| GLOBAL_BACKEND_OPTS={ | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried to set these GLOBAL_OPTS in the bokeh Theme but could not get it working. Especially the |
||
| 'plot.xgrid.visible': False, # Only horizontal grid lines | ||
| 'plot.ygrid.visible': True, | ||
| 'plot.ygrid.grid_line_color': "black", | ||
| 'plot.ygrid.grid_line_alpha': 0.1, | ||
| 'plot.min_border_left': 80, # Add padding on left (for y-axis label) | ||
| 'plot.min_border_bottom': 80, # Add padding on bottom (for x-axis label) | ||
| 'plot.min_border_right': 30, # Add padding on right | ||
| 'plot.min_border_top': 80, # Add padding on top | ||
| } | ||
|
|
||
| ElementPlot.param.backend_opts.default = GLOBAL_BACKEND_OPTS | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this way of setting global opts is a hack. But it did not work setting them via |
||
| ElementPlot.param.yformatter.default = NumeralTickFormatter(format='0a') # 1k, ... | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The default scientific notation on the y-axis is always a problem in a business context. Nobody uses it. |
||
|
|
||
| hv.opts.defaults( | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the defaults below would give holoviews a more fresh and modern look. |
||
| hv.opts.Bars( | ||
| color=ACCENT_COLOR, # Professional blue | ||
| line_color=None, # Remove bar borders | ||
| ), | ||
| hv.opts.Labels( | ||
| text_baseline='bottom', | ||
| text_font_size='11pt', | ||
| text_font_style='normal', | ||
| text_color='#333333', | ||
| ), | ||
| ) | ||
| hv.Cycle.default_cycles["default_colors"] = [ACCENT_COLOR, '#00948A', '#7E59BD', '#FFA20C', '#DA4341', '#D6F1FF', '#DAF5F4', '#F0E8FF', '#FFF8EA', '#FFF1EA', '#001142', '#003336', '#290031', '#371F00', '#3A0C13'] | ||
|
|
||
| # ============================================================================ | ||
| # DATA PIPELINE - Separate extraction, transformation, and plotting | ||
| # ============================================================================ | ||
|
|
||
| def get_earthquake_data(): | ||
| """Extract raw earthquake data from sample dataset""" | ||
| return hvsampledata.earthquakes("pandas") | ||
|
|
||
|
|
||
| def aggregate_by_magnitude(earthquake_data): | ||
| """Transform: Group earthquakes by magnitude class with statistics""" | ||
| # Aggregate: count events and calculate average depth per magnitude class | ||
| aggregated = ( | ||
| earthquake_data | ||
| .groupby('mag_class', observed=True) | ||
| .agg({'mag': 'count', 'depth': 'mean'}) | ||
| .reset_index() | ||
| .rename(columns={'mag': 'event_count', 'depth': 'avg_depth'}) | ||
| .sort_values('event_count', ascending=False) | ||
| ) | ||
|
|
||
| # Add percentage column for tooltips | ||
| aggregated['percentage'] = ( | ||
| aggregated['event_count'] / aggregated['event_count'].sum() * 100 | ||
| ) | ||
|
|
||
| return aggregated | ||
|
|
||
|
|
||
| def create_bar_chart(aggregated_data): | ||
| """Create publication-quality bar chart with labels and tooltips""" | ||
| default_tools=['save'] | ||
|
|
||
| # Main bar chart with professional styling | ||
| bar_chart = hv.Bars(aggregated_data, kdims='mag_class', vdims=['event_count', 'percentage', 'avg_depth']).opts( | ||
| # Titles and labels | ||
| title='Earthquake Distribution by Magnitude', | ||
| xlabel='Magnitude', | ||
| ylabel='Number of Events', | ||
|
|
||
| # Interactivity | ||
| # hover_cols = ["mag_class", "event_count", "percentage", "avg_depth"], | ||
| hover_tooltips=[ | ||
| ('Magnitude', '@mag_class'), | ||
| ('Events', '@event_count{0,0}'), # Format: 1,234 | ||
| ('Percentage', '@percentage{0 a}%'), # Format: 45% | ||
| ('Avg Depth', '@avg_depth{0f} km') # Format: 99 km | ||
| ], | ||
| default_tools=default_tools | ||
| ) | ||
|
|
||
| # Add text labels above bars | ||
| labels_data = aggregated_data.copy() | ||
| labels_data['label_y'] = labels_data['event_count'] + 20 # Offset above bars | ||
|
|
||
| text_labels = hv.Labels(labels_data, kdims=['mag_class', 'label_y'], vdims=['event_count', 'percentage', 'avg_depth']).opts( | ||
| hover_tooltips=[ | ||
| ('Magnitude', '@mag_class'), | ||
| ('Events', '@event_count{0,0}'), # Format: 1,234 | ||
| # tooltips below do currently not work on Labels | ||
| # ('Percentage', '@percentage{0 a}%'), # Format: 45% | ||
| # ('Avg Depth', '@avg_depth{0f} km'), # Format: 99 km | ||
| ], | ||
| default_tools=default_tools | ||
| ) | ||
|
|
||
| # Overlay: bar chart * text labels | ||
| return bar_chart * text_labels | ||
|
|
||
|
|
||
| def create_plot(): | ||
| """Main function: Extract → Transform → Plot""" | ||
| # Extract: Get raw data | ||
| earthquake_data = get_earthquake_data() | ||
|
|
||
| # Transform: Aggregate and calculate statistics | ||
| aggregated = aggregate_by_magnitude(earthquake_data) | ||
|
|
||
| # Visualize: Create publication-quality chart | ||
| chart = create_bar_chart(aggregated) | ||
|
|
||
| return chart | ||
|
|
||
|
|
||
| # ============================================================================ | ||
| # PANEL APP SETUP | ||
| # ============================================================================ | ||
|
|
||
| # Serve the chart when running with Panel | ||
| if pn.state.served: | ||
| # Load Panel JavaScript extensions | ||
| pn.extension() | ||
|
|
||
| # Apply custom Bokeh theme (override the global theme) | ||
| # Create and serve the chart | ||
| plot = create_plot() | ||
| pn.panel(plot, sizing_mode="stretch_both", margin=25).servable() | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wish there was a better font default in HoloViews/ Bokeh. Or a very easy way to change it.