From 077b8b0460000416a4beb1c4d7642f22fb94569e Mon Sep 17 00:00:00 2001 From: palewire Date: Tue, 21 Oct 2025 12:46:29 -0400 Subject: [PATCH 1/2] feat(charts): auto-enable mixed colors when color_negative is set Add model validator to AreaFill that automatically enables use_mixed_colors when a non-default color_negative value is provided. This improves UX by eliminating the need for users to manually enable the flag when they want to use negative colors, making the feature work as expected out of the box. The validator only triggers when color_negative differs from the default "#cc0000" and use_mixed_colors is currently False. For #459 --- datawrapper/charts/annos.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/datawrapper/charts/annos.py b/datawrapper/charts/annos.py index 340e9c1..a8aae35 100644 --- a/datawrapper/charts/annos.py +++ b/datawrapper/charts/annos.py @@ -1,6 +1,6 @@ from typing import Any, Literal -from pydantic import BaseModel, ConfigDict, Field, field_validator +from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from .enums import ArrowHead, ConnectorLineType, LineInterpolation, StrokeWidth @@ -360,6 +360,18 @@ def validate_interpolation( ) return v + @model_validator(mode="after") + def auto_enable_mixed_colors(self) -> "AreaFill": + """Auto-enable use_mixed_colors when color_negative is provided. + + If a user provides a color_negative value (different from the default), + automatically enable use_mixed_colors to make the feature work as expected. + """ + # Only auto-enable if color_negative differs from default and use_mixed_colors is False + if self.color_negative != "#cc0000" and not self.use_mixed_colors: + self.use_mixed_colors = True + return self + def serialize_model(self) -> dict: """Serialize the model to a dictionary for the Datawrapper API. From 72cacb3a16e3dc746f96101b97d4723317cef967 Mon Sep 17 00:00:00 2001 From: palewire Date: Tue, 21 Oct 2025 12:52:54 -0400 Subject: [PATCH 2/2] feat(charts): support palette indices for AreaFill colors Update AreaFill model to accept both hex strings and palette indices for color fields. Changes include: - Allow `color` field to accept int (palette index) or str (hex color) - Change default `color` from "#15607a" to 0 (first palette color) - Allow `color_negative` to accept int, str, or None - Change default `color_negative` from "#cc0000" to None (disabled) - Update auto-enable logic to check for None instead of default hex value - Only serialize `colorNegative` when explicitly set (not None) - Update field descriptions to reflect new behavior This provides more flexibility for users to reference theme palette colors by index while maintaining backward compatibility with hex color strings. --- datawrapper/charts/annos.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/datawrapper/charts/annos.py b/datawrapper/charts/annos.py index a8aae35..b01dd97 100644 --- a/datawrapper/charts/annos.py +++ b/datawrapper/charts/annos.py @@ -321,8 +321,10 @@ class AreaFill(BaseModel): #: The line to fill upwards to to_column: str = Field(alias="to", description="The line to fill upwards to") - #: The color of the fill - color: str = Field(default="#15607a", description="The color of the fill") + #: The color of the fill (hex string or palette index) + color: str | int = Field( + default=0, description="The color of the fill (hex string or palette index)" + ) #: The opacity of the fill opacity: float = Field(default=0.3, description="The opacity of the fill") @@ -334,11 +336,11 @@ class AreaFill(BaseModel): description="Whether to use different colors when there are negative values", ) - #: The color of the fill when it is negative - color_negative: str = Field( - default="#cc0000", + #: The color of the fill when it is negative (hex string or palette index, None = disabled) + color_negative: str | int | None = Field( + default=None, alias="colorNegative", - description="The color of the fill when it is negative", + description="The color of the fill when it is negative (hex string or palette index, None = disabled)", ) #: The interpolation method to use when drawing lines @@ -364,11 +366,11 @@ def validate_interpolation( def auto_enable_mixed_colors(self) -> "AreaFill": """Auto-enable use_mixed_colors when color_negative is provided. - If a user provides a color_negative value (different from the default), + If a user provides a color_negative value (not None), automatically enable use_mixed_colors to make the feature work as expected. """ - # Only auto-enable if color_negative differs from default and use_mixed_colors is False - if self.color_negative != "#cc0000" and not self.use_mixed_colors: + # Only auto-enable if color_negative is provided and use_mixed_colors is False + if self.color_negative is not None and not self.use_mixed_colors: self.use_mixed_colors = True return self @@ -376,17 +378,23 @@ def serialize_model(self) -> dict: """Serialize the model to a dictionary for the Datawrapper API. Note: The 'id' field is not included in the output as it's used as the dict key. + Only includes colorNegative if it's not None. """ - return { + result = { "from": self.from_column, "to": self.to_column, "color": self.color, "opacity": self.opacity, "useMixedColors": self.use_mixed_colors, - "colorNegative": self.color_negative, "interpolation": self.interpolation, } + # Only include colorNegative if it's provided (not None) + if self.color_negative is not None: + result["colorNegative"] = self.color_negative + + return result + @classmethod def deserialize_model(cls, api_data: dict[str, dict] | list | None) -> list[dict]: """Deserialize area fills from API response format.