Basic Views Tutorial

This tutorial will guide you through creating some basic figures using figpack.

Prerequisites

Before starting this tutorial, make sure you have figpack installed:

pip install figpack

Markdown View

The Markdown view displays formatted text and documentation:

import figpack.views as vv

# Create a simple markdown view
view = vv.Markdown(f"""
# Welcome to Figpack!

This is a **markdown view** that supports:

- **Bold text** and *italic text*
- [Links](https://github.com/flatironinstitute/figpack)
- Code blocks
""")

view.show(title="Markdown Example", open_in_browser=True)

Image View

The Image view displays images from files or generated data:

import io
import numpy as np
from PIL import Image as PILImage
import figpack.views as vv

# Generate a simple gradient image
width, height = 300, 200
x = np.linspace(0, 1, width)
y = np.linspace(0, 1, height)
X, Y = np.meshgrid(x, y)

# Create RGB gradient
R = (X * 255).astype(np.uint8)
G = (Y * 255).astype(np.uint8)
B = ((X + Y) * 127).astype(np.uint8)
rgb = np.stack([R, G, B], axis=2)

# Convert to image bytes
img = PILImage.fromarray(rgb)
img_bytes = io.BytesIO()
img.save(img_bytes, format="PNG")

# Create figpack view
view = vv.Image(img_bytes.getvalue())
view.show(title="Image Example", open_in_browser=True)

Simple TimeseriesGraph

Create a basic line plot:

import numpy as np
import figpack.views as vv

# Create a simple timeseries
graph = vv.TimeseriesGraph(y_label="Amplitude")

# Generate data
t = np.linspace(0, 10, 500)
y = np.sin(2 * np.pi * t) * np.exp(-t / 5)

# Add line series
graph.add_line_series(
    name="Damped Sine",
    t=t.astype(np.float32),
    y=y.astype(np.float32),
    color="blue"
)

graph.show(title="Simple Timeseries", open_in_browser=True)

Multiple Series

Add different types of series to one plot:

import numpy as np
import figpack.views as vv

# Create graph with multiple series
graph = vv.TimeseriesGraph(
    y_label="Value",
    legend_opts={"location": "northwest"}
)

t = np.linspace(0, 8, 400)

# Add line series
y1 = np.sin(2 * np.pi * 0.5 * t)
graph.add_line_series(
    name="Sine Wave",
    t=t.astype(np.float32),
    y=y1.astype(np.float32),
    color="blue"
)

# Add dashed line
y2 = 0.7 * np.cos(2 * np.pi * 0.3 * t)
graph.add_line_series(
    name="Cosine Wave",
    t=t.astype(np.float32),
    y=y2.astype(np.float32),
    color="red",
    dash=[8, 4]
)

# Add markers
t_markers = np.arange(0, 8, 1)
y_markers = 1.2 * np.sin(2 * np.pi * 0.2 * t_markers)
graph.add_marker_series(
    name="Data Points",
    t=t_markers.astype(np.float32),
    y=y_markers.astype(np.float32),
    color="green",
    radius=6
)

# Add intervals
graph.add_interval_series(
    name="Highlighted",
    t_start=np.array([1, 4]).astype(np.float32),
    t_end=np.array([2, 5]).astype(np.float32),
    color="yellow",
    alpha=0.3
)

graph.show(title="Multi-Series Plot", open_in_browser=True)

Uniform Timeseries

For large datasets with regular sampling intervals, use uniform series for efficient visualization:

import numpy as np
import figpack.views as vv

# Create graph for uniform series
graph = vv.TimeseriesGraph(
    y_label="Amplitude",
    legend_opts={"location": "northwest"}
)

# Generate large uniform dataset (1M points)
n_timepoints = 1_000_000
sampling_freq_hz = 1000.0  # 1 kHz
start_time_sec = 0.0

# Create 3 channels with different signals and offsets
t = np.linspace(0, n_timepoints / sampling_freq_hz, n_timepoints)

