Every dashboard needs charts. Revenue over time. User growth. Error rates. Conversion funnels. Geographic distribution. The data is already in the application -- entities, lists, aggregations -- but turning it into a visual representation has traditionally required importing a heavyweight library. Chart.js (200KB). Recharts (130KB). D3 (280KB). Each with its own API, its own rendering model, and its own learning curve.
Session 039 added 25 chart components to FlinUI. Not wrappers around a JavaScript charting library. Native FLIN components that accept data as props and render SVG-based visualizations directly. Declarative. Reactive. Zero-import. When the data changes, the chart updates automatically.
The Design Principle: Charts Are Components
The fundamental insight behind FlinUI's chart system is that a chart is just a component. It takes data as props and renders markup. The fact that the markup happens to be SVG circles and paths instead of HTML divs and spans is an implementation detail.
// A chart is used exactly like any other FlinUI component
<LineChart
data={monthly_revenue}
x="month"
y="revenue"
height={300}
/>No configuration objects. No imperative chart.update() calls. No canvas context. Just props in, visualization out. When monthly_revenue changes (because the user selected a different date range, or new data arrived from the API), the chart re-renders automatically through FLIN's reactivity system.
The Chart Components
Line Chart
monthly_data = [
{ month: "Jan", revenue: 12000, expenses: 8000 },
{ month: "Feb", revenue: 15000, expenses: 9000 },
{ month: "Mar", revenue: 18000, expenses: 10000 },
{ month: "Apr", revenue: 22000, expenses: 11000 },
{ month: "May", revenue: 25000, expenses: 12000 },
{ month: "Jun", revenue: 30000, expenses: 13000 }
]The LineChart component renders an SVG with properly scaled axes, a grid, data points, connecting lines, and an optional legend. The y prop accepts either a single field name (for one line) or a list of field names (for multiple lines). Colors are assigned in order.
The curve prop controls line interpolation: "linear" for straight segments, "smooth" for Bezier curves, "step" for step functions. The default is "linear".
Bar Chart
<BarChart
data={sales_by_region}
x="region"
y="sales"
color="primary"
height={300}
orientation="vertical"
show_values={true}
/>Bar charts support vertical and horizontal orientation, grouped bars (multiple Y values), stacked bars, and value labels. The show_values prop renders the numeric value above each bar -- a common request for business dashboards where the exact number matters more than the visual proportion.
Pie and Donut Charts
<PieChart
data={market_share}
value="percentage"
label="company"
height={300}
show_labels={true}
/>The DonutChart is a PieChart with an inner_radius prop that creates the hole. The center_text prop renders text in the center of the donut -- typically showing the total value.
Area Chart
<AreaChart
data={traffic_data}
x="date"
y={["visitors", "page_views"]}
stacked={true}
opacity={0.6}
height={300}
/>Area charts are line charts with the area below the line filled. The stacked prop stacks multiple data series instead of overlapping them. The opacity prop controls the fill transparency.
Scatter Plot
<ScatterPlot
data={student_data}
x="study_hours"
y="exam_score"
size="attendance"
color="grade"
height={400}
show_trend_line={true}
/>The scatter plot supports a third dimension through point size (size prop) and a fourth through color (color prop). The show_trend_line prop renders a linear regression line through the data points -- useful for showing correlations.
Specialized Charts
// Radar chart for multi-dimensional comparison
<RadarChart
data={skill_assessment}
categories={["Frontend", "Backend", "Database", "DevOps", "Design"]}
series={[
{ name: "Current", values: [8, 6, 7, 4, 5] },
{ name: "Target", values: [9, 8, 8, 7, 6] }
]}
/>// Funnel chart for conversion analysis
// Gauge chart for single metrics
Sparklines and Mini Charts
Not every chart needs to be a full visualization. Sparklines are tiny inline charts for tables and cards:
<DataTable data={stocks} columns={[
{ key: "symbol", label: "Symbol" },
{ key: "price", label: "Price" },
{ key: "change", label: "Change" },
{ key: "history", label: "7-Day Trend",
render: row => <Sparkline data={row.history} height={30} width={100} /> }
]} />The Sparkline component renders a miniature line chart without axes, labels, or grid -- just the data trend. It is designed to fit inside table cells, card headers, and inline text.
Responsive Charts
Every chart component is responsive by default. The ResponsiveChart wrapper handles resizing:
<ResponsiveChart>
<BarChart data={data} x="category" y="value" />
</ResponsiveChart>The wrapper observes its container's dimensions and passes width and height to the inner chart. When the browser window resizes or a sidebar collapses, the chart redraws at the new size.
For charts without the wrapper, the height prop is respected and the width defaults to 100% of the parent container.
Interactivity
Chart components support hover tooltips and click events:
<LineChart
data={revenue_data}
x="month"
y="revenue"
onHover={point => show_tooltip(point)}
onClick={point => navigate_to_detail(point.month)}
/>Hover events trigger a tooltip showing the data point's values. Click events allow navigation or drilling down into detailed data. Both are optional -- by default, charts show tooltips on hover and do nothing on click.
The ChartTooltip component can be customized:
<LineChart data={data} x="month" y="revenue">
<ChartTooltip>
{point => <div>
<Text weight="bold">{point.month}</Text>
<Text>Revenue: {point.revenue.format(2)} XOF</Text>
</div>}
</ChartTooltip>
</LineChart>SVG Rendering: Why Not Canvas
FlinUI charts render to SVG, not Canvas. This is a deliberate choice with specific tradeoffs:
Advantages of SVG: - DOM-based, so FLIN's reactivity system works naturally - Accessible (screen readers can navigate SVG elements) - Crisp at any resolution (vector, not raster) - Individual elements can have event handlers - CSS styling works (including dark mode transitions)
Disadvantages of SVG: - Slower than Canvas for very large datasets (10,000+ data points) - Larger DOM size for complex visualizations
For web application dashboards -- where datasets typically have 10 to 1,000 data points -- SVG is the better choice. The interactivity, accessibility, and integration with FLIN's reactivity system outweigh the performance difference that only matters at scales most applications never reach.
Implementation: The Math Behind Charts
Each chart component is a .flin file that performs geometric calculations and emits SVG markup. The LineChart, for example:
// LineChart.flin (simplified)
data = props.data
x_field = props.x
y_field = props.y
width = props.width || 600
height = props.height || 300
padding = 40// Calculate scales x_values = data.map(d => d[x_field]) y_values = data.map(d => d[y_field]) y_min = y_values.min y_max = y_values.max y_range = y_max - y_min
// Scale functions fn scale_x(index) { padding + (index (width - 2 padding) / (data.len - 1)) }
fn scale_y(value) { height - padding - ((value - y_min) / y_range (height - 2 padding)) }
// Build SVG path points = data.map((d, i) => "{scale_x(i)},{scale_y(d[y_field])}") path = "M " + points.join(" L ")
// Data line
// Data points
{for d, i in data}
// X-axis labels
{for d, i in data}
The chart is pure FLIN code. It computes scales from the data, generates SVG coordinates, and renders the markup. No external rendering library. No canvas API. Just math and SVG.
Dashboard Integration
Charts integrate naturally with other FlinUI components to build complete dashboards:
<Grid cols={2} gap={6}>
<Card>
<CardHeader>Revenue Trend</CardHeader>
<CardBody>
<LineChart data={revenue_monthly} x="month" y="revenue"
height={250} curve="smooth" />
</CardBody>
</Card>
<Card>
<CardHeader>Sales by Region</CardHeader>
<CardBody>
<PieChart data={regional_sales} value="amount"
label="region" height={250} />
</CardBody>
</Card>
<Card>
<CardHeader>User Growth</CardHeader>
<CardBody>
<AreaChart data={user_growth} x="week" y="users"
height={250} color="success" />
</CardBody>
</Card>
<Card>
<CardHeader>Conversion Funnel</CardHeader>
<CardBody>
<FunnelChart data={conversion_data} height={250}
show_percentages={true} />
</CardBody>
</Card>
</Grid>Four charts in a grid, each inside a Card component, each driven by reactive data. When the data changes, the charts update. No manual refresh. No imperative update calls. Just reactive components doing what reactive components do.
Accessibility and Fallbacks
Charts are inherently visual, which creates an accessibility challenge. Screen readers cannot "see" a line chart. FlinUI charts address this with two mechanisms:
ARIA labels. Every chart component includes an aria-label that describes the chart type and data summary:
<svg role="img" aria-label="Line chart showing revenue from January to June, trending upward from 12,000 to 30,000">The label is generated automatically from the data: chart type, axis labels, data range, and trend direction.
Data table fallback. For users who need to access the underlying data, the show_table prop renders a hidden but accessible data table below the chart:
<LineChart
data={revenue_data}
x="month"
y="revenue"
show_table={true}
/>The table is visually hidden (using CSS) but accessible to screen readers. This provides full data access without compromising the visual design.
Theme-Aware Charts
Charts use design tokens for all colors, which means they automatically adapt to light and dark themes:
// In LineChart.flin
grid_color = "var(--flin-border-color)"
text_color = "var(--flin-text-secondary)"
bg_color = "var(--flin-bg-surface)"In light mode, grid lines are light gray on white. In dark mode, they are dark gray on dark blue. The chart adapts without any theme-specific code. This is the payoff of the design token system -- even SVG charts integrate seamlessly with theme switching.
Twenty-Five Charts, Zero Libraries
FlinUI's chart components replace Chart.js, Recharts, D3, Victory, Nivo, and every other charting library in the JavaScript ecosystem. Not with wrappers. Not with bridges. With native FLIN components that render SVG from data using FLIN's built-in math functions. The developer writes and gets a production-ready visualization. No npm install. No bundle size explosion. No configuration file. Just a component that does what its name says.
---
This is Part 84 of the "How We Built FLIN" series, documenting how a CEO in Abidjan and an AI CTO built 25 data visualization components into a UI library.
Series Navigation: - [83] FlinUI Complete: 365+ Components - [84] Charts and Data Visualization Components (you are here) - [85] Design Tokens and Theming System - [86] The Layout System