agricidaniel/blog-chart
> Generate dark-mode-compatible inline SVG data visualization charts for blog posts. Supports horizontal bar, grouped bar, donut, line, lollipop, area, and radar charts with automatic platform detection (HTML vs JSX/MDX). Enforces chart type diversity, accessible markup (role=img, aria-labelledby), source attribution, and transparent backgrounds. Use whenever the user mentions data visualization, charts, graphs, comparison tables that need to be visualized, or wants to embed inline SVG visualizations in a blog post, even if not invoking blog-write. Use when user says "blog chart", "generate chart", "data visualization", "svg chart", "blog graph", "visualize data", or when the blog-write workflow identifies chart-worthy data points (3+ comparable metrics, trends, before/after data).
npx skills add https://github.com/AgriciDaniel/claude-blog --skill blog-chart
Generates dark-mode-compatible inline SVG charts for blog posts. Invoked
internally by blog-write and blog-rewrite when chart-worthy data is
identified. Not a standalone user-facing command.
Styling source of truth: skills/blog/references/visual-media.md
For supported chart types, prefer the deterministic CLI:
python3 skills/blog-chart/scripts/generate_chart_svg.py --input chart.json --output chart.html --json
The writer or researcher passes a chart request:
Chart Request:
- Type: horizontal bar
- Title: "AI Citation Sources by Platform"
- Data: ChatGPT 43.8%, Perplexity 6.6%, Google AI Overviews 2.2%, Reddit 7.15%
- Source: [Verified source], [publication date]
- Platform: mdx (or html)
Select based on the data pattern. Prefer chart type diversity, but repeat a
type when comparability or reader comprehension clearly benefits.
| Data Pattern | Best Chart Type |
|-------------|-----------------|
| Before/after comparison | Grouped bar chart |
| Ranked factors / correlations | Lollipop chart |
| Parts of whole / market share | Donut chart |
| Trend over time | Line chart |
| Percentage improvement | Horizontal bar chart |
| Distribution / range | Area chart |
| Multi-dimensional scoring | Radar chart |
All charts must work on both dark and light backgrounds:
Text elements: fill="currentColor"
Grid lines: stroke="currentColor" opacity="0.08"
Axis lines: stroke="currentColor" opacity="0.3"
Background: transparent (no fill on root SVG)
Subtitle text: fill="var(--chart-muted, currentColor)"
Source text: fill="var(--chart-muted, currentColor)"
Label text: fill="currentColor" opacity="0.8"
Set --chart-muted to an accessible text token in the host theme. If no token
exists, use #4b5563 on light backgrounds and #d1d5db on dark backgrounds.
Do not rely on low-opacity source or subtitle text for visible attribution.
| Color | Hex | Use Case |
|-------|-----|----------|
| Orange | #f97316 | Primary / highest value |
| Sky Blue | #38bdf8 | Secondary / comparison |
| Purple | #a78bfa | Tertiary / special category |
| Green | #22c55e | Quaternary / positive indicator |
For text inside approved colored elements: use fill="#111827" with
fontWeight="800". Only use white text after checking the contrast ratio is
at least 4.5:1 against that specific fill color.
Do not rely on color alone. Add direct labels, patterns, line dashes, marker
shapes, or legend text so colorblind readers can distinguish series.
<svg
viewBox="0 0 560 380"
style="max-width: 100%; height: auto; font-family: 'Inter', system-ui, sans-serif"
role="img"
aria-labelledby="chart-title chart-desc"
>
<title id="chart-title">Chart Title</title>
<desc id="chart-desc">Description for screen readers with all key data points and source</desc>
<!-- Chart content -->
<text x="280" y="372" text-anchor="middle" font-size="10" fill="var(--chart-muted, currentColor)">
Source: Source Name (Year)
</text>
</svg>
<svg
viewBox="0 0 560 380"
style={{maxWidth: '100%', height: 'auto', fontFamily: "'Inter', system-ui, sans-serif"}}
role="img"
aria-labelledby="chart-title chart-desc"
>
<title id="chart-title">Chart Title</title>
<desc id="chart-desc">Description for screen readers</desc>
{/* Chart content */}
<text x="280" y="372" textAnchor="middle" fontSize="10" fill="var(--chart-muted, currentColor)">
Source: Source Name (Year)
</text>
</svg>
| HTML | JSX |
|------|-----|
| stroke-width | strokeWidth |
| stroke-dasharray | strokeDasharray |
| stroke-linecap | strokeLinecap |
| text-anchor | textAnchor |
| font-size | fontSize |
| font-weight | fontWeight |
| font-family | fontFamily |
| class | className |
| style="..." | style={{...}} |
Best for: percentage improvements, single-metric comparisons.
chartHeight / dataCount - gap (gap=8)(value / maxValue) * chartWidthy = chartY + index * (barHeight + gap)Best for: before/after, A vs B comparisons.
Best for: parts of whole, market share.
<path d="M... A... L... A... Z" fill="color" />Best for: trends over time.
stroke="currentColor" opacity="0.08"<circle cx=... cy=... r="4" fill="color" /><polyline points="..." fill="none" stroke="color" strokeWidth="2" />opacity="0.1"Best for: ranked factors, correlations.
stroke="currentColor" opacity="0.15" strokeWidth="1"r="6" with fill colorBest for: distribution, cumulative data.
<path d="M... L... L... Z" fill="color" opacity="0.15" />stroke="color" strokeWidth="2" fill="none"Best for: multi-dimensional scoring (5-7 axes).
fill="color" opacity="0.2" stroke="color"<tspan> lines.label in <desc> or adjacent prose.
max-width: 100%; height: autostyle, or choose a justified wider viewBox for dense labels.
Wrap every chart in a <figure> element:
HTML:
<figure>
<svg viewBox="0 0 560 380" style="max-width: 100%; height: auto; font-family: 'Inter', system-ui, sans-serif" role="img" aria-labelledby="chart-title chart-desc">
<title id="chart-title">[Chart Title]</title>
<desc id="chart-desc">[Full description with data points for screen readers]</desc>
<!-- chart content -->
<text x="280" y="372" text-anchor="middle" font-size="10" fill="var(--chart-muted, currentColor)">
Source: [Source Name] ([Year])
</text>
</svg>
<figcaption>Source: <a href="[Source URL]">[Source Name]</a>, [publication date].</figcaption>
</figure>
MDX:
<figure className="chart-container" style={{margin: '2.5rem 0', textAlign: 'center', padding: '1.5rem', borderRadius: '12px'}}>
<svg viewBox="0 0 560 380" style={{maxWidth: '100%', height: 'auto', fontFamily: "'Inter', system-ui, sans-serif"}} role="img" aria-labelledby="chart-title chart-desc">
<title id="chart-title">[Chart Title]</title>
<desc id="chart-desc">[Full description]</desc>
{/* chart content with camelCase attributes */}
<text x="280" y="372" textAnchor="middle" fontSize="10" fill="var(--chart-muted, currentColor)">
Source: [Source Name] ([Year])
</text>
</svg>
<figcaption>Source: <a href="[Source URL]">[Source Name]</a>, [publication date].</figcaption>
</figure>
<figcaption> presentrole="img" and aria-labelledby present on <svg><title id> and <desc id> present inside <svg>0 0 560 380 (standard) or justified alternativeTake agricidaniel/blog-chart from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.