# Channel data with vertical offsets for clarity
channel_0 = 1.5 * np.sin(2 * np.pi * 2 * t) + 4    # 2 Hz sine, offset +4
channel_1 = 2.0 * np.sin(2 * np.pi * 5 * t) + 0    # 5 Hz sine, offset 0
channel_2 = 1.0 * np.sin(2 * np.pi * 10 * t) - 4   # 10 Hz sine, offset -4

# Combine into 2D array (timepoints × channels)
uniform_data = np.column_stack([channel_0, channel_1, channel_2]).astype(np.float32)

# Add uniform series (automatically handles downsampling for performance)
graph.add_uniform_series(
    name="Multi-channel Signal",
    start_time_sec=start_time_sec,
    sampling_frequency_hz=sampling_freq_hz,
    data=uniform_data,
    channel_names=["2 Hz Signal", "5 Hz Signal", "10 Hz Signal"],
    colors=["blue", "red", "green"]
)

graph.show(title="Uniform Timeseries Example", open_in_browser=True)

MultiChannelTimeseries

For multi-channel data like sensor arrays:

import numpy as np
import figpack.views as vv

# Generate multi-channel data
sampling_freq = 1000
duration = 3
n_timepoints = int(sampling_freq * duration)
n_channels = 4

t = np.linspace(0, duration, n_timepoints)
data = np.zeros((n_timepoints, n_channels), dtype=np.float32)

# Different signal on each channel
data[:, 0] = np.sin(2 * np.pi * 2 * t)  # 2 Hz
data[:, 1] = np.sin(2 * np.pi * 5 * t)  # 5 Hz
data[:, 2] = np.sin(2 * np.pi * 10 * t)  # 10 Hz
data[:, 3] = np.random.randn(n_timepoints) * 0.5  # Noise

# Create view
view = vv.MultiChannelTimeseries(
    start_time_sec=0.0,
    sampling_frequency_hz=sampling_freq,
    data=data,
    channel_ids=["2Hz Signal", "5Hz Signal", "10Hz Signal", "Noise"]
)

view.show(title="Multi-Channel Data", open_in_browser=True)

Matplotlib Integration

Embed matplotlib plots:

import numpy as np
import matplotlib.pyplot as plt
import figpack.views as vv

# Create matplotlib figure
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))

# Plot 1: Scatter with regression
x = np.random.randn(100)
y = 0.5 * x + 0.3 * np.random.randn(100)
ax1.scatter(x, y, alpha=0.6)
ax1.plot(x, 0.5 * x, 'r--', label='y = 0.5x')
ax1.set_xlabel('X')
ax1.set_ylabel('Y')
ax1.set_title('Scatter Plot')
ax1.legend()
ax1.grid(True, alpha=0.3)

# Plot 2: Histogram
data = np.random.normal(0, 1, 1000)
ax2.hist(data, bins=30, alpha=0.7, color='skyblue')
ax2.set_xlabel('Value')
ax2.set_ylabel('Frequency')
ax2.set_title('Histogram')
ax2.grid(True, alpha=0.3)

plt.tight_layout()

# Create figpack view
view = vv.MatplotlibFigure(fig=fig)
view.show(title="Matplotlib Example", open_in_browser=True)

DataFrame View

Create and display interactive data tables using pandas DataFrames:

import pandas as pd
import numpy as np
import figpack.views as vv

# Create sample data
data = {
    'Date': pd.date_range('2023-01-01', periods=5),
    'Value': np.random.normal(100, 10, 5).round(2),
    'Category': ['A', 'B', 'A', 'C', 'B'],
    'IsActive': [True, True, False, True, False]
}

# Create DataFrame
df = pd.DataFrame(data)

# Create view
view = vv.DataFrame(df)
view.show(title="DataFrame Example", open_in_browser=True)

Spectrogram

Visualize time-frequency data with interactive heatmaps:

import numpy as np
import figpack.views as vv

