smooth_line_chart

Interactive line + area chart for Flutter. Built with CustomPainter — zero external dependencies.

Light Dark Async + range
Basic chart Dark theme Async chart

Features

  • Smooth bezier curves (Catmull-Rom)
  • Tap & drag tooltips (mobile) + hover (desktop/web)
  • Tap active point again to dismiss tooltip
  • Animated range selector (1W / 1M / 3M / etc.)
  • Async data loading with built-in loading & error states
  • Gradient fill, dots, value labels above each point
  • Fully themeable — colors, line style, grid, font styles
  • Dark mode preset
  • onPointTap callback — open bottom sheets, navigate, etc.
  • yMin for high-baseline data
  • Custom empty state widget
  • Fixed height or aspectRatio

Installation

dependencies:
  smooth_line_chart: ^0.0.4
import 'package:smooth_line_chart/smooth_line_chart.dart';

Usage

Basic

SmoothLineChart(
  xValues: const ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
  yValues: const [120000, 340000, 210000, 480000, 390000, 520000],
  title: 'Monthly Revenue',
  labelFormatter: (v) => 'Rp ${shortNumber(v)}',
)

onPointTap — open a bottom sheet

Requires a BuildContext, so use it inside a StatefulWidget or Builder.

SmoothLineChart(
  xValues: xValues,
  yValues: yValues,
  qtyValues: qtyValues,
  labelFormatter: (v) => 'Rp ${shortNumber(v)}',
  onPointTap: (index, x, y, qty, dateRange) {
    showModalBottomSheet(
      context: context,
      builder: (_) => Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(x, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
            Text('Rp ${shortNumber(y)}'),
            if (qty != null) Text('Orders: $qty'),
          ],
        ),
      ),
    );
  },
)

Async data + range selector

SmoothLineChartAsync(
  title: 'Sales',
  initialRange: '1M',
  rangeOptions: const [
    RangeOption(label: '1W', value: '1W'),
    RangeOption(label: '1M', value: '1M'),
    RangeOption(label: '3M', value: '3M'),
    RangeOption(label: '6M', value: '6M'),
    RangeOption(label: '1Y', value: '1Y'),
  ],
  dataLoader: (range) async {
    final json = await api.fetchChart(range);
    return ChartData.fromPayload(json);
  },
  onRangeChanged: (range) => print('switched to $range'),
  labelFormatter: (v) => 'Rp ${shortNumber(v)}',
)

Gradient fill + dots + value labels

SmoothLineChart(
  xValues: xValues,
  yValues: yValues,
  theme: const ChartTheme(
    lineColor: Color(0xFF6366F1),
    fillGradient: LinearGradient(
      begin: Alignment.topCenter,
      end: Alignment.bottomCenter,
      colors: [Color(0x556366F1), Colors.transparent],
    ),
    showDots: true,
    dotRadius: 4,
    showValueLabels: true,
  ),
)

yMin — zoom in on high-baseline data

// Without yMin: chart looks flat because baseline is 0
// With yMin: variations are clearly visible
SmoothLineChart(
  xValues: xValues,
  yValues: const [400000, 420000, 390000, 450000, 430000, 470000],
  yMin: 350000,
  labelFormatter: (v) => shortNumber(v),
)

Custom empty state

SmoothLineChart(
  xValues: const [],
  yValues: const [],
  emptyWidget: Center(
    child: Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Icon(Icons.bar_chart_outlined, size: 32, color: Colors.grey),
        Text('No data yet'),
      ],
    ),
  ),
)

Card styling + padding

SmoothLineChart(
  xValues: xValues,
  yValues: yValues,
  backgroundColor: const Color(0xFFF0F0FF),
  borderRadius: 16,
  padding: const EdgeInsets.all(16),
)

Dark mode

SmoothLineChart(
  xValues: xValues,
  yValues: yValues,
  theme: ChartTheme.dark,
  backgroundColor: const Color(0xFF111827),
  borderRadius: 16,
  padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
)

Custom Y-axis labels

SmoothLineChart(
  xValues: xValues,
  yValues: yValues,
  yTickValues: const [0, 500000, 1000000, 1500000],
  yAxisLabels: const ['0', '500 Rb', '1 Jt', '1.5 Jt'],
)

