Widget System

All components in Headless Chart are based on Flitter’s widget system. Understanding this allows you to freely customize every part of your charts.

Understanding StatelessWidget

Basic Concept

StatelessWidget is a widget that holds no state. Once created, it doesn’t change, and declares UI through the build method.

import { StatelessWidget } from '@meursyphus/flitter';

class MyWidget extends StatelessWidget {
  build(context) {
    return Container({
      width: 100,
      height: 100,
      color: '#3b82f6'
    });
  }
}

Usage in Headless Chart

All chart components are StatelessWidgets:

// Actual structure of Bar Chart
class BarChart extends StatelessWidget {
  build(context) {
    return new Layout();
  }
}

class Layout extends StatelessWidget {
  build(context) {
    const config = BarChartConfigProvider.of(context);
    return config.custom.layout({
      title: new Title(),
      plot: new Plot(),
      legends: new Legend()
    });
  }
}

Build Context Pattern

What is Context?

Context contains the current widget’s position information in the widget tree and data passed down from parent widgets.

class MyChart extends StatelessWidget {
  build(context) {
    // Get chart configuration from context
    const config = BarChartConfigProvider.of(context);
    const { data, custom } = config;
    
    return Container({
      child: custom.bar({
        value: data.datasets[0].data[0],
        label: data.labels[0]
      })
    });
  }
}

Provider Pattern

Provider is a pattern for passing data through the widget tree:

// Provider supplies data
BarChartConfigProvider({
  value: {
    data: chartData,
    custom: customComponents,
    direction: 'vertical'
  },
  child: new BarChart()
})

// Access data in child widgets
class BarComponent extends StatelessWidget {
  build(context) {
    const config = BarChartConfigProvider.of(context);
    // Use config
  }
}

Creating Custom Widgets

1. Simple Custom Widget

class CustomBar extends StatelessWidget {
  constructor({ value, color }) {
    super();
    this.value = value;
    this.color = color;
  }

  build(context) {
    return Container({
      width: 40,
      height: this.value * 2,
      decoration: new BoxDecoration({
        color: this.color,
        borderRadius: BorderRadius.circular(4)
      })
    });
  }
}

2. Context-Aware Widget

class SmartBar extends StatelessWidget {
  constructor({ datasetIndex, valueIndex }) {
    super();
    this.datasetIndex = datasetIndex;
    this.valueIndex = valueIndex;
  }

  build(context) {
    const config = BarChartConfigProvider.of(context);
    const dataset = config.data.datasets[this.datasetIndex];
    const value = dataset.data[this.valueIndex];
    
    return Container({
      width: Infinity,
      height: Infinity,
      decoration: new BoxDecoration({
        color: this.getColor(value),
        borderRadius: BorderRadius.only({
          topLeft: 4,
          topRight: 4
        })
      })
    });
  }

  getColor(value) {
    if (value > 100) return '#10b981';
    if (value > 50) return '#3b82f6';
    return '#ef4444';
  }
}

Functional vs Class Widgets

Functional Style

Headless Chart provides functional APIs for ease of use:

// Functional API (recommended)
const chart = BarChart({
  data: myData,
  custom: {
    bar: ({ value, label }) => Container({
      width: 40,
      height: value * 2,
      color: '#3b82f6'
    })
  }
});

Class Style

Internally implemented as classes:

// Class style (for advanced users)
class MyBarChart extends StatelessWidget {
  constructor({ data, custom }) {
    super();
    this.data = data;
    this.custom = custom;
  }

  build(context) {
    return BarChartConfigProvider({
      value: { data: this.data, custom: this.custom },
      child: new Chart()
    });
  }
}

Widget Composition Patterns

1. Composition

Build complex UIs by composing small widgets:

class ChartWithTitle extends StatelessWidget {
  build(context) {
    return Column({
      children: [
        // Title
        Container({
          padding: EdgeInsets.all(16),
          child: Text('Monthly Sales Report', {
            style: new TextStyle({
              fontSize: 20,
              fontWeight: 'bold'
            })
          })
        }),
        // Chart
        Expanded({
          child: new BarChart()
        })
      ]
    });
  }
}

2. Conditional Rendering

class ConditionalChart extends StatelessWidget {
  constructor({ showLegend = true }) {
    super();
    this.showLegend = showLegend;
  }

  build(context) {
    const children = [new Plot()];
    
    if (this.showLegend) {
      children.push(new Legend());
    }
    
    return Column({ children });
  }
}

Performance Optimization

1. Memoization

Reuse widgets for identical inputs:

class MemoizedBar extends StatelessWidget {
  static cache = new Map();

  static create({ value, color }) {
    const key = `${value}-${color}`;
    
    if (!this.cache.has(key)) {
      this.cache.set(key, new MemoizedBar({ value, color }));
    }
    
    return this.cache.get(key);
  }

  build(context) {
    return Container({
      width: 40,
      height: this.value * 2,
      color: this.color
    });
  }
}

2. Rebuild Only When Necessary

class OptimizedChart extends StatelessWidget {
  shouldRebuild(oldWidget) {
    // Rebuild only when data changes
    return this.data !== oldWidget.data;
  }

  build(context) {
    // Chart rendering
  }
}

Real Example: Creating Custom Axis

class CustomAxis extends StatelessWidget {
  build(context) {
    const config = BarChartConfigProvider.of(context);
    const { labels } = config.data;
    
    return Row({
      mainAxisAlignment: MainAxisAlignment.spaceAround,
      children: labels.map((label, index) => 
        Container({
          padding: EdgeInsets.all(8),
          decoration: new BoxDecoration({
            color: index % 2 === 0 ? '#f3f4f6' : 'transparent',
            borderRadius: BorderRadius.circular(4)
          }),
          child: Text(label, {
            style: new TextStyle({
              fontSize: 12,
              color: '#6b7280'
            })
          })
        })
      )
    });
  }
}

// Usage
BarChart({
  data: myData,
  custom: {
    xAxis: () => new CustomAxis()
  }
});

Next Steps

Now that you understand the widget system, learn about the overall chart structure in Component Architecture.

See real chart implementation examples in the Bar Chart Guide.