--- description: --- --- --- title: "Chart Component Patterns" purpose: "Creating or modifying chart components in LiquidRender" answers: - "How do I use Recharts in LiquidRender?" - "What color palette should charts use?" - "How do I configure axes in chart components?" - "How do I handle SSR for charts?" - "How do I add legends and tooltips consistently?" - "How do I transform data for Recharts?" - "How do I handle empty chart states?" read_when: "You're creating, modifying, or debugging chart components" skip_when: "You're working on non-chart components like tables or forms" depends_on: files: - "packages/liquid-render/src/renderer/components/utils.ts" - "packages/liquid-render/src/renderer/components/line-chart.tsx" - "packages/liquid-render/src/renderer/components/bar-chart.tsx" - "packages/liquid-render/src/renderer/components/pie-chart.tsx" - "packages/liquid-render/src/renderer/components/area-chart.tsx" entities: - "chartColors" - "tokens" - "isBrowser" - "fieldToLabel" - "ResponsiveContainer" concepts: - "Recharts library" - "SSR hydration" - "Design tokens" confidence: 0.85 verified_at: "2025-12-27" --- # Chart Component Patterns > **Read when:** You're creating, modifying, or debugging chart components > > **Skip when:** You're working on non-chart components like tables or forms ## Sections | Section | Summary | |---------|---------| | [Recharts Setup](#recharts-setup) | ResponsiveContainer and SSR handling | | [Color Palette](#color-palette) | chartColors array from utils.ts | | [Axis Configuration](#axis-configuration) | XAxis and YAxis consistent styling | | [Legend & Tooltip](#legend--tooltip) | Tooltip and Legend styling patterns | | [Data Transformation](#data-transformation) | Auto-detecting x/y fields from data | | [Empty State Handling](#empty-state-handling) | Placeholder UI for no-data scenarios | --- ## Recharts Setup > **TL;DR:** Wrap all charts in `ResponsiveContainer` and check `isBrowser` before rendering Recharts components. Charts must be responsive and handle server-side rendering gracefully. The `ResponsiveContainer` makes charts fluid, while `isBrowser` prevents hydration mismatches. ### Basic Structure ```tsx import { LineChart as RechartsLineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, } from 'recharts'; import { isBrowser } from './utils'; // SSR fallback - always check before rendering Recharts if (!isBrowser) { return (
{label &&
{label}
}
[Line chart - {chartData.length} points]
); } // Client-side render with ResponsiveContainer return ( {/* Chart components */} ); ``` ### Key Points - **Always** use `ResponsiveContainer` with `width="100%"` and explicit `height` - Default chart height is `220px` across all chart types - Use `margin={{ top: 5, right: 20, bottom: 5, left: 0 }}` for consistent spacing - The SSR placeholder shows chart type and data summary in brackets --- ## Color Palette > **TL;DR:** Import `chartColors` from utils.ts and cycle through with modulo for multi-series charts. The color palette is defined in `utils.ts` and provides 8 harmonious colors for data visualization: ```tsx import { chartColors } from './utils'; // chartColors = [ // '#3b82f6', // blue // '#22c55e', // green // '#f59e0b', // amber // '#ef4444', // red // '#8b5cf6', // violet // '#ec4899', // pink // '#06b6d4', // cyan // '#84cc16', // lime // ] // Single series - use first color // Multiple series - cycle with modulo {numericFields.map((field, i) => ( ))} ``` ### Color Usage by Chart Type | Chart Type | Color Property | Example | |------------|----------------|---------| | Line | `stroke`, `dot.fill` | `stroke={chartColors[0]}` | | Bar | `fill` | `fill={chartColors[i % chartColors.length]}` | | Area | `stroke`, `fill` (often gradient) | `stroke={chartColors[0]}` | | Pie | `Cell` fill | `` | --- ## Axis Configuration > **TL;DR:** Use `tokens.colors.mutedForeground` for tick text and `tokens.colors.border` for axis lines. All charts follow the same axis styling pattern for visual consistency: ```tsx import { tokens } from './utils'; ``` ### Axis Style Reference | Property | Value | Purpose | |----------|-------|---------| | `tick.fontSize` | `12` | Readable but compact labels | | `tick.fill` | `tokens.colors.mutedForeground` | Subdued text color | | `stroke` | `tokens.colors.border` | Axis line color | | `strokeDasharray` | `"3 3"` | CartesianGrid dash pattern | --- ## Legend & Tooltip > **TL;DR:** Style tooltips with `tokens.colors.card` background and `tokens.colors.border`; use `fontSize: tokens.fontSize.sm` for legends. Consistent tooltip and legend styling across all chart types: ```tsx import { tokens } from './utils'; ``` ### Tooltip Variations ```tsx // Bar chart adds cursor highlight // Pie chart has no cursor (circular) ``` --- ## Data Transformation > **TL;DR:** Use `detectXYFields()` to auto-detect category (string) and value (numeric) fields; use `fieldToLabel()` for display names. Charts automatically detect which fields to use based on data types: ```tsx import { fieldToLabel } from './utils'; // Helper to check numeric values function isNumeric(value: unknown): value is number { return typeof value === 'number' && !isNaN(value); } // Auto-detect x (category) and y (numeric) fields function detectXYFields(data: ChartDataPoint[]): { x: string; y: string } { if (data.length === 0) return { x: 'x', y: 'y' }; const firstRow = data[0]!; const keys = Object.keys(firstRow); // Find first string-like field for x (category/date) const xField = keys.find(k => { const val = firstRow[k]; return typeof val === 'string' || val instanceof Date; }) || keys[0] || 'x'; // Find first numeric field for y const yField = keys.find(k => { const val = firstRow[k]; return isNumeric(val) && k !== xField; }) || keys[1] || 'y'; return { x: xField, y: yField }; } // Detect ALL numeric fields for multi-series charts function detectAllNumericFields(data: ChartDataPoint[], xField: string): string[] { if (data.length === 0) return []; const firstRow = data[0]!; return Object.keys(firstRow).filter(k => k !== xField && isNumeric(firstRow[k]) ); } // Usage: convert field names to labels "Total Sales" /> ``` ### Explicit vs Auto-Detection ```tsx // Allow explicit binding override const { x: xKey, y: yKey } = useMemo(() => { const explicitX = block.binding?.x; const explicitY = block.binding?.y; if (explicitX && explicitY) { return { x: explicitX, y: explicitY }; } return detectXYFields(chartData); // Fallback to auto-detect }, [block.binding, chartData]); ``` --- ## Empty State Handling > **TL;DR:** Return a styled placeholder with "No data available" message when `chartData.length === 0`. Every chart must handle empty data gracefully: ```tsx // Empty state with consistent styling if (chartData.length === 0) { return (
{label &&
{label}
}
No data available
); } // Placeholder style definition const styles = { placeholder: { display: 'flex', alignItems: 'center', justifyContent: 'center', height: '220px', color: tokens.colors.mutedForeground, fontSize: tokens.fontSize.sm, textAlign: 'center', } as React.CSSProperties, }; ``` ### State Hierarchy 1. **SSR fallback** - Check `isBrowser` first, return text placeholder 2. **Empty state** - Check `chartData.length === 0`, return "No data available" 3. **Normal render** - Full chart with data --- ## See Also - [utils.ts](/packages/liquid-render/src/renderer/components/utils.ts) - Design tokens and shared utilities - [COMPONENT-GUIDE.md](/packages/liquid-render/docs/COMPONENT-GUIDE.md) - General component patterns - [Recharts Documentation](https://recharts.org/en-US/) - Official Recharts API reference