Gradient fill color

Hey,

Here is an example of how to use a custom gradient fill colour. The hatch_extra does the job really nice.

from bokeh.plotting import figure, show
from bokeh.io import curdoc
from bokeh.models import HoverTool,ImageURLTexture
curdoc().theme = 'dark_minimal'

fruits = ['Apples', 'Pears', 'Nectarines', 'Plums', 'Grapes', 'Strawberries']
counts = [5, 3, 4, 2, 4, 6]

hover = HoverTool(
    tooltips = [
        ("val", "@values"),
        ("(x,y)", "($x{int}, $y{int})")
    ]
)


p = figure(x_range=fruits, height=350, title="Fruit Counts",
           toolbar_location=None, tools=[hover, "pan, wheel_zoom, save"], active_scroll="wheel_zoom", sizing_mode="scale_both")

p.vbar(x=fruits, top=counts, width=0.9,border_radius=19,
             hatch_extra={ 'mycustom': ImageURLTexture(url='https://www.publicdomainpictures.net/pictures/240000/nahled/color-gradient-background.jpg') },
       hatch_pattern = dict(value="mycustom"),
      hover_fill_alpha=1,
             hover_fill_color="fill_colors", hover_line_color="black", hover_line_width=6)

p.xgrid.grid_line_color = None
p.y_range.start = 0

show(p)
1 Like

This is related to this actually → Defined gradient fill color on varea/patches - #2 by Bryan

The MRE posted suffers from the same “problem” I ran into (i.e. undesired behaviour) - using a gradient image as a texture only “works” at a “sweet spot” in y range start/end values (the image starts repeating otherwise):

wonk

2 Likes

Ah, I didn’t catch that—thanks for pointing it out!

1 Like

It’s still a neat setup, and nice to have MRE posted here :smiley:

2 Likes

A try for varea:

from bokeh.plotting import figure, show
from bokeh.models import InlineStyleSheet
from bokeh.layouts import column
import numpy as np

# Generate sample data
x = np.linspace(0, 4 * np.pi, 100)
y = np.sin(x) * 2 - 1 

# Create figure
p = figure(
    width=800,
    height=600,
    title="Gradient Background with Line Masking",
    x_axis_label="X",
    y_axis_label="Y",
    y_range=(y.min()-np.abs(y.min())/2, y.max()+np.abs(y.max())/2),  # Set fixed y range
    x_range=(x.min(), x.max())
)

max_y = y.max()*100
p.varea(
    x=x,
    y1=y,        # Bottom of mask = the line
    y2=max_y,    # Top of mask = top of plot
    fill_color='tan',
    fill_alpha=1,
)

# make tan before the x.min
p.varea(
    x=np.linspace(-100, 0, 100),
    y1=-max_y,       
    y2=max_y,    
    fill_color='tan',
    fill_alpha=1,
)

# make tan after the x.max
p.varea(
    x=np.linspace(4 * np.pi, 100, 100),
    y1=-max_y,       
    y2=max_y,    
    fill_color='tan',
    fill_alpha=1,
)

# Step 3: Draw the line on top (this creates the "border" between areas)
p.line(
    x=x,
    y=y,
    line_color='black',
    line_width=3,
    line_alpha=1.0
)

# Styling
p.background_fill_color = None    
p.border_fill_color = "white"         
p.outline_line_color = "#cccccc"
p.grid.grid_line_color = None
p.grid.grid_line_alpha = 0
p.title.text_font_size = "16pt"
p.title.text_color = "#2c3e50"

gradient_css = InlineStyleSheet(css="""
:host {
background: linear-gradient(to bottom, #1de9b6 0%, #b2ff59 50%, #ffd600 100%);
}
""")
show(column(p, stylesheets=[gradient_css]))


from bokeh.plotting import figure, show
from bokeh.models import InlineStyleSheet, ColumnDataSource, HoverTool
from bokeh.layouts import row, column
import numpy as np

