Skip to content

Commit c5410ab

Browse files
committed
2 parents 8d11b0f + fa95219 commit c5410ab

17 files changed

Lines changed: 446 additions & 610 deletions

.devcontainer/devcontainer.json

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"name": "Datawrapper Python Development",
3+
"image": "mcr.microsoft.com/devcontainers/python:3.12",
4+
5+
"features": {
6+
"ghcr.io/devcontainers/features/git:1": {},
7+
"ghcr.io/devcontainers/features/github-cli:1": {}
8+
},
9+
10+
"customizations": {
11+
"vscode": {
12+
"extensions": [
13+
"ms-python.python",
14+
"ms-python.vscode-pylance",
15+
"charliermarsh.ruff",
16+
"ms-python.pytest",
17+
"ms-toolsai.jupyter"
18+
],
19+
20+
"settings": {
21+
"python.defaultInterpreterPath": "/usr/local/bin/python",
22+
"python.testing.pytestEnabled": true,
23+
"python.testing.unittestEnabled": false,
24+
"python.testing.pytestArgs": [
25+
"tests"
26+
],
27+
"[python]": {
28+
"editor.defaultFormatter": "charliermarsh.ruff",
29+
"editor.formatOnSave": true,
30+
"editor.codeActionsOnSave": {
31+
"source.fixAll": "explicit",
32+
"source.organizeImports": "explicit"
33+
}
34+
},
35+
"files.exclude": {
36+
"**/__pycache__": true,
37+
"**/*.pyc": true,
38+
"**/.pytest_cache": true,
39+
"**/.ruff_cache": true,
40+
"**/htmlcov": true,
41+
"**/*.egg-info": true
42+
}
43+
}
44+
}
45+
},
46+
47+
"forwardPorts": [8000],
48+
"portsAttributes": {
49+
"8000": {
50+
"label": "Documentation Server",
51+
"onAutoForward": "notify"
52+
}
53+
},
54+
55+
"postCreateCommand": "bash .devcontainer/setup.sh",
56+
57+
"shutdownAction": "stopContainer"
58+
}

.devcontainer/setup.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/bash
2+
set -e
3+
4+
echo "🚀 Setting up Datawrapper development environment..."
5+
6+
# Install uv package manager
7+
echo "📦 Installing uv package manager..."
8+
curl -LsSf https://astral.sh/uv/install.sh | sh
9+
export PATH="$HOME/.cargo/bin:$PATH"
10+
11+
# Install all project dependencies
12+
echo "📚 Installing project dependencies..."
13+
uv install --all-extras
14+
15+
# Install pre-commit hooks
16+
echo "🔧 Installing pre-commit hooks..."
17+
uv run pre-commit install

CHANGELOG.md

Lines changed: 0 additions & 90 deletions
This file was deleted.

README.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ A lightweight Python wrapper for the Datawrapper API
99
* Get, update and delete folders, users and teams.
1010
* Retrieve lists of recently edited and updated charts
1111
* Access metadata about your account
12-
* Get a list of all available themes
1312

1413
## Installation
1514

@@ -23,18 +22,18 @@ Create beautiful charts with type-safe, object-oriented Python:
2322

