chartificial 0.0.2 copy "chartificial: ^0.0.2" to clipboard
chartificial: ^0.0.2 copied to clipboard

Cartesian charts for Flutter on the two-dimensional viewport: lazy paged loading, recycled widget axis labels, scrolling time axes, pluggable series.

chartificial #

Cartesian charts for Flutter, built on the framework's two-dimensional viewport. Designed for the charts that are awkward everywhere else: time series that scroll, history that loads as you reach it, and axis labels that are real widgets.

pub package pub points CI license: MIT

Scrollable AQI history with a value-banded stroke

Why chartificial #

  • Lazy paged loading. Give it a ChartLoader and history is fetched before the user scrolls to the edge, with a configurable prefetch margin. Loading never blocks scrolling: the user can keep going into regions whose data hasn't arrived yet, and pages land behind them without moving the pixels already on screen.
  • Axis labels are widgets. AxisLabelConfig.builder returns whatever you like — an icon, a chip, a two-line date. The viewport recycles them instead of rebuilding, so scrolling costs nothing extra.
  • A fixed spine, pluggable policies. The frame sequence is closed; every decision inside it is an interface you can implement. Series, decorations, range policies, tick strategies, scales and pointer snapping are all open seams — the built-in bar series is itself just a client of the public ChartSeries.
  • No dependencies. flutter and nothing else. Android, iOS, web, macOS, Windows and Linux.
Scrolling time axis — newest point pinned right, scroll into the past
Value-space color bands — stroke colored by value, not by position
Line styles — curves, dashes, area fill, gradient stroke, end badge
Floating bars — min–max pills over a background track
Stacked + grouped bars — cumulative segments, clustered per slot
Mixed series — bars and a line sharing a scrolling axis
Custom decorations — replace the grid, ring the viewport edge crossings
Custom series — a lollipop series defined outside the package

Install #

$ flutter pub add chartificial
import 'package:chartificial/chartificial.dart';

Quick start #

Everything but the axes and series has a working default, and it is all const-constructible:

SizedBox(
  height: 240,
  child: Chartificial(
    xAxis: const NumericAxis(),
    yAxis: const NumericAxis(),
    series: const [
      LineSeries(
        id: 'demo',
        data: [
          ChartPoint(0, 1),
          ChartPoint(1, 3),
          ChartPoint(2, 2),
          ChartPoint(3, 5),
        ],
      ),
    ],
  ),
)

Like every viewport, the chart cannot size itself to its content — give it bounded constraints in both directions (SizedBox, Expanded, AspectRatio).

Scrolling time series #

The case the package is built for. AxisAnchor.end starts at the newest point and scrolls left into history; AxisResolution.interval fixes how many pixels an hour occupies, which is what makes the axis scrollable at all; and ChartLoader fetches older pages before the user reaches them.

final controller = ChartificialController(
  followLatest: true, // stay pinned to "now" until the user scrolls away
  loader: ChartLoader(
    onLoadOlder: (request) async {
      final page = await api.fetchBefore(request.edgeDateTime);
      controller.prependPoints('aqi', page.points);
      return page.hasMore; // returning false latches exhaustion
    },
  ),
);

Chartificial(
  controller: controller,
  xAxis: DateTimeAxis(
    anchor: AxisAnchor.end,
    resolution: AxisResolution.interval(const Duration(hours: 1), 24),
    ticks: AxisTicks.timeInterval(const Duration(hours: 6)),
    labels: AxisLabelConfig(
      builder: (context, tick) => Text('${tick.dateTime.hour}:00'),
    ),
  ),
  yAxis: const NumericAxis(range: AxisRange.fixed(0, 300)),
  series: const [
    LineSeries(
      id: 'aqi',
      colorScale: ChartColorScale.bands([
        ColorBand(0, 50, Color(0xFF4CAF50)),
        ColorBand(50, 100, Color(0xFFFFC107)),
        ColorBand(100, 300, Color(0xFFF44336)),
      ]),
    ),
  ],
  trackball: TrackballBehavior(
    builder: (context, details) => MyTooltip(details),
  ),
)

