From e83df3a993d9b6ab9ef234fd163a4b924b4102a6 Mon Sep 17 00:00:00 2001 From: palewire Date: Mon, 1 Dec 2025 15:24:22 -0500 Subject: [PATCH] feat(charts): add comprehensive export options for PNG, PDF, and SVG Add missing export parameters to align with Datawrapper API capabilities: - PNG: add scale, logo, logo_id, dark, ligatures, full_vector, download - PDF: add zoom, transparent, logo, logo_id, dark, ligatures, full_vector, download - SVG: add zoom, logo, logo_id, dark, ligatures, full_vector, download Also clarify distinction between scale (size multiplier) and zoom (resolution/DPI multiplier) in docstrings. --- datawrapper/charts/base.py | 92 ++++++++++++++++++++++++++- tests/integration/test_base_export.py | 45 ++++++++++++- 2 files changed, 133 insertions(+), 4 deletions(-) diff --git a/datawrapper/charts/base.py b/datawrapper/charts/base.py index 977c2cb..fb69530 100644 --- a/datawrapper/charts/base.py +++ b/datawrapper/charts/base.py @@ -737,10 +737,17 @@ def export_png( width: int | None = None, height: int | None = None, plain: bool = False, + scale: int = 1, zoom: int = 2, transparent: bool = False, border_width: int = 0, border_color: str | None = None, + logo: Literal["auto", "on", "off"] = "auto", + logo_id: str | None = None, + dark: bool = False, + ligatures: bool = True, + full_vector: bool = False, + download: bool = False, access_token: str | None = None, timeout: int = 30, ) -> bytes: @@ -750,10 +757,17 @@ def export_png( width: Width of visualization in pixels. If not specified, uses chart width. height: Height of visualization in pixels. If not specified, uses chart height. plain: If True, exports only the visualization without header/footer. - zoom: Scale multiplier for PNG resolution (e.g., 2 = 2x resolution). + scale: Size multiplier that changes actual dimensions (e.g., 2 = 2x size). + zoom: Resolution multiplier for sharper output at same visual size (e.g., 2 = 2x DPI). transparent: If True, exports with transparent background. border_width: Margin around visualization in pixels. border_color: Color of the border (e.g., "#FFFFFF"). If not specified, uses chart background color. + logo: Logo display mode: "auto", "on", or "off". + logo_id: Custom logo ID (pattern: ^[a-zA-Z0-9-]+$). + dark: If True, exports in dark mode. + ligatures: If True (default), enables typography ligatures. + full_vector: If True, exports as full vector output. + download: If True, includes download headers in response. access_token: Optional Datawrapper API access token. timeout: Timeout for the API request in seconds. @@ -780,9 +794,15 @@ def export_png( params = { "unit": "px", "plain": str(plain).lower(), + "scale": str(scale), "zoom": str(zoom), "transparent": str(transparent).lower(), "borderWidth": str(border_width), + "logo": logo, + "dark": str(dark).lower(), + "ligatures": str(ligatures).lower(), + "fullVector": str(full_vector).lower(), + "download": str(download).lower(), } if width is not None: @@ -791,6 +811,8 @@ def export_png( params["height"] = str(height) if border_color is not None: params["borderColor"] = border_color + if logo_id is not None: + params["logoId"] = logo_id # Make the API request response = client.get( @@ -813,8 +835,16 @@ def export_pdf( unit: Literal["px", "mm", "inch"] = "px", mode: Literal["rgb", "cmyk"] = "rgb", scale: int = 1, + zoom: int = 2, + transparent: bool = False, border_width: int = 0, border_color: str | None = None, + logo: Literal["auto", "on", "off"] = "auto", + logo_id: str | None = None, + dark: bool = False, + ligatures: bool = True, + full_vector: bool = False, + download: bool = False, access_token: str | None = None, timeout: int = 30, ) -> bytes: @@ -826,9 +856,17 @@ def export_pdf( plain: If True, exports only the visualization without header/footer. unit: Unit for measurements: "px", "mm", or "inch". mode: Color mode: "rgb" or "cmyk". - scale: Scale multiplier for PDF resolution. + scale: Size multiplier that changes actual dimensions (e.g., 2 = 2x size). + zoom: Resolution multiplier for sharper output at same visual size (e.g., 2 = 2x DPI). + transparent: If True, exports with transparent background. border_width: Margin around visualization. border_color: Color of the border (e.g., "#FFFFFF"). If not specified, uses chart background color. + logo: Logo display mode: "auto", "on", or "off". + logo_id: Custom logo ID (pattern: ^[a-zA-Z0-9-]+$). + dark: If True, exports in dark mode. + ligatures: If True (default), enables typography ligatures. + full_vector: If True, exports as full vector output. + download: If True, includes download headers in response. access_token: Optional Datawrapper API access token. timeout: Timeout for the API request in seconds. @@ -863,7 +901,14 @@ def export_pdf( "mode": mode, "plain": str(plain).lower(), "scale": str(scale), + "zoom": str(zoom), + "transparent": str(transparent).lower(), "borderWidth": str(border_width), + "logo": logo, + "dark": str(dark).lower(), + "ligatures": str(ligatures).lower(), + "fullVector": str(full_vector).lower(), + "download": str(download).lower(), } if width is not None: @@ -872,11 +917,14 @@ def export_pdf( params["height"] = str(height) if border_color is not None: params["borderColor"] = border_color + if logo_id is not None: + params["logoId"] = logo_id # Make the API request response = client.get( f"{client._CHARTS_URL}/{self.chart_id}/export/pdf", params=params, + timeout=timeout, ) # Return raw bytes @@ -890,6 +938,17 @@ def export_svg( width: int | None = None, height: int | None = None, plain: bool = False, + scale: int = 1, + zoom: int = 2, + transparent: bool = False, + border_width: int = 0, + border_color: str | None = None, + logo: Literal["auto", "on", "off"] = "auto", + logo_id: str | None = None, + dark: bool = False, + ligatures: bool = True, + full_vector: bool = False, + download: bool = False, access_token: str | None = None, timeout: int = 30, ) -> bytes: @@ -899,6 +958,17 @@ def export_svg( width: Width of visualization. If not specified, uses chart width. height: Height of visualization. If not specified, uses chart height. plain: If True, exports only the visualization without header/footer. + scale: Size multiplier that changes actual dimensions (e.g., 2 = 2x size). + zoom: Resolution multiplier for sharper output at same visual size (e.g., 2 = 2x DPI). + transparent: If True, exports with transparent background. + border_width: Margin around visualization in pixels. + border_color: Color of the border (e.g., "#FFFFFF"). If not specified, uses chart background color. + logo: Logo display mode: "auto", "on", or "off". + logo_id: Custom logo ID (pattern: ^[a-zA-Z0-9-]+$). + dark: If True, exports in dark mode. + ligatures: If True (default), enables typography ligatures. + full_vector: If True, exports as full vector output. + download: If True, includes download headers in response. access_token: Optional Datawrapper API access token. timeout: Timeout for the API request in seconds. @@ -922,12 +992,28 @@ def export_svg( client = self._get_client(access_token) # Build query parameters - params: dict[str, str] = {} + params = { + "unit": "px", + "plain": str(plain).lower(), + "scale": str(scale), + "zoom": str(zoom), + "transparent": str(transparent).lower(), + "borderWidth": str(border_width), + "logo": logo, + "dark": str(dark).lower(), + "ligatures": str(ligatures).lower(), + "fullVector": str(full_vector).lower(), + "download": str(download).lower(), + } if width is not None: params["width"] = str(width) if height is not None: params["height"] = str(height) + if border_color is not None: + params["borderColor"] = border_color + if logo_id is not None: + params["logoId"] = logo_id # Make the API request response = client.get( diff --git a/tests/integration/test_base_export.py b/tests/integration/test_base_export.py index b088e49..9695f49 100644 --- a/tests/integration/test_base_export.py +++ b/tests/integration/test_base_export.py @@ -184,8 +184,16 @@ def mock_client_factory(access_token=None): unit="mm", mode="cmyk", scale=2, + zoom=3, + transparent=True, border_width=10, border_color="#FF0000", + logo="on", + logo_id="my-logo", + dark=True, + ligatures=False, + full_vector=True, + download=True, ) # Verify @@ -200,8 +208,16 @@ def mock_client_factory(access_token=None): assert params["unit"] == "mm" assert params["mode"] == "cmyk" assert params["scale"] == "2" + assert params["zoom"] == "3" + assert params["transparent"] == "true" assert params["borderWidth"] == "10" assert params["borderColor"] == "#FF0000" + assert params["logo"] == "on" + assert params["logoId"] == "my-logo" + assert params["dark"] == "true" + assert params["ligatures"] == "false" + assert params["fullVector"] == "true" + assert params["download"] == "true" def test_export_pdf_no_chart_id(self): """Test that export_pdf raises ValueError when no chart_id is set.""" @@ -287,7 +303,22 @@ def mock_client_factory(access_token=None): # Create chart and export with all parameters chart = BarChart(title="Test Chart") chart.chart_id = "abc123" - result = chart.export_svg(width=800, height=600, plain=True) + result = chart.export_svg( + width=800, + height=600, + plain=True, + scale=2, + zoom=3, + transparent=True, + border_width=10, + border_color="#FF0000", + logo="off", + logo_id="custom-logo", + dark=True, + ligatures=False, + full_vector=True, + download=True, + ) # Verify assert result == b"SVG_DATA" @@ -299,6 +330,18 @@ def mock_client_factory(access_token=None): assert url == "https://api.datawrapper.de/v3/charts/abc123/export/svg" assert params["width"] == "800" assert params["height"] == "600" + assert params["plain"] == "true" + assert params["scale"] == "2" + assert params["zoom"] == "3" + assert params["transparent"] == "true" + assert params["borderWidth"] == "10" + assert params["borderColor"] == "#FF0000" + assert params["logo"] == "off" + assert params["logoId"] == "custom-logo" + assert params["dark"] == "true" + assert params["ligatures"] == "false" + assert params["fullVector"] == "true" + assert params["download"] == "true" def test_export_svg_no_chart_id(self): """Test SVG export raises error when no chart_id is set."""