Custom Jupyter widgets made easy with modern web technologies and seamless platform integration
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Cell magic support for creating virtual files and managing widget development workflows within Jupyter notebooks. This integration provides seamless development experiences with inline content management and dynamic file creation.
Function to load the anywidget IPython extension, enabling cell magic functionality.
def load_ipython_extension(ipython):
"""
Load the IPython extension for anywidget magics.
Registers the AnyWidgetMagics class with the IPython shell,
enabling cell magic commands for virtual file management.
Parameters:
ipython: IPython.core.interactiveshell.InteractiveShell instance
Usage:
%load_ext anywidget
"""IPython magics class providing cell magic commands for virtual file management and widget development.
class AnyWidgetMagics(Magics):
"""
A set of IPython magics for working with virtual files.
Provides cell magic commands that enable inline definition
of ES modules and CSS for widget development within notebooks.
"""
def __init__(self, shell):
"""
Initialize the magics with IPython shell reference.
Parameters:
shell: InteractiveShell instance
"""Cell magic for creating virtual files from notebook cell contents.
def vfile(self, line: str, cell: str):
"""
Create a virtual file with the contents of the cell.
Cell magic that captures cell content and creates a virtual file
that can be referenced by widgets using the vfile: protocol.
Parameters:
line (str): Magic line containing file name argument
cell (str): Cell contents to store as virtual file
Usage:
%%vfile my-widget.js
function render({ model, el }) {
el.innerHTML = "<h1>Hello from virtual file!</h1>";
}
export default { render };
"""Line magic for clearing virtual file registry.
def clear_vfiles(self, line: str):
"""
Clear all virtual files from the registry.
Line magic that removes all virtual files created with %%vfile,
useful for cleaning up development environment.
Parameters:
line (str): Magic line (unused)
Usage:
%clear_vfiles
"""# Load anywidget IPython extension
%load_ext anywidget
# Alternative: Load programmatically
import anywidget
anywidget.load_ipython_extension(get_ipython())# Create virtual JavaScript file
%%vfile counter.js
function render({ model, el }) {
let count = () => model.get("value");
let btn = document.createElement("button");
btn.innerHTML = `Count: ${count()}`;
btn.addEventListener("click", () => {
model.set("value", count() + 1);
});
model.on("change:value", () => {
btn.innerHTML = `Count: ${count()}`;
});
el.appendChild(btn);
}
export default { render };# Create virtual CSS file
%%vfile counter.css
button {
background: #007cba;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.2s;
}
button:hover {
background: #005a8b;
}# Use virtual files in widget
import anywidget
import traitlets as t
class CounterWidget(anywidget.AnyWidget):
_esm = "vfile:counter.js" # Reference virtual file
_css = "vfile:counter.css" # Reference virtual file
value = t.Int(0).tag(sync=True)
widget = CounterWidget()
widget # Displays counter with styling from virtual files# Define widget template
%%vfile template.js
function render({ model, el }) {
let template = model.get("template");
let data = model.get("data");
// Simple template rendering
let html = template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
return data[key] || match;
});
el.innerHTML = html;
model.on("change", () => {
let newTemplate = model.get("template");
let newData = model.get("data");
let newHtml = newTemplate.replace(/\{\{(\w+)\}\}/g, (match, key) => {
return newData[key] || match;
});
el.innerHTML = newHtml;
});
}
export default { render };import anywidget
import traitlets as t
class TemplateWidget(anywidget.AnyWidget):
_esm = "vfile:template.js"
template = t.Unicode("<h1>{{title}}</h1><p>{{message}}</p>").tag(sync=True)
data = t.Dict({"title": "Hello", "message": "World"}).tag(sync=True)
widget = TemplateWidget()
widget.data = {"title": "Dynamic Title", "message": "This content updates!"}# Create interactive chart widget
%%vfile chart.js
function render({ model, el }) {
// Simple bar chart implementation
let data = model.get("data");
let options = model.get("options");
let renderChart = () => {
el.innerHTML = "";
let container = document.createElement("div");
container.style.display = "flex";
container.style.alignItems = "end";
container.style.height = "200px";
container.style.padding = "20px";
container.style.background = options.background || "#f9f9f9";
data.forEach((value, index) => {
let bar = document.createElement("div");
bar.style.width = "30px";
bar.style.height = `${value * 2}px`;
bar.style.background = options.barColor || "#007cba";
bar.style.margin = "0 2px";
bar.style.display = "flex";
bar.style.alignItems = "end";
bar.style.justifyContent = "center";
bar.style.color = "white";
bar.style.fontSize = "12px";
bar.textContent = value;
container.appendChild(bar);
});
el.appendChild(container);
};
model.on("change", renderChart);
renderChart();
}
export default { render };import anywidget
import traitlets as t
class ChartWidget(anywidget.AnyWidget):
_esm = "vfile:chart.js"
data = t.List([]).tag(sync=True)
options = t.Dict({}).tag(sync=True)
# Create chart
chart = ChartWidget(
data=[10, 25, 15, 30, 45, 20],
options={"background": "#f0f0f0", "barColor": "#e74c3c"}
)
chart
# Update data interactively
chart.data = [5, 35, 25, 40, 15, 50]
chart.options = {"background": "#ffffff", "barColor": "#2ecc71"}# Create data table widget
%%vfile datatable.js
function render({ model, el }) {
let columns = model.get("columns");
let rows = model.get("rows");
let renderTable = () => {
let table = document.createElement("table");
table.style.width = "100%";
table.style.borderCollapse = "collapse";
table.style.fontFamily = "Arial, sans-serif";
// Header
let thead = document.createElement("thead");
let headerRow = document.createElement("tr");
headerRow.style.background = "#f8f9fa";
columns.forEach(col => {
let th = document.createElement("th");
th.textContent = col;
th.style.padding = "12px";
th.style.textAlign = "left";
th.style.borderBottom = "2px solid #dee2e6";
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
table.appendChild(thead);
// Body
let tbody = document.createElement("tbody");
rows.forEach((row, index) => {
let tr = document.createElement("tr");
tr.style.background = index % 2 === 0 ? "#ffffff" : "#f8f9fa";
columns.forEach(col => {
let td = document.createElement("td");
td.textContent = row[col] || "";
td.style.padding = "12px";
td.style.borderBottom = "1px solid #dee2e6";
tr.appendChild(td);
});
tbody.appendChild(tr);
});
table.appendChild(tbody);
el.innerHTML = "";
el.appendChild(table);
};
model.on("change", renderTable);
renderTable();
}
export default { render };import anywidget
import traitlets as t
class DataTableWidget(anywidget.AnyWidget):
_esm = "vfile:datatable.js"
columns = t.List([]).tag(sync=True)
rows = t.List([]).tag(sync=True)
# Sample data
data_table = DataTableWidget(
columns=["Name", "Age", "City", "Score"],
rows=[
{"Name": "Alice", "Age": 30, "City": "New York", "Score": 85},
{"Name": "Bob", "Age": 25, "City": "San Francisco", "Score": 92},
{"Name": "Charlie", "Age": 35, "City": "Chicago", "Score": 78},
{"Name": "Diana", "Age": 28, "City": "Austin", "Score": 88}
]
)
data_table# Clear all virtual files
%clear_vfiles
# Verify files are cleared
print("Virtual files cleared")
# Re-create files as needed
%%vfile simple.js
function render({ model, el }) {
el.innerHTML = "<p>Simple widget after clearing files</p>";
}
export default { render };# Create reusable component library
%%vfile components.js
// Reusable UI components
export function createButton(text, onClick) {
let btn = document.createElement("button");
btn.textContent = text;
btn.style.cssText = `
background: #007cba;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
margin: 4px;
`;
btn.addEventListener("click", onClick);
return btn;
}
export function createInput(placeholder, onChange) {
let input = document.createElement("input");
input.placeholder = placeholder;
input.style.cssText = `
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
margin: 4px;
`;
input.addEventListener("input", (e) => onChange(e.target.value));
return input;
}# Use component library in widget
%%vfile app.js
import { createButton, createInput } from "vfile:components.js";
function render({ model, el }) {
let value = model.get("value");
let input = createInput("Enter text", (newValue) => {
model.set("value", newValue);
});
let button = createButton("Clear", () => {
model.set("value", "");
input.value = "";
});
let display = document.createElement("div");
display.style.cssText = "margin: 10px; padding: 10px; background: #f0f0f0;";
let updateDisplay = () => {
display.textContent = `Current value: ${model.get("value")}`;
};
model.on("change:value", updateDisplay);
updateDisplay();
el.appendChild(input);
el.appendChild(button);
el.appendChild(display);
}
export default { render };import anywidget
import traitlets as t
class ComponentWidget(anywidget.AnyWidget):
_esm = "vfile:app.js"
value = t.Unicode("").tag(sync=True)
widget = ComponentWidget()
widgetInstall with Tessl CLI
npx tessl i tessl/pypi-anywidget