Blog Chart: Built-In SVG Data Visualization

Generate dark-mode-compatible inline SVG data visualization charts for blog posts.

How to use it

  1. Hit Copy SKILL.md — or use the Claude Code line below to get every file.
  2. Claude: ⋯ → Download .md, then Customize → Skills → Add → Upload skill.
    ChatGPT: make a Project and paste it into Instructions.
    Neither? Paste it at the top of a new chat — it works for that chat.
  3. Describe your job in plain words. The AI follows the skill from there.
Claude Code — installs the whole folder, not just SKILL.md
npx degit AgriciDaniel/claude-blog/skills/blog-chart#main ~/.claude/skills/blog-chart-2

For one project only, change the path to .claude/skills/blog-chart-2. This skill also uses chart.json — copying SKILL.md alone won't be enough. See the folder on GitHub.

Not working?
  • Check which app you pasted it into — the steps above name the right one.
  • Some skills need the paid tier of Claude or ChatGPT.
Step-by-step guide with screenshots · Ask in the forum

Paste into Claude, ChatGPT or Cursor.

Show the full text275 lines
blog-chart-2/SKILL.md275 lines9.5 KBpushed 60d agoRawView on GitHub

Blog Chart: Built-In SVG Data Visualization

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

Input Format

The writer or researcher passes a chart request:

Chart Request:
- Type: horizontal bar
- Title: "Quarterly Signups by Product"
- Data: Product A 420, Product B 315, Product C 180
- Source: [Verified source], [publication date]
- Platform: mdx (or html)

Chart Type Selection

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

Styling Rules (Non-Negotiable)

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 Palette

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.

Standard SVG Shell (HTML)

<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>

JSX/MDX Shell (camelCase attributes)

<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>

JSX Attribute Conversion (Required for MDX)

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={{...}}

Chart Type Construction

Horizontal Bar Chart

Best for: percentage improvements, single-metric comparisons.

  1. Define chart area: x=80, y=40, width=440, height=280
  2. Calculate bar height: chartHeight / dataCount - gap (gap=8)
  3. Calculate bar width: (value / maxValue) * chartWidth
  4. Position bars: y = chartY + index * (barHeight + gap)
  5. Label on left (right-aligned at x=75): category name
  6. Value label at end of bar: percentage or number
  7. Source text at bottom center

Grouped Bar Chart

Best for: before/after, A vs B comparisons.

  1. Define groups along Y axis, bars within each group
  2. Use 2 colors (primary + secondary) for the two series
  3. Add legend at top: colored square + label for each series
  4. Gap between groups > gap within groups

Donut Chart

Best for: parts of whole, market share.

  1. Center: cx=280, cy=180, outer radius=140, inner radius=80
  2. Calculate arc segments using cumulative angles
  3. Each segment: <path d="M... A... L... A... Z" fill="color" />
  4. Center text: total or key label
  5. Legend below chart with color squares + labels + values

Line Chart

Best for: trends over time.

  1. X axis: time periods, evenly spaced
  2. Y axis: value range with 4-5 grid lines
  3. Draw grid lines: stroke="currentColor" opacity="0.08"
  4. Plot data points: <circle cx=... cy=... r="4" fill="color" />
  5. Connect with: <polyline points="..." fill="none" stroke="color" strokeWidth="2" />
  6. Optional: area fill below line with opacity="0.1"

Lollipop Chart

Best for: ranked factors, correlations.

  1. Horizontal orientation (like bar chart but with circles)
  2. Thin line from axis to data point: stroke="currentColor" opacity="0.15" strokeWidth="1"
  3. Circle at data point: r="6" with fill color
  4. Value label next to circle
  5. Categories on Y axis (left-aligned)

Area Chart

Best for: distribution, cumulative data.

  1. Same as line chart but with filled area below
  2. Area fill: <path d="M... L... L... Z" fill="color" opacity="0.15" />
  3. Line on top: stroke="color" strokeWidth="2" fill="none"
  4. Grid lines behind the area

Radar Chart