def make_neon_plot(gradient_css, title):
    x = np.linspace(0, 4 * np.pi, 100)
    y = np.sin(x) * 2 - 1 
    source = ColumnDataSource(data=dict(x=x, y=y))
    max_y = y.max()*100
    dark_mask = "#181843"

    p = figure(
        width=500,
        height=400,
        title=title,
        x_axis_label="X",
        y_axis_label="Y",
        y_range=(y.min()-np.abs(y.min())/2, y.max()+np.abs(y.max())/2),
        x_range=(x.min(), x.max())
    )

    # varea: under the curve (to top)
    p.varea(
        x=x,
        y1=y,
        y2=max_y,
        fill_color=dark_mask,
        fill_alpha=0.98,
    )
    # varea: before left, after right
    p.varea(
        x=np.linspace(-100, 0, 100),
        y1=-max_y,       
        y2=max_y,    
        fill_color=dark_mask,
        fill_alpha=0.98,
    )
    p.varea(
        x=np.linspace(4 * np.pi, 100, 100),
        y1=-max_y,       
        y2=max_y,    
        fill_color=dark_mask,
        fill_alpha=0.98,
    )

    # Neon line (cyan)
    p.line(
        x='x',
        y='y',
        source=source,
        line_color="#03e9f4",  # Neon cyan
        line_width=4,
        line_alpha=1.0,
        name="main_line"
    )

    hover = HoverTool(
        tooltips=[
            ("X", "@x{0.00}"),
            ("Y", "@y{0.00}")
        ],
        mode='vline',
        renderers=[p.select(name="main_line")[0]]
    )
    p.add_tools(hover)

    # Styling
    p.background_fill_color = None
    p.border_fill_color = "#0b0824"
    p.outline_line_color = "#181843"
    p.grid.grid_line_color = "#282872"
    p.grid.grid_line_alpha = 0.12
    p.title.text_font_size = "15pt"
    p.title.text_color = "#f500ea"
    p.xaxis.axis_label_text_color = "#1de9b6"
    p.yaxis.axis_label_text_color = "#1de9b6"
    p.xaxis.major_label_text_color = "#b2ff59"
    p.yaxis.major_label_text_color = "#b2ff59"

    return p, InlineStyleSheet(css=gradient_css)

# Neon gradients
vertical_css = """
:host {
  background: linear-gradient(to bottom,
    #22223b 0%,
    #383ba8 40%,
    #7b2ff2 55%,
    #f357a8 75%,
    #03e9f4 100%);
}
"""

horizontal_css = """
:host {
  background: linear-gradient(to right,
    #22223b 0%,
    #383ba8 40%,
    #7b2ff2 55%,
    #f357a8 75%,
    #03e9f4 100%);
}
"""

diagonal_css = """
:host {
  background: linear-gradient(45deg,
    #22223b 0%,
    #383ba8 40%,
    #7b2ff2 55%,
    #f357a8 75%,
    #03e9f4 100%);
}
"""

p1, css1 = make_neon_plot(vertical_css, "Top-to-Bottom Neon Gradient")
p2, css2 = make_neon_plot(horizontal_css, "Left-to-Right Neon Gradient")
p3, css3 = make_neon_plot(diagonal_css, "45° Angle Neon Gradient")

show(row(
    column(p1, stylesheets=[css1]),
    column(p2, stylesheets=[css2]),
    column(p3, stylesheets=[css3])
))

from bokeh.plotting import figure, show
from bokeh.models import InlineStyleSheet, ColumnDataSource, HoverTool
from bokeh.layouts import row, column
import numpy as np

