build-html-dashboard
GitHub指导构建自包含HTML仪表板,涵盖ECharts集成、暗黑主题及大数据处理。强调增量写入磁盘以规避上下文限制,严格禁止硬编码凭据,确保生成单文件且安全可靠的可视化应用。
Trigger Scenarios
Install
npx skills add mindsdb/anton --skill build-html-dashboard -g -y
SKILL.md
Frontmatter
{
"name": "build-html-dashboard",
"metadata": {
"provenance": "builtin",
"display_name": "HTML dashboard & visualization output format"
},
"description": "MANDATORY reading before building ANY HTML dashboard, chart, plot, interactive report, or browser-based visualization (create_artifact type=\"html-app\", or the frontend of a fullstack app). Contains the full HTML output contract: self-contained file rules, Apache ECharts setup, dark theme, layout\/design standards, and large-dataset handling. Recall it BEFORE writing the first line of dashboard HTML. When in doubt, recall it."
}
LIST THE INSIGHTS (terse — one line each, not an essay):
Before coding, list the insights you want to present/convey/highlight as 1 - <chart/infographic/etc>: <insight it conveys and why it matters>..
Example: 1 - Line chart of weekly signups: shows growth inflection after the March launch, flags whether momentum is sustained.
This is a checklist, not a brief — no narrative prose, no design discussion.
BUILD THE DASHBOARD — use multiple scratchpad cells, but produce ONE single self-contained HTML file:
Before the first write, call create_artifact(type="html-app", name=..., description=..., primary="dashboard.html") and use the returned <artifact_path> for every file you write (the HTML, any sibling data files, images, etc.). All paths below referring to "the output directory" mean <artifact_path>. The final dashboard MUST be a single .html file with ALL data, CSS, and JS inlined. Do NOT reference external local files (like data.js) — browsers block local file:// cross-references for security reasons and the dashboard will silently fail to load data.
REROUND DISCIPLINE (critical — most "round-cap exhaustion" failures we've seen on real dashboards come from drifting off one or more of these):
- ONE scratchpad, ONE name. Pick a name on the first cell (e.g.
dash) and reuse it for the entire build. Switching names (build_pres→write_html→pres1…) creates separate isolated environments — variables in one don't exist in another — and burns rounds on recovery. - WRITE TO DISK INCREMENTALLY. Open the output
.htmlonce in 'w' mode, thenopen(path, 'a')to append head → body skeleton → each chart section → nav/JS → closing tags. Each cell appends a small chunk you can sanity-check. Do NOT build a single 20KB+ HTML string in memory and write it at the end. - CAP STRING SIZE PER CELL at ~5KB. Large-string scratchpad calls are the single biggest cause of silent failures (the tool occasionally drops the
codepayload on oversized inputs and the cell comes back with an empty-code error, which still counts against the round cap). If a section is too big, split it. - NEVER re-emit the full HTML mid-build. Append deltas, don't re-print the world. Assembly is a one-line concat at the end, not a re-render of everything you've written so far.
- KEEP READS SMALL. To verify what landed,
os.path.getsize(path)oropen(path).read(2000)— neveropen(path).read()on a multi-KB HTML.
SECURITY (critical): Dashboards may be published to the web. NEVER embed API keys, tokens, passwords, connection strings, or any credentials in the HTML, JS, or inline data. Fetch data in scratchpad cells using credentials from environment variables, then serialize only the resulting data into the dashboard. If the user explicitly asks to embed a credential (e.g. for a live-updating dashboard), warn them that publishing will expose it and get confirmation before proceeding.
Build the parts in separate cells, then assemble at the end:
CELL 1 — Serialize data to a JS string variable (programmatic, no HTML):
Serialize all computed data (dataframes, metrics, KPIs) into a Python string. Build a Python dict with keys like "kpis", "tables", "charts" — each containing the relevant data. Convert DataFrames with df.to_dict(orient='records'). Use json.dumps(data, default=str) to handle dates, Decimal, numpy types. Store as a Python variable: data_js = 'const D = ' + json_string + ';' — do NOT write to a separate file.
CELL 2 — Build CSS + HTML structure as a Python string variable:
Write the HTML head (styles, CDN script tags) and body structure (header, KPIs, chart divs, tabs, tables) as a Python string variable html_body. This cell builds the template.
CELL 3+ — Build JS chart rendering logic as Python string variables:
Write the JavaScript that initializes charts, populates tables, handles tabs, etc. Split across multiple cells if needed to avoid token limits. Store as js_charts etc.
FINAL CELL — Assemble and write the HTML file:
Combine: html = html_body.replace('</body>', f'<script>{data_js}{js_charts}</script></body>') or similar.
SELF-CONTAINED OUTPUT (critical):
Prefer inlining everything — CSS in <style>, JS in <script>, data as JS variables. A single .html file is the most portable and publishable format. If the dataset is very large (>100KB of JSON), you may write it to a separate .js file in the SAME directory and reference it with a relative <script src="dashboard_data.js"> tag. The publisher will auto-bundle sibling files referenced in the HTML. Never reference files outside the output directory.
WHY: (1) Browsers block local file:// cross-references across directories. (2) Splitting the build across cells catches JS/CSS errors early — if a cell has a syntax issue in a string, you'll see it before the final assembly. (3) Large datasets in single cells timeout. (4) Self-contained files can be published to the web via /publish without missing assets.
PYTHON → JS STRING SAFETY (critical): When building JS code inside Python strings, escape sequences get resolved by Python BEFORE writing to the file. This means '\n' in Python becomes a literal newline in the output, which breaks JavaScript string literals. Rules:
- Use '\n' in Python if you need a literal \n in the JS output
- Use raw strings (r"...") for JS code blocks when possible
- NEVER use '\n', '\t', or '"' inside JS strings within Python — double-escape them
- After writing the file, sanity-check that no string literals span multiple lines
Output format:
- Unless the user explicitly asks for a different format, always output visualizations as polished, single-file HTML pages — never raw PNGs or bare image files.
Visual design:
- Make it look good by default. Use a dark theme (#0d1117 background, #e6edf3 text), clean typography (system sans-serif stack), generous padding, and responsive layout.
- ALWAYS use Apache ECharts for interactive charts. Load it via CDN:
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>. No Python dependencies needed — just write the HTML with inline JS. Use ECharts' built-in dark theme:echarts.init(dom, 'dark'), then customize colors to match #0d1117 background. - NEVER use Plotly, matplotlib, or other charting libraries unless the user explicitly asks.
Line smoothing (critical — smooth: true misrepresents volatile data):
- DEFAULT:
smooth: falseon ALL line series. Straight segments between data points are the honest representation — they show actual volatility, drawdowns, and inflection points. - EXCEPTION: Use
smooth: trueONLY for cumulative/monotonic series (cumulative returns, running totals, growth curves) where the trend matters more than point-to-point moves. - Decision heuristic: Does the line ever reverse direction meaningfully? If yes → smooth: false. Is it a running sum, cumulative metric, or long-horizon trend? → smooth: true is acceptable.
- Line widths: 2.5 for hero/primary lines, 1.5 for multi-line comparisons, 1 for secondary/reference lines.
Chart readability (critical — labels must NEVER overlap):
- Use
axisLabel: { rotate: -45 }or{ rotate: 45 }on crowded axes. Setgrid: { containLabel: true }so labels never clip. Uselegend: { type: 'scroll', bottom: 0 }to place scrollable legends below the chart. For pie/donut charts uselabel: { show: true, position: 'outside' }withlabelLayout: { hideOverlap: true }. For bar charts with many categories, use horizontal bars (yAxisas category) or abbreviate labels withaxisLabel: { formatter }. Always configure richtooltipwithformatterfunctions for precise value display on hover. UsedataZoomfor time series so users can zoom into ranges.
Multi-tab / multi-view dashboards (critical — charts fail silently on hidden containers):
- ECharts, Chart.js, and Plotly all render nothing when called on a container with
display: noneor 0×0 dimensions — no error, no warning, just a blank chart. NEVER callecharts.init()insideDOMContentLoadedfor tabs/pages that start hidden. - Initialize charts lazily, gated on first visibility: in the tab-click handler, check a
Setof already-rendered tabs and call the page's init function only on first visit. Example pattern:const _rendered = new Set(['overview']); function showPage(name) { /* toggle classes */ if (!_rendered.has(name)) { _rendered.add(name); initChartsFor(name); } }— only the default-visible page initializes on load.
Layout and composition:
- For non-chart visualizations (tables, reports, dashboards), write clean HTML/CSS directly. Use CSS grid or flexbox. Add subtle styling: rounded corners, soft shadows, hover effects.
- When showing multiple related visuals, combine them into a single page with sections, not separate files. Ensure each chart has enough height (min 400px) and breathing room between them so nothing feels cramped.
- Hero KPI cards at the top (large numbers, color-coded positive/negative, with delta arrows).
- Main narrative chart immediately below the KPIs — this is the chart that tells the story.
- Supporting charts below, each with a clear subtitle explaining what it reveals.
- Annotations on charts: use ECharts
markLinefor thresholds,markPointfor outliers, andmarkAreafor highlighted regions. A chart without annotations is a missed opportunity. - The goal: every visualization should look like a polished product page, not a homework assignment. Think dark-mode dashboard, not Jupyter default.
Responsive layout (critical — dashboards must work on phones too):
- ALWAYS include
<meta name="viewport" content="width=device-width, initial-scale=1.0">in<head>. Without this, mobile browsers render at desktop width and the user pinch-zooms. - Multi-card sections use
grid-template-columns: repeat(auto-fit, minmax(360px, 1fr))(or 300px on dense layouts). This lets the browser reflow to single-column on narrow viewports without a media query — cards stack vertically instead of getting squashed into unreadable columns. - Chart containers use
width: 100%andheight: min(420px, 60vh)(NOT fixed pixel widths). For each ECharts instance, register a window resize hook so it refits:window.addEventListener('resize', () => myChart.resize());— without this, rotating a phone or resizing the window leaves charts the wrong size. - Tables wrap in
<div style="overflow-x: auto;">so they scroll horizontally on narrow screens rather than overflowing the page. Do NOT set fixed table widths. - Default to one column on narrow viewports unless the user explicitly asks for a fixed multi-column layout (e.g. for a printable PDF).
Version History
- 0f2b69b Current 2026-08-20 00:13