Best for: multi-dimensional scoring (5-7 axes).

  1. Center: cx=280, cy=190
  2. Draw concentric polygons for grid (3-4 levels)
  3. Calculate axis endpoints at equal angles
  4. Plot data points on each axis proportional to value
  5. Connect data points with filled polygon: fill="color" opacity="0.2" stroke="color"
  6. Label each axis at the outer edge

Label Rules

  • Wrap long labels at word boundaries into <tspan> lines.
  • Truncate only when wrapping would collide with data marks, and keep the full label in <desc> or adjacent prose.
  • Use stable chart dimensions with a responsive max-width: 100%; height: auto style, or choose a justified wider viewBox for dense labels.
  • Check mobile widths so axis labels, legends, and value labels do not overlap.

Output Format

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>

Quality Checklist (Verify Before Returning)

  • No hardcoded text colors except contrast-checked labels inside colored elements
  • No white/light backgrounds (transparent or none)
  • Source attribution text present at bottom and semantic <figcaption> present
  • role="img" and aria-labelledby present on <svg>
  • <title id> and <desc id> present inside <svg>
  • Chart type choice supports comprehension and comparability
  • If MDX: all attributes camelCased (no hyphens in attribute names)
  • Data values match the source data exactly
  • Color palette uses only approved colors
  • ViewBox is 0 0 560 380 (standard) or justified alternative
  • Labels, shapes, patterns, or line styles provide redundancy beyond color