2423
```python
2524
import pandas as pd
26-
from datawrapper import BarChart, NumberFormat
25+
import datawrapper as dw
2726

28-
# Create a bar chart with type-safe configuration
29-
chart = BarChart(
27+
# Configure a bar chart
28+
chart = dw.BarChart(
3029
title="Top Programming Languages 2024",
3130
data=pd.DataFrame({"Language": ["Python", "JavaScript", "Java"], "Users": [45.3, 38.2, 30.5]}),
32-
axis_label_format=NumberFormat.ONE_DECIMAL, # Type-safe enum
33-
value_label_format=NumberFormat.ABBREVIATED, # IDE autocomplete support
31+
axis_label_format=NumberFormat.ONE_DECIMAL,
32+
value_label_format=NumberFormat.ABBREVIATED,
3433
)
3534

3635
# Create and publish (uses DATAWRAPPER_ACCESS_TOKEN environment variable)
37-
chart_id = chart.create()
36+
chart.create()
3837
chart.publish()
3938
```
4039
See the [full documentation](https://datawrapper.readthedocs.io/) for comprehensive guides on all chart types.
@@ -53,7 +52,6 @@ Install pre-commit to run a battery of automatic quick fixes against your work.
5352

5453
```bash
5554
uv run pre-commit install
56-
uv run pre-commit install
5755
```
5856

5957
Run tests with

datawrapper/charts/arrow.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ class ArrowChart(BaseChart):
4949
# Customize arrows
5050
#
5151

52+
#: The base color for the arrows
53+
base_color: str | int = Field(
54+
default=0,
55+
alias="base-color",
56+
description="The base color for the arrows",
57+
)
58+
5259
#: A mapping of layer names to colors
5360
color_category: dict[str, str] = Field(
5461
default_factory=dict,
@@ -152,6 +159,20 @@ class ArrowChart(BaseChart):
152159
description="The column that arrows should end at",
153160
)
154161

162+
#: The axes to color by
163+
axis_colors: str = Field(
164+
default="",
165+
alias="axis-colors",
166+
description="The axes to color by",
167+
)
168+
169+
#: The axes to label by
170+
axis_labels: str = Field(
171+
default="",
172+
alias="axis-labels",
173+
description="The axes to label by",
174+
)
175+
155176
#
156177
# Features
157178
#
@@ -203,6 +224,7 @@ def serialize_model(self) -> dict:
203224
"y-grid": self.y_grid,
204225
"reverse-order": self.reverse_order,
205226
"thick-arrows": self.thick_arrows,
227+
"base-color": self.base_color,
206228
"color-category": ColorCategory.serialize(self.color_category),
207229
"range-value-labels": self.range_value_labels,
208230
"sort-range": {
@@ -224,6 +246,10 @@ def serialize_model(self) -> dict:
224246
"start": self.axis_start,
225247
"end": self.axis_end,
226248
}
249+
if self.axis_colors:
250+
model["metadata"]["axes"]["colors"] = self.axis_colors
251+
if self.axis_labels:
252+
model["metadata"]["axes"]["labels"] = self.axis_labels
227253

228254
# Return the serialized data
229255
return model
@@ -254,6 +280,10 @@ def deserialize_model(cls, api_response: dict[str, Any]) -> dict[str, Any]:
254280
if "thick-arrows" in visualize:
255281
init_data["thick_arrows"] = visualize["thick-arrows"]
256282

283+
# Base color
284+
if "base-color" in visualize:
285+
init_data["base_color"] = visualize["base-color"]
286+
257287
# Parse color-category using utility
258288
color_data = ColorCategory.deserialize(visualize.get("color-category"))
259289
init_data["color_category"] = color_data["color_category"]
@@ -291,6 +321,10 @@ def deserialize_model(cls, api_response: dict[str, Any]) -> dict[str, Any]:
291321
init_data["axis_start"] = axes["start"]
292322
if "end" in axes:
293323
init_data["axis_end"] = axes["end"]
324+
if "colors" in axes:
325+
init_data["axis_colors"] = axes["colors"]
326+
if "labels" in axes:
327+
init_data["axis_labels"] = axes["labels"]
294328

295329
# Features
296330
if "color-by-column" in visualize:

datawrapper/charts/base.py

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,11 @@
55
from typing import Any, Literal
66

77
import pandas as pd
8-
from IPython.display import Image
8+
from IPython.display import IFrame, Image
99
from pydantic import BaseModel, ConfigDict, Field, model_serializer, model_validator
1010

1111
from datawrapper.__main__ import Datawrapper
12-
from datawrapper.charts.models import (
13-
Annotate,
14-
Describe,
15-
Publish,
16-
Transform,
17-
Visualize,
18-
)
12+
from datawrapper.charts.models import Annotate, Describe, Publish, Transform, Visualize
1913

2014

2115
class BaseChart(BaseModel):
@@ -986,6 +980,31 @@ def get_iframe_code(
986980
# Call the get_iframe_code method from the client
987981
return client.get_iframe_code(chart_id=self.chart_id, responsive=responsive)
988982

983+
def display(self, access_token: str | None = None) -> IFrame:
984+
"""Display the chart as an IFrame in a Jupyter notebook.
985+
986+
Args:
987+
access_token: Optional Datawrapper API access token.
988+
If not provided, will use DATAWRAPPER_ACCESS_TOKEN environment variable.
989+
990+
Returns:
991+
An IPython.display.IFrame object displaying the chart.
992+
993+
Raises:
994+
ValueError: If no chart_id is set or no access token is available.
995+
Exception: If the API request fails.
996+
"""
997+
if not self.chart_id:
998+
raise ValueError(
999+
"No chart_id set. Use create() first or set chart_id manually."
1000+
)
1001+
1002+
# Get the client
1003+
client = self._get_client(access_token)
1004+
1005+
# Call the display_chart method from the client
1006+
return client.display_chart(chart_id=self.chart_id)
1007+
9891008
def get_editor_url(self) -> str:
9901009
"""Get the Datawrapper editor URL for this chart.
9911010

0 commit comments

Comments
 (0)