Skip to main content

Vertical Bar Chart

vertical-bar-chart.tsx
import {loadFont} from '@remotion/google-fonts/Inter'; import React from 'react'; import {Easing, Interactive, interpolate, useCurrentFrame} from 'remotion'; const {fontFamily} = loadFont('normal', { subsets: ['latin'], weights: ['700', '800'], }); const data = [ {highlighted: false, label: 'Jonny', value: 34}, {highlighted: false, label: 'Igor', value: 89}, {highlighted: true, label: 'Mehmet', value: 163}, ]; const maxValue = Math.max(...data.map(({value}) => value)); const growthEasing = Easing.spring({ damping: 14.5, mass: 0.8, overshootClamping: false, stiffness: 100, }); const Bar: React.FC<{ readonly animationDelay: number; readonly highlighted: boolean; readonly label: string; readonly value: number; }> = ({animationDelay, highlighted, label, value}) => { const frame = useCurrentFrame(); const barAnimationStart = 22 + animationDelay; const barAnimationEnd = barAnimationStart + 16 + Math.round((value / maxValue) * 8); const growthProgress = frame >= barAnimationEnd ? 1 : interpolate(frame, [barAnimationStart, barAnimationEnd], [0, 1], { easing: [growthEasing], extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }); const valueAnimationStart = barAnimationEnd - 8; const valueAnimationEnd = barAnimationEnd + 2; const valueProgress = interpolate( frame, [valueAnimationStart, valueAnimationEnd], [0, 1], { easing: growthEasing, extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }, ); return ( <div style={{ display: 'flex', flexDirection: 'column', gap: 20, height: '100%', margin: '0 auto', width: 280, }} > <div style={{ alignItems: 'flex-end', display: 'flex', flex: 1, }} > <div style={{ height: `${(value / maxValue) * 90}%`, position: 'relative', width: '100%', }} > <div style={{ bottom: `calc(${growthProgress * 100}% + 12px)`, left: 0, overflow: frame < valueAnimationEnd ? 'hidden' : 'visible', position: 'absolute', visibility: frame <= valueAnimationStart ? 'hidden' : 'visible', width: '100%', }} > <div style={{ transform: 'perspective(100px)', translate: `0 ${(1 - valueProgress) * 100}%`, willChange: 'transform', }} > <Interactive.Div name="Value" style={{ color: '#111827', fontSize: 48, fontWeight: 800, letterSpacing: -1.6, lineHeight: 1, textAlign: 'center', whiteSpace: 'nowrap', width: '100%', }} > {value} </Interactive.Div> </div> </div> <div style={{ bottom: 0, height: `${growthProgress * 100}%`, position: 'absolute', width: '100%', }} > <Interactive.Div name="Bar" style={{ backgroundColor: highlighted ? '#2858e8' : '#b9c0ca', borderRadius: '12px 12px 0 0', height: '100%', width: '100%', }} /> </div> </div> </div> <div style={{ overflow: frame < barAnimationEnd ? 'hidden' : 'visible', }} > <div style={{ transform: 'perspective(100px)', translate: `0 ${(1 - growthProgress) * 100}%`, willChange: 'transform', }} > <Interactive.Div name="Label" style={{ color: '#111827', fontSize: 40, fontWeight: 700, lineHeight: 1.2, textAlign: 'center', whiteSpace: 'nowrap', }} > {label} </Interactive.Div> </div> </div> </div> ); }; export const VerticalBarChart: React.FC = () => { return ( <Interactive.Div name="Chart" style={{ display: 'flex', flexDirection: 'column', fontFamily, fontVariantNumeric: 'tabular-nums', gap: 42, height: '100%', justifyContent: 'center', }} > <Interactive.H1 name="Title" style={{ color: '#111827', fontSize: 76, fontWeight: 800, letterSpacing: -3.8, lineHeight: 0.95, margin: 0, }} > Browser tabs open </Interactive.H1> <div style={{ alignSelf: 'center', display: 'flex', flex: 1, justifyContent: 'space-between', minHeight: 0, position: 'relative', width: 1080, }} > <Interactive.Div name="Baseline" style={{ backgroundColor: '#c5cad2', bottom: 68, height: 3, left: '50%', position: 'absolute', translate: '-50% 50%', width: '100%', zIndex: 1, }} /> {data.map(({highlighted, label, value}, index) => ( <div key={label} style={{ flex: '0 0 280px', height: '100%', }} > <Bar animationDelay={index * 24} highlighted={highlighted} label={label} value={value} /> </div> ))} </div> </Interactive.Div> ); };