def make_fancy_light_plot(gradient_css, title):
    x = np.linspace(0, 4 * np.pi, 100)
    y = np.sin(x) * 2 - 1 
    source = ColumnDataSource(data=dict(x=x, y=y))
    max_y = y.max()*100
    pastel_mask = "#fff3e0"  # Light pastel orange/peach

    p = figure(
        width=500,
        height=400,
        title=title,
        x_axis_label="X",
        y_axis_label="Y",
        y_range=(y.min()-np.abs(y.min())/2, y.max()+np.abs(y.max())/2),
        x_range=(x.min(), x.max())
    )

    # Mask area: pastel for contrast, not too dark
    p.varea(
        x=x,
        y1=y,
        y2=max_y,
        fill_color=pastel_mask,
        fill_alpha=0.95,
    )
    p.varea(
        x=np.linspace(-100, 0, 100),
        y1=-max_y,       
        y2=max_y,    
        fill_color=pastel_mask,
        fill_alpha=0.95,
    )
    p.varea(
        x=np.linspace(4 * np.pi, 100, 100),
        y1=-max_y,       
        y2=max_y,    
        fill_color=pastel_mask,
        fill_alpha=0.95,
    )

    # Bright line: electric blue/violet for eye-catching contrast
    p.line(
        x='x',
        y='y',
        source=source,
        line_color="#5f2eea",  # Vibrant violet
        line_width=4,
        line_alpha=1.0,
        name="main_line"
    )

    hover = HoverTool(
        tooltips=[
            ("X", "@x{0.00}"),
            ("Y", "@y{0.00}")
        ],
        mode='vline',
        renderers=[p.select(name="main_line")[0]]
    )
    p.add_tools(hover)

    # Fancy light theme styling
    p.background_fill_color = None
    p.border_fill_color = "#fffde7"   # Light yellow
    p.outline_line_color = "#ffe082"
    p.grid.grid_line_color = "#ffb74d"
    p.grid.grid_line_alpha = 0.17
    p.title.text_font_size = "15pt"
    p.title.text_color = "#ea4c89"    # Fancy pink
    p.xaxis.axis_label_text_color = "#00c9b7"  # Aqua
    p.yaxis.axis_label_text_color = "#00c9b7"
    p.xaxis.major_label_text_color = "#fe8c00"  # Orange
    p.yaxis.major_label_text_color = "#fe8c00"

    return p, InlineStyleSheet(css=gradient_css)

# Fancy light gradients
vertical_css = """
:host {
  background: linear-gradient(to bottom,
    #fffde7 0%,
    #ffe082 25%,
    #ffb74d 55%,
    #fcb69f 75%,
    #a1c4fd 100%);
}
"""

horizontal_css = """
:host {
  background: linear-gradient(to right,
    #fffde7 0%,
    #ffe082 25%,
    #ffb74d 55%,
    #eaafc8 75%,
    #d4fc79 100%);
}
"""

diagonal_css = """
:host {
  background: linear-gradient(45deg,
    #fffde7 0%,
    #b2fefa 30%,
    #fcb69f 55%,
    #fdc094 75%,
    #eaafc8 100%);
}
"""

p1, css1 = make_fancy_light_plot(vertical_css, "Top-to-Bottom Fancy Gradient")
p2, css2 = make_fancy_light_plot(horizontal_css, "Left-to-Right Fancy Gradient")
p3, css3 = make_fancy_light_plot(diagonal_css, "45° Angle Fancy Gradient")

show(row(
    column(p1, stylesheets=[css1]),
    column(p2, stylesheets=[css2]),
    column(p3, stylesheets=[css3])
))

A try for Bar plot!

A. Using colormap:

import numpy as np
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, HoverTool, InlineStyleSheet, FixedTicker
from bokeh.layouts import column
import matplotlib.cm as cm

# Data
categories = ['A', 'B', 'C', 'D']
values = [2, 4, 3, 5]
num_bars = len(values)
bar_width = 0.8
image_height = 100

x_numeric = np.arange(num_bars)
max_val = max(values)

# Create figure
p = figure(
    width=600, height=400,
    x_range=(-0.5, num_bars - 0.5),
    y_range=(0, max_val + 2),
    title="🌈 Vertical Gradient Bars",
    tools="pan,wheel_zoom,box_zoom,reset,save",
    toolbar_location="right"
)

rgba_images = []
for val in values:
    frac = val / max_val
    h_px = max(1, int(frac * image_height))  # Height in px for this bar
    gradient = np.linspace(0, frac, h_px)
    colors = (cm.get_cmap('plasma')(gradient)[:, :3] * 255).astype(np.uint8)

    rgba = np.zeros((image_height, 20, 4), dtype=np.uint8)
    if h_px > 0:
        rgba[:h_px, :, :3] = colors[:, None, :]        # Fill from bottom pixel up!
        rgba[:h_px, :, 3] = 255
    # The top stays transparent!

    # No flipud!
    packed = rgba.view(dtype=np.uint32).reshape((image_height, 20))
    rgba_images.append(packed)

