import numpy as np
from bokeh.plotting import figure, save, output_file
from bokeh.models import (
ColumnDataSource, HoverTool, CustomAction, CustomJS,
DataTable, TableColumn, NumberEditor, StringEditor, TextInput, Button
)
from bokeh.layouts import column, row
# ---- Generate random data for bar plot ----
months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
values = [151,168,193,223,240,245,238,221,195,170,154,150]
bar_data_source = ColumnDataSource(dict(x=months, y=values))
# ---- Bar plot ----
bar_plot = figure(
width=800, height=500, title='Interactive Bar Plot with Editable Data',
x_range=bar_data_source.data["x"]
)
bar_plot.xaxis.axis_label = 'Categories'
bar_plot.yaxis.axis_label = 'Values'
bars = bar_plot.vbar(
x='x', top='y', width=0.8, source=bar_data_source,
fill_color='steelblue', line_color='black', alpha=0.8,
hover_fill_color='navy', hover_line_color='white'
)
bar_plot.add_tools(HoverTool(tooltips=[('Category', '@x'), ('Value', '@y')], renderers=[bars]))
# ---- Data Table ----
columns = [
TableColumn(field="x", title="Category (X)", editor=StringEditor()), # Accepts text!
TableColumn(field="y", title="Value (Y)", editor=NumberEditor())
]
data_table = DataTable(source=bar_data_source, columns=columns, width=400, height=300, editable=True)
# Input fields for adding new data
new_x_input = TextInput(value="", title="New Category:", width=120)
new_y_input = TextInput(value="", title="New Value:", width=120)
add_button = Button(label="Add Data Point", button_type="success", width=120)
# Initially hide the table and inputs
data_table.visible = False
new_x_input.visible = False
new_y_input.visible = False
add_button.visible = False
# ---- Custom toolbar action for data table ----
table_callback = CustomJS(
args=dict(
data_table=data_table,
new_x_input=new_x_input,
new_y_input=new_y_input,
add_button=add_button,
),
code="""
const currently_visible = data_table.visible;
data_table.visible = !currently_visible;
new_x_input.visible = !currently_visible;
new_y_input.visible = !currently_visible;
add_button.visible = !currently_visible;
"""
)
table_action = CustomAction(
icon="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cGF0aCBkPSJNMyAzaDE4djE4SDNWMyIgc3Ryb2tlPSIjMDA3YWNjIiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9Im5vbmUiLz4KICA8cGF0aCBkPSJNMyA5aDE4IiBzdHJva2U9IiMwMDdhY2MiIHN0cm9rZS13aWR0aD0iMiIvPgogIDxwYXRoIGQ9Ik0zIDE1aDE4IiBzdHJva2U9IiMwMDdhY2MiIHN0cm9rZS13aWR0aD0iMiIvPgogIDxwYXRoIGQ9Ik05IDNoMTIiIHN0cm9rZT0iIzAwN2FjYyIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgPHBhdGggZD0iTTE1IDNoNiIgc3Ryb2tlPSIjMDA3YWNjIiBzdHJva2Utd2lkdGg9IjIiLz4KPC9zdmc+",
description="Toggle Data Table",
callback=table_callback
)
bar_plot.add_tools(table_action)
# ---- CustomJS callback for updating bar plot when data changes ----
update_bar_callback = CustomJS(
args=dict(
source=bar_data_source,
plot=bar_plot
),
code="""
// Update bar plot x_range to accommodate new data
const new_x_values = source.data['x'].map(x => x.toString());
plot.x_range.factors = new_x_values;
"""
)
# ---- CustomJS callback for adding new data point ----
add_data_callback = CustomJS(
args=dict(
source=bar_data_source,
x_input=new_x_input,
y_input=new_y_input,
plot=bar_plot
),
code="""
try {
const new_x = x_input.value; // Accept as string!
const new_y = parseFloat(y_input.value);
if (!new_x || isNaN(new_y)) {
return;
}
// Get current data as arrays
const current_x = Array.from(source.data['x']);
const current_y = Array.from(source.data['y']);
// Add new point
current_x.push(new_x);
current_y.push(new_y);
// Update data source
source.data = {
'x': current_x,
'y': current_y
};
// Update bar plot x_range
const new_x_values = current_x.map(x => x.toString());
plot.x_range.factors = new_x_values;
// Clear input fields
x_input.value = "";
y_input.value = "";
} catch (error) {
console.log("Error adding data point:", error);
}
"""
)
# Attach callbacks
bar_data_source.js_on_change('data', update_bar_callback)
add_button.js_on_click(add_data_callback)
# ---- Layout ----
input_row = row(new_x_input, new_y_input, add_button)
table_section = column(data_table, input_row)
layout = row(
bar_plot,
table_section
)
# Save to HTML file
output_file("interactive_bar_plot_with_table.html")
save(layout)
The server version:
import numpy as np
from bokeh.plotting import figure, curdoc
from bokeh.models import (
ColumnDataSource, HoverTool, CustomAction, CustomJS,
DataTable, TableColumn, StringEditor, NumberEditor, TextInput, Button
)
from bokeh.layouts import column, row
# ---- Data ----
months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
values = [151,168,193,223,240,245,238,221,195,170,154,150]
source = ColumnDataSource(dict(x=months, y=values))
# ---- Figure ----
line_plot = figure(
width=800, height=500, title='Interactive Line Plot with Editable Data',
x_range=source.data["x"]
)
line_plot.xaxis.axis_label = 'Categories'
line_plot.yaxis.axis_label = 'Values'
line = line_plot.line(
x='x', y='y', source=source, line_width=2, color='royalblue', legend_label="Value"
)
scatter = line_plot.circle(
x='x', y='y', source=source, size=10, color='orangered', legend_label="Data Point"
)
# Add hover tool for scatter (shows on points)
line_plot.add_tools(HoverTool(tooltips=[('Category', '@x'), ('Value', '@y')], renderers=[scatter]))
# ---- Data Table ----
columns = [
TableColumn(field="x", title="Category (X)", editor=StringEditor()), # Accepts strings/words!
TableColumn(field="y", title="Value (Y)", editor=NumberEditor())
]
data_table = DataTable(
source=source, columns=columns, width=400, height=300, editable=True
)
# Input fields for adding new data
new_x_input = TextInput(value="", title="New Category:", width=120)
new_y_input = TextInput(value="", title="New Value:", width=120)
add_button = Button(label="Add Data Point", button_type="success", width=120)
# Initially hide the table and inputs
data_table.visible = False
new_x_input.visible = False
new_y_input.visible = False
add_button.visible = False
# ---- Custom toolbar action for data table ----
table_callback = CustomJS(
args=dict(
data_table=data_table,
new_x_input=new_x_input,
new_y_input=new_y_input,
add_button=add_button,
),
code="""
// Toggle visibility of table and related elements
const currently_visible = data_table.visible;
data_table.visible = !currently_visible;
new_x_input.visible = !currently_visible;
new_y_input.visible = !currently_visible;
add_button.visible = !currently_visible;
"""
)
table_action = CustomAction(
icon="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cGF0aCBkPSJNMyAzaDE4djE4SDNWMyIgc3Ryb2tlPSIjMDA3YWNjIiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9Im5vbmUiLz4KICA8cGF0aCBkPSJNMyA5aDE4IiBzdHJva2U9IiMwMDdhY2MiIHN0cm9rZS13aWR0aD0iMiIvPgogIDxwYXRoIGQ9Ik0zIDE1aDE4IiBzdHJva2U9IiMwMDdhY2MiIHN0cm9rZS13aWR0aD0iMiIvPgogIDxwYXRoIGQ9Ik05IDNoMTIiIHN0cm9rZT0iIzAwN2FjYyIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgPHBhdGggZD0iTTE1IDNoNiIgc3Ryb2tlPSIjMDA3YWNjIiBzdHJva2Utd2lkdGg9IjIiLz4KPC9zdmc+",
description="Toggle Data Table",
callback=table_callback
)
line_plot.add_tools(table_action)
# ---- Callback functions ----
def update_line_plot(attr, old, new):
"""Update plot when table data changes"""
new_x_values = [str(x) for x in source.data['x']]
line_plot.x_range.factors = new_x_values
def add_data_point():
"""Add a new data point from input fields"""
new_x = new_x_input.value.strip()
try:
new_y = float(new_y_input.value)
except Exception:
return # Ignore invalid
if not new_x or np.isnan(new_y):
return
current_x = list(source.data['x'])
current_y = list(source.data['y'])
current_x.append(new_x)
current_y.append(new_y)
source.data = {'x': current_x, 'y': current_y}
new_x_input.value = ""
new_y_input.value = ""
# Connect callbacks
source.on_change('data', update_line_plot)
add_button.on_click(add_data_point)
# ---- Layout ----
input_row = row(new_x_input, new_y_input, add_button)
table_section = column(data_table, input_row)
layout = row(
line_plot,
table_section
)
curdoc().add_root(layout)
curdoc().title = "Interactive Line+Scatter Plot with Editable Data Table"