CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-panel

The powerful data exploration & web app framework for Python.

Pending
Overview
Eval results
Files

custom-components.mddocs/

Custom Components

Classes for extending Panel with custom functionality using Python, JavaScript, React, or ESM modules. Custom components enable developers to create specialized widgets and visualizations that integrate seamlessly with Panel's component system.

Capabilities

Python-Based Components

Create custom components using pure Python with reactive parameters and custom rendering logic.

class PyComponent:
    """
    Python-based custom component for creating widgets with pure Python.
    
    Parameters:
    - _template: HTML template string for component rendering
    - _stylesheets: List of CSS stylesheets to include
    - _extension_name: Name for the component extension
    - **params: Component parameters that become reactive
    """

JavaScript Components

Create custom components using JavaScript for client-side interactivity and custom behavior.

class JSComponent:
    """
    JavaScript-based component for client-side custom widgets.
    
    Parameters:
    - _esm: ECMAScript module code or file path
    - _template: HTML template for the component
    - _stylesheets: CSS stylesheets to include
    - _importmap: Import map for module dependencies
    - **params: Component parameters
    """

React Components

Create custom components using React for building complex interactive interfaces.

class ReactComponent:
    """
    React-based component for building React-powered widgets.
    
    Parameters:
    - _template: React JSX template
    - _stylesheets: CSS stylesheets for styling
    - _importmap: Import map for React dependencies
    - **params: Component parameters passed as React props
    """

ESM Module Components

Create components from ECMAScript modules for modern JavaScript functionality.

class ReactiveESM:
    """
    ESM module-based component with reactive parameter binding.
    
    Parameters:
    - _esm: ECMAScript module source code or file path
    - _template: HTML template for component
    - _stylesheets: CSS stylesheets
    - _importmap: Module import map
    - **params: Reactive parameters
    """

AnyWidget Integration

Create components compatible with the AnyWidget specification for cross-framework compatibility.

class AnyWidgetComponent:
    """
    AnyWidget-compatible component for cross-framework integration.
    
    Parameters:
    - _esm: JavaScript module implementing AnyWidget interface
    - _css: CSS styles for the component
    - **params: Widget parameters
    """

Usage Examples

Basic Python Component

import panel as pn
import param

class CounterComponent(pn.custom.PyComponent):
    
    value = param.Integer(default=0)
    step = param.Integer(default=1)
    
    _template = """
    <div class="counter">
        <button id="decrement" onclick="${script('decrement')}">-</button>
        <span class="value">${value}</span>
        <button id="increment" onclick="${script('increment')}">+</button>
    </div>
    """
    
    _stylesheets = ["""
    .counter {
        display: flex;
        align-items: center;
        gap: 10px;
        font-family: sans-serif;
    }
    .counter button {
        padding: 5px 10px;
        font-size: 16px;
        border: 1px solid #ccc;
        background: #f8f9fa;
        cursor: pointer;
    }
    .counter .value {
        font-size: 18px;
        font-weight: bold;
        min-width: 40px;
        text-align: center;
    }
    """]
    
    def _handle_increment(self, event):
        self.value += self.step
    
    def _handle_decrement(self, event):
        self.value -= self.step

# Use the custom component
counter = CounterComponent(value=10, step=2)

JavaScript Component

class ChartComponent(pn.custom.JSComponent):
    
    data = param.List(default=[])
    chart_type = param.Selector(default='line', objects=['line', 'bar', 'pie'])
    
    _esm = """
    export function render({ model, el }) {
        // Create chart using Chart.js or D3
        const canvas = document.createElement('canvas');
        el.appendChild(canvas);
        
        const ctx = canvas.getContext('2d');
        const chart = new Chart(ctx, {
            type: model.chart_type,
            data: {
                datasets: [{
                    data: model.data,
                    backgroundColor: 'rgba(75, 192, 192, 0.2)',
                    borderColor: 'rgba(75, 192, 192, 1)',
                }]
            },
            options: {
                responsive: true,
                plugins: {
                    legend: { display: false }
                }
            }
        });
        
        // Update chart when parameters change
        model.on('change:data', () => {
            chart.data.datasets[0].data = model.data;
            chart.update();
        });
        
        model.on('change:chart_type', () => {
            chart.config.type = model.chart_type;
            chart.update();
        });
    }
    """
    
    _importmap = {
        "imports": {
            "chart.js": "https://cdn.jsdelivr.net/npm/chart.js@4.4.0/+esm"
        }
    }

# Use the chart component
chart = ChartComponent(
    data=[10, 20, 30, 25, 15],
    chart_type='bar'
)

React Component

class ReactButton(pn.custom.ReactComponent):
    
    clicks = param.Integer(default=0)
    label = param.String(default="Click me")
    variant = param.Selector(default='primary', objects=['primary', 'secondary', 'success', 'danger'])
    
    _template = """
    export default function ReactButton({ model }) {
        const [clicks, setClicks] = React.useState(model.clicks);
        
        const handleClick = () => {
            const newClicks = clicks + 1;
            setClicks(newClicks);
            model.clicks = newClicks;
        };
        
        return (
            <button 
                className={`btn btn-${model.variant}`}
                onClick={handleClick}
                style={{
                    padding: '8px 16px',
                    fontSize: '16px',
                    border: 'none',
                    borderRadius: '4px',
                    cursor: 'pointer',
                    backgroundColor: getVariantColor(model.variant)
                }}
            >
                {model.label} ({clicks})
            </button>
        );
    }
    
    function getVariantColor(variant) {
        const colors = {
            primary: '#007bff',
            secondary: '#6c757d', 
            success: '#28a745',
            danger: '#dc3545'
        };
        return colors[variant] || colors.primary;
    }
    """
    
    _importmap = {
        "imports": {
            "react": "https://esm.sh/react@18",
            "react-dom": "https://esm.sh/react-dom@18"
        }
    }