# Draw bars, all bottom-aligned at y=0, growing up
dw = bar_width
dh = max_val
for i in range(num_bars):
    p.image_rgba(
        image=[rgba_images[i]],
        x=[i - dw / 2],  # center on tick
        y=[0],           # always starts at y=0!
        dw=dw,
        dh=dh
    )

# Invisible vbars for hover
source = ColumnDataSource(data=dict(
    x=x_numeric,
    top=values,
    tooltip=[f"Category: {cat}<br>Value: {val}" for cat, val in zip(categories, values)]
))
p.vbar(
    x='x',
    top='top',
    width=bar_width,
    source=source,
    fill_alpha=0,
    line_alpha=0,
    name="bars"
)

hover = HoverTool(
    tooltips="@tooltip",
    mode='vline',
    name="bars"
)
p.add_tools(hover)

# Set tick labels and style
p.xaxis.ticker = FixedTicker(ticks=list(x_numeric))
p.xaxis.major_label_overrides = {i: cat for i, cat in enumerate(categories)}
p.xaxis.major_label_text_color = "white"
p.title.text_color = "white"
p.title.text_font_size = "16pt"
p.yaxis.major_label_text_color = "white"
p.xaxis.axis_line_color = "white"
p.yaxis.axis_line_color = "white"
p.yaxis.axis_label_text_color = "white"
p.xaxis.axis_label_text_color = "white"
p.background_fill_color = None
p.border_fill_color = "#121212"
p.outline_line_color = "#444"
p.grid.grid_line_color = "#333"
p.grid.grid_line_alpha = 0.3

gradient_css = InlineStyleSheet(css="""
:host {
  background: radial-gradient(circle at center, #263238 0%, #000000 100%);
}
""")

show(column(p, stylesheets=[gradient_css]))

import numpy as np
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, HoverTool, InlineStyleSheet, FixedTicker
from bokeh.layouts import column
import matplotlib.cm as cm

# Data
categories = ['A', 'B', 'C', 'D']
values = [2, 4, 3, 5]
num_bars = len(values)
bar_height = 0.8
image_width = 100  # max gradient resolution

# Use numeric y
y_numeric = np.arange(num_bars)

# Figure
p = figure(
    width=600, height=400,
    y_range=(-0.5, num_bars - 0.5),
    x_range=(0, max(values) + 2),
    title="🌈 Horizontal Gradient Bars",
    tools="pan,wheel_zoom,box_zoom,reset,save",
    toolbar_location="right"
)

rgba_images = []
for val in values:
    # Compute how much of the colorbar to use for this bar
    frac = val / max(values)
    w_px = max(1, int(frac * image_width))  # at least 1px wide!
    gradient = np.linspace(0, frac, w_px)
    colors = (cm.get_cmap('plasma')(gradient)[:, :3] * 255).astype(np.uint8)

    # Create a blank (transparent) image, fill only left part with gradient
    rgba = np.zeros((20, image_width, 4), dtype=np.uint8)
    if w_px > 0:
        rgba[:, :w_px, :3] = colors[None, :, :]
        rgba[:, :w_px, 3] = 255  # fully opaque
    # The rest stays transparent/black

    packed = rgba.view(dtype=np.uint32).reshape((20, image_width))
    rgba_images.append(packed)

# Draw bars: each with their own partial colorbar
dh = bar_height
dw = max(values)  # Each image always spans max value width, but only part is filled
for i in range(num_bars):
    p.image_rgba(
        image=[rgba_images[i]],
        x=[0],
        y=[i - dh / 2],
        dw=dw,
        dh=dh
    )

# Overlay invisible hbars for hover interactivity
source = ColumnDataSource(data=dict(
    y=y_numeric,
    right=values,
    tooltip=[f"Category: {cat}<br>Value: {val}" for cat, val in zip(categories, values)]
))
p.hbar(
    y='y',
    right='right',
    height=bar_height,
    source=source,
    fill_alpha=0,
    line_alpha=0,
    name="bars"
)