Fixed height

SmoothLineChart(
  xValues: xValues,
  yValues: yValues,
  height: 200, // ignores aspectRatio when set
)

Custom tooltip

ChartCard(
  xValues: xValues,
  yValues: yValues,
  tooltipBuilder: (index, x, y, qty, range) => MyTooltip(label: x, value: y),
)

ChartData — JSON parsing

// Direct
ChartData(
  xValues: ['Jan', 'Feb', 'Mar'],
  yValues: [120000.0, 340000.0, 210000.0],
)

// From a plain map
ChartData.fromMap({'Jan': 120000, 'Feb': 340000, 'Mar': 210000})

// From a list of maps
ChartData.fromList(rows, xKey: 'month', yKey: 'revenue', qtyKey: 'orders')

// From any nested API response
// Accepts: { "data": { "record": [...] } } or a flat list directly
ChartData.fromPayload(jsonResponse)

API reference

SmoothLineChart

Parameter Type Default Description
xValues List<String> required X-axis labels
yValues List<double> required Data points
qtyValues List<int>? Secondary metric shown in tooltip
dateRanges List<String>? Subtitle per point in tooltip
title String? Title above the chart
theme ChartTheme? default Colors, grid, font styles
yMin double? 0 Minimum Y value (useful for high-baseline data)
yMaxOverride double? auto Override Y-axis maximum
yTickValues List<num>? auto Custom Y tick positions
yAxisLabels List<String>? auto Custom Y tick labels
yLabelOverlay bool false Float Y labels inside the chart area
tooltipArrow bool false Show downward arrow on tooltip
backgroundColor Color? transparent Card background color
borderRadius double 0 Card corner radius
padding EdgeInsets EdgeInsets.zero Inner padding around the chart
emptyWidget Widget? "No data" text Widget shown when yValues is empty
onPointTap void Function(int, String, double, int?, String?)? Called on first tap of a point
labelFormatter String Function(double)? v.toString() Format Y-axis values
qtyFormatter String Function(int)? 'Qty: $n' Format qty in tooltip
tooltipBuilder Widget Function(...)? built-in Replace tooltip with a custom widget
aspectRatio double 2.9 Width ÷ height ratio (ignored when height is set)
height double? Fixed height in pixels

SmoothLineChartAsync (extra params)

Parameter Type Default Description
dataLoader Future<ChartData> Function(String) required Called on mount and on range change
rangeOptions List<RangeOption> required Pills shown in the range selector
initialRange String? first option Range selected on mount
onRangeChanged void Function(String)? Called when user switches range
showRangeSelector bool true Show/hide the pill row
loadingWidget Widget? spinner Widget shown while loading
errorBuilder Widget Function(Object)? red text Widget shown on error

ChartTheme

Property Type Default Description
lineColor Color indigo Line stroke color
fillColor Color indigo 20% Area fill (used when fillGradient is null)
fillGradient Gradient? Gradient fill below the line
gridColor Color dark 12% Dashed grid line color
labelColor Color grey Fallback label + empty-state text color
tooltipBackground Color white Tooltip card background
tooltipBorder Color light grey Tooltip card border
tooltipTextColor Color dark Tooltip text color
lineWidth double 3.2 Stroke width
curveSmoothness double 0.22 Bezier tension (0 = straight, 0.5 = max curve)
ySteps int 5 Number of auto Y-axis ticks
showGrid bool true Show/hide dashed grid lines
showDots bool false Show dots at every data point
dotRadius double 3.5 Dot size
showValueLabels bool false Show Y value text above each point
valueLabelStyle TextStyle? Style for value labels
yLabelStyle TextStyle? Y-axis label font style
xLabelStyle TextStyle? X-axis label font style

ChartTheme.dark is a ready-to-use dark mode preset.

Utility functions

shortNumber(1500000)     // '1.5 Jt'
shortNumber(1500)        // '1.5 K'
formatThousands(1500000) // '1.500.000'

License

MIT

Libraries

smooth_line_chart
smooth_line_chart — interactive line + area chart for Flutter.