Loading never gates scrolling. While controller.canLoadMore(direction) is true the scroll boundary stays ahead of the finger, so the user can fling straight past the loaded edge: the not-yet-loaded region renders as an empty plot with a live axis, and pages fill it in without shifting what is on screen. ChartLoadRequest.suggestedSpanX covers the whole scrolled-ahead gap, so a source that honors the hint fills it in one page. Only exhaustion (returning false) bounds the scroll — the chart then eases back to the oldest loaded point, and isAtLatest/scrollToLatest always mean the newest datum, never a loading boundary.

ChartColorScale stops are defined in value space, so a band boundary sits exactly at 50 however the axis is transformed — including a log axis.

Extension points #

The spine being closed is the point: paint order, label recycling, scroll anchoring and load triggering are invariants you compose within.

Seam Use
ChartSeries Define your own series type: painting, wide-mark overhang, marker color, pointer snapping. BarSeries is built on this same interface.
ChartSeries.snap Per-series trackball/hit snapping — clustered bars shift by their offset; custom series redefine "nearest" or opt out.
AxisRange Custom range policies. The auto/fixed/visible built-ins are exported so yours can delegate to them.
AxisTicks Custom tick strategies via resolve(TickResolveContext)ResolvedTicks.
AxisScale Any strictly-increasing domain transform; LinearScale and LogScale ship.
AxisLabelConfig.builder Any widget as an axis label, recycled while scrolling.
ChartDecoration Paint layers behind or above the series — the default grid is one of these, and is removable.
ChartColorScale Value-space gradient stops or hard bands for series strokes.
ChartPoint Subclass it; yExtent lets range-like data auto-range correctly. BarPoint does exactly this.
SeriesRenderSnapshot.geometry Per-frame channel for a series to publish paint products to decorations above it.
ChartLoader Async paged loading triggered by scroll position, with a prefetch margin.

Every open seam takes a single context object (ChartPaintContext, AxisRangeContext, TickResolveContext), so new capabilities arrive as new context fields rather than as signature breaks.

Example app #

example/ has twelve runnable demos:

Demo Shows
Infinite past + lazy loading Newest at the right, pages loaded ahead of the scroll
Live values + followLatest A point per second, pinned to "now" until you scroll away
AQI gradient line Value-band stroke coloring with band backgrounds
Line styles Curves, dashes, area fill, gradient stroke, end badge, opposed axis
Bar chart: daily range Floating min–max pill bars on a background track
Stacked + grouped bars Pollutant mix stacked per bar, indoor and outdoor clustered
Scrolling time bars Hourly bars plus an average line, stable at the edges
Custom grid + edge markers A replaced grid, rings where the line crosses the edges
Custom lollipop series A series defined by the app — the ChartSeries seam
Fixed axes + callbacks Non-scrollable chart with gaps, markers, raw hit callbacks
Logarithmic axis Four decades of particle counts with decade ticks
Visible-window y range The y axis rescales to whatever is on screen
$ cd example && flutter run

Requirements #

Flutter >=3.44.0 / Dart ^3.12.0, for the TwoDimensionalViewport APIs the chart is built on.

Contributing #

Issues and pull requests are welcome — see CONTRIBUTING.md.

License #

MIT — see LICENSE.

10
likes
160
points
158
downloads
screenshot

Documentation

API reference

Publisher

verified publisherglynskyi.com

Weekly Downloads

Cartesian charts for Flutter on the two-dimensional viewport: lazy paged loading, recycled widget axis labels, scrolling time axes, pluggable series.

Repository (GitHub)
View/report issues
Contributing

Topics

#chart #visualization #graph #time-series

License

MIT (license)

Dependencies

flutter

More

Packages that depend on chartificial