Skip to content

Commit debc34f

Browse files
committed
feat: add Chart class and chart method in SlideBuilder for creating charts in slides
1 parent 78cbcc0 commit debc34f

File tree

3 files changed

+309
-0
lines changed

3 files changed

+309
-0
lines changed

src/tppt/pptx/chart/__init__.py

Whitespace-only changes.

src/tppt/pptx/chart/chart.py

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
from typing import Literal, Self, TypedDict, assert_never
2+
3+
from pptx.chart.chart import Chart as PptxChart
4+
from pptx.chart.data import ChartData as PptxChartData
5+
from pptx.enum.chart import XL_CHART_TYPE
6+
7+
from tppt.pptx.converter import PptxConvertible
8+
from tppt.types._length import Length, LiteralLength
9+
10+
LiteralChartType = Literal[
11+
"3D Area",
12+
"3D Stacked Area",
13+
"3D 100% Stacked Area",
14+
"3D Clustered Bar",
15+
"3D Stacked Bar",
16+
"3D 100% Stacked Bar",
17+
"3D Column",
18+
"3D Clustered Column",
19+
"3D Stacked Column",
20+
"3D 100% Stacked Column",
21+
"3D Line",
22+
"3D Pie",
23+
"3D Exploded Pie",
24+
"Area",
25+
"Stacked Area",
26+
"100% Stacked Area",
27+
"Clustered Bar",
28+
"Bar of Pie",
29+
"Stacked Bar",
30+
"100% Stacked Bar",
31+
"Bubble",
32+
"Bubble with 3D effects",
33+
"Clustered Column",
34+
"Stacked Column",
35+
"100% Stacked Column",
36+
"Clustered Cone Bar",
37+
"Stacked Cone Bar",
38+
"100% Stacked Cone Bar",
39+
"3D Cone Column",
40+
"Clustered Cone Column",
41+
"Stacked Cone Column",
42+
"100% Stacked Cone Column",
43+
"Clustered Cylinder Bar",
44+
"Stacked Cylinder Bar",
45+
"100% Stacked Cylinder Bar",
46+
"3D Cylinder Column",
47+
"Clustered Cylinder Column",
48+
"Stacked Cylinder Column",
49+
"100% Stacked Cylinder Column",
50+
"Doughnut",
51+
"Exploded Doughnut",
52+
"Line",
53+
"Line with Markers",
54+
"Stacked Line with Markers",
55+
"100% Stacked Line with Markers",
56+
"Stacked Line",
57+
"100% Stacked Line",
58+
"Pie",
59+
"Exploded Pie",
60+
"Pie of Pie",
61+
"Clustered Pyramid Bar",
62+
"Stacked Pyramid Bar",
63+
"100% Stacked Pyramid Bar",
64+
"3D Pyramid Column",
65+
"Clustered Pyramid Column",
66+
"Stacked Pyramid Column",
67+
"100% Stacked Pyramid Column",
68+
"Radar",
69+
"Filled Radar",
70+
"Radar with Data Markers",
71+
"High-Low-Close",
72+
"Open-High-Low-Close",
73+
"Volume-High-Low-Close",
74+
"Volume-Open-High-Low-Close",
75+
"3D Surface",
76+
"Surface (Top View)",
77+
"Surface (Top View wireframe)",
78+
"3D Surface (wireframe)",
79+
"Scatter",
80+
"Scatter with Lines",
81+
"Scatter with Lines and No Data Markers",
82+
"Scatter with Smoothed Lines",
83+
"Scatter with Smoothed Lines and No Data Markers",
84+
]
85+
86+
87+
def to_pptx_chart_type(chart_type: XL_CHART_TYPE | LiteralChartType) -> XL_CHART_TYPE:
88+
match chart_type:
89+
case XL_CHART_TYPE():
90+
return chart_type
91+
case "3D Area":
92+
return XL_CHART_TYPE.THREE_D_AREA
93+
case "3D Stacked Area":
94+
return XL_CHART_TYPE.THREE_D_AREA_STACKED
95+
case "3D 100% Stacked Area":
96+
return XL_CHART_TYPE.THREE_D_AREA_STACKED_100
97+
case "3D Clustered Bar":
98+
return XL_CHART_TYPE.THREE_D_BAR_CLUSTERED
99+
case "3D Stacked Bar":
100+
return XL_CHART_TYPE.THREE_D_BAR_STACKED
101+
case "3D 100% Stacked Bar":
102+
return XL_CHART_TYPE.THREE_D_BAR_STACKED_100
103+
case "3D Column":
104+
return XL_CHART_TYPE.THREE_D_COLUMN
105+
case "3D Clustered Column":
106+
return XL_CHART_TYPE.THREE_D_COLUMN_CLUSTERED
107+
case "3D Stacked Column":
108+
return XL_CHART_TYPE.THREE_D_COLUMN_STACKED
109+
case "3D 100% Stacked Column":
110+
return XL_CHART_TYPE.THREE_D_COLUMN_STACKED_100
111+
case "3D Line":
112+
return XL_CHART_TYPE.THREE_D_LINE
113+
case "3D Pie":
114+
return XL_CHART_TYPE.THREE_D_PIE
115+
case "3D Exploded Pie":
116+
return XL_CHART_TYPE.THREE_D_PIE_EXPLODED
117+
case "Area":
118+
return XL_CHART_TYPE.AREA
119+
case "Stacked Area":
120+
return XL_CHART_TYPE.AREA_STACKED
121+
case "100% Stacked Area":
122+
return XL_CHART_TYPE.AREA_STACKED_100
123+
case "Clustered Bar":
124+
return XL_CHART_TYPE.BAR_CLUSTERED
125+
case "Bar of Pie":
126+
return XL_CHART_TYPE.BAR_OF_PIE
127+
case "Stacked Bar":
128+
return XL_CHART_TYPE.BAR_STACKED
129+
case "100% Stacked Bar":
130+
return XL_CHART_TYPE.BAR_STACKED_100
131+
case "Bubble":
132+
return XL_CHART_TYPE.BUBBLE
133+
case "Bubble with 3D effects":
134+
return XL_CHART_TYPE.BUBBLE_THREE_D_EFFECT
135+
case "Clustered Column":
136+
return XL_CHART_TYPE.COLUMN_CLUSTERED
137+
case "Stacked Column":
138+
return XL_CHART_TYPE.COLUMN_STACKED
139+
case "100% Stacked Column":
140+
return XL_CHART_TYPE.COLUMN_STACKED_100
141+
case "Clustered Cone Bar":
142+
return XL_CHART_TYPE.CONE_BAR_CLUSTERED
143+
case "Stacked Cone Bar":
144+
return XL_CHART_TYPE.CONE_BAR_STACKED
145+
case "100% Stacked Cone Bar":
146+
return XL_CHART_TYPE.CONE_BAR_STACKED_100
147+
case "3D Cone Column":
148+
return XL_CHART_TYPE.CONE_COL
149+
case "Clustered Cone Column":
150+
return XL_CHART_TYPE.CONE_COL_CLUSTERED
151+
case "Stacked Cone Column":
152+
return XL_CHART_TYPE.CONE_COL_STACKED
153+
case "100% Stacked Cone Column":
154+
return XL_CHART_TYPE.CONE_COL_STACKED_100
155+
case "Clustered Cylinder Bar":
156+
return XL_CHART_TYPE.CYLINDER_BAR_CLUSTERED
157+
case "Stacked Cylinder Bar":
158+
return XL_CHART_TYPE.CYLINDER_BAR_STACKED
159+
case "100% Stacked Cylinder Bar":
160+
return XL_CHART_TYPE.CYLINDER_BAR_STACKED_100
161+
case "3D Cylinder Column":
162+
return XL_CHART_TYPE.CYLINDER_COL
163+
case "Clustered Cylinder Column":
164+
return XL_CHART_TYPE.CYLINDER_COL_CLUSTERED
165+
case "Stacked Cylinder Column":
166+
return XL_CHART_TYPE.CYLINDER_COL_STACKED
167+
case "100% Stacked Cylinder Column":
168+
return XL_CHART_TYPE.CYLINDER_COL_STACKED_100
169+
case "Doughnut":
170+
return XL_CHART_TYPE.DOUGHNUT
171+
case "Exploded Doughnut":
172+
return XL_CHART_TYPE.DOUGHNUT_EXPLODED
173+
case "Line":
174+
return XL_CHART_TYPE.LINE
175+
case "Line with Markers":
176+
return XL_CHART_TYPE.LINE_MARKERS
177+
case "Stacked Line with Markers":
178+
return XL_CHART_TYPE.LINE_MARKERS_STACKED
179+
case "100% Stacked Line with Markers":
180+
return XL_CHART_TYPE.LINE_MARKERS_STACKED_100
181+
case "Stacked Line":
182+
return XL_CHART_TYPE.LINE_STACKED
183+
case "100% Stacked Line":
184+
return XL_CHART_TYPE.LINE_STACKED_100
185+
case "Pie":
186+
return XL_CHART_TYPE.PIE
187+
case "Exploded Pie":
188+
return XL_CHART_TYPE.PIE_EXPLODED
189+
case "Pie of Pie":
190+
return XL_CHART_TYPE.PIE_OF_PIE
191+
case "Clustered Pyramid Bar":
192+
return XL_CHART_TYPE.PYRAMID_BAR_CLUSTERED
193+
case "Stacked Pyramid Bar":
194+
return XL_CHART_TYPE.PYRAMID_BAR_STACKED
195+
case "100% Stacked Pyramid Bar":
196+
return XL_CHART_TYPE.PYRAMID_BAR_STACKED_100
197+
case "3D Pyramid Column":
198+
return XL_CHART_TYPE.PYRAMID_COL
199+
case "Clustered Pyramid Column":
200+
return XL_CHART_TYPE.PYRAMID_COL_CLUSTERED
201+
case "Stacked Pyramid Column":
202+
return XL_CHART_TYPE.PYRAMID_COL_STACKED
203+
case "100% Stacked Pyramid Column":
204+
return XL_CHART_TYPE.PYRAMID_COL_STACKED_100
205+
case "Radar":
206+
return XL_CHART_TYPE.RADAR
207+
case "Filled Radar":
208+
return XL_CHART_TYPE.RADAR_FILLED
209+
case "Radar with Data Markers":
210+
return XL_CHART_TYPE.RADAR_MARKERS
211+
case "High-Low-Close":
212+
return XL_CHART_TYPE.STOCK_HLC
213+
case "Open-High-Low-Close":
214+
return XL_CHART_TYPE.STOCK_OHLC
215+
case "Volume-High-Low-Close":
216+
return XL_CHART_TYPE.STOCK_VHLC
217+
case "Volume-Open-High-Low-Close":
218+
return XL_CHART_TYPE.STOCK_VOHLC
219+
case "3D Surface":
220+
return XL_CHART_TYPE.SURFACE
221+
case "Surface (Top View)":
222+
return XL_CHART_TYPE.SURFACE_TOP_VIEW
223+
case "Surface (Top View wireframe)":
224+
return XL_CHART_TYPE.SURFACE_TOP_VIEW_WIREFRAME
225+
case "3D Surface (wireframe)":
226+
return XL_CHART_TYPE.SURFACE_WIREFRAME
227+
case "Scatter":
228+
return XL_CHART_TYPE.XY_SCATTER
229+
case "Scatter with Lines":
230+
return XL_CHART_TYPE.XY_SCATTER_LINES
231+
case "Scatter with Lines and No Data Markers":
232+
return XL_CHART_TYPE.XY_SCATTER_LINES_NO_MARKERS
233+
case "Scatter with Smoothed Lines":
234+
return XL_CHART_TYPE.XY_SCATTER_SMOOTH
235+
case "Scatter with Smoothed Lines and No Data Markers":
236+
return XL_CHART_TYPE.XY_SCATTER_SMOOTH_NO_MARKERS
237+
case _:
238+
assert_never(chart_type)
239+
240+
241+
class ChartProps(TypedDict):
242+
"""Chart properties."""
243+
244+
chart_type: LiteralChartType | XL_CHART_TYPE
245+
x: Length | LiteralLength
246+
y: Length | LiteralLength
247+
cx: Length | LiteralLength
248+
cy: Length | LiteralLength
249+
chart_data: PptxChartData
250+
251+
252+
class ChartData(ChartProps):
253+
"""Chart data."""
254+
255+
type: Literal["chart"]
256+
257+
258+
class Chart(PptxConvertible[PptxChart]):
259+
"""Chart data class."""
260+
261+
def __init__(
262+
self,
263+
pptx_obj: PptxChart,
264+
/,
265+
) -> None:
266+
self._pptx = pptx_obj
267+
268+
def to_pptx(self) -> PptxChart:
269+
"""Convert to pptx shape."""
270+
return self._pptx
271+
272+
@classmethod
273+
def from_pptx(cls, pptx_obj: PptxChart) -> Self:
274+
"""Create from pptx shape."""
275+
return cls(pptx_obj)

