kensaurus/cursor-kenji

data-visualization

Build interactive, accessible charts, graphs, and data dashboards using Recharts, D3, or Victory. Use when the user says "chart", "graph", "data visualization", "analytics dashboard", "metrics display", "D3", "Recharts", "time-series", or "data display".

First seen Apr 2, 2026

Installation

$ npx skills add kensaurus/cursor-kenji --skill data-visualization

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from kensaurus/cursor-kenji · top by installs.

npx skills add kensaurus/cursor-kenji

Browse all from kensaurus/cursor-kenji

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 9
License LICENSE
Default branch main
Open issues 0
Status Active

Skill metadata

Parsed from SKILL.md frontmatter.

LicenseMIT

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 11,548 B
  • docs SUMMARY.md 280 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 33 installs

SKILL.md

Data Visualization Skill

Degree of freedom: MIXED. Chart type and encoding [HIGH freedom]; existing-library/token probes and a11y fallback [LOW freedom — run exactly].

How to reason

  1. Observe — existing chart library, tokens, data shape
  2. Interpret — reuse a component vs new chart type
  3. Classify — line / bar / area / donut / sparkline / table-fallback
  4. Severity — inaccessible or empty-state-less chart outranks a missing gradient

Worked example

Observe: package.json has recharts; no src/components/charts; tokens define --primary.
Interpret: reuse Recharts + tokens, not D3.
Classify: ResponsiveContainer line chart + sr-only table; skeleton while loading.
Verify: resizes; screen-reader table present; no second chart library.

Self-critique before reporting

  • Existing lib — grepped package.json; did not add a second chart library
  • Accessible — sr-only table or text alternative present
  • States — loading + empty handled
  • Right owner — event taxonomy → audit-analytics; empty/loading frames → audit-ui-states

Create beautiful, accessible, and interactive data visualizations for dashboards and reports.

Product-event coverage ("are we tracking the right events?") is audit-analytics, not this skill. Empty/loading chart frames are audit-ui-states.

CRITICAL: Check Existing First [LOW freedom — run exactly]

Before creating ANY visualization, verify:

  1. Check for existing chart libraries:
cat package.json | grep -i "recharts\|chart\|d3\|visx\|nivo\|tremor"
rg "LineChart|BarChart|PieChart" --type tsx -l | head -10
  1. Check for existing chart components:
ls -la src/components/charts/ src/components/dashboard/ 2>/dev/null
rg "ResponsiveContainer|Chart" --type tsx | head -10
  1. Check for design tokens:
cat tailwind.config.* | grep -A10 "chart\|colors"

Why: Use existing chart library and styling conventions.

Recharts (Recommended for React) [HIGH freedom]

Line Chart

'use client'
import {
 LineChart,
 Line,
 XAxis,
 YAxis,
 CartesianGrid,
 Tooltip,
 ResponsiveContainer,
 Legend,
} from 'recharts'

const data = [
 { month: 'Jan', revenue: 4000, users: 2400 },
 { month: 'Feb', revenue: 3000, users: 1398 },
 { month: 'Mar', revenue: 2000, users: 9800 },
]

export function RevenueChart() {
 return (
 <ResponsiveContainer width="100%" height={350}>
 <LineChart data={data}>
 <CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
 <XAxis
 dataKey="month"
 className="text-xs fill-muted-foreground"
 tickLine={false}
 axisLine={false}
 />
 <YAxis
 className="text-xs fill-muted-foreground"
 tickLine={false}
 axisLine={false}
 tickFormatter={(value) => `$${value}`}
 />
 <Tooltip
 content={<CustomTooltip />}
 cursor={{ stroke: 'hsl(var(--muted))' }}
 />
 <Legend />
 <Line
 type="monotone"
 dataKey="revenue"
 stroke="hsl(var(--primary))"
 strokeWidth={2}
 dot={{ fill: 'hsl(var(--primary))' }}
 activeDot={{ r: 6 }}
 />
 <Line
 type="monotone"
 dataKey="users"
 stroke="hsl(var(--secondary))"
 strokeWidth={2}
 dot={{ fill: 'hsl(var(--secondary))' }}
 />
 </LineChart>
 </ResponsiveContainer>
 )
}

function CustomTooltip({ active, payload, label }: any) {
 if (!active || !payload) return null

 return (
 <div className="rounded-lg border bg-background p-2 shadow-sm">
 <p className="text-sm font-medium">{label}</p>
 {payload.map((entry: any, index: number) => (
 <p key={index} className="text-sm" style={{ color: entry.color }}>
 {entry.name}: {entry.value}
 </p>
 ))}
 </div>
 )
}

