Skip to main content
Glama
template.tsx.j2.bak9.37 kB
{/* chuk-mcp-remotion/src/chuk_mcp_remotion/components/charts/LineChart/template.tsx.j2 */} import React from 'react'; import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from 'remotion'; interface DataPoint { x: number; y: number; label?: string; } interface LineChartProps { data?: Array<[number, number] | { x: number; y: number; label?: string }>; title?: string; xlabel?: string; ylabel?: string; startFrame: number; durationInFrames: number; } export const LineChart: React.FC<LineChartProps> = ({ data = [], title, xlabel, ylabel, startFrame, durationInFrames }) => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); const relativeFrame = frame - startFrame; // Don't render if outside the time range if (frame < startFrame || frame >= startFrame + durationInFrames) { return null; } // Convert data to DataPoint format const dataPoints: DataPoint[] = data.map((point, idx) => { if (Array.isArray(point)) { return { x: point[0], y: point[1] }; } else { return { x: point.x, y: point.y, label: point.label }; } }); if (dataPoints.length === 0) { return null; } // Calculate bounds const yValues = dataPoints.map(p => p.y); const yMin = Math.min(...yValues); const yMax = Math.max(...yValues); // Chart dimensions const chartWidth = 800; const chartHeight = 400; const padding = parseInt('[[ spacing.spacing['3xl'] ]]'); // Entrance animation const entranceProgress = spring({ frame: relativeFrame, fps, config: { damping: 200, mass: 0.5, stiffness: 200 } }); // Exit animation const exitDuration = 20; const exitProgress = interpolate( relativeFrame, [durationInFrames - exitDuration, durationInFrames], [1, 0], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' } ); const opacity = entranceProgress * exitProgress; const scale = interpolate(entranceProgress, [0, 1], [0.85, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }); const titleTranslateY = interpolate(entranceProgress, [0, 1], [-30, 0], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }); // Line drawing animation const lineDelay = 10; const lineProgress = spring({ frame: Math.max(0, relativeFrame - lineDelay), fps, config: { damping: 200, mass: 0.5, stiffness: 200 } }); // Scale coordinates to chart space const scaleX = (x: number) => { const normalized = x / (dataPoints.length - 1); return padding + normalized * (chartWidth - 2 * padding); }; const scaleY = (y: number) => { const normalized = (y - yMin) / (yMax - yMin); return chartHeight - padding - normalized * (chartHeight - 2 * padding); }; // Generate path with animation const numPointsToShow = Math.floor(lineProgress * dataPoints.length); const visiblePoints = dataPoints.slice(0, Math.max(1, numPointsToShow)); // Create SVG path const pathData = visiblePoints.map((point, idx) => { const x = scaleX(point.x); const y = scaleY(point.y); return idx === 0 ? `M ${x} ${y}` : `L ${x} ${y}`; }).join(' '); return ( <AbsoluteFill style={{ pointerEvents: 'none' }}> <div style={{ position: 'absolute', top: '50%', left: '50%', transform: `translate(-50%, -50%) scale(${scale})`, opacity, fontFamily: "'[[ "', '".join(typography.primary_font.fonts) ]]'", filter: 'drop-shadow(0 20px 40px [[ colors.background.dark ]])' }} > {title && ( <h3 style={{ fontSize: parseInt('[[ typography.font_sizes[typography.default_resolution].base ]]'), fontWeight: '[[ typography.font_weights.bold ]]', color: '[[ colors.text.on_dark ]]', marginBottom: parseInt('[[ spacing.spacing.lg ]]'), textAlign: 'center', transform: `translateY(${titleTranslateY}px)`, textShadow: '0 [[ spacing.border_width.medium ]] [[ spacing.spacing.lg ]]px [[ colors.primary[0] ]]80', letterSpacing: '[[ typography.letter_spacing.tight ]]' }} > {title} </h3> )} <svg width={chartWidth} height={chartHeight} style={{ borderRadius: parseInt('[[ spacing.border_radius.xl ]]'), padding: parseInt('[[ spacing.spacing.lg ]]'), overflow: 'visible' }} > <defs> <linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%"> <stop offset="0%" stopColor="[[ colors.background.dark ]]" /> <stop offset="100%" stopColor="[[ colors.background.darker ]]" /> </linearGradient> <linearGradient id="lineGradient" x1="0%" y1="0%" x2="100%" y2="0%"> <stop offset="0%" stopColor="[[ colors.primary[0] ]]" /> <stop offset="100%" stopColor="[[ colors.accent[0] ]]" /> </linearGradient> <filter id="glow"> <feGaussianBlur stdDeviation="3" result="coloredBlur"/> <feMerge> <feMergeNode in="coloredBlur"/> <feMergeNode in="SourceGraphic"/> </feMerge> </filter> </defs> {/* Background rect */} <rect x="0" y="0" width={chartWidth} height={chartHeight} fill="url(#bgGradient)" rx="[[ spacing.border_radius.xl ]]" /> {/* Grid lines */} <g stroke="[[ colors.border.light ]]" strokeWidth="[[ spacing.border_width.thin ]]"> {[0, 0.25, 0.5, 0.75, 1].map((ratio) => { const y = padding + ratio * (chartHeight - 2 * padding); return ( <line key={`grid-${ratio}`} x1={padding} y1={y} x2={chartWidth - padding} y2={y} /> ); })} </g> {/* Axes */} <g stroke="[[ colors.border.medium ]]" strokeWidth="[[ spacing.border_width.medium ]]"> <line x1={padding} y1={padding} x2={padding} y2={chartHeight - padding} /> <line x1={padding} y1={chartHeight - padding} x2={chartWidth - padding} y2={chartHeight - padding} /> </g> {/* Line path */} <path d={pathData} fill="none" stroke="url(#lineGradient)" strokeWidth="[[ spacing.spacing.xxs ]]" strokeLinecap="round" strokeLinejoin="round" filter="url(#glow)" style={{ opacity: lineProgress }} /> {/* Data points */} {visiblePoints.map((point, idx) => { const x = scaleX(point.x); const y = scaleY(point.y); const pointDelay = idx * 3; const pointProgress = interpolate( relativeFrame, [pointDelay, pointDelay + 15], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' } ); const pulseScale = 1 + Math.sin((relativeFrame - pointDelay) * 0.1) * 0.1 * pointProgress; return ( <g key={idx} style={{ opacity: pointProgress }}> <circle cx={x} cy={y} r={10 * pulseScale} fill="[[ colors.accent[0] ]]" opacity={0.2} /> <circle cx={x} cy={y} r="7" fill="[[ colors.accent[0] ]]" stroke="[[ colors.background.dark ]]" strokeWidth="[[ spacing.border_width.medium ]]" filter="url(#glow)" /> <circle cx={x} cy={y} r="3" fill="[[ colors.text.muted ]]" /> </g> ); })} {/* Labels */} {xlabel && ( <text x={chartWidth / 2} y={chartHeight - 10} textAnchor="middle" fill="[[ colors.text.muted ]]" fontSize="[[ typography.font_sizes[typography.default_resolution].xs ]]" fontFamily="'[[ "', '".join(typography.primary_font.fonts) ]]'" > {xlabel} </text> )} {ylabel && ( <text x="[[ spacing.spacing.lg ]]" y={chartHeight / 2} textAnchor="middle" fill="[[ colors.text.muted ]]" fontSize="[[ typography.font_sizes[typography.default_resolution].xs ]]" fontFamily="'[[ "', '".join(typography.primary_font.fonts) ]]'" transform={`rotate(-90 [[ spacing.spacing.lg ]] ${chartHeight / 2})`} > {ylabel} </text> )} </svg> </div> </AbsoluteFill> ); };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/chrishayuk/chuk-mcp-remotion'

If you have feedback or need assistance with the MCP directory API, please join our Discord server