# Use the React component
react_btn = ReactButton(label="React Button", variant='success')

Complex Interactive Component

import param
import pandas as pd

class DataExplorer(pn.custom.PyComponent):
    
    data = param.DataFrame()
    selected_columns = param.List(default=[])
    chart_type = param.Selector(default='scatter', objects=['scatter', 'line', 'bar', 'histogram'])
    
    _template = """
    <div class="data-explorer">
        <div class="controls">
            <h3>Data Explorer</h3>
            <div class="column-selector">
                <label>Select Columns:</label>
                <select multiple size="5" onchange="${script('update_columns')}">
                    {% for col in data.columns %}
                    <option value="{{ col }}" 
                            {% if col in selected_columns %}selected{% endif %}>
                        {{ col }}
                    </option>
                    {% endfor %}
                </select>
            </div>
            <div class="chart-type">
                <label>Chart Type:</label>
                <select onchange="${script('update_chart_type')}">
                    <option value="scatter" {% if chart_type == 'scatter' %}selected{% endif %}>Scatter</option>
                    <option value="line" {% if chart_type == 'line' %}selected{% endif %}>Line</option>
                    <option value="bar" {% if chart_type == 'bar' %}selected{% endif %}>Bar</option>
                    <option value="histogram" {% if chart_type == 'histogram' %}selected{% endif %}>Histogram</option>
                </select>
            </div>
        </div>
        <div class="preview">
            {% if selected_columns %}
            <h4>Selected Data Preview:</h4>
            <div class="data-preview">
                {{ data[selected_columns].head().to_html() }}
            </div>
            {% endif %}
        </div>
    </div>
    """
    
    _stylesheets = ["""
    .data-explorer {
        display: flex;
        flex-direction: column;
        gap: 20px;
        padding: 20px;
        border: 1px solid #ddd;
        border-radius: 8px;
        background: #f9f9f9;
    }
    .controls {
        display: flex;
        gap: 20px;
        align-items: flex-start;
    }
    .column-selector select, .chart-type select {
        padding: 5px;
        min-width: 150px;
    }
    .data-preview {
        max-height: 300px;
        overflow: auto;
        background: white;
        padding: 10px;
        border-radius: 4px;
    }
    """]
    
    def _handle_update_columns(self, event):
        # Extract selected columns from event
        selected = event.target.selectedValues
        self.selected_columns = list(selected)
    
    def _handle_update_chart_type(self, event):
        self.chart_type = event.target.value
    
    @param.depends('selected_columns', 'chart_type', watch=True)
    def _update_visualization(self):
        # Trigger re-render when parameters change
        self._render()

# Use the data explorer
df = pd.DataFrame({
    'x': range(100),
    'y': [i**2 for i in range(100)],
    'category': ['A', 'B'] * 50
})

explorer = DataExplorer(data=df)

AnyWidget Component

class AnyWidgetSlider(pn.custom.AnyWidgetComponent):
    
    value = param.Number(default=50)
    min = param.Number(default=0)
    max = param.Number(default=100)
    step = param.Number(default=1)
    
    _esm = """
    function render({ model, el }) {
        const slider = document.createElement('input');
        slider.type = 'range';
        slider.min = model.get('min');
        slider.max = model.get('max');
        slider.step = model.get('step');
        slider.value = model.get('value');
        
        slider.addEventListener('input', () => {
            model.set('value', parseFloat(slider.value));
            model.save_changes();
        });
        
        model.on('change:value', () => {
            slider.value = model.get('value');
        });
        
        el.appendChild(slider);
    }
    
    export default { render };
    """
    
    _css = """
    input[type="range"] {
        width: 100%;
        height: 20px;
        background: #ddd;
        outline: none;
        border-radius: 10px;
    }
    """

# Use the AnyWidget component
slider = AnyWidgetSlider(value=75, min=0, max=200, step=5)

Integration Patterns

Custom Component with Panel Widgets

class ComponentWithControls(pn.custom.PyComponent):
    
    def __init__(self, **params):
        super().__init__(**params)
        
        # Create control widgets
        self.controls = pn.Column(
            pn.widgets.ColorPicker(name="Color", value="#ff0000"),
            pn.widgets.IntSlider(name="Size", start=10, end=100, value=50),
            pn.widgets.Select(name="Shape", options=['circle', 'square', 'triangle'])
        )
        
        # Bind control changes to component updates
        for widget in self.controls:
            widget.param.watch(self._update_from_controls, 'value')
    
    def _update_from_controls(self, event):
        # Update component based on control values
        self._render()
    
    def panel(self):
        return pn.Row(self.controls, self)

component = ComponentWithControls()

Custom Component Factory

def create_custom_widget(widget_type, **config):
    """Factory function for creating custom widgets"""
    
    if widget_type == 'counter':
        return CounterComponent(**config)
    elif widget_type == 'chart':
        return ChartComponent(**config)
    elif widget_type == 'data_explorer':
        return DataExplorer(**config)
    else:
        raise ValueError(f"Unknown widget type: {widget_type}")

# Use the factory
custom_widgets = [
    create_custom_widget('counter', value=0, step=1),
    create_custom_widget('chart', data=[1,2,3,4,5], chart_type='line'),
    create_custom_widget('data_explorer', data=df)
]

Install with Tessl CLI

npx tessl i tessl/pypi-panel

docs

authentication-system.md

chat-interface.md

core-functions.md

custom-components.md

index.md

layout-components.md

links-system.md

pane-system.md

parameter-integration.md

pipeline-system.md

template-system.md

widget-system.md

tile.json