from bokeh.plotting import figure, show, output_file
from bokeh.models import Label, GlobalInlineStyleSheet
from bokeh.layouts import row
import numpy as np
def get_dark_stylesheet():
"""Create a new dark theme stylesheet instance."""
return GlobalInlineStyleSheet(css="""
html, body, .bk, .bk-root {
background-color: #343838;
margin: 0;
padding: 0;
height: 100%;
color: white;
font-family: 'Consolas', 'Courier New', monospace;
}
.bk { color: white; }
.bk-input, .bk-btn, .bk-select, .bk-slider-title, .bk-headers,
.bk-label, .bk-title, .bk-legend, .bk-axis-label {
color: white !important;
}
.bk-input::placeholder { color: #aaaaaa !important; }
""")
def get_light_stylesheet():
"""Create a new light theme stylesheet instance."""
return GlobalInlineStyleSheet(css="""
html, body, .bk, .bk-root {
background-color: #FDFBD4;
margin: 0;
padding: 0;
height: 100%;
color: black;
font-family: 'Consolas', 'Courier New', monospace;
}
.bk { color: black; }
.bk-input, .bk-btn, .bk-select, .bk-slider-title, .bk-headers,
.bk-label, .bk-title, .bk-legend, .bk-axis-label {
color: black !important;
}
.bk-input::placeholder { color: #555555 !important; }
""")
def darken_color(hex_color, factor=0.7):
"""
Darken a hex color by a factor.
Parameters:
-----------
hex_color : str
Hex color code (e.g., '#ff0000')
factor : float
Darkening factor (0-1, lower is darker)
Returns:
--------
str : Darkened hex color
"""
hex_color = hex_color.lstrip('#')
r, g, b = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
r, g, b = int(r * factor), int(g * factor), int(b * factor)
return f'#{r:02x}{g:02x}{b:02x}'
def plot_3d_bars(categories, values, colors, labels=None,
title='3D Bar Chart', xlabel='', ylabel='',
width=800, height=600, bar_width=0.45,
dx=0.35, dy=80, dark_bg=True):
"""
Create a 3D bar chart with simple (non-stacked) bars.
Parameters:
-----------
categories : list
Category names for x-axis
values : list
Values for each category
colors : list
Colors for each bar
labels : list, optional
Labels for legend (if None, uses categories)
title : str
Chart title
xlabel, ylabel : str
Axis labels
width, height : int
Figure dimensions
bar_width : float
Width of bars (0-1)
dx, dy : float
3D depth offsets (horizontal and vertical)
dark_bg : bool
Use dark background theme
Returns:
--------
bokeh figure object
"""
# Validate inputs
if len(categories) != len(values) != len(colors):
raise ValueError("categories, values, and colors must have same length")
# Theme colors
bg_color = '#343838' if dark_bg else '#FDFBD4'
text_color = 'white' if dark_bg else 'black'
grid_color = '#404040' if dark_bg else '#e0e0e0'
# Calculate y-range with padding
max_val = max(values) * 1.5
# Create figure
p = figure(
width=width,
height=height,
title=title,
x_range=(-0.5, len(categories)),
y_range=(-10, max_val),
toolbar_location='right',
tools='pan,wheel_zoom,reset,save',
background_fill_color=bg_color,
border_fill_color=bg_color,
)
# Apply styling
p.title.text_color = text_color
p.title.text_font_size = '18pt'
p.title.text_font_style = 'bold'
p.xgrid.grid_line_color = grid_color
p.ygrid.grid_line_color = grid_color
p.xaxis.axis_line_color = text_color
p.yaxis.axis_line_color = text_color
p.xaxis.major_tick_line_color = text_color
p.yaxis.major_tick_line_color = text_color
p.xaxis.minor_tick_line_color = None
p.yaxis.minor_tick_line_color = None
p.xaxis.major_label_text_color = text_color
p.yaxis.major_label_text_color = text_color
p.xaxis.major_label_text_font_size = '11pt'
p.yaxis.major_label_text_font_size = '11pt'
p.outline_line_color = None
p.xaxis.ticker = list(range(len(categories)))
p.xaxis.major_label_overrides = {i: cat for i, cat in enumerate(categories)}
if ylabel:
p.yaxis.axis_label = ylabel
p.yaxis.axis_label_text_color = text_color
p.yaxis.axis_label_text_font_size = '12pt'
# Draw 3D bars
for i, (value, color) in enumerate(zip(values, colors)):
x_left = i - bar_width/2
x_right = i + bar_width/2
# Right side face (darker)
right_x = [x_right, x_right + dx, x_right + dx, x_right, x_right]
right_y = [0, dy, value + dy, value, 0]
p.patch(right_x, right_y, color=darken_color(color, 0.6),
alpha=1.0, line_color='#000000', line_width=1)
# Top face (medium shade)
top_x = [x_left, x_right, x_right + dx, x_left + dx, x_left]
top_y = [value, value, value + dy, value + dy, value]
p.patch(top_x, top_y, color=darken_color(color, 0.8),
alpha=1.0, line_color='#000000', line_width=1)
# Front face (brightest)
p.quad(left=[x_left], right=[x_right], bottom=[0], top=[value],
color=color, alpha=1.0, line_color='#000000', line_width=1.5)
return p
def plot_3d_stacked_bars(categories, data_dict, colors, labels,
title='3D Stacked Bar Chart', xlabel='', ylabel='',
width=800, height=600, bar_width=0.45,
dx=0.35, dy=80, dark_bg=True):
"""
Create a 3D stacked bar chart.
Parameters:
-----------
categories : list
Category names for x-axis
data_dict : dict
Dictionary mapping categories to lists of values (bottom to top)
colors : list
Colors for each stack segment
labels : list
Labels for each stack segment
title : str
Chart title
xlabel, ylabel : str
Axis labels
width, height : int
Figure dimensions
bar_width : float
Width of bars (0-1)
dx, dy : float
3D depth offsets (horizontal and vertical)
dark_bg : bool
Use dark background theme
Returns:
--------
bokeh figure object
"""
# Validate inputs
if not all(cat in data_dict for cat in categories):
raise ValueError("data_dict must contain all categories")
# Theme colors
bg_color = '#343838' if dark_bg else '#FDFBD4'
text_color = 'white' if dark_bg else 'black'
grid_color = '#404040' if dark_bg else '#e0e0e0'
# Calculate y-range
max_val = max(sum(data_dict[cat]) for cat in categories) * 1.4
# Create figure
p = figure(
width=width,
height=height,
title=title,
x_range=(-0.5, len(categories)),
y_range=(-50, max_val),
toolbar_location='right',
tools='pan,wheel_zoom,reset,save',
background_fill_color=bg_color,
border_fill_color=bg_color,
)
# Apply styling
p.title.text_color = text_color
p.title.text_font_size = '18pt'
p.title.text_font_style = 'bold'
p.xgrid.grid_line_color = grid_color
p.ygrid.grid_line_color = grid_color
p.xaxis.axis_line_color = text_color
p.yaxis.axis_line_color = text_color
p.xaxis.major_tick_line_color = text_color
p.yaxis.major_tick_line_color = text_color
p.xaxis.minor_tick_line_color = None
p.yaxis.minor_tick_line_color = None
p.xaxis.major_label_text_color = text_color
p.yaxis.major_label_text_color = text_color
p.xaxis.major_label_text_font_size = '11pt'
p.yaxis.major_label_text_font_size = '11pt'
p.outline_line_color = None
p.xaxis.ticker = list(range(len(categories)))
p.xaxis.major_label_overrides = {i: cat for i, cat in enumerate(categories)}
if ylabel:
p.yaxis.axis_label = ylabel
p.yaxis.axis_label_text_color = text_color
p.yaxis.axis_label_text_font_size = '12pt'
# Draw 3D stacked bars
for i, category in enumerate(categories):
cumulative = 0
category_data = data_dict[category]
for j, (value, color) in enumerate(zip(category_data, colors)):
bottom = cumulative
top = cumulative + value
x_left = i - bar_width/2
x_right = i + bar_width/2
# Right side face (darker)
right_x = [x_right, x_right + dx, x_right + dx, x_right, x_right]
right_y = [bottom, bottom + dy, top + dy, top, bottom]
p.patch(right_x, right_y, color=darken_color(color, 0.6),
alpha=1.0, line_color='#000000', line_width=1)
# Top face (only for top segment)
if j == len(category_data) - 1:
top_x = [x_left, x_right, x_right + dx, x_left + dx, x_left]
top_y = [top, top, top + dy, top + dy, top]
p.patch(top_x, top_y, color=darken_color(color, 0.8),
alpha=1.0, line_color='#000000', line_width=1)
# Front face (brightest)
p.quad(left=[x_left], right=[x_right], bottom=[bottom], top=[top],
color=color, alpha=1.0, line_color='#000000', line_width=1.5)
cumulative = top
return p
def create_legend(labels, colors, dark_bg=True, height=600):
"""
Create a separate legend figure.
Parameters:
-----------
labels : list
Legend labels
colors : list
Colors corresponding to labels
dark_bg : bool
Use dark background theme
height : int
Height of legend figure
Returns:
--------
bokeh figure object
"""
text_color = 'white' if dark_bg else 'black'
bg_color = '#343838' if dark_bg else '#FDFBD4'
# Calculate required height based on number of items
item_height = 40 # Height per legend item
total_height = len(labels) * item_height + 60
legend_fig = figure(
width=250,
height=min(total_height, height),
toolbar_location=None,
background_fill_color=bg_color,
border_fill_color=bg_color,
outline_line_color=None,
x_range=(0, 1),
y_range=(0, len(labels) * item_height + 20)
)
# Remove axes and grid
legend_fig.xaxis.visible = False
legend_fig.yaxis.visible = False
legend_fig.xgrid.visible = False
legend_fig.ygrid.visible = False
# Add legend items from top to bottom
for i, (label, color) in enumerate(zip(labels, colors)):
y_pos = (len(labels) - i) * item_height - 10
# Colored circle
legend_fig.circle(x=[0.1], y=[y_pos], size=18, color=color,
alpha=1.0, line_color='#000000', line_width=2)
# Label text positioned right next to the circle
label_obj = Label(
x=0.18, y=y_pos - 7,
text=label,
text_color=text_color,
text_font_size='12pt'
)
legend_fig.add_layout(label_obj)
return legend_fig
# ============================================================================
# EXAMPLE USAGE
# ============================================================================
if __name__ == "__main__":
from bokeh.io import reset_output
# Example 1: 3D Stacked Bar Chart - Electricity Production (Dark Theme)
print("Creating 3D Stacked Bar Chart...")
reset_output()
years = ['2018', '2019', '2020', '2021', '2022']
countries = ['Saudi Arabia', 'France', 'South Korea', 'Germany']
colors_stacked = ['#f4d03f', '#e67e22', '#5dade2', '#2ecc71']
data = {
'2018': [250, 500, 1000, 700],
'2019': [250, 450, 900, 700],
'2020': [200, 300, 850, 700],
'2021': [150, 250, 900, 700],
'2022': [100, 100, 900, 600]
}
stacked_chart = plot_3d_stacked_bars(
categories=years,
data_dict=data,
colors=colors_stacked,
labels=countries,
title='Electricity Production by Country (2018-2022)',
ylabel='TWh (Terawatt-hours)',
width=900,
height=600
)
legend_stacked = create_legend(countries, colors_stacked)
layout_stacked = row(stacked_chart, legend_stacked, stylesheets=[get_dark_stylesheet()])
output_file("3d_stacked_bar_chart.html")
show(layout_stacked)
# Example 2: Simple 3D Bar Chart - Quarterly Sales (Light Theme)
print("Creating Simple 3D Bar Chart - Quarterly Sales...")
reset_output()
categories = ['Q1', 'Q2', 'Q3', 'Q4']
values = [450, 580, 620, 700]
colors_simple = ['#3498db', '#e74c3c', '#2ecc71', '#f39c12']
labels_simple = ['Quarter 1', 'Quarter 2', 'Quarter 3', 'Quarter 4']
simple_chart = plot_3d_bars(
categories=categories,
values=values,
colors=colors_simple,
labels=labels_simple,
title='Quarterly Sales Performance - 2024',
ylabel='Revenue ($K)',
width=900,
height=600,
dark_bg=False
)
legend_simple = create_legend(labels_simple, colors_simple, dark_bg=False)
layout_simple = row(simple_chart, legend_simple, stylesheets=[get_light_stylesheet()])
output_file("3d_simple_bar_chart.html")
show(layout_simple)
# Example 3: Product Sales Comparison (Dark Theme)
print("Creating Product Sales Chart...")
reset_output()
products = ['Product A', 'Product B', 'Product C', 'Product D', 'Product E']
sales = [1200, 950, 1450, 800, 1100]
colors_products = ['#9b59b6', '#e91e63', '#00bcd4', '#ff9800', '#4caf50']
product_chart = plot_3d_bars(
categories=products,
values=sales,
colors=colors_products,
labels=products,
title='Product Sales Comparison - December 2024',
ylabel='Units Sold',
width=900,
height=600
)
legend_products = create_legend(products, colors_products)
layout_products = row(product_chart, legend_products, stylesheets=[get_dark_stylesheet()])
output_file("3d_product_sales.html")
show(layout_products)
# Example 4: Regional Market Share Stacked (Light Theme)
print("Creating Regional Market Share Chart...")
reset_output()
regions = ['North', 'South', 'East', 'West']
segments = ['Enterprise', 'SMB', 'Consumer']
colors_segments = ['#e74c3c', '#3498db', '#2ecc71']
market_data = {
'North': [300, 450, 250],
'South': [200, 350, 300],
'East': [400, 500, 350],
'West': [350, 400, 280]
}
market_chart = plot_3d_stacked_bars(
categories=regions,
data_dict=market_data,
colors=colors_segments,
labels=segments,
title='Regional Market Share by Segment',
ylabel='Revenue ($M)',
width=900,
height=600,
dark_bg=False
)
legend_market = create_legend(segments, colors_segments, dark_bg=False)
layout_market = row(market_chart, legend_market, stylesheets=[get_light_stylesheet()])
output_file("3d_market_share.html")
show(layout_market)
1 Like


