Source code for datawrapper.charts.area

from typing import Any, Literal

import pandas as pd
from pydantic import ConfigDict, Field, field_validator

from .base import BaseChart
from .enums import (
    DateFormat,
    GridLabelAlign,
    GridLabelPosition,
    LineInterpolation,
    NumberFormat,
    PlotHeightMode,
)
from .models import (
    AnnotationsMixin,
    CustomRangeMixin,
    CustomTicksMixin,
    GridDisplayMixin,
    GridFormatMixin,
)
from .serializers import (
    ColorCategory,
    PlotHeight,
)


[docs] class AreaChart( GridDisplayMixin, GridFormatMixin, CustomRangeMixin, CustomTicksMixin, AnnotationsMixin, BaseChart, ): """A base class for the Datawrapper API's area chart.""" model_config = ConfigDict( populate_by_name=True, strict=True, validate_assignment=True, validate_default=True, use_enum_values=True, json_schema_extra={ "examples": [ { "chart-type": "d3-area", "title": "Population Growth Over Time", "source_name": "Census Bureau", "data": pd.DataFrame( { "year": ["2020", "2021", "2022"], "Region A": [100, 120, 140], "Region B": [80, 90, 100], } ), "stack_areas": True, "area_opacity": 0.8, } ] }, ) #: The type of datawrapper chart to create chart_type: Literal["d3-area"] = Field( default="d3-area", alias="chart-type", description="The type of datawrapper chart to create", ) #: The labeling of the y grid labels y_grid_labels: GridLabelPosition | str = Field( default="auto", alias="y-grid-labels", description="The labeling of the y grid labels", ) #: Which side to put the y-axis labels on y_grid_label_align: GridLabelAlign | str = Field( default="left", alias="y-grid-label-align", description="Which side to put the y-axis labels on", ) # # Customize areas # #: The base color for layers (palette index or hex string) base_color: str | int = Field( default=0, alias="base-color", description="The base color for layers (palette index or hex string)", ) #: The opacity of the areas area_opacity: float = Field( default=0.8, alias="area-opacity", description="The opacity of the areas", ) #: The interpolation method to use when drawing lines interpolation: LineInterpolation | str = Field( default="linear", description="The interpolation method to use when drawing lines", ) #: How to sort area layers sort_areas: Literal["keep", "asc", "desc"] = Field( default="keep", alias="sort-areas", description="How to sort area layers", ) #: Whether or not to stack areas stack_areas: bool = Field( default=False, alias="stack-areas", description="Whether or not to stack areas", ) #: Whether or not to stack areas to 100% stack_to_100: bool = Field( default=False, alias="stack-to-100", description="Whether or not to stack areas to 100%", ) #: Whether or not to show separator lines between areas area_separator_lines: bool = Field( default=False, alias="area-separator-lines", description="Whether or not to show separator lines between areas", ) #: The color of the separator lines between areas area_separator_color: str | int = Field( default="#4682b4", alias="area-separator-color", description="The color of the separator lines between areas", ) #: A mapping of layer names to colors color_category: dict[str, str] = Field( default_factory=dict, alias="color-category", description="A mapping of layer names to colors", ) # # Labels # #: Whether or not to show the color key above the chart show_color_key: bool = Field( default=False, alias="show-color-key", description="Whether or not to show the color key above the chart", ) # # Tooltips # #: Whether or not to show tooltips on hover show_tooltips: bool = Field( default=True, alias="show-tooltips", description="Whether or not to show tooltips on hover", ) #: The format for the x-axis tooltips (use DateFormat or NumberFormat enum or custom format strings) tooltip_x_format: DateFormat | NumberFormat | str = Field( default="", alias="tooltip-x-format", description="The format for the x-axis tooltips. Use DateFormat for temporal data, NumberFormat for numeric data, or provide custom format strings.", ) #: The format of the number tooltip (use DateFormat or NumberFormat enum or custom format strings) tooltip_number_format: DateFormat | NumberFormat | str = Field( default="", alias="tooltip-number-format", description="The format of the number tooltip. Use DateFormat for temporal data, NumberFormat for numeric data, or provide custom format strings.", ) # # Appearance # #: How to set the plot height plot_height_mode: PlotHeightMode | str = Field( default="fixed", alias="plot-height-mode", description="How to set the plot height", ) #: The fixed height of the plot plot_height_fixed: int | float = Field( default=300, alias="plot-height-fixed", description="The fixed height of the plot", ) #: The ratio of the plot height plot_height_ratio: float = Field( default=0.5, alias="plot-height-ratio", description="The ratio of the plot height", )
[docs] @field_validator("interpolation") @classmethod def validate_interpolation( cls, v: LineInterpolation | str ) -> LineInterpolation | str: """Validate that interpolation is a valid LineInterpolation value.""" if isinstance(v, str): valid_values = [e.value for e in LineInterpolation] if v not in valid_values: raise ValueError( f"Invalid interpolation: {v}. Must be one of {valid_values}" ) return v
[docs] @field_validator("plot_height_mode") @classmethod def validate_plot_height_mode(cls, v: PlotHeightMode | str) -> PlotHeightMode | str: """Validate that plot_height_mode is a valid PlotHeightMode value.""" if isinstance(v, str): valid_values = [e.value for e in PlotHeightMode] if v not in valid_values: raise ValueError(f"Invalid value: {v}. Must be one of {valid_values}") return v
[docs] def serialize_model(self) -> dict: """Serialize the model to a dictionary.""" # Call the parent class's serialize_model method model = super().serialize_model() # Add chart specific properties visualize_data = { # Horizontal and vertical axis (from mixins) **self._serialize_grid_config(), **self._serialize_grid_format(), **self._serialize_custom_range(), **self._serialize_custom_ticks(), # Vertical axis (chart-specific) "y-grid-labels": self.y_grid_labels, "y-grid-label-align": self.y_grid_label_align, # Customize areas "area-opacity": self.area_opacity, "base-color": self.base_color, "interpolation": self.interpolation, "sort-areas": self.sort_areas, "stack-areas": self.stack_areas, "stack-to-100": self.stack_to_100, "area-separator-lines": self.area_separator_lines, "area-separator-color": self.area_separator_color, # Customize specific layers "color-category": ColorCategory.serialize(self.color_category), # Labels "show-color-key": self.show_color_key, # Tooltips "show-tooltips": self.show_tooltips, "tooltip-x-format": self.tooltip_x_format, "tooltip-number-format": self.tooltip_number_format, # Appearance **PlotHeight.serialize( self.plot_height_mode, self.plot_height_fixed, self.plot_height_ratio, ), } # Add the visualize data to the model model["metadata"]["visualize"].update(visualize_data) # Add annotations from mixin model["metadata"]["visualize"].update(self._serialize_annotations()) # Return the serialized data return model
[docs] @classmethod def deserialize_model(cls, api_response: dict[str, Any]) -> dict[str, Any]: """Parse Datawrapper API response including area chart specific fields. Args: api_response: The JSON response from the chart metadata endpoint Returns: Dictionary that can be used to initialize the AreaChart model """ # Call parent to get base fields init_data = super().deserialize_model(api_response) # Extract area-specific sections metadata = api_response.get("metadata", {}) visualize = metadata.get("visualize", {}) # Horizontal and vertical axis (from mixins) init_data.update(cls._deserialize_grid_config(visualize)) init_data.update(cls._deserialize_grid_format(visualize)) init_data.update(cls._deserialize_custom_range(visualize)) init_data.update(cls._deserialize_custom_ticks(visualize)) # Vertical axis (chart-specific) if "y-grid-labels" in visualize: init_data["y_grid_labels"] = visualize["y-grid-labels"] if "y-grid-label-align" in visualize: init_data["y_grid_label_align"] = visualize["y-grid-label-align"] # Customize areas if "base-color" in visualize: init_data["base_color"] = visualize["base-color"] # Parse area_opacity (may come as string or float) if "area-opacity" in visualize: area_opacity_val = visualize["area-opacity"] init_data["area_opacity"] = ( float(area_opacity_val) if area_opacity_val else 0.8 ) if "interpolation" in visualize: init_data["interpolation"] = visualize["interpolation"] if "sort-areas" in visualize: init_data["sort_areas"] = visualize["sort-areas"] if "stack-areas" in visualize: init_data["stack_areas"] = visualize["stack-areas"] if "stack-to-100" in visualize: init_data["stack_to_100"] = visualize["stack-to-100"] if "area-separator-lines" in visualize: init_data["area_separator_lines"] = visualize["area-separator-lines"] if "area-separator-color" in visualize: init_data["area_separator_color"] = visualize["area-separator-color"] # Parse color-category using utility color_data = ColorCategory.deserialize(visualize.get("color-category")) init_data["color_category"] = color_data["color_category"] # Labels if "show-color-key" in visualize: init_data["show_color_key"] = visualize["show-color-key"] # Tooltips if "show-tooltips" in visualize: init_data["show_tooltips"] = visualize["show-tooltips"] if "tooltip-x-format" in visualize: init_data["tooltip_x_format"] = visualize["tooltip-x-format"] if "tooltip-number-format" in visualize: init_data["tooltip_number_format"] = visualize["tooltip-number-format"] # Appearance init_data.update(PlotHeight.deserialize(visualize)) # Annotations (from mixin) init_data.update(cls._deserialize_annotations(visualize)) return init_data