# Generate synthetic spectrogram data
duration_sec = 10.0
sampling_freq_hz = 100.0
n_timepoints = int(duration_sec * sampling_freq_hz)

# Frequency parameters
freq_min_hz = 10.0
freq_max_hz = 200.0
freq_delta_hz = 5.0
n_frequencies = int((freq_max_hz - freq_min_hz) / freq_delta_hz) + 1

# Create synthetic data with spectral features
np.random.seed(42)
data = np.zeros((n_timepoints, n_frequencies), dtype=np.float32)

time_axis = np.linspace(0, duration_sec, n_timepoints)
freq_axis = np.linspace(freq_min_hz, freq_max_hz, n_frequencies)

for i, t in enumerate(time_axis):
    for j, f in enumerate(freq_axis):
        # Base noise
        data[i, j] = 0.1 * np.random.random()

        # Chirp signal (frequency sweep)
        chirp_freq = 50 + 100 * (t / duration_sec)
        if abs(f - chirp_freq) < 10:
            data[i, j] += 0.8 * np.exp(-((f - chirp_freq) / 5) ** 2)

        # Constant tone at 120 Hz
        if abs(f - 120) < 5:
            data[i, j] += 0.6

# Create spectrogram view
view = vv.Spectrogram(
    start_time_sec=0.0,
    sampling_frequency_hz=sampling_freq_hz,
    frequency_min_hz=freq_min_hz,
    frequency_delta_hz=freq_delta_hz,
    data=data,
)

view.show(title="Spectrogram Example", open_in_browser=True)

It is also possible to specify non-uniform frequencies in the spectrogram view. See the examples folder for details.

Plotly Integration

Create interactive plotly visualizations:

import numpy as np
import plotly.graph_objects as go
import figpack.views as vv

# Create subplot figure
from plotly.subplots import make_subplots
fig = make_subplots(rows=1, cols=2, specs=[[{'type': 'xy'}, {'type': 'scene'}]])

# Add 2D traces
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.cos(x)

fig.add_trace(go.Scatter(x=x, y=y1, name='sin(x)', line=dict(color='blue')), row=1, col=1)
fig.add_trace(go.Scatter(x=x, y=y2, name='cos(x)', line=dict(color='red')), row=1, col=1)

# Add 3D spiral
t = np.linspace(0, 10*np.pi, 100)
x3d = np.cos(t)
y3d = np.sin(t)
z3d = t/10

fig.add_trace(go.Scatter3d(x=x3d, y=y3d, z=z3d, name='3D Spiral',
                          line=dict(color='green', width=4)), row=1, col=2)

# Update layout
fig.update_layout(
    title="2D and 3D Interactive Plots",
    scene=dict(camera=dict(eye=dict(x=1.5, y=1.5, z=1.5))),
    showlegend=True,
    width=900  # Make wider to accommodate both plots
)

# Create figpack view
view = vv.PlotlyFigure(fig=fig)
view.show(title="Plotly Example", open_in_browser=True)

Box Layout

Arrange multiple views in rows and columns. Box layouts can have their own title that appears at the top:

import numpy as np
import figpack.views as vv

# Create some simple graphs
def create_simple_graph(name, color, freq, *, hide_nav=False, hide_time_labels=False):
    graph = vv.TimeseriesGraph(
        y_label="Amplitude",
        hide_nav_toolbar=hide_nav,
        hide_time_axis_labels=hide_time_labels
    )
    t = np.linspace(0, 5, 200)
    y = np.sin(2 * np.pi * freq * t)
    graph.add_line_series(
        name=name,
        t=t.astype(np.float32),
        y=y.astype(np.float32),
        color=color
    )
    return graph

graph1 = create_simple_graph("1 Hz", "blue", 1, hide_nav=True, hide_time_labels=True)
graph2 = create_simple_graph("2 Hz", "red", 2, hide_nav=True, hide_time_labels=True)
graph3 = create_simple_graph("3 Hz", "green", 3)  # Bottom graph shows navigation and labels

