Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,14 @@ def _convert_chart_to_markdown(self, chart):
# XPath scan of all points), which is extremely slow on large charts.
series_values = [list(s.values) for s in series_list]

# XY (scatter) and bubble charts have no category axis, so their
# data points only exist in the series themselves. Reuse the point
# position as the row label; otherwise every value below would be
# dropped along with the missing categories.
if not category_names:
point_count = max((len(sv) for sv in series_values), default=0)
category_names = [str(idx + 1) for idx in range(point_count)]

for idx, category in enumerate(category_names):
row = [category]
for sv in series_values:
Expand Down
74 changes: 74 additions & 0 deletions packages/markitdown/tests/test_pptx_chart_no_categories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""PowerPoint charts that have no category axis (XY scatter and bubble).

Those charts keep their data points inside the series as ``(x, y)`` pairs, so
``chart.plots[0].categories`` is empty and the converter used to emit a table
that ended right after its separator line, dropping every value.
"""

import io

import pptx
from pptx.chart.data import BubbleChartData, XyChartData
from pptx.enum.chart import XL_CHART_TYPE
from pptx.util import Inches

from markitdown import MarkItDown, StreamInfo

SCATTER_VALUES = [10.0, 20.0, 30.0]


def _chart_presentation(chart_type, chart_data) -> io.BytesIO:
"""Build a one-slide deck holding a single chart titled "Sales"."""
prs = pptx.Presentation()
slide = prs.slides.add_slide(prs.slide_layouts[6])
chart = slide.shapes.add_chart(
chart_type, Inches(1), Inches(1), Inches(6), Inches(4), chart_data
).chart
chart.has_title = True
chart.chart_title.text_frame.text = "Sales"
buf = io.BytesIO()
prs.save(buf)
buf.seek(0)
return buf


def _scatter_presentation() -> io.BytesIO:
"""Build a one-slide deck holding an XY (scatter) chart."""
chart_data = XyChartData()
series = chart_data.add_series("Revenue")
for idx, value in enumerate(SCATTER_VALUES, start=1):
series.add_data_point(float(idx), value)
return _chart_presentation(XL_CHART_TYPE.XY_SCATTER, chart_data)


def _bubble_presentation() -> io.BytesIO:
"""Build a one-slide deck holding a bubble chart."""
chart_data = BubbleChartData()
series = chart_data.add_series("Revenue")
for idx, value in enumerate(SCATTER_VALUES, start=1):
series.add_data_point(float(idx), value, 1.0)
return _chart_presentation(XL_CHART_TYPE.BUBBLE, chart_data)


def _convert(stream: io.BytesIO) -> str:
return (
MarkItDown()
.convert_stream(stream, stream_info=StreamInfo(extension=".pptx"))
.markdown
)


def test_pptx_scatter_chart_keeps_data_points() -> None:
markdown = _convert(_scatter_presentation())

assert "### Chart: Sales" in markdown
for idx, value in enumerate(SCATTER_VALUES, start=1):
assert f"| {idx} | {value} |" in markdown


def test_pptx_bubble_chart_keeps_data_points() -> None:
markdown = _convert(_bubble_presentation())

assert "### Chart: Sales" in markdown
for idx, value in enumerate(SCATTER_VALUES, start=1):
assert f"| {idx} | {value} |" in markdown