from typing import List, Union, Literal, Optional, Dict, Any
from odmantic import Model, Field, EmbeddedModel, ObjectId
from simstack.models import simstack_model
# Chart Series Definitions
[docs]
class AGChartSeriesBase(EmbeddedModel):
"""Base class for AG-Charts series configuration."""
type: str = Field(..., description="Chart series type")
xKey: str = Field(..., description="Key for x-axis data")
yKey: str = Field(..., description="Key for y-axis data")
visible: Optional[bool] = Field(
default=True, description="Whether series is visible"
)
showInLegend: Optional[bool] = Field(default=True, description="Show in legend")
title: Optional[str] = Field(default=None, description="Series title")
# Chart data
data: List[Dict[str, Any]] = Field(default_factory=list, description="Chart data")
[docs]
class AGLineSeriesConfig(AGChartSeriesBase):
"""AG-Charts line series configuration."""
type: Literal["line"] = "line"
strokeWidth: Optional[float] = Field(default=2, description="Line stroke width")
strokeOpacity: Optional[float] = Field(default=1, description="Line stroke opacity")
lineDash: Optional[List[float]] = Field(
default=None, description="Line dash pattern"
)
marker: Optional[Dict[str, Any]] = Field(
default=None, description="Marker configuration"
)
tooltip: Optional[Dict[str, Any]] = Field(
default=None, description="Tooltip configuration"
)
[docs]
class AGBarSeriesConfig(AGChartSeriesBase):
"""AG-Charts bar series configuration."""
type: Literal["bar"] = "bar"
fillOpacity: Optional[float] = Field(default=1, description="Bar fill opacity")
strokeWidth: Optional[float] = Field(default=0, description="Bar stroke width")
cornerRadius: Optional[float] = Field(default=0, description="Bar corner radius")
tooltip: Optional[Dict[str, Any]] = Field(
default=None, description="Tooltip configuration"
)
[docs]
class AGRangeBarSeriesConfig(EmbeddedModel):
"""AG-Charts range-bar series configuration."""
type: Literal["range-bar"] = "range-bar"
xKey: str = Field(..., description="Key for x-axis data")
yLowKey: str = Field(..., description="Key for low range value")
yHighKey: str = Field(..., description="Key for high range value")
xName: Optional[str] = Field(default=None, description="X axis display name")
yName: Optional[str] = Field(default=None, description="Y axis display name")
yLowName: Optional[str] = Field(default=None, description="Low value display name")
yHighName: Optional[str] = Field(default=None, description="High value display name")
direction: Optional[Literal["horizontal", "vertical"]] = Field(
default=None, description="Bar rendering direction"
)
visible: Optional[bool] = Field(
default=True, description="Whether series is visible"
)
showInLegend: Optional[bool] = Field(default=True, description="Show in legend")
title: Optional[str] = Field(default=None, description="Series title")
data: List[Dict[str, Any]] = Field(default_factory=list, description="Chart data")
fillOpacity: Optional[float] = Field(default=1, description="Bar fill opacity")
strokeWidth: Optional[float] = Field(default=0, description="Bar stroke width")
cornerRadius: Optional[float] = Field(default=0, description="Bar corner radius")
tooltip: Optional[Dict[str, Any]] = Field(
default=None, description="Tooltip configuration"
)
[docs]
class AGColumnSeriesConfig(AGChartSeriesBase):
"""AG-Charts column series configuration."""
type: Literal["column"] = "column"
fillOpacity: Optional[float] = Field(default=1, description="Column fill opacity")
strokeWidth: Optional[float] = Field(default=0, description="Column stroke width")
cornerRadius: Optional[float] = Field(default=0, description="Column corner radius")
tooltip: Optional[Dict[str, Any]] = Field(
default=None, description="Tooltip configuration"
)
[docs]
class AGAreaSeriesConfig(AGChartSeriesBase):
"""AG-Charts area series configuration."""
type: Literal["area"] = "area"
fillOpacity: Optional[float] = Field(default=0.8, description="Area fill opacity")
strokeWidth: Optional[float] = Field(default=2, description="Area stroke width")
marker: Optional[Dict[str, Any]] = Field(
default=None, description="Marker configuration"
)
tooltip: Optional[Dict[str, Any]] = Field(
default=None, description="Tooltip configuration"
)
[docs]
class AGScatterSeriesConfig(AGChartSeriesBase):
"""AG-Charts scatter series configuration."""
type: Literal["scatter"] = "scatter"
marker: Optional[Dict[str, Any]] = Field(
default=None, description="Marker configuration"
)
tooltip: Optional[Dict[str, Any]] = Field(
default=None, description="Tooltip configuration"
)
[docs]
class AGHeatmapSeriesConfig(AGChartSeriesBase):
"""AG-Charts heatmap series configuration."""
type: Literal["heatmap"] = "heatmap"
colorKey: str = Field(..., description="Key for heatmap color values")
xName: Optional[str] = Field(default=None, description="X axis display name")
yName: Optional[str] = Field(default=None, description="Y axis display name")
colorName: Optional[str] = Field(default=None, description="Color value display name")
colorRange: Optional[List[str]] = Field(
default=None, description="Color interpolation range"
)
colorDomain: Optional[List[float]] = Field(
default=None, description="Color domain [min, max]"
)
stroke: Optional[str] = Field(default=None, description="Cell border color")
strokeWidth: Optional[float] = Field(default=None, description="Cell border width")
tooltip: Optional[Dict[str, Any]] = Field(
default=None, description="Tooltip configuration"
)
[docs]
class AGPieSeriesConfig(EmbeddedModel):
"""AG-Charts pie series configuration."""
type: Literal["pie"] = "pie"
angleKey: str = Field(..., description="Key for pie slice angles")
radiusKey: Optional[str] = Field(
default=None, description="Key for pie slice radius"
)
labelKey: Optional[str] = Field(
default=None, description="Key for pie slice labels"
)
legendItemKey: Optional[str] = Field(
default=None, description="Key for legend item labels"
)
calloutLabelKey: Optional[str] = Field(
default=None, description="Key for callout labels"
)
sectorLabelKey: Optional[str] = Field(
default=None, description="Key for sector labels"
)
visible: Optional[bool] = Field(
default=True, description="Whether series is visible"
)
showInLegend: Optional[bool] = Field(default=True, description="Show in legend")
title: Optional[str] = Field(default=None, description="Series title")
tooltip: Optional[Dict[str, Any]] = Field(
default=None, description="Tooltip configuration"
)
data: List[Dict[str, Any]] = Field(default_factory=list, description="Chart data")
[docs]
class AGDonutSeriesConfig(EmbeddedModel):
"""AG-Charts donut series configuration."""
type: Literal["donut"] = "donut"
angleKey: str = Field(..., description="Key for donut slice angles")
radiusKey: Optional[str] = Field(
default=None, description="Key for donut slice radius"
)
labelKey: Optional[str] = Field(
default=None, description="Key for donut slice labels"
)
legendItemKey: Optional[str] = Field(
default=None, description="Key for legend item labels"
)
calloutLabelKey: Optional[str] = Field(
default=None, description="Key for callout labels"
)
sectorLabelKey: Optional[str] = Field(
default=None, description="Key for sector labels"
)
innerRadiusRatio: Optional[float] = Field(
default=0.6, description="Inner radius ratio"
)
visible: Optional[bool] = Field(
default=True, description="Whether series is visible"
)
showInLegend: Optional[bool] = Field(default=True, description="Show in legend")
title: Optional[str] = Field(default=None, description="Series title")
tooltip: Optional[Dict[str, Any]] = Field(
default=None, description="Tooltip configuration"
)
# Union type for all series configurations
AGChartSeries = Union[
AGLineSeriesConfig,
AGBarSeriesConfig,
AGRangeBarSeriesConfig,
AGColumnSeriesConfig,
AGAreaSeriesConfig,
AGScatterSeriesConfig,
AGHeatmapSeriesConfig,
AGPieSeriesConfig,
AGDonutSeriesConfig,
]
# Axis Configurations
[docs]
class AGChartAxisConfig(EmbeddedModel):
"""AG-Charts axis configuration."""
type: Literal["category", "number", "time", "log"] = Field(
..., description="Axis type"
)
position: Literal["top", "right", "bottom", "left"] = Field(
..., description="Axis position"
)
title: Optional[str] = Field(default=None, description="Axis title")
min: Optional[float] = Field(default=None, description="Minimum axis value")
max: Optional[float] = Field(default=None, description="Maximum axis value")
tick: Optional[Dict[str, Any]] = Field(
default=None, description="Tick configuration"
)
label: Optional[Dict[str, Any]] = Field(
default=None, description="Label configuration"
)
gridStyle: Optional[List[Dict[str, Any]]] = Field(
default=None, description="Grid line style"
)
# Legend Configuration
[docs]
class AGChartLegendConfig(EmbeddedModel):
"""AG-Charts legend configuration."""
enabled: bool = Field(default=True, description="Enable legend")
position: Literal["top", "right", "bottom", "left"] = Field(
default="right", description="Legend position"
)
spacing: float = Field(default=20, description="Legend spacing")
item: Dict[str, Any] = Field(
default_factory=dict, description="Legend item configuration"
)
# Title Configuration
[docs]
class AGChartTitleConfig(EmbeddedModel):
"""AG-Charts title configuration."""
text: str = Field("Chart Title", description="Title text")
# fontSize: Optional[int] = Field(default=16, description="Title font size")
# fontWeight: Optional[str] = Field(default="bold", description="Title font weight")
# color: Optional[str] = Field(default=None, description="Title color")
# Subtitle Configuration
[docs]
class AGChartSubtitleConfig(EmbeddedModel):
"""AG-Charts subtitle configuration."""
text: str = Field(..., description="Subtitle text")
fontSize: Optional[int] = Field(default=12, description="Subtitle font size")
color: Optional[str] = Field(default=None, description="Subtitle color")
# Frame Configuration
[docs]
class AGChartFrameConfig(EmbeddedModel):
"""AG-Charts frame (border) configuration."""
enabled: bool = Field(default=True, description="Enable chart frame/border")
stroke: str = Field(default="black", description="Frame stroke color")
strokeWidth: float = Field(default=1, description="Frame stroke width")
cornerRadius: float = Field(default=0, description="Frame corner radius")
opacity: float = Field(default=1, description="Frame opacity")
# Main Chart Model
[docs]
@simstack_model
class ChartArtifactModel(Model):
"""AG-Charts configuration model."""
parent_id: Optional[ObjectId] = None
# Chart data
data: List[Dict[str, Any]] = Field(default_factory=list, description="Chart data")
# Chart configuration
title: AGChartTitleConfig = Field(..., description="Chart title configuration")
# subtitle: AGChartSubtitleConfig = Field(..., description="Chart subtitle configuration")
# Series configuration
series: List[AGChartSeries] = Field(
default_factory=list, description="Chart series configurations"
)
# Axes configuration
axes: List[AGChartAxisConfig] = Field(
default_factory=list, description="Chart axes configurations"
)
# # Legend configuration
legend: AGChartLegendConfig = Field(
default=AGChartLegendConfig(), description="Legend configuration"
)
#
# Chart styling and behavior
width: int = Field(default=800, description="Chart width in pixels")
height: int = Field(default=400, description="Chart height in pixels")
padding: Optional[Dict[str, int]] = Field(default=None, description="Chart padding")
background: Optional[Dict[str, Any]] = Field(
default=None, description="Background configuration"
)
frame: AGChartFrameConfig = Field(
default=AGChartFrameConfig(enabled=False), description="Frame/border configuration"
)
# Animation
animation: Optional[Dict[str, Any]] = Field(
default=None, description="Animation configuration"
)
# Tooltip
tooltip: Optional[Dict[str, Any]] = Field(
default=None, description="Global tooltip configuration"
)
# Theme
theme: Optional[str] = Field(default="ag-default", description="Chart theme")
# Additional options
options: Optional[Dict[str, Any]] = Field(
default_factory=dict, description="Additional chart options"
)
[docs]
def make_table_entries(
self,
max_recursion_level=1,
drop_id=True,
current_level=0,
visited=None,
field_prefix="",
):
return {"title": self.title.text[:10]}
[docs]
def make_column_defs_instance(
self,
table_name=None,
max_recursion_level=1,
drop_id=True,
current_level=0,
visited=None,
field_prefix="",
):
return [{"field": "title", "headerName": "Chart"}]
# Helper functions for creating specific chart types
[docs]
def create_simple_line_chart(
data: List[Dict[str, Any]],
x_key: str,
y_key: str,
title: Optional[str] = None,
parent_id: ObjectId = None,
) -> ChartArtifactModel:
"""Create a simple line chart."""
chart_title = (
AGChartTitleConfig(text=title) if title else AGChartTitleConfig(text="Chart")
)
# subtitle = AGChartSubtitleConfig(text="Subtitle")
series = [
AGLineSeriesConfig(
type="line", xKey=x_key, yKey=y_key, title=y_key.title(), data=data
)
]
axes = [
AGChartAxisConfig(type="number", position="bottom", title=x_key.title()),
AGChartAxisConfig(type="number", position="left", title=y_key.title()),
]
return ChartArtifactModel(
parent_id=parent_id,
data=data,
title=chart_title,
# subtitle=subtitle,
series=series,
axes=axes,
)
[docs]
def create_simple_bar_chart(
data: List[Dict[str, Any]],
x_key: str,
y_key: str,
title: Optional[str] = None,
parent_id: Optional[ObjectId] = None,
) -> ChartArtifactModel:
"""Create a simple bar chart."""
chart_title = AGChartTitleConfig(text=title) if title else None
series = [
AGColumnSeriesConfig(type="column", xKey=x_key, yKey=y_key, title=y_key.title())
]
axes = [
AGChartAxisConfig(type="category", position="bottom", title=x_key.title()),
AGChartAxisConfig(type="number", position="left", title=y_key.title()),
]
return ChartArtifactModel(
parent_id=parent_id, data=data, title=chart_title, series=series, axes=axes
)
[docs]
def create_simple_pie_chart(
data: List[Dict[str, Any]],
angle_key: str,
label_key: str,
title: Optional[str] = None,
legend_item_key: Optional[str] = None,
callout_label_key: Optional[str] = None,
sector_label_key: Optional[str] = None,
parent_id: Optional[ObjectId] = None,
) -> ChartArtifactModel:
"""Create a simple pie chart."""
chart_title = AGChartTitleConfig(text=title) if title else None
series = [
AGPieSeriesConfig(
type="pie",
angleKey=angle_key,
labelKey=label_key,
legendItemKey=legend_item_key,
calloutLabelKey=callout_label_key,
sectorLabelKey=sector_label_key,
title="Distribution",
data=data,
)
]
return ChartArtifactModel(
parent_id=parent_id, data=data, title=chart_title, series=series
)
[docs]
def create_simple_range_bar_chart(
data: List[Dict[str, Any]],
x_key: str,
y_low_key: str,
y_high_key: str,
title: Optional[str] = None,
parent_id: Optional[ObjectId] = None,
direction: Optional[Literal["horizontal", "vertical"]] = None,
) -> ChartArtifactModel:
"""Create a simple range-bar chart."""
chart_title = AGChartTitleConfig(text=title) if title else None
series = [
AGRangeBarSeriesConfig(
type="range-bar",
xKey=x_key,
yLowKey=y_low_key,
yHighKey=y_high_key,
title=f"{y_low_key.title()} - {y_high_key.title()}",
direction=direction,
data=data,
)
]
axes = [
AGChartAxisConfig(type="category", position="bottom", title=x_key.title()),
AGChartAxisConfig(
type="number",
position="left",
title=f"{y_low_key.title()} / {y_high_key.title()}",
),
]
return ChartArtifactModel(
parent_id=parent_id, data=data, title=chart_title, series=series, axes=axes
)
[docs]
def create_simple_area_chart(
data: List[Dict[str, Any]],
x_key: str,
y_key: str,
title: Optional[str] = None,
parent_id: Optional[ObjectId] = None,
) -> ChartArtifactModel:
"""Create a simple area chart."""
chart_title = AGChartTitleConfig(text=title) if title else None
series = [
AGAreaSeriesConfig(type="area", xKey=x_key, yKey=y_key, title=y_key.title())
]
axes = [
AGChartAxisConfig(type="category", position="bottom", title=x_key.title()),
AGChartAxisConfig(type="number", position="left", title=y_key.title()),
]
return ChartArtifactModel(
parent_id=parent_id, data=data, title=chart_title, series=series, axes=axes
)
[docs]
def create_simple_scatter_chart(
data: List[Dict[str, Any]],
x_key: str,
y_key: str,
title: Optional[str] = None,
parent_id: Optional[ObjectId] = None,
) -> ChartArtifactModel:
"""Create a simple scatter chart."""
chart_title = AGChartTitleConfig(text=title) if title else None
series = [
AGScatterSeriesConfig(
type="scatter", xKey=x_key, yKey=y_key, title=y_key.title()
)
]
axes = [
AGChartAxisConfig(type="number", position="bottom", title=x_key.title()),
AGChartAxisConfig(type="number", position="left", title=y_key.title()),
]
return ChartArtifactModel(
parent_id=parent_id, data=data, title=chart_title, series=series, axes=axes
)
[docs]
def create_simple_heatmap_chart(
data: List[Dict[str, Any]],
x_key: str,
y_key: str,
color_key: str,
title: Optional[str] = None,
parent_id: Optional[ObjectId] = None,
x_axis_type: Literal["category", "number", "time", "log"] = "category",
y_axis_type: Literal["category", "number", "time", "log"] = "category",
) -> ChartArtifactModel:
"""Create a simple heatmap chart."""
chart_title = (
AGChartTitleConfig(text=title) if title else AGChartTitleConfig(text="Chart")
)
series = [
AGHeatmapSeriesConfig(
type="heatmap",
xKey=x_key,
yKey=y_key,
colorKey=color_key,
title=color_key.title(),
data=data,
)
]
axes = [
AGChartAxisConfig(type=x_axis_type, position="bottom", title=x_key.title()),
AGChartAxisConfig(type=y_axis_type, position="left", title=y_key.title()),
]
return ChartArtifactModel(
parent_id=parent_id, data=data, title=chart_title, series=series, axes=axes
)
[docs]
def create_simple_donut_chart(
data: List[Dict[str, Any]],
angle_key: str,
label_key: str,
title: Optional[str] = None,
legend_item_key: Optional[str] = None,
callout_label_key: Optional[str] = None,
sector_label_key: Optional[str] = None,
inner_radius_ratio: float = 0.6,
parent_id: Optional[ObjectId] = None,
) -> ChartArtifactModel:
"""Create a simple donut chart."""
chart_title = AGChartTitleConfig(text=title) if title else None
series = [
AGDonutSeriesConfig(
type="donut",
angleKey=angle_key,
labelKey=label_key,
legendItemKey=legend_item_key,
calloutLabelKey=callout_label_key,
sectorLabelKey=sector_label_key,
innerRadiusRatio=inner_radius_ratio,
title="Distribution",
)
]
return ChartArtifactModel(
parent_id=parent_id, data=data, title=chart_title, series=series
)
[docs]
def create_multi_series_line_chart(
data: List[Dict[str, Any]],
x_key: str,
y_keys: List[str],
title: Optional[str] = None,
parent_id: Optional[ObjectId] = None,
) -> ChartArtifactModel:
"""Create a line chart with multiple y-axis series."""
chart_title = AGChartTitleConfig(text=title) if title else AGChartTitleConfig(text="Chart")
series = []
for y_key in y_keys:
series.append(
AGLineSeriesConfig(
type="line", xKey=x_key, yKey=y_key, title=y_key.title(), data=data
)
)
axes = [
AGChartAxisConfig(type="number", position="bottom", title=x_key.title()),
AGChartAxisConfig(type="number", position="left", title="Values"),
]
return ChartArtifactModel(
parent_id=parent_id,
data=data,
title=chart_title,
series=series,
axes=axes,
)