hover = HoverTool(
    tooltips="@tooltip",
    mode='hline',
    name="bars"
)
p.add_tools(hover)

# Set category names as y-tick labels at integer positions
p.yaxis.ticker = FixedTicker(ticks=list(y_numeric))
p.yaxis.major_label_overrides = {i: cat for i, cat in enumerate(categories)}
p.yaxis.major_label_text_color = "white"

# Dark theme styling
p.title.text_color = "white"
p.title.text_font_size = "16pt"
p.xaxis.major_label_text_color = "white"
p.xaxis.axis_line_color = "white"
p.yaxis.axis_line_color = "white"
p.yaxis.axis_label_text_color = "white"
p.xaxis.axis_label_text_color = "white"
p.background_fill_color = None
p.border_fill_color = "#121212"
p.outline_line_color = "#444"
p.grid.grid_line_color = "#333"
p.grid.grid_line_alpha = 0.3

gradient_css = InlineStyleSheet(css="""
:host {
  background: radial-gradient(circle at center, #263238 0%, #000000 100%);
}
""")

show(column(p, stylesheets=[gradient_css]))

B. With custom colors:

import numpy as np
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, HoverTool, InlineStyleSheet, FixedTicker
from bokeh.layouts import column

# Data
categories = ['A', 'B', 'C', 'D']
values = [2, 4, 3, 5]
num_bars = len(values)
bar_width = 0.8
image_height = 100

x_numeric = np.arange(num_bars)
max_val = max(values)

# Define custom colors: Cyan -> Yellow -> Red
color_stops = np.array([
    [0, 255, 255],   # Cyan
    [255, 255, 0],   # Yellow
    [255, 0, 0],     # Red
])

def custom_gradient(gradient, color_stops):
    positions = np.linspace(0, 1, len(color_stops))
    reds   = np.interp(gradient, positions, color_stops[:, 0])
    greens = np.interp(gradient, positions, color_stops[:, 1])
    blues  = np.interp(gradient, positions, color_stops[:, 2])
    return np.stack([reds, greens, blues], axis=1).astype(np.uint8)

# Create figure
p = figure(
    width=600, height=400,
    x_range=(-0.5, num_bars - 0.5),
    y_range=(0, max_val + 2),
    title="🌈 Vertical Gradient Bars",
    tools="pan,wheel_zoom,box_zoom,reset,save",
    toolbar_location="right"
)

rgba_images = []
for val in values:
    frac = val / max_val
    h_px = max(1, int(frac * image_height))
    gradient = np.linspace(0, frac, h_px)
    colors = custom_gradient(gradient, color_stops)

    rgba = np.zeros((image_height, 20, 4), dtype=np.uint8)
    if h_px > 0:
        rgba[:h_px, :, :3] = colors[:, None, :]
        rgba[:h_px, :, 3] = 255
    packed = rgba.view(dtype=np.uint32).reshape((image_height, 20))
    rgba_images.append(packed)

dw = bar_width
dh = max_val
for i in range(num_bars):
    p.image_rgba(
        image=[rgba_images[i]],
        x=[i - dw / 2],
        y=[0],
        dw=dw,
        dh=dh
    )

# Invisible vbars for hover
source = ColumnDataSource(data=dict(
    x=x_numeric,
    top=values,
    tooltip=[f"Category: {cat}<br>Value: {val}" for cat, val in zip(categories, values)]
))
p.vbar(
    x='x',
    top='top',
    width=bar_width,
    source=source,
    fill_alpha=0,
    line_alpha=0,
    name="bars"
)

hover = HoverTool(
    tooltips="@tooltip",
    mode='vline',
    name="bars"
)
p.add_tools(hover)

