external/ag-shared/prompts/skills/example/ag-charts/features/legends.md
Apply: 3 minutes, Impact: HIGH ⭐ RECOMMENDED
// 1. DEFAULT: Bottom position for most charts
legend: {
position: 'bottom',
}
// 2. FLOATING: Only when space permits and data won't be obscured
legend: {
position: 'top-right', // or 'top-left' if right side has data
maxWidth: 200, // Constrain to prevent overflow
}
// 3. HIDDEN: For single series or when labels are sufficient
legend: {
enabled: false,
}
// MANDATORY verification process for floating legends:
// Step 1: Apply floating legend
legend: {
position: 'top-right',
maxWidth: 200,
}
// Step 2: Take screenshot and verify
await puppeteer_screenshot({ name: 'floating-legend-check' });
// Step 3: Check these specific areas:
// - Top-right corner data points
// - Peak values that might extend upward
// - Axis labels and titles
// - Tooltip appearance near legend
// Step 4: If ANY overlap detected → REVERT to bottom
legend: {
position: 'bottom', // Safe fallback
}
legend: {
position: 'bottom',
item: {
label: {
// Don't set fontSize or fontFamily
maxLength: 20, // Truncate long names
},
marker: {
size: 15, // Slightly larger for better visibility
strokeWidth: 0, // Clean look
},
},
spacing: 20, // Better separation between items
pagination: {
marker: {
size: 10,
},
},
}
Apply: 5 minutes, Impact: MEDIUM
// Use context parameter for data-aware legend labels
legend: {
position: 'bottom',
item: {
label: {
formatter: (params) => {
const { seriesId, itemId, value, datum, context } = params;
// Access context data if provided
if (context?.totalValue) {
const percentage = ((datum?.value / context.totalValue) * 100).toFixed(1);
return `${value} (${percentage}%)`;
}
// Format based on series-specific logic
if (seriesId === 'revenue') {
return `${value}: $${(datum?.total / 1000000).toFixed(1)}M`;
}
return value; // Default formatting
},
},
},
}
// For charts with many series (6+)
legend: {
position: 'bottom',
orientation: 'horizontal', // Better use of space
maxHeight: 60, // Prevent excessive height
pagination: {
enabled: true, // Enable pagination for many items
},
item: {
toggleSeriesVisible: true, // Click to show/hide series
label: {
maxLength: 15, // Shorter labels for space
},
},
}
Apply: 4 minutes, Impact: MEDIUM
series: [
{
type: 'range-area',
xKey: 'month',
yLowKey: 'low',
yHighKey: 'high',
legendItemName: 'Projected Range',
},
{
type: 'line',
xKey: 'month',
yKey: 'forecast',
legendItemName: 'Projected Range', // Shares toggle with the range band
},
];
listeners: {
legendItemClick: ({ itemId, legendItemName, enabled }) => {
console.log(`Legend '${legendItemName}' toggled`, { itemId, enabled });
},
};
legendItemName now flows through legend events, making cross-chart sync trivial.legend: {
item: {
marker: {
shape: 'square', // Better for bar charts
// or 'circle' for line charts
size: 12, // Standard size
strokeWidth: 0, // Clean appearance
},
paddingX: 16, // Horizontal spacing
paddingY: 8, // Vertical spacing
},
}
legend: {
position: 'bottom',
item: {
marker: {
shape: 'circle',
size: 8,
},
},
}
legend: {
position: 'bottom',
item: {
marker: {
shape: 'square',
size: 12,
},
},
}
legend: {
position: 'right', // Often better for scatter
item: {
marker: {
shape: 'circle',
size: 10,
},
},
}
// WRONG - No verification
legend: {
position: 'top-right',
}
// RIGHT - With verification
legend: {
position: 'top-right',
maxWidth: 200, // Constrain size
}
// Then take screenshot and verify no overlap
// WRONG - Will likely overlap data
// For time series with 100+ points
legend: {
position: 'top-left', // Data will reach here!
}
// RIGHT - Safe position
legend: {
position: 'bottom',
}
Before implementing floating legend:
If ANY check fails → Use position: 'bottom'
// For 90% of cases, this is all you need:
const legendConfig = {
position: 'bottom', // Safe, professional, always works
item: {
toggleSeriesVisible: true, // Interactive
},
};
// Only consider floating if:
// 1. Chart has tons of empty space
// 2. You've verified with screenshots
// 3. PREVis score improves
// 4. No data/label overlap exists