Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions datawrapper/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
StackedBarChart,
TextAnnotation,
Transform,
XLineAnnotation,
XRangeAnnotation,
YLineAnnotation,
YRangeAnnotation,
)
from datawrapper.charts.enums import (
ArrowHead,
Expand Down Expand Up @@ -104,6 +108,10 @@
"StackedBarChart",
"TextAnnotation",
"RangeAnnotation",
"XRangeAnnotation",
"YRangeAnnotation",
"XLineAnnotation",
"YLineAnnotation",
"ConnectorLine",
"ArrowHead",
"ConnectorLineType",
Expand Down
14 changes: 13 additions & 1 deletion datawrapper/charts/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
from .annos import ConnectorLine, RangeAnnotation, TextAnnotation
from .annos import (
ConnectorLine,
RangeAnnotation,
TextAnnotation,
XLineAnnotation,
XRangeAnnotation,
YLineAnnotation,
YRangeAnnotation,
)
from .area import AreaChart
from .arrow import ArrowChart
from .bar import BarChart, BarOverlay
Expand Down Expand Up @@ -60,6 +68,10 @@
"ConnectorLine",
"RangeAnnotation",
"TextAnnotation",
"XLineAnnotation",
"XRangeAnnotation",
"YLineAnnotation",
"YRangeAnnotation",
"Annotate",
"ColumnFormat",
"ColumnFormatList",
Expand Down
108 changes: 108 additions & 0 deletions datawrapper/charts/annos.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,14 @@ class RangeAnnotation(BaseModel):
default="x", description="The axis of the annotation"
)

@field_validator("type")
@classmethod
def validate_type(cls, v: str) -> str:
"""Validate that type is either 'x' or 'y'."""
if v not in ["x", "y"]:
raise ValueError(f"Invalid type: {v}. Must be either 'x' or 'y'")
return v

#: The color of the annotation
color: str = Field(default="#989898", description="The color of the annotation")

Expand All @@ -449,9 +457,25 @@ class RangeAnnotation(BaseModel):
default="range", description="The display style of the annotation"
)

@field_validator("display")
@classmethod
def validate_display(cls, v: str) -> str:
"""Validate that display is either 'line' or 'range'."""
if v not in ["line", "range"]:
raise ValueError(f"Invalid display: {v}. Must be either 'line' or 'range'")
return v

#: The opacity of the annotation
opacity: int = Field(default=50, description="The opacity of the annotation")

@field_validator("opacity")
@classmethod
def validate_opacity(cls, v: int) -> int:
"""Validate that opacity is between 0 and 100."""
if not 0 <= v <= 100:
raise ValueError(f"Invalid opacity: {v}. Must be between 0 and 100")
return v

#: The first x position (required for type="x" annotations)
x0: Any | None = Field(
default=None,
Expand Down Expand Up @@ -555,3 +579,87 @@ def deserialize_model(cls, api_data: dict[str, dict] | None) -> list[dict]:
return []

return [{**anno_data, "id": anno_id} for anno_id, anno_data in api_data.items()]


class XRangeAnnotation(RangeAnnotation):
"""A horizontal range annotation (shaded area between two x positions).

Automatically sets type="x" and display="range".
Requires both x0 and x1 to be provided.
"""

def __init__(self, **data):
"""Initialize with type="x" and display="range" automatically set."""
data.setdefault("type", "x")
data.setdefault("display", "range")
super().__init__(**data)

@model_validator(mode="after")
def validate_x_positions_required(self) -> "XRangeAnnotation":
"""Validate that both x0 and x1 are provided."""
if self.x0 is None or self.x1 is None:
raise ValueError("XRangeAnnotation requires both x0 and x1 to be set")
return self


class YRangeAnnotation(RangeAnnotation):
"""A vertical range annotation (shaded area between two y positions).

Automatically sets type="y" and display="range".
Requires both y0 and y1 to be provided.
"""

def __init__(self, **data):
"""Initialize with type="y" and display="range" automatically set."""
data.setdefault("type", "y")
data.setdefault("display", "range")
super().__init__(**data)

@model_validator(mode="after")
def validate_y_positions_required(self) -> "YRangeAnnotation":
"""Validate that both y0 and y1 are provided."""
if self.y0 is None or self.y1 is None:
raise ValueError("YRangeAnnotation requires both y0 and y1 to be set")
return self


class XLineAnnotation(RangeAnnotation):
"""A vertical line annotation at a specific x position.

Automatically sets type="x" and display="line".
Requires x0 to be provided.
"""

def __init__(self, **data):
"""Initialize with type="x" and display="line" automatically set."""
data.setdefault("type", "x")
data.setdefault("display", "line")
super().__init__(**data)

@model_validator(mode="after")
def validate_x0_required(self) -> "XLineAnnotation":
"""Validate that x0 is provided."""
if self.x0 is None:
raise ValueError("XLineAnnotation requires x0 to be set")
return self


class YLineAnnotation(RangeAnnotation):
"""A horizontal line annotation at a specific y position.

Automatically sets type="y" and display="line".
Requires y0 to be provided.
"""

def __init__(self, **data):
"""Initialize with type="y" and display="line" automatically set."""
data.setdefault("type", "y")
data.setdefault("display", "line")
super().__init__(**data)

@model_validator(mode="after")
def validate_y0_required(self) -> "YLineAnnotation":
"""Validate that y0 is provided."""
if self.y0 is None:
raise ValueError("YLineAnnotation requires y0 to be set")
return self
20 changes: 20 additions & 0 deletions docs/user-guide/api/models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,26 @@ Annotations
:members:
:show-inheritance:

.. autoclass:: XRangeAnnotation
:members:
:show-inheritance:

.. autoclass:: YRangeAnnotation
:members:
:show-inheritance:

.. autoclass:: XLineAnnotation
:members:
:show-inheritance:

.. autoclass:: YLineAnnotation
:members:
:show-inheritance:

.. autoclass:: AreaFill
:members:
:show-inheritance:

.. autoclass:: ConnectorLine
:members:
:show-inheritance:
Expand Down
Loading
Loading