1---
2name: blog-chart
3description: >
4 Generate dark-mode-compatible inline SVG data visualization charts for blog
5 posts. Supports horizontal bar, grouped bar, donut, line, lollipop, area,
6 and radar charts with automatic platform detection (HTML vs JSX/MDX).
7 Enforces chart type diversity, accessible markup (role=img, aria-labelledby),
8 source attribution, and transparent backgrounds. Use when user says "blog
9 chart", "generate chart", "data visualization", "svg chart", "blog graph",
10 or "visualize data".
11user-invokable: false
12license: MIT
13---
14 
15# Blog Chart: Built-In SVG Data Visualization
16 
17Generates dark-mode-compatible inline SVG charts for blog posts. Invoked
18internally by `blog-write` and `blog-rewrite` when chart-worthy data is
19identified. Not a standalone user-facing command.
20 
21**Styling source of truth:** `skills/blog/references/visual-media.md`
22 
23For supported chart types, prefer the deterministic CLI:
24 
25```bash
26python3 skills/blog-chart/scripts/generate_chart_svg.py --input chart.json --output chart.html --json
27```
28 
29## Input Format
30 
31The writer or researcher passes a chart request:
32 
33```
34Chart Request:
35- Type: horizontal bar
36- Title: "Quarterly Signups by Product"
37- Data: Product A 420, Product B 315, Product C 180
38- Source: [Verified source], [publication date]
39- Platform: mdx (or html)
40```
41 
42## Chart Type Selection
43 
44Select based on the data pattern. Prefer chart type diversity, but repeat a
45type when comparability or reader comprehension clearly benefits.
46 
47| Data Pattern | Best Chart Type |
48|-------------|-----------------|
49| Before/after comparison | Grouped bar chart |
50| Ranked factors / correlations | Lollipop chart |
51| Parts of whole / market share | Donut chart |
52| Trend over time | Line chart |
53| Percentage improvement | Horizontal bar chart |
54| Distribution / range | Area chart |
55| Multi-dimensional scoring | Radar chart |
56 
57## Styling Rules (Non-Negotiable)
58 
59All charts must work on both dark and light backgrounds:
60 
61```
62Text elements: fill="currentColor"
63Grid lines: stroke="currentColor" opacity="0.08"
64Axis lines: stroke="currentColor" opacity="0.3"
65Background: transparent (no fill on root SVG)
66Subtitle text: fill="var(--chart-muted, currentColor)"
67Source text: fill="var(--chart-muted, currentColor)"
68Label text: fill="currentColor" opacity="0.8"
69```
70 
71Set `--chart-muted` to an accessible text token in the host theme. If no token
72exists, use `#4b5563` on light backgrounds and `#d1d5db` on dark backgrounds.
73Do not rely on low-opacity source or subtitle text for visible attribution.
74 
75### Color Palette
76 
77| Color | Hex | Use Case |
78|-------|-----|----------|
79| Orange | `#f97316` | Primary / highest value |
80| Sky Blue | `#38bdf8` | Secondary / comparison |
81| Purple | `#a78bfa` | Tertiary / special category |
82| Green | `#22c55e` | Quaternary / positive indicator |
83 
84For text inside approved colored elements: use `fill="#111827"` with
85`fontWeight="800"`. Only use white text after checking the contrast ratio is
86at least 4.5:1 against that specific fill color.
87 
88Do not rely on color alone. Add direct labels, patterns, line dashes, marker
89shapes, or legend text so colorblind readers can distinguish series.
90 
91## Standard SVG Shell (HTML)
92 
93```xml
94<svg
95 viewBox="0 0 560 380"
96 style="max-width: 100%; height: auto; font-family: 'Inter', system-ui, sans-serif"
97 role="img"
98 aria-labelledby="chart-title chart-desc"
99>
100 <title id="chart-title">Chart Title</title>
101 <desc id="chart-desc">Description for screen readers with all key data points and source</desc>
102 
103 <!-- Chart content -->
104 
105 <text x="280" y="372" text-anchor="middle" font-size="10" fill="var(--chart-muted, currentColor)">
106 Source: Source Name (Year)
107 </text>
108</svg>
109```
110 
111## JSX/MDX Shell (camelCase attributes)
112 
113```jsx
114<svg
115 viewBox="0 0 560 380"
116 style={{maxWidth: '100%', height: 'auto', fontFamily: "'Inter', system-ui, sans-serif"}}
117 role="img"
118 aria-labelledby="chart-title chart-desc"
119>
120 <title id="chart-title">Chart Title</title>
121 <desc id="chart-desc">Description for screen readers</desc>
122 
123 {/* Chart content */}
124 
125 <text x="280" y="372" textAnchor="middle" fontSize="10" fill="var(--chart-muted, currentColor)">
126 Source: Source Name (Year)
127 </text>
128</svg>
129```
130 
131## JSX Attribute Conversion (Required for MDX)
132 
133| HTML | JSX |
134|------|-----|
135| `stroke-width` | `strokeWidth` |
136| `stroke-dasharray` | `strokeDasharray` |
137| `stroke-linecap` | `strokeLinecap` |
138| `text-anchor` | `textAnchor` |
139| `font-size` | `fontSize` |
140| `font-weight` | `fontWeight` |
141| `font-family` | `fontFamily` |
142| `class` | `className` |
143| `style="..."` | `style={{...}}` |
144 
145## Chart Type Construction
146 
147### Horizontal Bar Chart
148 
149Best for: percentage improvements, single-metric comparisons.
150 
1511. Define chart area: x=80, y=40, width=440, height=280
1522. Calculate bar height: `chartHeight / dataCount - gap` (gap=8)
1533. Calculate bar width: `(value / maxValue) * chartWidth`
1544. Position bars: `y = chartY + index * (barHeight + gap)`
1555. Label on left (right-aligned at x=75): category name
1566. Value label at end of bar: percentage or number
1577. Source text at bottom center
158 
159### Grouped Bar Chart
160 
161Best for: before/after, A vs B comparisons.
162 
1631. Define groups along Y axis, bars within each group
1642. Use 2 colors (primary + secondary) for the two series
1653. Add legend at top: colored square + label for each series
1664. Gap between groups > gap within groups
167 
168### Donut Chart
169 
170Best for: parts of whole, market share.
171 
1721. Center: cx=280, cy=180, outer radius=140, inner radius=80
1732. Calculate arc segments using cumulative angles
1743. Each segment: `<path d="M... A... L... A... Z" fill="color" />`
1754. Center text: total or key label
1765. Legend below chart with color squares + labels + values
177 
178### Line Chart
179 
180Best for: trends over time.
181 
1821. X axis: time periods, evenly spaced
1832. Y axis: value range with 4-5 grid lines
1843. Draw grid lines: `stroke="currentColor" opacity="0.08"`
1854. Plot data points: `<circle cx=... cy=... r="4" fill="color" />`
1865. Connect with: `<polyline points="..." fill="none" stroke="color" strokeWidth="2" />`
1876. Optional: area fill below line with `opacity="0.1"`
188 
189### Lollipop Chart
190 
191Best for: ranked factors, correlations.
192 
1931. Horizontal orientation (like bar chart but with circles)
1942. Thin line from axis to data point: `stroke="currentColor" opacity="0.15" strokeWidth="1"`
1953. Circle at data point: `r="6"` with fill color
1964. Value label next to circle
1975. Categories on Y axis (left-aligned)
198 
199### Area Chart
200 
201Best for: distribution, cumulative data.
202 
2031. Same as line chart but with filled area below
2042. Area fill: `<path d="M... L... L... Z" fill="color" opacity="0.15" />`
2053. Line on top: `stroke="color" strokeWidth="2" fill="none"`
2064. Grid lines behind the area
207 
208### Radar Chart
209 
210Best for: multi-dimensional scoring (5-7 axes).
211 
2121. Center: cx=280, cy=190
2132. Draw concentric polygons for grid (3-4 levels)
2143. Calculate axis endpoints at equal angles
2154. Plot data points on each axis proportional to value
2165. Connect data points with filled polygon: `fill="color" opacity="0.2" stroke="color"`
2176. Label each axis at the outer edge
218 
219## Label Rules
220 
221- Wrap long labels at word boundaries into `<tspan>` lines.
222- Truncate only when wrapping would collide with data marks, and keep the full
223 label in `<desc>` or adjacent prose.
224- Use stable chart dimensions with a responsive `max-width: 100%; height: auto`
225 style, or choose a justified wider viewBox for dense labels.
226- Check mobile widths so axis labels, legends, and value labels do not overlap.
227 
228## Output Format
229 
230Wrap every chart in a `<figure>` element:
231 
232**HTML:**
233```html
234<figure>
235 <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">
236 <title id="chart-title">[Chart Title]</title>
237 <desc id="chart-desc">[Full description with data points for screen readers]</desc>
238 <!-- chart content -->
239 <text x="280" y="372" text-anchor="middle" font-size="10" fill="var(--chart-muted, currentColor)">
240 Source: [Source Name] ([Year])
241 </text>
242 </svg>
243 <figcaption>Source: <a href="[Source URL]">[Source Name]</a>, [publication date].</figcaption>
244</figure>
245```
246 
247**MDX:**
248```mdx
249<figure className="chart-container" style={{margin: '2.5rem 0', textAlign: 'center', padding: '1.5rem', borderRadius: '12px'}}>
250 <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">
251 <title id="chart-title">[Chart Title]</title>
252 <desc id="chart-desc">[Full description]</desc>
253 {/* chart content with camelCase attributes */}
254 <text x="280" y="372" textAnchor="middle" fontSize="10" fill="var(--chart-muted, currentColor)">
255 Source: [Source Name] ([Year])
256 </text>
257 </svg>
258 <figcaption>Source: <a href="[Source URL]">[Source Name]</a>, [publication date].</figcaption>
259</figure>
260```
261 
262## Quality Checklist (Verify Before Returning)
263 
264- [ ] No hardcoded text colors except contrast-checked labels inside colored elements
265- [ ] No white/light backgrounds (transparent or none)
266- [ ] Source attribution text present at bottom and semantic `<figcaption>` present
267- [ ] `role="img"` and `aria-labelledby` present on `<svg>`
268- [ ] `<title id>` and `<desc id>` present inside `<svg>`
269- [ ] Chart type choice supports comprehension and comparability
270- [ ] If MDX: all attributes camelCased (no hyphens in attribute names)
271- [ ] Data values match the source data exactly
272- [ ] Color palette uses only approved colors
273- [ ] ViewBox is `0 0 560 380` (standard) or justified alternative
274- [ ] Labels, shapes, patterns, or line styles provide redundancy beyond color
275 

Discussion