# Create vertical layout with a title
view = vv.Box(
    direction="vertical",
    title="Signal Comparison Dashboard",  # Box title appears at the top
    items=[
        vv.LayoutItem(graph1, title="Graph 1"),
        vv.LayoutItem(graph2, title="Graph 2"),
        vv.LayoutItem(graph3, title="Graph 3")
    ]
)

view.show(title="Box Layout Example", open_in_browser=True)

TabLayout

Organize views in tabs:

import numpy as np
import figpack.views as vv

# Create different views for tabs
def create_graph(freq, color):
    graph = vv.TimeseriesGraph()
    t = np.linspace(0, 4, 200)
    y = np.sin(2 * np.pi * freq * t)
    graph.add_line_series(
        name=f"{freq} Hz",
        t=t.astype(np.float32),
        y=y.astype(np.float32),
        color=color
    )
    return graph

# Create tab layout
view = vv.TabLayout(
    items=[
        vv.TabLayoutItem(label="Low Freq", view=create_graph(1, "blue")),
        vv.TabLayoutItem(label="Mid Freq", view=create_graph(3, "red")),
        vv.TabLayoutItem(label="High Freq", view=create_graph(8, "green")),
        vv.TabLayoutItem(label="Info", view=vv.Markdown("# Analysis Results\nClick tabs to see different frequencies."))
    ]
)

view.show(title="Tab Layout Example", open_in_browser=True)

Splitter Layout

Create resizable panes:

import numpy as np
import figpack.views as vv

# Create two different visualizations
# Left: Time series
graph = vv.TimeseriesGraph(y_label="Signal")
t = np.linspace(0, 6, 300)
y = np.sin(2 * np.pi * t) + 0.5 * np.sin(2 * np.pi * 3 * t)
graph.add_line_series(
    name="Complex Signal",
    t=t.astype(np.float32),
    y=y.astype(np.float32),
    color="purple"
)

# Right: Documentation
docs = vv.Markdown("""
# Signal Analysis

This signal contains:
- **Fundamental**: 1 Hz sine wave
- **Harmonic**: 3 Hz component

## Features
- Interactive zooming
- Resizable panes
""")

# Create splitter
view = vv.Splitter(
    direction="horizontal",
    item1=vv.LayoutItem(graph, title="Signal Data"),
    item2=vv.LayoutItem(docs, title="Analysis Notes"),
    split_pos=0.7  # 70% for graph, 30% for docs
)

view.show(title="Splitter Layout Example", open_in_browser=True)

Complex Nested Layout

Combine multiple layout types:

import numpy as np
import figpack.views as vv

# Create various components
def create_signal(freq, name, color):
    graph = vv.TimeseriesGraph()
    t = np.linspace(0, 3, 150)
    y = np.sin(2 * np.pi * freq * t)
    graph.add_line_series(name=name, t=t.astype(np.float32), y=y.astype(np.float32), color=color)
    return graph

# Top row: Three signals side by side
top_row = vv.Box(
    direction="horizontal",
    items=[
        vv.LayoutItem(create_signal(1, "1 Hz", "blue"), title="Low"),
        vv.LayoutItem(create_signal(3, "3 Hz", "red"), title="Mid"),
        vv.LayoutItem(create_signal(8, "8 Hz", "green"), title="High")
    ]
)

# Bottom: Tabs with analysis
analysis_tabs = vv.TabLayout(
    items=[
        vv.TabLayoutItem(label="Summary", view=vv.Markdown("# Signal Analysis\nThree different frequency components analyzed.")),
        vv.TabLayoutItem(label="Methods", view=vv.Markdown("# Methods\n- Fourier analysis\n- Time-domain features")),
    ]
)

# Combine with splitter
view = vv.Splitter(
    direction="vertical",
    item1=vv.LayoutItem(top_row, title="Signal Comparison"),
    item2=vv.LayoutItem(analysis_tabs, title="Analysis"),
    split_pos=0.6
)

view.show(title="Complex Layout Example", open_in_browser=True)