Bar Chart

'use client'
import { BarChart, Bar, XAxis, YAxis, ResponsiveContainer, Cell } from 'recharts'

const data = [
 { name: 'Mon', value: 12 },
 { name: 'Tue', value: 19 },
 { name: 'Wed', value: 3 },
 { name: 'Thu', value: 5 },
 { name: 'Fri', value: 2 },
]

export function WeeklyChart() {
 return (
 <ResponsiveContainer width="100%" height={200}>
 <BarChart data={data}>
 <XAxis
 dataKey="name"
 tickLine={false}
 axisLine={false}
 className="text-xs fill-muted-foreground"
 />
 <YAxis hide />
 <Bar
 dataKey="value"
 radius={[4, 4, 0, 0]}
 className="fill-primary"
 >
 {data.map((entry, index) => (
 <Cell
 key={index}
 className={index === data.length - 1 ? 'fill-primary' : 'fill-primary/60'}
 />
 ))}
 </Bar>
 </BarChart>
 </ResponsiveContainer>
 )
}

Area Chart with Gradient

'use client'
import { AreaChart, Area, XAxis, YAxis, ResponsiveContainer } from 'recharts'

export function GradientAreaChart({ data }: { data: any[] }) {
 return (
 <ResponsiveContainer width="100%" height={200}>
 <AreaChart data={data}>
 <defs>
 <linearGradient id="colorValue" x1="0" y1="0" x2="0" y2="1">
 <stop offset="5%" stopColor="hsl(var(--primary))" stopOpacity={0.3} />
 <stop offset="95%" stopColor="hsl(var(--primary))" stopOpacity={0} />
 </linearGradient>
 </defs>
 <XAxis dataKey="date" hide />
 <YAxis hide />
 <Area
 type="monotone"
 dataKey="value"
 stroke="hsl(var(--primary))"
 strokeWidth={2}
 fill="url(#colorValue)"
 />
 </AreaChart>
 </ResponsiveContainer>
 )
}

Pie/Donut Chart

'use client'
import { PieChart, Pie, Cell, ResponsiveContainer, Label } from 'recharts'

const COLORS = [
 'hsl(var(--primary))',
 'hsl(var(--secondary))',
 'hsl(var(--accent))',
 'hsl(var(--muted))',
]

export function DonutChart({ data, total }: { data: any[]; total: number }) {
 return (
 <ResponsiveContainer width="100%" height={200}>
 <PieChart>
 <Pie
 data={data}
 cx="50%"
 cy="50%"
 innerRadius={60}
 outerRadius={80}
 paddingAngle={2}
 dataKey="value"
 >
 {data.map((_, index) => (
 <Cell key={index} fill={COLORS[index % COLORS.length]} />
 ))}
 <Label
 value={total}
 position="center"
 className="fill-foreground text-2xl font-bold"
 />
 </Pie>
 </PieChart>
 </ResponsiveContainer>
 )
}

Sparklines (Mini Charts) [HIGH freedom]

'use client'
import { LineChart, Line, ResponsiveContainer } from 'recharts'

interface SparklineProps {
 data: number[]
 color?: string
 height?: number
}

export function Sparkline({ data, color = 'hsl(var(--primary))', height = 40 }: SparklineProps) {
 const chartData = data.map((value, index) => ({ index, value }))

 return (
 <ResponsiveContainer width="100%" height={height}>
 <LineChart data={chartData}>
 <Line
 type="monotone"
 dataKey="value"
 stroke={color}
 strokeWidth={1.5}
 dot={false}
 />
 </LineChart>
 </ResponsiveContainer>
 )
}

// Usage in stats card
<div className="flex items-center justify-between">
 <div>
 <p className="text-sm text-muted-foreground">Revenue</p>
 <p className="text-2xl font-bold">$45,231</p>
 </div>
 <div className="w-20">
 <Sparkline data={[10, 15, 8, 22, 18, 25, 30]} />
 </div>
</div>

Stat Cards with Trends [HIGH freedom]

import { ArrowUpIcon, ArrowDownIcon } from 'lucide-react'
import { cn } from '@/lib/utils'

interface StatCardProps {
 title: string
 value: string
 change: number
 trend: 'up' | 'down'
 sparklineData?: number[]
}

