Data analysis dashboard (instrument panel) development skill. Use when users need to develop data dashboards, create/edit Dashboard projects, build large-screen data boards, or perform dashboard data cleaning. Includes dashboard project creation, card plan, data cleaning (data_cleaning.py), card management tools (create_dashboard_cards, update_dashboard_cards, delete_dashboard_cards, query_dashboard_cards), map download tool (download_dashboard_maps), dashboard development, and validation.
72
88%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
Provides full data analysis dashboard (instrument panel) development capabilities—project creation, card plan, data cleaning, dashboard development, validation, and delivery—as one end-to-end workflow. Data cleaning is an important part of dashboard development.
All tool calls via from sdk.tool import tool in this skill must be executed by passing code to run_sdk_snippet's python_code parameter.
When a skill snippet calls tools through the SDK, always use result.ok to determine success and read failure details from result.content. Do not read or call result.error, because SDK Result stores failure text in content.
Important: Detailed rules are inlined later in this document; while executing steps, consult Workflow Summary, Project Setup, Dashboard Development, and Dashboard Data Cleaning Guide.
Runtime base files: Do not modify, overwrite, or delete index.html, dashboard.js, or index.css; violations make the data dashboard unusable.
Default card counts for new dashboards — MANDATORY unless the user explicitly requests a reduced scope:
| Card type | Required count | Notes |
|---|---|---|
| metric | ≥ 6 | Key KPI overview cards |
| echarts | 26 – 30 | Core of the dashboard; pad with same-type charts across different dimensions |
| table | 2 – 3 | At least 1 must be a detail-level table |
| markdown | 0 | Omit unless the user asks for notes/commentary |
| Total | 34 – 39 | Self-check before delivery |
Enforcement rules (non-negotiable):
cards_plan before calling create_dashboard_project; do not defer cards to later.card_id in cards_plan must exactly match the id used in subsequent create_dashboard_cards calls.query_dashboard_cards and count by type; if any type is below the required count, create the missing cards before proceeding.Path overview
Step details
create_dashboard_project, brainstorm questions and angles; read sources in depth (fields, grain, definitions, time, distributions, missingness, comparable dimensions), then author cards_plan strictly following the mandatory counts in Quick Start (metric ≥6, echarts 26–30, table 2–3, total 34–39)—every card must be listed before project creation; a handful of representative charts is not acceptablecreate_dashboard_project with the required cards_plan in the same call; the tool writes cards_plan.md from it; plan card identifiers must match id in later create_dashboard_cardscards_plan.md and data goals, create and run data_cleaning.py in the project to supply data for the dashboardcards_plan.md, use card tools (create_dashboard_cards, update_dashboard_cards, delete_dashboard_cards, query_dashboard_cards) to create or maintain cardsvalidate_dashboard(project_path="PROJECT_NAME"); fix errors and re-run until passing (do not delete cards to pass validation)Core principles
Follow the steps in order; validation must pass before delivery or the page will not work.
Never modify, overwrite, or delete index.html, dashboard.js, or index.css, or the data dashboard becomes unusable (maintain allowed files such as data.js only via card tools and other permitted paths).
Preferences
cards_plan; strictly follow the mandatory card counts table in Quick Start — metric ≥6, echarts 26–30, table 2–3, total 34–39; self-check with query_dashboard_cards before deliverytitle on cards (skip on metric cards when it would duplicate the metric label){w:4,h:3}, chart {w:8,h:8}, table {w:12,h:8}, Markdown {w:12,h:(calculate height based on content)}New or edit dashboard? ├─ New → Planning prep (brainstorm, read sources) → Author cards_plan [metric ≥6, echarts 26–30, table 2–3, total 34–39, ALL cards listed] → create_dashboard_project → Data cleaning → Dashboard development → Validate → Complete delivery └─ Edit → Identify existing dashboard project → Data cleaning (as needed) → Dashboard development/editing → Validate → Complete delivery
Need data cleaning? ├─ New dashboard → Must execute data_cleaning.py ├─ Edit dashboard with data/requirement changes → Execute as needed └─ Edit dashboard without changes → Can skip
Card count check before delivery? ├─ call query_dashboard_cards → count by type ├─ metric < 6 or echarts < 26 or table < 2 → create missing cards first └─ counts met → proceed to validate
Validation failed? → Fix issues and re-run validate_dashboard until result.ok with no errors
| Param | Required | Type | Description |
|---|---|---|---|
name | Yes | string | Project name; the dashboard is created under this directory in the workspace |
cards_plan | Yes | array | Card plan list (at least one item); the tool generates cards_plan.md from this; field definitions and how to author entries are in the tool usage example below |
Finish Planning prep, then author cards_plan. For new dashboards, the mandatory per-type counts are defined in Quick Start under Default card counts for new dashboards (metric ≥6, echarts 26–30, table 2–3, total 34–39). You must list every card in cards_plan before calling this tool — partial plans are not allowed. Only if the user explicitly requests a reduced dashboard may you go below these counts; otherwise every card must be listed upfront. You may call create_dashboard_cards in batches after project creation. This tool does not validate counts — it is your responsibility to meet them.
Return (result): On success result.ok is true and result.content includes the created path and related information; on failure result.ok is false and result.content contains the error (e.g. "Directory already exists").
| Param | Required | Type | Description |
|---|---|---|---|
project_path | Yes | string | Dashboard project directory path, relative to workspace root |
Return (result): On success result.ok is true with no errors; on failure result.ok is false and result.content contains validation failure reasons. Validation must pass before delivery; otherwise the page will not be accessible.
You must use the card management tools to work on data.js; direct editing is strictly prohibited. Card fields and CardData are defined under Card Data DSL below; layout workflow and validation are under Dashboard Development.
| Tool | Description |
|---|---|
| create_dashboard_cards | Batch create; prefer ≤6 cards/call; auto_layout (may omit layout) |
| update_dashboard_cards | Batch update; 1–10 updates/call, prefer ≤6; single-field edits supported |
| delete_dashboard_cards | Batch delete; card_ids 1–20; auto-compacts layout after delete |
| query_dashboard_cards | Query all or by id; optional fields to filter returned properties |
create_dashboard_cards
auto_layout (default false): When true, omit per-card layout; the tool generates gap-free layout by type order to reduce validate churn. Recommend auto_layout=true for new dashboards. Prefer about 6 or fewer cards per call when feasible (not a hard cap).
| Param | Required | Type | Description |
|---|---|---|---|
project_path | Yes | string | Dashboard project path |
cards | Yes | List | Card list; each item has id, type, source, getCardData; when auto_layout=true, layout may be omitted |
auto_layout | No | boolean | When true, omit layout and let the tool fill the grid |
update_dashboard_cards
Each update must include id and at least one other field to change (type, source, title, titleAlign, layout, getCardData, etc.). layout accepts partial objects (e.g. only y).
| Param | Required | Type | Description |
|---|---|---|---|
project_path | Yes | string | Dashboard project path |
updates | Yes | List | Update objects, 1–10 items; prefer ≤6 per call |
delete_dashboard_cards
| Param | Required | Type | Description |
|---|---|---|---|
project_path | Yes | string | Dashboard project path |
card_ids | Yes | List[str] | IDs to delete, 1–20 items, no duplicates |
query_dashboard_cards
| Param | Required | Type | Description |
|---|---|---|---|
project_path | Yes | string | Dashboard project path |
card_ids | No | List[str] | Omit for all cards; if set, 1–20 IDs |
fields | No | List[str] | Omit for all fields; else a subset of id, type, title, source, layout, titleAlign, getCardData |
# Create dashboard project (see structure below)
# Abbreviated example: shows one cards_plan row shape; real new dashboards must list the full per-type plan per the skill.
result = tool.call('create_dashboard_project', {
"name": "Sales Data Dashboard",
"cards_plan": [
{
"display_name": "Total Sales",
"card_id": "total_sales",
"type": "metric",
"data_detail": "Sum of sales amount",
},
{
"display_name": "Monthly Sales Trend",
"card_id": "monthly_trend",
"type": "echarts",
"data_detail": "Sales by month",
},
],
})
if result.ok:
# Read project path and other info from result.content
pass
else:
# result.content explains failure, e.g. "Directory already exists"
pass
# create_dashboard_cards: auto_layout=True; prefer ≤6 cards/call
result = tool.call('create_dashboard_cards', {
"project_path": "Sales Data Dashboard",
"auto_layout": True,
"cards": [...]
})
# query_dashboard_cards: omit card_ids for all cards; optional fields, card_ids (1–20)
result = tool.call('query_dashboard_cards', {
"project_path": "Sales Data Dashboard",
})
# update_dashboard_cards: each update needs id and ≥1 other field; prefer ≤6 updates/call
result = tool.call('update_dashboard_cards', {
"project_path": "Sales Data Dashboard",
"updates": [...]
})
# delete_dashboard_cards: card_ids 1–20; do not delete to pass validation
result = tool.call('delete_dashboard_cards', {
"project_path": "Sales Data Dashboard",
"card_ids": [...]
})
# validate_dashboard: delivery requires result.ok with no errors
result = tool.call('validate_dashboard', {"project_path": "Sales Data Dashboard"})
if not result.ok:
# Fix issues from result.content, then validate again
pass
# download_dashboard_maps (when needed)
result = tool.call('download_dashboard_maps', {
"project_path": "Sales Data Dashboard",
"area_names": ["中国", "广东省", "深圳市"]
})File and directory naming intelligently determined based on file content, business domain, and user preferred language, e.g.:
data.js must be maintained only through those card tools; direct edits or overwriting data.js are prohibitedindex.html, dashboard.js, or index.css; any change or removal makes the data dashboard unusableData source role: Provide foundational data support for data analysis dashboard development. Supported type examples: Excel, CSV, JSON, plain text, PDF, web-sourced data, MCP tool data. Operating rule: Do not change user-uploaded source files; read-only.
Data source identification and validation:
The tree matches the table: path, purpose, and editing rules for each item.
Project Directory/
├── geo/ # Map GeoJSON data
├── cleaned_data/ # Cleaned data (CSV)
├── data_cleaning.py # Data cleaning script
├── data.js # Card config (DASHBOARD_CARDS)
├── config.js # Global config (colors, themes, etc.)
├── index.html # Page markup (system-managed; do not modify or delete)
├── index.css # Styles (system-managed; do not modify or delete)
├── dashboard.js # Dashboard runtime (system-managed; do not modify or delete)
└── magic.project.js # Project configuration file| Path | Purpose | Editing rules |
|---|---|---|
| geo/ | GeoJSON for maps | System-managed; do not edit |
| cleaned_data/ | Cleaned CSV for card data sources | Editable: add or modify files |
| data_cleaning.py | Raw data → cleaned_data/ | Editable: adjust cleaning logic as needed |
| data.js | DASHBOARD_CARDS definitions | Tools only: create_dashboard_cards, update_dashboard_cards, delete_dashboard_cards; no direct file edits |
| config.js | Global colors, themes, fonts, etc. | Restricted: field values only; do not add, remove, rename fields, or change structure |
| index.html | Page HTML | System-managed; do not edit, delete, or overwrite or the data dashboard becomes unusable |
| index.css | Visual styling | System-managed; do not edit, delete, or overwrite or the data dashboard becomes unusable |
| dashboard.js | Rendering, loading, charts | System-managed; do not edit, delete, or overwrite or the data dashboard becomes unusable |
| magic.project.js | Project metadata | Prohibited: system-managed |
Card Basic Structure:
CardType Card Types:
CardData Data Structure Specification:
icon is setvalue is the current cell value as a string; return value must be a string; plain text onlygetCardData Data Loading Function:
csv.load("filename") loads CSV file in cleaned_data directory (without .csv extension){data: row array, fields: column name array, name: filename, url: path}row["field_name"], avoid special character issuesparseFloat(row["field_name"]) converts string to number, CSV data defaults to stringMath.round() or .toFixed() when necessary; percentages use .toFixed(2), amounts use .toLocaleString()window.DASHBOARD_CONFIG, read config.js to confirm real key names and value types; example: window.DASHBOARD_CONFIG.COLORS_PRIMARY// MetricCard example
getCardData: async (csv) => {
const result = await csv.load("sales_data");
const totalSales = result.data.reduce((sum, row) => {
return sum + parseFloat(row["sales_amount"]);
}, 0);
return {
label: "Total Sales",
value: totalSales,
unit: "USD",
icon: "ti-currency-dollar",
};
};
// TableCard example
getCardData: async (csv) => {
const result = await csv.load("sales_data");
return {
columns: [
{
title: "Region",
dataIndex: "region",
dataType: "string",
sortable: false,
filterable: false,
},
{
title: "Sales",
dataIndex: "sales",
dataType: "number",
sortable: true,
filterable: false,
formatter: (value) => `$${parseFloat(value).toLocaleString()}`,
},
{
title: "Customers",
dataIndex: "customers",
dataType: "number",
sortable: true,
filterable: false,
},
],
data: result.data,
};
};
// ECharts chart example
getCardData: async (csv) => {
const result = await csv.load("sales_data");
return {
grid: { left: 0, right: 0, top: 0, bottom: 0, containLabel: false },
tooltip: {
trigger: "axis",
formatter: function (params) {
return (
params[0].name + ": " + params[0].value.toLocaleString() + " USD"
);
},
},
xAxis: {
type: "category",
data: result.data.map((row) => row["region"]),
},
yAxis: {
type: "value",
axisLabel: { formatter: (value) => value.toLocaleString() },
},
series: [
{
type: "bar",
data: result.data.map((row) => parseFloat(row["sales"])),
label: {
show: true,
formatter: (params) => params.value.toLocaleString(),
},
},
],
};
};
// MarkdownCard example
getCardData: async (csv) => {
const salesData = await csv.load("sales_base_data");
const productData = await csv.load("product_sales_ranking");
const totalSales = salesData.data.reduce(
(sum, row) => sum + parseFloat(row["sales_amount"]),
0,
);
const topProduct = productData.data.sort(
(a, b) => parseFloat(b["sales_amount"]) - parseFloat(a["sales_amount"]),
)[0];
return {
content: `### Sales Analysis Report\n\n**Total Sales**: ${totalSales.toLocaleString()} USD\n**Top Product**: ${topProduct["product_name"]}`,
};
};Data processing example (same CSV shape as the English examples above):
// CSV file: sales_data.csv
// Example content:
// region,sales,customers
// East,120000,150
// South,95000,120
const parsedData = [
{ region: "East", sales: "120000", customers: "150" },
{ region: "South", sales: "95000", customers: "120" },
];
const parsedFields = ["region", "sales", "customers"];Card Actual Size Calculation Logic:
UI theme customization (only when the user explicitly asks): config.js (global theme).
Card Layout:
Dashboard development only
ECharts v6.0.0 key settings:
{ left: 0, right: 0, top: 0, bottom: 0, containLabel: false }, in v6.0.0 this config already allows axis and axis labels to display fully edge-aligned, so no need to reserve any space for XY axis labels or axis titles{ grid: { left: 0, right: 0, top: 0, bottom: 0, containLabel: false, outerBounds: { bottom: 30 } }, legend: { type: "scroll", bottom: 0 } }{ grid: { left: 0, right: 0, top: 0, bottom: 0, containLabel: false, outerBounds: { left: 50 } }, legend: { type: "scroll", orient: "vertical", left: 0 } }{ grid: { left: 0, right: 0, top: 0, bottom: 0, containLabel: false, outerBounds: { bottom: 50 } }, visualMap: { orient: "horizontal", bottom: 0, left: "center" } }{ grid: { left: 0, right: 0, top: 0, bottom: 0, containLabel: false, outerBounds: { left: 50 } }, visualMap: { orient: "vertical", left: 0, bottom: 0 } }outerBounds: { right: 120 }, legend: { orient: "vertical", right: 0 } (legend should be on left)outerBounds: { left: 60 } but no left component configured (meaningless space reservation)outerBounds: { top: 30 } but component at bottom (direction mismatch){ bottom: 0, type: "scroll" }, vertical legend use { left: 0, orient: "vertical", type: "scroll" }{ orient: "horizontal", bottom: 0, left: "center" }, vertical use { orient: "vertical", left: 0, bottom: 0 }{ orient: "vertical", left: 0, bottom: 0 } + outerBounds: { left: 50 }dataZoom; ECharts dataZoom is visually poor and its use is not recommended herelabel.formatter receives a params object; use params.value for the numeric value; set labelLayout.hideOverlap: true where needed to reduce overlap; consider font stroke for readabilitytitle in options (duplicates the card title)name on numeric value axes; avoid name on category axes. Prefer the vertical layout pattern: Y = value axis, X = category axislabel.formatter and tooltip.formatter take params; axisLabel.formatter takes valueconfig.js; read config.js before using window.DASHBOARD_CONFIG to confirm key names and value typesParameter tables, auto_layout, and batch limits are under Core Tools, Card Management Tools above. Typical usage (pick as needed):
create_dashboard_cards; ≤6/call, auto_layout=Trueupdate_dashboard_cards; ≤6 updates/call; partial fields OK (e.g. title, layout.y)delete_dashboard_cards; do not delete to pass validationquery_dashboard_cards without card_ids (and optional fields) for a quick full summarycard_ids for full config including getCardDataquery_dashboard_cards with fields (e.g. id, type, layout only)import os
import pandas as pd
# Required statements (strictly follow this format)
PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
OUTPUT_DIR = os.path.join(PROJECT_ROOT, "cleaned_data")
os.makedirs(OUTPUT_DIR, exist_ok=True)
# Data source definition (if file data sources exist)
FILE_DATA_SOURCES = {
'main_data': os.path.join(PROJECT_ROOT, "..", "data_source.csv"),
'additional_data': os.path.join(PROJECT_ROOT, "..", "additional_data.csv")
}
def main():
# 1. Data loading
df = pd.read_csv(FILE_DATA_SOURCES['main_data'])
# 2. Data cleaning: Handle missing values, remove duplicates, type conversion, outlier treatment
# 3. Data splitting: Split into multiple thematic files by business logic, time dimension, geographic region, etc.
# 4. Metric calculation: Descriptive statistics, group aggregation, derived metrics, advanced analysis
# 5. Data output: CSV format to cleaned_data directory
df.to_csv(os.path.join(OUTPUT_DIR, "cleaned_data.csv"), index=False, encoding='utf-8')
if __name__ == "__main__":
main()Data cleaning core principles:
cleaned_data only by executing the data_cleaning.py script41d7ef4
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.