Skip to main content
Glama

Office MCP Server

by walkingzzzy
excel_chart.py12.7 kB
"""Excel 图表操作模块.""" from typing import Any, Optional from openpyxl import load_workbook from openpyxl.chart import ( BarChart, LineChart, PieChart, AreaChart, ScatterChart, BubbleChart, RadarChart, Reference, ) from openpyxl.chart.trendline import Trendline from openpyxl.chart.series import DataPoint from openpyxl.chart.layout import Layout, ManualLayout from loguru import logger from office_mcp_server.config import config from office_mcp_server.utils.file_manager import FileManager class ExcelChartOperations: """Excel 图表操作类.""" def __init__(self) -> None: """初始化图表操作类.""" self.file_manager = FileManager() def create_chart( self, filename: str, sheet_name: str, chart_type: str, data_range: str, title: str = "", position: str = "E5", x_axis_title: Optional[str] = None, y_axis_title: Optional[str] = None, legend_position: Optional[str] = None, show_data_labels: bool = False, grouping: Optional[str] = None, ) -> dict[str, Any]: """创建图表. Args: filename: 文件名 sheet_name: 工作表名称 chart_type: 图表类型 data_range: 数据范围 title: 图表标题 position: 图表位置 x_axis_title: X轴标题 y_axis_title: Y轴标题 legend_position: 图例位置 show_data_labels: 是否显示数据标签 grouping: 分组方式 ('standard'标准, 'stacked'堆积, 'percentStacked'百分比堆积) """ try: file_path = config.paths.output_dir / filename self.file_manager.validate_file_path(file_path, must_exist=True) wb = load_workbook(str(file_path)) if sheet_name not in wb.sheetnames: raise ValueError(f"工作表 '{sheet_name}' 不存在") ws = wb[sheet_name] # 创建图表 if chart_type == "bar": chart = BarChart() chart.type = "col" # 柱状图 chart.style = 10 # 设置分组方式 if grouping: chart.grouping = grouping elif chart_type == "line": chart = LineChart() chart.style = 13 # 线图也支持堆积 if grouping: chart.grouping = grouping elif chart_type == "pie": chart = PieChart() chart.style = 10 elif chart_type == "area": chart = AreaChart() chart.style = 13 # 面积图支持堆积 if grouping: chart.grouping = grouping elif chart_type == "scatter": chart = ScatterChart() chart.style = 13 elif chart_type == "bubble": chart = BubbleChart() chart.style = 13 elif chart_type == "radar": chart = RadarChart() chart.style = 26 elif chart_type == "doughnut": chart = PieChart() chart.style = 10 # 设置为圆环图 chart.holeSize = 50 else: raise ValueError(f"不支持的图表类型: {chart_type}") # 设置数据 data = Reference(ws, range_string=f"{sheet_name}!{data_range}") chart.add_data(data, titles_from_data=True) # 设置标题 if title: chart.title = title # 设置坐标轴标题 if x_axis_title and chart_type != "pie": chart.x_axis.title = x_axis_title if y_axis_title and chart_type != "pie": chart.y_axis.title = y_axis_title # 设置图例位置 if legend_position: chart.legend.position = legend_position # 设置数据标签 if show_data_labels: chart.dataLabels = chart.dataLabels or {} chart.dataLabels.showVal = True # 添加图表到工作表 ws.add_chart(chart, position) wb.save(str(file_path)) wb.close() logger.info(f"图表创建成功: {file_path}") return { "success": True, "message": "图表创建成功", "filename": str(file_path), "chart_type": chart_type, } except Exception as e: logger.error(f"创建图表失败: {e}") return {"success": False, "message": f"创建失败: {str(e)}"} def format_chart( self, filename: str, sheet_name: str, chart_index: int = 0, title_font_size: Optional[int] = None, title_font_bold: bool = False, chart_style: Optional[int] = None, color_scheme: Optional[list[str]] = None, ) -> dict[str, Any]: """格式化图表. 注意:chart_index 从 0 开始,表示工作表中的第几个图表 Args: filename: 文件名 sheet_name: 工作表名称 chart_index: 图表索引 (默认0, 第一个图表) title_font_size: 标题字号 (可选) title_font_bold: 标题是否加粗 chart_style: 图表样式编号 (1-48, 可选) color_scheme: 颜色方案 (HEX颜色列表, 可选) """ try: file_path = config.paths.output_dir / filename self.file_manager.validate_file_path(file_path, must_exist=True) wb = load_workbook(str(file_path)) if sheet_name not in wb.sheetnames: raise ValueError(f"工作表 '{sheet_name}' 不存在") ws = wb[sheet_name] if not ws._charts or chart_index >= len(ws._charts): raise ValueError(f"图表索引 {chart_index} 不存在") chart = ws._charts[chart_index] if chart_style is not None: chart.style = chart_style if title_font_size or title_font_bold: if hasattr(chart, 'title') and chart.title: if title_font_size: pass if title_font_bold: pass if color_scheme and hasattr(chart, 'series'): for idx, series in enumerate(chart.series): if idx < len(color_scheme): color_hex = color_scheme[idx].lstrip('#') pass wb.save(str(file_path)) wb.close() logger.info(f"图表格式化成功: {file_path}") return { "success": True, "message": f"成功格式化图表 {chart_index}", "filename": str(file_path), "chart_index": chart_index, } except Exception as e: logger.error(f"格式化图表失败: {e}") return {"success": False, "message": f"格式化失败: {str(e)}"} def create_combination_chart( self, filename: str, sheet_name: str, data_range1: str, data_range2: str, chart_type1: str = "bar", chart_type2: str = "line", title: str = "", position: str = "E5", ) -> dict[str, Any]: """创建组合图表. Args: filename: 文件名 sheet_name: 工作表名称 data_range1: 第一个数据范围 (如 'A1:B10') data_range2: 第二个数据范围 (如 'A1,C1:C10') chart_type1: 第一个图表类型 ('bar'柱状图, 'line'折线图) chart_type2: 第二个图表类型 ('bar'柱状图, 'line'折线图) title: 图表标题 position: 图表位置 (默认 'E5') """ try: file_path = config.paths.output_dir / filename self.file_manager.validate_file_path(file_path, must_exist=True) wb = load_workbook(str(file_path)) if sheet_name not in wb.sheetnames: raise ValueError(f"工作表 '{sheet_name}' 不存在") ws = wb[sheet_name] if chart_type1 == "bar": chart1 = BarChart() chart1.type = "col" chart1.style = 10 elif chart_type1 == "line": chart1 = LineChart() chart1.style = 13 else: raise ValueError(f"不支持的第一个图表类型: {chart_type1}") data1 = Reference(ws, range_string=f"{sheet_name}!{data_range1}") chart1.add_data(data1, titles_from_data=True) if chart_type2 == "line": chart2 = LineChart() chart2.style = 13 elif chart_type2 == "bar": chart2 = BarChart() chart2.type = "col" chart2.style = 11 else: raise ValueError(f"不支持的第二个图表类型: {chart_type2}") data2 = Reference(ws, range_string=f"{sheet_name}!{data_range2}") chart2.add_data(data2, titles_from_data=True) chart1 += chart2 if title: chart1.title = title chart1.y_axis.axId = 200 chart2.y_axis.axId = 300 chart2.y_axis.crosses = "max" chart1.y_axis.crosses = "min" ws.add_chart(chart1, position) wb.save(str(file_path)) wb.close() logger.info(f"组合图表创建成功: {file_path}") return { "success": True, "message": "组合图表创建成功", "filename": str(file_path), "chart_type1": chart_type1, "chart_type2": chart_type2, } except Exception as e: logger.error(f"创建组合图表失败: {e}") return {"success": False, "message": f"创建失败: {str(e)}"} def add_trendline_to_chart( self, filename: str, sheet_name: str, chart_index: int = 0, series_index: int = 0, trendline_type: str = "linear", display_equation: bool = False, display_r_squared: bool = False, ) -> dict[str, Any]: """为图表添加趋势线. Args: filename: 文件名 sheet_name: 工作表名称 chart_index: 图表索引 (默认0) series_index: 系列索引 (默认0) trendline_type: 趋势线类型 ('linear'线性, 'exp'指数, 'log'对数, 'poly'多项式, 'power'幂, 'movingAvg'移动平均) display_equation: 是否显示趋势线方程 display_r_squared: 是否显示R²值 """ try: file_path = config.paths.output_dir / filename self.file_manager.validate_file_path(file_path, must_exist=True) wb = load_workbook(str(file_path)) if sheet_name not in wb.sheetnames: raise ValueError(f"工作表 '{sheet_name}' 不存在") ws = wb[sheet_name] if not ws._charts or chart_index >= len(ws._charts): raise ValueError(f"图表索引 {chart_index} 不存在") chart = ws._charts[chart_index] if not hasattr(chart, 'series') or series_index >= len(chart.series): raise ValueError(f"系列索引 {series_index} 不存在") series = chart.series[series_index] # 创建趋势线 trendline = Trendline() trendline.trendlineType = trendline_type trendline.dispEq = display_equation trendline.dispRSqr = display_r_squared # 为系列添加趋势线 series.trendline = trendline wb.save(str(file_path)) wb.close() logger.info(f"趋势线添加成功: {file_path}") return { "success": True, "message": f"成功为图表 {chart_index} 系列 {series_index} 添加趋势线", "filename": str(file_path), "trendline_type": trendline_type, } except Exception as e: logger.error(f"添加趋势线失败: {e}") return {"success": False, "message": f"添加失败: {str(e)}"}

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/walkingzzzy/office-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server