export function StatCard({ title, value, change, trend, sparklineData }: StatCardProps) {
 return (
 <div className="rounded-xl border bg-card p-6">
 <p className="text-sm font-medium text-muted-foreground">{title}</p>
 <div className="mt-2 flex items-baseline justify-between">
 <p className="text-3xl font-semibold">{value}</p>
 <div className={cn(
 'flex items-center text-sm font-medium',
 trend === 'up' ? 'text-green-600' : 'text-red-600'
 )}>
 {trend === 'up' ? (
 <ArrowUpIcon className="mr-1 h-4 w-4" />
 ) : (
 <ArrowDownIcon className="mr-1 h-4 w-4" />
 )}
 {Math.abs(change)}%
 </div>
 </div>
 {sparklineData && (
 <div className="mt-4">
 <Sparkline
 data={sparklineData}
 color={trend === 'up' ? 'rgb(22 163 74)' : 'rgb(220 38 38)'}
 />
 </div>
 )}
 </div>
 )
}

Real-time Data Updates [HIGH freedom]

'use client'
import { useEffect, useState } from 'react'
import { LineChart, Line, ResponsiveContainer, YAxis } from 'recharts'

export function RealtimeChart() {
 const [data, setData] = useState<{ time: number; value: number }[]>([])

 useEffect(() => {
 const interval = setInterval(() => {
 setData((prev) => {
 const newPoint = {
 time: Date.now(),
 value: Math.random() * 100,
 }
 // Keep last 20 points
 const updated = [...prev, newPoint].slice(-20)
 return updated
 })
 }, 1000)

 return () => clearInterval(interval)
 }, [])

 return (
 <ResponsiveContainer width="100%" height={100}>
 <LineChart data={data}>
 <YAxis domain={[0, 100]} hide />
 <Line
 type="monotone"
 dataKey="value"
 stroke="hsl(var(--primary))"
 strokeWidth={2}
 dot={false}
 isAnimationActive={false}
 />
 </LineChart>
 </ResponsiveContainer>
 )
}

D3.js for Custom Visualizations [HIGH freedom]

'use client'
import { useEffect, useRef } from 'react'
import * as d3 from 'd3'

export function CustomD3Chart({ data }: { data: { label: string; value: number }[] }) {
 const svgRef = useRef<SVGSVGElement>(null)

 useEffect(() => {
 if (!svgRef.current || !data.length) return

 const svg = d3.select(svgRef.current)
 const width = svgRef.current.clientWidth
 const height = svgRef.current.clientHeight
 const margin = { top: 20, right: 20, bottom: 30, left: 40 }

 svg.selectAll('*').remove()

 const x = d3
 .scaleBand()
 .domain(data.map((d) => d.label))
 .range([margin.left, width - margin.right])
 .padding(0.1)

 const y = d3
 .scaleLinear()
 .domain([0, d3.max(data, (d) => d.value) || 0])
 .nice()
 .range([height - margin.bottom, margin.top])

 // Bars
 svg
 .selectAll('rect')
 .data(data)
 .join('rect')
 .attr('x', (d) => x(d.label) || 0)
 .attr('y', height - margin.bottom)
 .attr('width', x.bandwidth())
 .attr('height', 0)
 .attr('fill', 'hsl(var(--primary))')
 .attr('rx', 4)
 .transition()
 .duration(750)
 .attr('y', (d) => y(d.value))
 .attr('height', (d) => y(0) - y(d.value))

 // X Axis
 svg
 .append('g')
 .attr('transform', `translate(0,${height - margin.bottom})`)
 .call(d3.axisBottom(x).tickSize(0))
 .selectAll('text')
 .attr('class', 'fill-muted-foreground text-xs')

 }, [data])

 return <svg ref={svgRef} className="w-full h-64" />
}

Accessibility [LOW freedom — run exactly]

// Always include ARIA labels and descriptions
<div role="img" aria-label="Revenue chart showing monthly data from January to December">
 <ResponsiveContainer>
 <LineChart data={data} aria-hidden="true">
 {/* Chart content */}
 </LineChart>
 </ResponsiveContainer>

 {/* Screen reader alternative */}
 <table className="sr-only">
 <caption>Monthly Revenue Data</caption>
 <thead>
 <tr><th>Month</th><th>Revenue</th></tr>
 </thead>
 <tbody>
 {data.map((d) => (
 <tr key={d.month}>
 <td>{d.month}</td>
 <td>${d.revenue}</td>
 </tr>
 ))}
 </tbody>
 </table>
</div>

Validation [LOW freedom — do not skip]

After creating visualizations:

  1. Responsive → Charts resize properly on all screens
  2. Accessible → Screen reader alternatives provided
  3. Performance → Large datasets use virtualization/sampling
  4. Loading states → Skeleton shown while data loads
  5. Empty states → Meaningful message when no data
  6. Color contrast → Meets WCAG guidelines
  7. Tooltips → Provide detailed data on hover