341 lines
9.3 KiB
Plaintext
341 lines
9.3 KiB
Plaintext
---
|
|
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 (
|
|
<div data-liquid-type="line" style={styles.wrapper}>
|
|
{label && <div style={styles.header}>{label}</div>}
|
|
<div style={styles.placeholder}>
|
|
[Line chart - {chartData.length} points]
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Client-side render with ResponsiveContainer
|
|
return (
|
|
<ResponsiveContainer width="100%" height={220}>
|
|
<RechartsLineChart data={chartData} margin={{ top: 5, right: 20, bottom: 5, left: 0 }}>
|
|
{/* Chart components */}
|
|
</RechartsLineChart>
|
|
</ResponsiveContainer>
|
|
);
|
|
```
|
|
|
|
### 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
|
|
<Line stroke={chartColors[0]} />
|
|
|
|
// Multiple series - cycle with modulo
|
|
{numericFields.map((field, i) => (
|
|
<Line
|
|
key={field}
|
|
stroke={chartColors[i % chartColors.length]}
|
|
fill={chartColors[i % chartColors.length]}
|
|
/>
|
|
))}
|
|
```
|
|
|
|
### 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 | `<Cell fill={chartColors[index % chartColors.length]} />` |
|
|
|
|
---
|
|
|
|
## 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';
|
|
|
|
<XAxis
|
|
dataKey={xKey}
|
|
tick={{ fontSize: 12, fill: tokens.colors.mutedForeground }}
|
|
stroke={tokens.colors.border}
|
|
/>
|
|
<YAxis
|
|
tick={{ fontSize: 12, fill: tokens.colors.mutedForeground }}
|
|
stroke={tokens.colors.border}
|
|
/>
|
|
<CartesianGrid strokeDasharray="3 3" stroke={tokens.colors.border} />
|
|
```
|
|
|
|
### 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
|
|
contentStyle={{
|
|
backgroundColor: tokens.colors.card,
|
|
border: `1px solid ${tokens.colors.border}`,
|
|
borderRadius: tokens.radius.md,
|
|
fontSize: tokens.fontSize.sm,
|
|
}}
|
|
// Bar charts also use cursor styling:
|
|
cursor={{ fill: tokens.colors.muted }}
|
|
/>
|
|
|
|
<Legend wrapperStyle={{ fontSize: tokens.fontSize.sm }} />
|
|
```
|
|
|
|
### Tooltip Variations
|
|
|
|
```tsx
|
|
// Bar chart adds cursor highlight
|
|
<Tooltip cursor={{ fill: tokens.colors.muted }} />
|
|
|
|
// Pie chart has no cursor (circular)
|
|
<Tooltip contentStyle={{...}} />
|
|
```
|
|
|
|
---
|
|
|
|
## 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
|
|
<Line
|
|
dataKey={field}
|
|
name={fieldToLabel(field)} // "total_sales" -> "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 (
|
|
<div
|
|
data-liquid-type="line" // Use appropriate type
|
|
style={styles.wrapper}
|
|
role="img"
|
|
aria-label={`${label ? label + ': ' : ''}Empty line chart - no data available`}
|
|
>
|
|
{label && <div style={styles.header}>{label}</div>}
|
|
<div style={styles.placeholder}>No data available</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// 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
|