src/tppt/pptx/slide.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from pptx.slide import Slide as PptxSlide
1616

17+
from tppt.pptx.chart.chart import Chart, ChartData, ChartProps, to_pptx_chart_type
1718
from tppt.pptx.shape.picture import (
1819
Movie,
1920
MovieData,
@@ -316,6 +317,39 @@ def _register(slide: Slide) -> Table:
316317

317318
return self
318319

320+
def chart(
321+
self,
322+
data: Callable[[Chart], Chart] | None = None,
323+
/,
324+
**kwargs: Unpack[ChartProps],
325+
) -> Self:
326+
chart_type = to_pptx_chart_type(kwargs["chart_type"])
327+
328+
def _register(slide: Slide) -> Chart:
329+
data: ChartData = {
330+
"type": "chart",
331+
**kwargs,
332+
}
333+
334+
chart_obj = Chart(
335+
slide.to_pptx().shapes.add_chart(
336+
chart_type=chart_type,
337+
x=to_pptx_length(data["x"]),
338+
y=to_pptx_length(data["y"]),
339+
cx=to_pptx_length(data["cx"]),
340+
cy=to_pptx_length(data["cy"]),
341+
chart_data=data["chart_data"],
342+
)
343+
)
344+
if isinstance(data, Callable):
345+
return data(chart_obj)
346+
else:
347+
return chart_obj
348+
349+
self._shape_registry.append(_register)
350+
351+
return self
352+
319353
def _build(self, slide: PptxSlide) -> Slide:
320354
tppt_slide = Slide(slide)
321355

0 commit comments

Comments
 (0)