microsoft/hve-core

Public

mirrored from https://github.com/microsoft/hve-coreAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
fix/1124-exclude-python-env-dirs-from-skill-validation

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

.github/skills/experimental/powerpoint/scripts/pptx_charts.py

154lines · modecode

1# Copyright (c) Microsoft Corporation.
2# SPDX-License-Identifier: MIT
3"""Chart build and extract utilities for PowerPoint skill scripts.
4
5Provides add_chart_element() for building charts from YAML definitions
6and extract_chart() for extracting chart data from existing presentations.
7"""
8
9from pptx.chart.data import BubbleChartData, CategoryChartData, XyChartData
10from pptx.enum.chart import XL_CHART_TYPE
11from pptx.util import Inches
12from pptx_colors import apply_color_to_fill, resolve_color
13from pptx_utils import emu_to_inches
14
15CHART_TYPE_MAP = {
16 "column_clustered": XL_CHART_TYPE.COLUMN_CLUSTERED,
17 "column_stacked": XL_CHART_TYPE.COLUMN_STACKED,
18 "bar_clustered": XL_CHART_TYPE.BAR_CLUSTERED,
19 "bar_stacked": XL_CHART_TYPE.BAR_STACKED,
20 "line": XL_CHART_TYPE.LINE,
21 "line_markers": XL_CHART_TYPE.LINE_MARKERS,
22 "pie": XL_CHART_TYPE.PIE,
23 "doughnut": XL_CHART_TYPE.DOUGHNUT,
24 "area": XL_CHART_TYPE.AREA,
25 "radar": XL_CHART_TYPE.RADAR,
26 "scatter": XL_CHART_TYPE.XY_SCATTER,
27 "bubble": XL_CHART_TYPE.BUBBLE,
28}
29
30CHART_TYPE_REVERSE = {v: k for k, v in CHART_TYPE_MAP.items()}
31
32SCATTER_CHART_TYPES = {"scatter", "scatter_lines", "scatter_smooth"}
33BUBBLE_CHART_TYPES = {"bubble"}
34
35
36def add_chart_element(slide, elem: dict, colors: dict):
37 """Add a chart element from a content.yaml definition.
38
39 YAML schema:
40 - type: chart
41 chart_type: column_clustered
42 left: 1.0
43 top: 2.0
44 width: 8.0
45 height: 4.5
46 title: "Quarterly Sales"
47 has_legend: true
48 chart_style: 10
49 categories: ["Q1", "Q2", "Q3", "Q4"]
50 series:
51 - name: "East"
52 values: [19.2, 22.3, 18.4, 23.1]
53 color: "#0078D4"
54 """
55 chart_type_name = elem.get("chart_type", "column_clustered")
56 chart_type = CHART_TYPE_MAP.get(chart_type_name, XL_CHART_TYPE.COLUMN_CLUSTERED)
57
58 # Choose data class based on chart type
59 if chart_type_name in SCATTER_CHART_TYPES:
60 chart_data = XyChartData()
61 for series_spec in elem.get("series", []):
62 series = chart_data.add_series(series_spec.get("name", ""))
63 x_values = series_spec.get("x_values", [])
64 y_values = series_spec.get("y_values", [])
65 for x_val, y_val in zip(x_values, y_values):
66 series.add_data_point(x_val, y_val)
67 elif chart_type_name in BUBBLE_CHART_TYPES:
68 chart_data = BubbleChartData()
69 for series_spec in elem.get("series", []):
70 series = chart_data.add_series(series_spec.get("name", ""))
71 x_values = series_spec.get("x_values", [])
72 y_values = series_spec.get("y_values", [])
73 sizes = series_spec.get("sizes", [])
74 for x, y, size in zip(x_values, y_values, sizes):
75 series.add_data_point(x, y, size)
76 else:
77 chart_data = CategoryChartData()
78 chart_data.categories = elem.get("categories", [])
79 for series_spec in elem.get("series", []):
80 chart_data.add_series(
81 series_spec.get("name", ""),
82 series_spec.get("values", []),
83 )
84
85 chart_shape = slide.shapes.add_chart(
86 chart_type,
87 Inches(elem["left"]),
88 Inches(elem["top"]),
89 Inches(elem["width"]),
90 Inches(elem["height"]),
91 chart_data,
92 )
93 chart = chart_shape.chart
94
95 # Chart properties
96 if "title" in elem:
97 chart.has_title = True
98 chart.chart_title.text_frame.text = elem["title"]
99 if "has_legend" in elem:
100 chart.has_legend = elem["has_legend"]
101 if "chart_style" in elem:
102 chart.style = elem["chart_style"]
103
104 # Series coloring
105 for i, series_spec in enumerate(elem.get("series", [])):
106 if "color" in series_spec and i < len(chart.series):
107 series = chart.series[i]
108 series.format.fill.solid()
109 color_spec = resolve_color(series_spec["color"], colors)
110 apply_color_to_fill(series.format.fill, color_spec)
111
112 if "name" in elem:
113 chart_shape.name = elem["name"]
114
115 return chart_shape
116
117
118def extract_chart(shape) -> dict:
119 """Extract a chart element definition from a GraphicFrame shape."""
120 chart = shape.chart
121
122 elem = {
123 "type": "chart",
124 "chart_type": CHART_TYPE_REVERSE.get(chart.chart_type, "column_clustered"),
125 "left": emu_to_inches(shape.left),
126 "top": emu_to_inches(shape.top),
127 "width": emu_to_inches(shape.width),
128 "height": emu_to_inches(shape.height),
129 "name": shape.name,
130 }
131
132 if chart.has_title:
133 try:
134 elem["title"] = chart.chart_title.text_frame.text
135 except (AttributeError, TypeError):
136 pass
137 elem["has_legend"] = chart.has_legend
138
139 # Extract categories and series data
140 try:
141 plot = chart.plots[0]
142 if hasattr(plot, "categories") and plot.categories:
143 elem["categories"] = list(plot.categories)
144 elem["series"] = []
145 for series in plot.series:
146 series_data = {
147 "name": series.name or "",
148 "values": list(series.values),
149 }
150 elem["series"].append(series_data)
151 except (IndexError, AttributeError):
152 pass
153
154 return elem
155