# Set tick labels and style
p.xaxis.ticker = FixedTicker(ticks=list(x_numeric))
p.xaxis.major_label_overrides = {i: cat for i, cat in enumerate(categories)}
p.xaxis.major_label_text_color = "white"
p.title.text_color = "white"
p.title.text_font_size = "16pt"
p.yaxis.major_label_text_color = "white"
p.xaxis.axis_line_color = "white"
p.yaxis.axis_line_color = "white"
p.yaxis.axis_label_text_color = "white"
p.xaxis.axis_label_text_color = "white"
p.background_fill_color = None
p.border_fill_color = "#121212"
p.outline_line_color = "#444"
p.grid.grid_line_color = "#333"
p.grid.grid_line_alpha = 0.3

gradient_css = InlineStyleSheet(css="""
:host {
  background: radial-gradient(circle at center, #263238 0%, #000000 100%);
}
""")

show(column(p, stylesheets=[gradient_css]))

import numpy as np
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, HoverTool, InlineStyleSheet, FixedTicker
from bokeh.layouts import column

# Data
categories = ['A', 'B', 'C', 'D']
values = [2, 4, 3, 5]
num_bars = len(values)
bar_height = 0.8
image_width = 100  # horizontal resolution

y_numeric = np.arange(num_bars)
max_val = max(values)

# Define custom colors: Cyan -> Yellow -> Red
color_stops = np.array([
    [0, 255, 255],   # Cyan
    [255, 255, 0],   # Yellow
    [255, 0, 0],     # Red
])

def custom_gradient(gradient, color_stops):
    positions = np.linspace(0, 1, len(color_stops))
    reds   = np.interp(gradient, positions, color_stops[:, 0])
    greens = np.interp(gradient, positions, color_stops[:, 1])
    blues  = np.interp(gradient, positions, color_stops[:, 2])
    return np.stack([reds, greens, blues], axis=1).astype(np.uint8)

# Create figure
p = figure(
    width=600, height=400,
    y_range=(-0.5, num_bars - 0.5),
    x_range=(0, max_val + 2),
    title="🌈 Horizontal Gradient Bars",
    tools="pan,wheel_zoom,box_zoom,reset,save",
    toolbar_location="right"
)

rgba_images = []
for val in values:
    frac = val / max_val
    w_px = max(1, int(frac * image_width))
    gradient = np.linspace(0, frac, w_px)
    colors = custom_gradient(gradient, color_stops)

    rgba = np.zeros((20, image_width, 4), dtype=np.uint8)
    if w_px > 0:
        rgba[:, :w_px, :3] = colors[None, :, :]
        rgba[:, :w_px, 3] = 255
    packed = rgba.view(dtype=np.uint32).reshape((20, image_width))
    rgba_images.append(packed)

dw = max_val
dh = bar_height
for i in range(num_bars):
    p.image_rgba(
        image=[rgba_images[i]],
        x=[0],
        y=[i - dh / 2],
        dw=dw,
        dh=dh
    )

# Invisible hbars for hover
source = ColumnDataSource(data=dict(
    y=y_numeric,
    right=values,
    tooltip=[f"Category: {cat}<br>Value: {val}" for cat, val in zip(categories, values)]
))
p.hbar(
    y='y',
    right='right',
    height=bar_height,
    source=source,
    fill_alpha=0,
    line_alpha=0,
    name="bars"
)

hover = HoverTool(
    tooltips="@tooltip",
    mode='hline',
    name="bars"
)
p.add_tools(hover)

# Set tick labels and style
p.yaxis.ticker = FixedTicker(ticks=list(y_numeric))
p.yaxis.major_label_overrides = {i: cat for i, cat in enumerate(categories)}
p.yaxis.major_label_text_color = "white"
p.title.text_color = "white"
p.title.text_font_size = "16pt"
p.xaxis.major_label_text_color = "white"
p.xaxis.axis_line_color = "white"
p.yaxis.axis_line_color = "white"
p.yaxis.axis_label_text_color = "white"
p.xaxis.axis_label_text_color = "white"
p.background_fill_color = None
p.border_fill_color = "#121212"
p.outline_line_color = "#444"
p.grid.grid_line_color = "#333"
p.grid.grid_line_alpha = 0.3

gradient_css = InlineStyleSheet(css="""
:host {
  background: radial-gradient(circle at center, #263238 0%, #000000 100%);
}
""")

show(column(p, stylesheets=[gradient_css]))

1 Like