# Radar and Polar Bar Plots

**URL:** <https://discourse.bokeh.org/t/radar-and-polar-bar-plots/12255>\
**Category:** Showcase\
**Created:** [January 24, 2025, 4:30pm UTC](https://discourse.bokeh.org/t/radar-and-polar-bar-plots/12255 "2025-01-24T16:30:13Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![mixstam1453](https://discourse.bokeh.org/user_avatar/discourse.bokeh.org/mixstam1453/32/7102_2.png) [@mixstam1453](https://discourse.bokeh.org/u/mixstam1453)\
**Post date:** [January 24, 2025, 4:30pm UTC](https://discourse.bokeh.org/t/radar-and-polar-bar-plots/12255/1 "2025-01-24T16:30:13Z")

</div>

Hello,

My tries for radar and polar bar plots. I think they look good. Feel free to share or extend.

 ![radar](https://discourse-uploads.bokeh.org/original/2X/a/a6465b7ddb08adcfe7a8d3179668db2e1164d115.png)  
 ![polar](https://discourse-uploads.bokeh.org/original/2X/1/1e887573b277518e1d7f41312993d20cfba5250d.png)

1. Radar plot

```auto
import numpy as np
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, HoverTool, Label
from bokeh.palettes import Spectral6
from bokeh.io import curdoc

# Generate sample data for multiple series
n_variables = 8
n_series = 3

angles = np.linspace(0, 2*np.pi, n_variables, endpoint=False)
categories = [f'Variable {i+1}' for i in range(n_variables)]

# Generate random data for each series
series_data = []
for i in range(n_series):
    values = np.random.normal(0.6, 0.2, n_variables)
    values = np.clip(values, 0.1, 1.0) # Ensure values are between 0.1 and 1
    series_data.append(values)

# Close the polygons by appending the first value
angles = np.append(angles, angles[0])
categories = np.append(categories, categories[0])
for i in range(n_series):
    series_data[i] = np.append(series_data[i], series_data[i][0])

# Convert to cartesian coordinates
def get_cartesian_coordinates(values, angles):
    x = values * np.cos(angles)
    y = values * np.sin(angles)
    return x, y

# Create the figure
p = figure(width=600, height=600, title="Polar Radar Chart",
           x_range=(-1.7, 1.7), y_range=(-1.7, 1.7),
           tools="pan,box_zoom,wheel_zoom,reset,save",
           background_fill_color="#1A1A1A",
           border_fill_color="#1A1A1A")

# Add the polygons for each series
colors = Spectral6[:n_series]
sources = []
for i in range(n_series):
    x, y = get_cartesian_coordinates(series_data[i], angles)
    source = ColumnDataSource(data=dict(
        x=x, y=y,
        value=series_data[i],
        category=categories,
        series=[f'Series {i+1}'] * len(x)
    ))
    sources.append(source)
    
    # Add filled polygon
    p.patch('x', 'y',
            fill_color=colors[i],
            fill_alpha=0.2,
            line_color=colors[i],
            line_width=2,
            legend_label=f'Series {i+1}',
            source=source)
    
    # Add points
    p.scatter('x', 'y',
              size=8,
              fill_color=colors[i],
              line_color="white",
              source=source)

# Add hover tool
hover = HoverTool(tooltips=[
    ('Series', '@series'),
    ('Category', '@category'),
    ('Value', '@value{0.00}')
])
p.add_tools(hover)

# Style the plot
p.grid.grid_line_color = "gray"
p.grid.grid_line_alpha = 0.2
p.outline_line_color = "white"
p.title.text_color = "white"
p.legend.background_fill_color = "#2A2A2A"
p.legend.background_fill_alpha = 0.7
p.legend.border_line_color = "white"
p.legend.border_line_alpha = 0.5
p.legend.label_text_color = "white"
p.xaxis.visible = False
p.yaxis.visible = False

# Add circular grid lines
radii = np.linspace(0.2, 1, 5)
theta = np.linspace(0, 2*np.pi, 100)
for radius in radii:
    x_circle = radius * np.cos(theta)
    y_circle = radius * np.sin(theta)
    p.line(x_circle, y_circle, line_color="gray", line_alpha=0.2)
    # Add radius label
    label = Label(x=radius-0.1, y=0, text=f'{radius:.1f}',
                 text_color="white", text_alpha=0.6)
    p.add_layout(label)

# Add radial lines and category labels
for angle, category in zip(angles[:-1], categories[:-1]):
    x_line = [0, np.cos(angle)]
    y_line = [0, np.sin(angle)]
    p.line(x_line, y_line, line_color="gray", line_alpha=0.2)
    
    # Add category label
    label_x = 1.3 * np.cos(angle)
    label_y = 1.3 * np.sin(angle)
    label = Label(x=label_x, y=label_y, text=category,
                 text_color="white", text_align="center")
    p.add_layout(label)

# Show the plot
show(p)

```

1. Polar Bar plot

```auto
from bokeh.plotting import figure, show
from bokeh.io import output_file
from bokeh.models import ColumnDataSource, HoverTool
from bokeh.palettes import Spectral6
import numpy as np
from math import pi

# Generate sample data
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
values = np.random.randint(10, 100, 12)

# Calculate angles for each month
angles = [i*2*pi/12 for i in range(12)]
start_angles = [angle - pi/12 for angle in angles]
end_angles = [angle + pi/12 for angle in angles]

# Create ColumnDataSource
source = ColumnDataSource(data=dict(
    months=months,
    values=values,
    start_angles=start_angles,
    end_angles=end_angles,
    colors=Spectral6 * 2, # Repeat colors to match number of months
    x=values * np.cos(angles),
    y=values * np.sin(angles)
))

# Create figure
p = figure(width=800, height=800, title="Polar Bar Chart",
          tools="pan,wheel_zoom,box_zoom,reset,save",
          x_range=(-150, 150), y_range=(-150, 150))

# Add wedges
wedges = p.wedge(x=0, y=0, radius='values',
                 start_angle='start_angles',
                 end_angle='end_angles',
                 fill_color='colors',
                 fill_alpha=0.7,
                 line_color="white",
                 source=source,
                 legend_field='months')

# Add hover tool
hover = HoverTool(renderers=[wedges],tooltips=[
    
    ('Month', '@months'),
    ('Value', '@values')
])
p.add_tools(hover)

# Add circular grid lines
for radius in np.linspace(0, 100, 11):
    circle = p.circle(x=0, y=0, radius=radius, fill_color=None,
                     line_color='gray', line_alpha=0.3)
    if radius > 0:
        label = p.text(x=[0], y=[radius], text=[f'{int(radius)}'],color="lime",
                      text_baseline="bottom", text_align="center")

# Add month labels at a fixed radius
label_radius = 110
x_labels = label_radius * np.cos(angles)
y_labels = label_radius * np.sin(angles)

label_source = ColumnDataSource(data=dict(
    x=x_labels,
    y=y_labels,
    months=months
))

labels = p.text(x='x', y='y', text='months',
               text_align='center', text_baseline='middle', color="grey",
               source=label_source)

# Customize the plot
p.grid.grid_line_color = None
p.axis.visible = False
p.outline_line_color = None

# Move legend to the right
p.legend.location = "right"
p.legend.click_policy = "hide" # Allow toggling bars by clicking legend

# Output to file
output_file("polar_bar.html")
show(p)

```

---

<div class="post-metadata">

**Author:** ![Bryan](https://discourse.bokeh.org/user_avatar/discourse.bokeh.org/bryan/32/3625_2.png) [@Bryan](https://discourse.bokeh.org/u/Bryan)\
**Post date:** [January 24, 2025, 5:20pm UTC](https://discourse.bokeh.org/t/radar-and-polar-bar-plots/12255/2 "2025-01-24T17:20:51Z")

</div>

@mixstam1453 you might be able to use a `PolarTransform` to simplify the radar plot

[polar — Bokeh 3.6.2 Documentation](https://docs.bokeh.org/en/latest/docs/examples/plotting/polar.html)

---

<div class="post-metadata">

**Author:** ![gmerritt123](https://discourse.bokeh.org/user_avatar/discourse.bokeh.org/gmerritt123/32/4101_2.png) [@gmerritt123](https://discourse.bokeh.org/u/gmerritt123)\
**Post date:** [January 24, 2025, 7:57pm UTC](https://discourse.bokeh.org/t/radar-and-polar-bar-plots/12255/3 "2025-01-24T19:57:35Z")

</div>

Ha nice, I had to cook these up on a project a while back, but the wrinkle was that the “radius” had to be log scaled. I’ll share if I find time to make an MRE 😃

---

<div class="post-metadata">

**Author:** ![mixstam1453](https://discourse.bokeh.org/user_avatar/discourse.bokeh.org/mixstam1453/32/7102_2.png) [@mixstam1453](https://discourse.bokeh.org/u/mixstam1453)\
**Post date:** [January 24, 2025, 11:47pm UTC](https://discourse.bokeh.org/t/radar-and-polar-bar-plots/12255/4 "2025-01-24T23:47:50Z")

</div>

Nice option!  
Here is the code for Radar using `PolarTransform`. Here I have also added the legend click policy and fixed the tooltip.

```auto
import numpy as np
from bokeh.plotting import figure, show
from bokeh.models import (
    ColumnDataSource, HoverTool, PolarTransform, 
    Label, FixedTicker
)
from bokeh.palettes import Spectral6
from bokeh.io import output_file

# Generate sample data
n_variables = 8
n_series = 3
angles = np.linspace(0, 2*np.pi, n_variables, endpoint=False)
categories = [f'Variable {i+1}' for i in range(n_variables)]

# Generate random data
np.random.seed(42)
series_data = []
for _ in range(n_series):
    values = np.random.uniform(0.2, 1.0, n_variables)
    series_data.append(values)

# Close the polygons
angles_closed = np.append(angles, angles[0])
categories_closed = np.append(categories, categories[0])
series_data_closed = [np.append(series, series[0]) for series in series_data]

# Create polar transform
polar_transform = PolarTransform()

# Create figure
p = figure(
    width=600, height=600, 
    title="Advanced Radar Chart",
    x_range=(-1.7, 1.7), 
    y_range=(-1.7, 1.7),
    tools="pan,box_zoom,wheel_zoom,reset,save"
)

# Plot each series
colors = Spectral6[:n_series]
rr=[]

for i, (series, color) in enumerate(zip(series_data_closed, colors)):
    source = ColumnDataSource(data=dict(
        radius=series,
        angle=angles_closed,
        category=categories_closed,
        series=[f'Series {i+1}'] * len(series)
    ))
    
    # Use PolarTransform for plotting
    p.patch(x=polar_transform.x, 
            y=polar_transform.y,
            fill_color=colors[i],
            fill_alpha=0.2,
            line_color=colors[i],
            line_width=2,
            legend_label=f'Series {i+1}',
            source=source)
    
    
    rri = p.scatter(
        x=polar_transform.x, 
        y=polar_transform.y,
        size=8,
        color=color,
        source=source
    )
    rr.append(rri)

# Circular grid lines with PolarTransform
radii = np.linspace(0.2, 1.0, 5)
for radius in radii:
    # Create circular source
    theta = np.linspace(0, 2*np.pi, 100)
    circle_source = ColumnDataSource(data=dict(
        radius=[radius]*100,
        angle=theta
    ))
    
    # Plot circular grid lines
    p.line(
        x=polar_transform.x, 
        y=polar_transform.y,
        line_color="gray", 
        line_alpha=0.2,
        source=circle_source
    )
    
    # Add radius labels
    label_source = ColumnDataSource(data=dict(
        radius=[radius],
        angle=[3*np.pi/2],
        text=[f'{radius:.1f}'] # Add the text as a column
    ))
    
    p.text(
        x=polar_transform.x, 
        y=polar_transform.y,
        text='text', # Reference the text column
        source=label_source,
        text_color="lime",
        text_alpha=0.6
    )
# Radial lines and category labels
for angle, category in zip(angles, categories):
    # Radial lines
    radial_source = ColumnDataSource(data=dict(
        radius=[0, 1],
        angle=[angle, angle]
    ))
    
    p.line(
        x=polar_transform.x, 
        y=polar_transform.y,
        line_color="gray", 
        line_alpha=0.2,
        source=radial_source
    )
    
    # Category labels
    label_source = ColumnDataSource(data=dict(
        radius=[1.3],
        angle=[angle],text = [category],
    ))
    
    p.text(
        x=polar_transform.x, 
        y=polar_transform.y,
        text='text',
        source=label_source,
        text_color="lime",
        text_align="center"
    )

# Hover tool
hover = HoverTool(
    
    renderers = rr,
    tooltips=[
    ('Series', '@series'),
    ('Category', '@category'),
    ('Value', '@radius{0.2f}')
])
p.add_tools(hover)

# Styling
p.title.text_color = "navy"
p.grid.grid_line_color = None
p.xaxis.visible = False
p.yaxis.visible = False
p.legend.click_policy = "hide"

output_file("polar_radar_chart_enhanced.html")
show(p)

```
