Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Data visualization chart generator. Use when user needs to create charts from data for reports, presentations, or documents. Supports bar, line, pie, scatter, radar charts with PNG/SVG output. 数据可视化、图表生成、数据报告。
.claude/skills/leoyeai-chart-generator/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-13 | ✗→✓ | ▲ Improved | 189% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 175% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 387% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 275% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 286% | 0% |
Professional data visualization chart generator for reports, presentations, and documents.
| Type | Use Case | Best For | |------|----------|----------| | Bar Chart | Compare values | Sales, rankings | | Line Chart | Show trends | Time series, growth | | Pie Chart | Show proportions | Market share, composition | | Scatter Plot | Show correlation | Data relationships | | Radar Chart | Multi-dimension | Performance comparison | | Area Chart | Cumulative values | Stacked data | | Stacked Bar | Composition | Multi-category breakdown |
请提供以下信息:
图表类型:(柱状图/折线图/饼图/散点图/雷达图)
数据来源:(手动输入/CSV/Excel/JSON)
数据内容:
标题:
X轴标签:
Y轴标签:
输出格式:(PNG/SVG)
颜色要求:(默认/自定义)pythonpython3 << 'PYEOF' import os import matplotlib.pyplot as plt import matplotlib import pandas as pd import numpy as np from matplotlib import font_manager # 设置中文字体 plt.rcParams['font.sans-serif'] = ['Noto Sans SC', 'SimHei', 'DejaVu Sans'] plt.rcParams['axes.unicode_minus'] = False class ChartGenerator: def __init__(self): self.fig = None self.ax = None def create_bar_chart(self, labels, values, title='', xlabel='', ylabel='', color='#3182ce', output_path=None): """Create bar chart""" self.fig, self.ax = plt.subplots(figsize=(10, 6)) bars = self.ax.bar(labels, values, color=color, edgecolor='white', linewidth=0.5) # Add value labels on bars for bar in bars: height = bar.get_height() self.ax.text(bar.get_x() + bar.get_width()/2., height, f'{height:,.0f}', ha='center', va='bottom', fontsize=10) self.ax.set_title(title, fontsize=16, fontweight='bold', pad=20) self.ax.set_xlabel(xlabel, fontsize=12) self.ax.set_ylabel(ylabel, fontsize=12) # Clean styling self.ax.spines['top'].set_visible(False) self.ax.spines['right'].set_visible(False) self.ax.grid(axis='y', alpha=0.3) plt.tight_layout() if output_path: self.fig.savefig(output_path, dpi=150, bbox_inches='tight') plt.close() return output_path return self.fig def create_line_chart(self, x_data, y_data_list, labels=None, title='', xlabel='', ylabel='', colors=None, output_path=None): """Create line chart""" self.fig, self.ax = plt.subplots(figsize=(10, 6)) if colors is None: colors = ['#3182ce', '#48bb78', '#ed8936', '#e53e3e', '#9f7aea'] for i, y_data in enumerate(y_data_list): color = colors[i % len(colors)] label = labels[i] if labels and i < len(labels) else f'Series {i+1}' self.ax.plot(x_data, y_data, marker='o', linewidth=2, color=color, label=label, markersize=6) self.ax.set_title(title, fontsize=16, fontweight='bold', pad=20) self.ax.set_xlabel(xlabel, fontsize=12) self.ax.set_ylabel(ylabel, fontsize=12) if labels: self.ax.legend(loc='best', framealpha=0.9) self.ax.spines['top'].set_visible(False) self.ax.spines['right'].set_visible(False) self.ax.grid(alpha=0.3) plt.tight_layout() if output_path: self.fig.savefig(output_path, dpi=150, bbox_inches='tight') plt.close() return output_path return self.fig def create_pie_chart(self, labels, values, title='', colors=None, output_path=None): """Create pie chart""" self.fig, self.ax = plt.subplots(figsize=(8, 8)) if colors is None: colors = ['#3182ce', '#48bb78', '#ed8936', '#e53e3e', '#9f7aea', '#38b2ac', '#d69e2e', '#667eea'] wedges, texts, autotexts = self.ax.pie( values, labels=labels, colors=colors[:len(values)], autopct='%1.1f%%', startangle=90, textprops={'fontsize': 11} ) for autotext in autotexts: autotext.set_color('white') autotext.set_fontweight('bold') self.ax.set_title(title, fontsize=16, fontweight='bold', pad=20) plt.tight_layout() if output_path: self.fig.savefig(output_path, dpi=150, bbox_inches='tight') plt.close() return output_path return self.fig def create_scatter_plot(self, x_data, y_data, title='', xlabel='', ylabel='', color='#3182ce', output_path=None): """Create scatter plot""" self.fig, self.ax = plt.subplots(figsize=(10, 6)) self.ax.scatter(x_data, y_data, c=color, alpha=0.6, s=50) # Add trend line z = np.polyfit(x_data, y_data, 1) p = np.poly1d(z) self.ax.plot(x_data, p(x_data), '--', color='#e53e3e', alpha=0.8, label='Trend') self.ax.set_title(title, fontsize=16, fontweight='bold', pad=20) self.ax.set_xlabel(xlabel, fontsize=12) self.ax.set_ylabel(ylabel, fontsize=12) self.ax.legend() self.ax.spines['top'].set_visible(False) self.ax.spines['right'].set_visible(False) self.ax.grid(alpha=0.3) plt.tight_layout() if output_path: self.fig.savefig(output_path, dpi=150, bbox_inches='tight') plt.close() return output_path return self.fig def create_multi_bar_chart(self, labels, data_dict, title='', xlabel='', ylabel='', output_path=None): """Create grouped bar chart""" self.fig, self.ax = plt.subplots(figsize=(12, 6)) x = np.arange(len(labels)) width = 0.8 / len(data_dict) colors = ['#3182ce', '#48bb78', '#ed8936', '#e53e3e', '#9f7aea'] for i, (name, values) in enumerate(data_dict.items()): offset = (i - len(data_dict)/2 + 0.5) * width bars = self.ax.bar(x + offset, values, width, label=name, color=colors[i % len(colors)], edgecolor='white') self.ax.set_title(title, fontsize=16, fontweight='bold', pad=20) self.ax.set_xlabel(xlabel, fontsize=12) self.ax.set_ylabel(ylabel, fontsize=12) self.ax.set_xticks(x) self.ax.set_xticklabels(labels) self.ax.legend() self.ax.spines['top'].set_visible(False) self.ax.spines['right'].set_visible(False) self.ax.grid(axis='y', alpha=0.3) plt.tight_layout() if output_path: self.fig.savefig(output_path, dpi=150, bbox_inches='tight') plt.close() return output_path return self.fig def load_from_csv(self, csv_path, x_col=None, y_cols=None): """Load data from CSV file""" df = pd.read_csv(csv_path) if x_col is None: x_col = df.columns[0] if y_cols is None: y_cols = [col for col in df.columns if col != x_col] return { 'x': df[x_col].tolist(), 'y': {col: df[col].tolist() for col in y_cols}, 'df': df } def load_from_excel(self, excel_path, sheet_name=0, x_col=None, y_cols=None): """Load data from Excel file""" df = pd.read_excel(excel_path, sheet_name=sheet_name) if x_col is None: x_col = df.columns[0] if y_cols is None: y_cols = [col for col in df.columns if col != x_col] return { 'x': df[x_col].tolist(), 'y': {col: df[col].tolist() for col in y_cols}, 'df': df } def load_from_json(self, json_path): """Load data from JSON file""" import json with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) return data def load_from_directory(self, dir_path, file_pattern='*.csv'): """Load and aggregate data from multiple files in directory""" import glob all_data = [] for file_path in glob.glob(os.path.join(dir_path, file_pattern)): if file_path.endswith('.csv'): df = pd.read_csv(file_path) elif file_path.endswith('.xlsx'): df = pd.read_excel(file_path) else: continue df['source_file'] = os.path.basename(file_path) all_data.append(df) if all_data: return pd.concat(all_data, ignore_index=True) return pd.DataFrame() def extract_data_from_text(self, text): """Extract numerical data from text content""" import re # Find patterns like "Sales: 100" or "销售额:100万" patterns = [ r'(\w+)\s*[::]\s*(\d+(?:\.\d+)?)', r'(\d+(?:\.\d+)?)\s*[::]\s*(\w+)', ] data = {} for pattern in patterns: matches = re.findall(pattern, text) for match in matches: if len(match) == 2: key, value = match try: data[key] = float(value) except ValueError: pass return data def save_to_png(self, output_path, dpi=150): """Save chart as PNG""" if self.fig: self.fig.savefig(output_path, dpi=dpi, bbox_inches='tight', facecolor='white', edgecolor='none') return output_path def save_to_svg(self, output_path): """Save chart as SVG""" if self.fig: self.fig.savefig(output_path, format='svg', bbox_inches='tight', facecolor='white', edgecolor='none') return output_path def save_to_pdf(self, output_path): """Save chart as PDF""" if self.fig: self.fig.savefig(output_path, format='pdf', bbox_inches='tight', facecolor='white', edgecolor='none') return output_path def save_to_base64(self, format='png'): """Convert chart to base64 string for embedding""" import io import base64 if self.fig: buffer = io.BytesIO() self.fig.savefig(buffer, format=format, bbox_inches='tight', facecolor='white', edgecolor='none') buffer.seek(0) img_str = base64.b64encode(buffer.read()).decode() return f'data:image/{format};base64,{img_str}' def embed_in_markdown(self, title='', caption=''): """Generate markdown with embedded chart""" base64_img = self.save_to_base64('png') md = f'\n' if title: md += f'## {title}\n\n' md += f'\n' if caption: md += f'\n*{caption}*\n' return md def embed_in_html(self, title='', width='100%'): """Generate HTML with embedded chart""" base64_img = self.save_to_base64('png') html = f''' <div class="chart-container"> {f'<h3>{title}</h3>' if title else ''} <img src="{base64_img}" alt="{title}" style="max-width: {width};"> </div> ''' return html def save_to_word(self, output_path, title='', caption=''): """Save chart to Word document""" from docx import Document from docx.shared import Inches doc = Document() if title: doc.add_heading(title, level=2) # Save chart as temporary image temp_img = output_path.replace('.docx', '_temp.png') self.save_to_png(temp_img) # Add image to document doc.add_picture(temp_img, width=Inches(6)) if caption: last_para = doc.paragraphs[-1] last_para.alignment = 1 # Center doc.save(output_path) # Clean up temp file if os.path.exists(temp_img): os.remove(temp_img) return output_path # Example usage generator = ChartGenerator() output_dir = os.environ.get('OPENCLAW_WORKSPACE', os.getcwd()) # Bar chart labels = ['Q1', 'Q2', 'Q3', 'Q4'] values = [150000, 180000, 220000, 280000] generator.create_bar_chart( labels, values, title='2026 Quarterly Sales', xlabel='Quarter', ylabel='Sales ($)', output_path=os.path.join(output_dir, 'bar_chart.png') ) # Line chart months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'] product_a = [100, 120, 140, 160, 180, 200] product_b = [80, 95, 110, 130, 150, 170] generator.create_line_chart( months, [product_a, product_b], labels=['Product A', 'Product B'], title='Sales Trend', xlabel='Month', ylabel='Sales', output_path=os.path.join(output_dir, 'line_chart.png') ) # Pie chart pie_labels = ['Product A', 'Product B', 'Product C', 'Others'] pie_values = [35, 25, 20, 20] generator.create_pie_chart( pie_labels, pie_values, title='Market Share', output_path=os.path.join(output_dir, 'pie_chart.png') ) print(f"✅ Charts generated in: {output_dir}") PYEOF
pythongenerator = ChartGenerator() data = generator.load_from_csv('data.csv', x_col='Month', y_cols=['Sales', 'Profit']) generator.create_line_chart( data['x'], [data['y']['Sales'], data['y']['Profit']], labels=['Sales', 'Profit'], title='Monthly Performance' )
pythondata = generator.load_from_excel('report.xlsx', sheet_name='Sheet1')
pythonlabels = ['A', 'B', 'C', 'D'] values = [100, 200, 150, 300] generator.create_bar_chart(labels, values)
python# Single color color='#3182ce' # Blue # Multiple colors colors=['#3182ce', '#48bb78', '#ed8936', '#e53e3e']
python# Default size figsize=(10, 6) # Large for presentations figsize=(16, 9) # Square for reports figsize=(8, 8)
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 11,288 | 7,384 | -35% | 1 | 1 | 0% | 2,437 | 6,228 | +156% | 0 | 0 | — |
case-02 | fail→fail | 13,543 | 11,227 | -17% | 1 | 1 | 0% | 2,710 | 6,590 | +143% | 0 | 0 | — |
case-13 | fail→pass | 13,375 | 2,596 | -81% | 1 | 1 | 0% | 1,851 | 5,341 | +189% | 0 | 0 | — |
case-03 | fail→fail | 11,812 | 7,375 | -38% | 1 | 1 | 0% | 1,887 | 6,072 | +222% | 0 | 0 | — |
case-04 | pass→pass | 23,898 | 12,247 | -49% | 1 | 1 | 0% | 1,864 | 7,192 | +286% | 0 | 0 | — |
case-05 | fail→fail | 6,110 | 6,342 | +4% | 1 | 1 | 0% | 923 | 6,252 | +577% | 0 | 0 | — |
case-06 | pass→pass | 8,347 | 4,992 | -40% | 1 | 1 | 0% | 1,676 | 5,867 | +250% | 0 | 0 | — |
case-07 | pass→pass | 12,219 | 7,019 | -43% | 1 | 1 | 0% | 1,973 | 6,181 | +213% | 0 | 0 | — |
case-08 | fail→pass | 12,134 | 11,231 | -7% | 1 | 1 | 0% | 2,537 | 6,983 | +175% | 0 | 0 | — |
case-09 | fail→fail | 12,208 | 15,303 | +25% | 1 | 1 | 0% | 2,322 | 8,211 | +254% | 0 | 0 | — |
case-10 | pass→pass | 9,876 | 9,596 | -3% | 1 | 1 | 0% | 1,736 | 6,897 | +297% | 0 | 0 | — |
case-11 | fail→fail | 10,230 | 5,430 | -47% | 1 | 1 | 0% | 2,453 | 6,110 | +149% | 0 | 0 | — |
case-12 | fail→fail | 13,552 | 12,273 | -9% | 1 | 1 | 0% | 2,798 | 7,584 | +171% | 0 | 0 | — |
case-14 | fail→pass | 6,556 | 7,835 | +20% | 1 | 1 | 0% | 1,344 | 6,549 | +387% | 0 | 0 | — |
case-15 | pass→pass | 11,804 | 8,683 | -26% | 1 | 1 | 0% | 1,993 | 6,517 | +227% | 0 | 0 | — |
case-16 | fail→pass | 11,848 | 13,909 | +17% | 1 | 1 | 0% | 1,955 | 7,333 | +275% | 0 | 0 | — |
case-17 | pass→pass | 11,874 | 8,524 | -28% | 1 | 1 | 0% | 1,923 | 6,172 | +221% | 0 | 0 | — |
case-18 | pass→pass | 7,508 | 5,766 | -23% | 1 | 1 | 0% | 1,414 | 5,948 | +321% | 0 | 0 | — |
case-19 | pass→pass | 4,994 | 4,521 | -9% | 1 | 1 | 0% | 971 | 5,823 | +500% | 0 | 0 | — |
case-20 | pass→pass | 23,354 | 16,495 | -29% | 1 | 1 | 0% | 4,448 | 8,174 | +84% | 0 | 0 | — |
case-21 | pass→pass | 15,095 | 16,194 | +7% | 1 | 1 | 0% | 3,018 | 8,355 | +177% | 0 | 0 | — |
case-22 | pass→pass | 21,351 | 19,840 | -7% | 1 | 1 | 0% | 4,384 | 8,774 | +100% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +18 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.