Charts¶
Castella provides native interactive chart widgets with GPU rendering via Skia (desktop) and CanvasKit (web). No external dependencies like Matplotlib required.
Features¶
- 8 Chart Types: Bar, Line, Pie, Scatter, Area, Stacked Bar, Gauge, Heatmap
- Full Interactivity: Tooltips, hover effects, click events
- Scientific Colormaps: Viridis, Plasma, Inferno, Magma (colorblind-friendly)
- Pydantic v2 Models: Type-safe, validated data models
- Theme Integration: Automatically uses current Castella theme
- Observable Pattern: Charts auto-update when data changes
Quick Start¶
from castella.chart import BarChart, CategoricalChartData, CategoricalSeries, SeriesStyle
# Create data
data = CategoricalChartData(title="Sales by Quarter")
data.add_series(CategoricalSeries.from_values(
name="2024",
categories=["Q1", "Q2", "Q3", "Q4"],
values=[100, 120, 90, 150],
style=SeriesStyle(color="#3b82f6"),
))
# Create chart
chart = BarChart(data, show_values=True)
Data Models¶
CategoricalChartData¶
For charts with category labels (Bar, Pie, Stacked Bar):
from castella.chart import CategoricalChartData, CategoricalSeries, SeriesStyle
data = CategoricalChartData(title="Monthly Sales")
data.add_series(CategoricalSeries.from_values(
name="Product A",
categories=["Jan", "Feb", "Mar", "Apr"],
values=[50, 75, 60, 90],
style=SeriesStyle(color="#3b82f6"),
))
data.add_series(CategoricalSeries.from_values(
name="Product B",
categories=["Jan", "Feb", "Mar", "Apr"],
values=[40, 55, 70, 65],
style=SeriesStyle(color="#22c55e"),
))
NumericChartData¶
For charts with numeric X/Y data (Line, Scatter, Area):
from castella.chart import NumericChartData, NumericSeries, SeriesStyle
data = NumericChartData(title="Temperature Over Time")
# Y values only (X auto-generated as 0, 1, 2, ...)
data.add_series(NumericSeries.from_y_values(
name="Sensor A",
y_values=[20, 22, 21, 25, 24, 23],
style=SeriesStyle(color="#3b82f6"),
))
# Explicit X and Y values
data.add_series(NumericSeries.from_values(
name="Sensor B",
x_values=[0, 1, 2, 3, 4, 5],
y_values=[18, 19, 20, 22, 21, 20],
style=SeriesStyle(color="#22c55e"),
))
GaugeChartData¶
For gauge/meter charts:
from castella.chart import GaugeChartData
data = GaugeChartData(
title="CPU Usage",
value=67,
min_value=0,
max_value=100,
value_format="{:.0f}%",
thresholds=[
(0.0, "#22c55e"), # Green for 0-50%
(0.5, "#f59e0b"), # Yellow for 50-80%
(0.8, "#ef4444"), # Red for 80-100%
],
)
HeatmapChartData¶
For heatmap charts with 2D matrix data:
from castella.chart import HeatmapChartData
# Create from 2D array
data = HeatmapChartData.from_2d_array(
values=[
[1.0, 0.8, 0.3],
[0.8, 1.0, 0.5],
[0.3, 0.5, 1.0],
],
row_labels=["A", "B", "C"],
column_labels=["X", "Y", "Z"],
title="Correlation Matrix",
)
# Set value range for color normalization
data.set_range(min_value=-1.0, max_value=1.0)
# Update single cell
data.set_cell(row=0, col=1, value=0.9)
# Update entire matrix
data.set_values([[1.0, 0.9], [0.9, 1.0]])
Chart Types¶
BarChart¶
Vertical bar chart for categorical data:
from castella.chart import BarChart, CategoricalChartData, CategoricalSeries
data = CategoricalChartData(title="Sales")
data.add_series(CategoricalSeries.from_values(
name="Revenue",
categories=["Q1", "Q2", "Q3", "Q4"],
values=[100, 150, 120, 180],
))
chart = BarChart(
data,
show_values=True, # Show value labels on bars
bar_width_ratio=0.7, # Bar width as ratio of available space
enable_tooltip=True, # Show tooltips on hover
)
LineChart¶
Line chart with optional points:
from castella.chart import LineChart, NumericChartData, NumericSeries
data = NumericChartData(title="Stock Price")
data.add_series(NumericSeries.from_y_values(
name="AAPL",
y_values=[150, 155, 148, 160, 158, 165],
))
chart = LineChart(
data,
show_points=True, # Show data points
point_radius=4.0, # Point size
line_width=2.0, # Line thickness
smooth=False, # Use straight lines (True for Catmull-Rom splines)
)
# Smooth curves using Catmull-Rom spline interpolation
smooth_chart = LineChart(data, smooth=True)
PieChart¶
Pie and donut charts:
from castella.chart import PieChart, CategoricalChartData, CategoricalSeries
data = CategoricalChartData(title="Market Share")
data.add_series(CategoricalSeries.from_values(
name="Companies",
categories=["Company A", "Company B", "Company C", "Others"],
values=[35, 28, 22, 15],
))
chart = PieChart(
data,
inner_radius_ratio=0.0, # 0.0 for pie, 0.5+ for donut
show_labels=True, # Show category labels
show_percentages=True, # Show percentage values
)
ScatterChart¶
Scatter plot with customizable point shapes:
from castella.chart import ScatterChart, PointShape, NumericChartData, NumericSeries
data = NumericChartData(title="Height vs Weight")
data.add_series(NumericSeries.from_values(
name="Male",
x_values=[170, 175, 180, 185],
y_values=[70, 75, 80, 85],
))
chart = ScatterChart(
data,
point_radius=6,
point_shape=PointShape.CIRCLE, # CIRCLE, SQUARE, DIAMOND, TRIANGLE
show_grid=True,
)
AreaChart¶
Filled area chart:
from castella.chart import AreaChart, NumericChartData, NumericSeries
data = NumericChartData(title="Website Traffic")
data.add_series(NumericSeries.from_y_values(
name="Visitors",
y_values=[1200, 1500, 1300, 1800, 2100],
))
chart = AreaChart(
data,
fill_opacity=0.3, # Area fill transparency
show_points=True, # Show data points
stacked=False, # Stack multiple series
)
StackedBarChart¶
Stacked bar chart for comparing parts of a whole:
from castella.chart import StackedBarChart, CategoricalChartData, CategoricalSeries
data = CategoricalChartData(title="Revenue by Region")
data.add_series(CategoricalSeries.from_values(
name="North", categories=["Q1", "Q2", "Q3", "Q4"], values=[100, 120, 90, 150],
))
data.add_series(CategoricalSeries.from_values(
name="South", categories=["Q1", "Q2", "Q3", "Q4"], values=[80, 100, 110, 95],
))
data.add_series(CategoricalSeries.from_values(
name="East", categories=["Q1", "Q2", "Q3", "Q4"], values=[60, 75, 85, 70],
))
chart = StackedBarChart(
data,
normalized=False, # True for 100% stacked chart
show_values=False,
)
GaugeChart¶
Gauge/meter chart with threshold colors:
from castella.chart import GaugeChart, GaugeStyle, GaugeChartData
data = GaugeChartData(
title="CPU Usage",
value=67,
min_value=0,
max_value=100,
value_format="{:.0f}%",
thresholds=[
(0.0, "#22c55e"), # Green
(0.5, "#f59e0b"), # Yellow
(0.8, "#ef4444"), # Red
],
)
chart = GaugeChart(
data,
style=GaugeStyle.HALF_CIRCLE, # HALF_CIRCLE, THREE_QUARTER, FULL_CIRCLE
show_ticks=True,
arc_width=25,
)
HeatmapChart¶
Heatmap for 2D matrix data visualization:
from castella.chart import HeatmapChart, HeatmapChartData, ColormapType
# Create correlation matrix data
data = HeatmapChartData.from_2d_array(
values=[
[1.00, 0.85, 0.42, -0.15],
[0.85, 1.00, 0.55, 0.08],
[0.42, 0.55, 1.00, 0.67],
[-0.15, 0.08, 0.67, 1.00],
],
row_labels=["Price", "Volume", "Volatility", "Beta"],
column_labels=["Price", "Volume", "Volatility", "Beta"],
title="Correlation Matrix",
)
# Set fixed value range for color normalization
data.set_range(-1.0, 1.0)
chart = HeatmapChart(
data,
colormap=ColormapType.VIRIDIS, # VIRIDIS, PLASMA, INFERNO, MAGMA
show_values=True, # Show value annotations in cells
show_colorbar=True, # Show color bar legend
cell_gap=2.0, # Gap between cells in pixels
)
Colormaps¶
Castella provides scientific colormaps that are perceptually uniform and colorblind-friendly:
from castella.chart import viridis, plasma, inferno, magma, get_colormap
# Get colormap by type
cmap = get_colormap(ColormapType.VIRIDIS)
# Get color for a normalized value (0.0 to 1.0)
color = cmap(0.5) # Returns "#26828e"
# Get N evenly spaced colors
colors = cmap.get_colors(5) # ["#440154", "#3e4a89", "#26828e", "#35b779", "#fde725"]
# Reversed colormap
reversed_cmap = cmap.reversed()
Heatmap with DataTable¶
You can also apply heatmap coloring to DataTable cells using HeatmapConfig:
from castella import DataTable, DataTableState, ColumnConfig, HeatmapConfig
from castella.chart import ColormapType
# Create table data
state = DataTableState(
columns=[
ColumnConfig(name="Region", width=120),
ColumnConfig(name="Q1", width=80),
ColumnConfig(name="Q2", width=80),
ColumnConfig(name="Q3", width=80),
ColumnConfig(name="Q4", width=80),
],
rows=[
["North America", 145, 162, 178, 195],
["Europe", 122, 138, 155, 168],
["Asia Pacific", 198, 215, 242, 267],
],
)
# Apply heatmap coloring to numeric columns
heatmap = HeatmapConfig(colormap=ColormapType.VIRIDIS)
for i in range(1, 5):
state.columns[i].cell_bg_color = heatmap.create_color_fn(col_idx=i, state=state)
table = DataTable(state)
Features:
- Auto-contrast text: Text color automatically adjusts for readability
- Per-column ranges: Each column can have its own value range
- Custom colormaps: Pass any Colormap instance
Interactivity¶
Event Handlers¶
All charts support hover and click events:
chart = BarChart(data).on_click(on_bar_click).on_hover(on_bar_hover)
def on_bar_click(event):
print(f"Clicked: {event.label} = {event.value}")
print(f"Series: {event.series_index}, Data: {event.data_index}")
def on_bar_hover(event):
print(f"Hovering: {event.label}")
Tooltips¶
Tooltips are enabled by default and show on hover:
chart = LineChart(data, enable_tooltip=True) # Default
chart = LineChart(data, enable_tooltip=False) # Disable
Series Visibility¶
Toggle series visibility (useful with legends):
# Hide/show series
data.set_series_visibility(series_index=1, visible=False)
data.toggle_series_visibility(series_index=0)
# Check visibility
if data.is_series_visible(0):
print("Series 0 is visible")
Reactive Updates¶
Charts automatically re-render when their data changes:
class LiveChart(Component):
def __init__(self):
super().__init__()
self._data = GaugeChartData(title="CPU", value=50, max_value=100)
self._data.attach(self) # Re-render on data change
def view(self):
return Column(
GaugeChart(self._data, style=GaugeStyle.HALF_CIRCLE),
Button("Update").on_click(self._update),
)
def _update(self, _):
import random
self._data.set_value(random.randint(0, 100)) # Triggers re-render
SeriesStyle¶
Customize series appearance:
from castella.chart import SeriesStyle
style = SeriesStyle(
color="#3b82f6", # Primary color
fill_opacity=0.3, # Fill transparency (for area charts)
line_width=2.0, # Line thickness
point_radius=4.0, # Point size
)
series = CategoricalSeries.from_values(
name="Sales",
categories=["A", "B", "C"],
values=[10, 20, 30],
style=style,
)
Complete Example¶
from castella import App, Component, Column, Row, Button
from castella.frame import Frame
from castella.chart import (
BarChart, LineChart, PieChart, GaugeChart, GaugeStyle,
CategoricalChartData, NumericChartData, GaugeChartData,
CategoricalSeries, NumericSeries, SeriesStyle,
)
class ChartDemo(Component):
def __init__(self):
super().__init__()
# Bar chart data
self._bar_data = CategoricalChartData(title="Quarterly Sales")
self._bar_data.add_series(CategoricalSeries.from_values(
name="2024",
categories=["Q1", "Q2", "Q3", "Q4"],
values=[120, 150, 130, 180],
style=SeriesStyle(color="#3b82f6"),
))
self._bar_data.attach(self)
# Line chart data
self._line_data = NumericChartData(title="Daily Views")
self._line_data.add_series(NumericSeries.from_y_values(
name="Page Views",
y_values=[100, 120, 90, 150, 180, 160, 200],
style=SeriesStyle(color="#22c55e"),
))
self._line_data.attach(self)
# Gauge data
self._gauge_data = GaugeChartData(
title="Performance",
value=75,
max_value=100,
value_format="{:.0f}%",
)
self._gauge_data.attach(self)
def view(self):
return Column(
Row(
BarChart(self._bar_data, show_values=True)
.on_click(lambda e: print(f"Bar: {e.label}")),
LineChart(self._line_data, show_points=True)
.on_hover(lambda e: print(f"Line: {e.value}")),
),
Row(
GaugeChart(self._gauge_data, style=GaugeStyle.HALF_CIRCLE),
Button("Randomize").on_click(self._randomize),
).fixed_height(200),
)
def _randomize(self, _):
import random
self._gauge_data.set_value(random.randint(0, 100))
App(Frame("Chart Demo", 1000, 700), ChartDemo()).run()
ASCII Charts (Terminal)¶
For terminal environments, Castella provides ASCII chart widgets that render using Unicode characters.
ASCIIBarChart¶
from castella.chart import ASCIIBarChart, ASCIIBarData
data = ASCIIBarData(
title="Quarterly Sales",
labels=["Q1", "Q2", "Q3", "Q4"],
values=[120, 180, 150, 200],
)
chart = ASCIIBarChart(data, width=30, show_values=True)
Output:
Quarterly Sales
Q1 │██████████████████ │ 120.0
Q2 │███████████████████████████ │ 180.0
Q3 │██████████████████████▌ │ 150.0
Q4 │██████████████████████████████│ 200.0
ASCIIPieChart¶
from castella.chart import ASCIIPieChart, ASCIIPieData
data = ASCIIPieData(
title="Market Share",
labels=["Product A", "Product B", "Others"],
values=[50, 30, 20],
)
chart = ASCIIPieChart(data)
Output:
ASCIILineChart¶
from castella.chart import ASCIILineChart
chart = ASCIILineChart(
values=[10, 25, 15, 30, 20, 35],
width=40,
height=8,
title="Trend",
)
ASCIIGaugeChart¶
from castella.chart import ASCIIGaugeChart
chart = ASCIIGaugeChart(
value=67.5,
max_value=100,
width=25,
title="CPU Usage",
)
Output:
Running in Terminal Mode¶
To run your app in terminal mode: