Line Chart

Line charts are optimized for expressing data changes over time. They visually show trends, patterns, and continuity, and Headless Chart’s Line Chart allows complete customization.

Interface

LineChart Props

interface LineChartProps {
  // Required properties
  data: LineChartData;
  
  // Optional properties
  custom?: Partial<LineChartCustom>;
  title?: string;
  getScale?: (data: LineChartData) => LineChartScale;
}

Data Structure

type LineChartData = {
  labels: string[];     // X-axis labels (time/categories)
  datasets: {
    legend: string;     // Dataset name
    values: number[];   // Y-axis values
  }[];
};

type LineChartScale = {
  min: number;
  max: number;
  step: number;
};

Basic Usage

import Widget from '@meursyphus/flitter-react';
import { LineChart } from '@meursyphus/headless-chart';

const data = {
  labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
  datasets: [{
    legend: 'Visitors',
    values: [1200, 1900, 3000, 2500, 4200, 3800]
  }]
};

function BasicLineChart() {
  return (
    <Widget 
      width="600px" 
      height="400px" 
      widget={LineChart({ data })} 
    />
  );
}

Chart Components

Line Chart consists of the following hierarchical structure:

LineChart
└── Layout (overall layout)
    ├── Title (chart title)
    ├── Legend (legend)
    └── Plot (plot area)
        ├── XAxis (X-axis)
        ├── YAxis (Y-axis)
        ├── Grid (grid)
        └── Series (data series)
            └── Line (individual line)

Customizable Elements

You can customize 18 components through the custom prop:

1. Layout Elements

layout

Controls the overall chart layout. Manages the arrangement of title, legend, and plot area.

custom: {
  layout: ({ title, legends, plot }) => {
    return Container({
      padding: EdgeInsets.all(20),
      child: Column({
        children: [
          Row({
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [title, Row({ children: legends })]
          }),
          SizedBox({ height: 16 }),
          Expanded({ child: plot })
        ]
      })
    });
  }
}

title

Displays the chart title.

legend

Renders individual legend items.

2. Plot Area Elements

plot

The main plot area containing axes, grid, and series.

3. Data Visualization Elements

series

Container for all lines.

custom: {
  series: ({ lines }) => {
    return Stack({
      children: lines
    });
  }
}

line

Individual line element. This is the most commonly customized element.

custom: {
  line: ({ values, legend, index }) => {
    return CustomPaint({
      painter: {
        svg: {
          createDefaultSvgEl: (context) => ({
            line: context.createSvgEl("path")
          }),
          paint: ({ line }, { width, height }) => {
            const path = createLinePath({ values, width, height });
            line.setAttribute("fill", "none");
            line.setAttribute("stroke", getColor(index));
            line.setAttribute("stroke-width", "2");
            line.setAttribute("d", path.getD());
          }
        },
        canvas: {
          paint: (context, { width, height }) => {
            const path = createLinePath({ values, width, height });
            context.canvas.strokeStyle = getColor(index);
            context.canvas.lineWidth = 2;
            context.canvas.stroke(path.toCanvasPath());
          }
        }
      }
    });
  }
}

dataLabel

Labels displayed at data points on the line.

4. Axis Elements

xAxis / yAxis

Containers for the entire axis.

xAxisLine / yAxisLine

The baseline of the axis.

xAxisLabel / yAxisLabel

Displays axis labels.

xAxisTick / yAxisTick

Tick marks on the axis.

5. Grid Elements

grid

Container for the entire grid.

gridXLine / gridYLine

Vertical/horizontal grid lines.

Real-World Usage Examples

1. Lines with Different Styles

import { CustomPaint, Path } from '@meursyphus/flitter';

const styledLineChart = LineChart({
  data,
  custom: {
    line: ({ values, index }) => {
      const styles = [
        { color: '#3b82f6', width: 2, dash: null },
        { color: '#10b981', width: 3, dash: [10, 5] },
        { color: '#f59e0b', width: 2, dash: [2, 2] }
      ];
      
      const style = styles[index % styles.length];
      
      return CustomPaint({
        painter: {
          svg: {
            createDefaultSvgEl: (context) => ({
              line: context.createSvgEl("path")
            }),
            paint: ({ line }, { width, height }) => {
              const path = createLinePath({ values, width, height });
              line.setAttribute("fill", "none");
              line.setAttribute("stroke", style.color);
              line.setAttribute("stroke-width", style.width.toString());
              if (style.dash) {
                line.setAttribute("stroke-dasharray", style.dash.join(' '));
              }
              line.setAttribute("d", path.getD());
            }
          }
        }
      });
    }
  }
});

2. Line with Data Points

import { Stack, Positioned, Container, BoxDecoration } from '@meursyphus/flitter';

const lineWithPoints = LineChart({
  data,
  custom: {
    line: ({ values, index }) => {
      const colors = ['#3b82f6', '#10b981', '#f59e0b'];
      const color = colors[index % colors.length];
      
      return Stack({
        clipBehavior: 'none',
        children: [
          // Line
          CustomPaint({
            painter: createLinePainter(values, color)
          }),
          // Data points
          ...values.map((value, i) => {
            const position = calculatePosition(i, value, values.length);
            
            return Positioned({
              left: position.x - 4,
              top: position.y - 4,
              child: Container({
                width: 8,
                height: 8,
                decoration: new BoxDecoration({
                  shape: 'circle',
                  color: '#ffffff',
                  border: Border.all({ 
                    color: color, 
                    width: 2 
                  })
                })
              })
            });
          })
        ]
      });
    }
  }
});

3. Area-Filled Line

const areaLineChart = LineChart({
  data,
  custom: {
    line: ({ values, index }) => {
      const colors = [
        { line: '#3b82f6', area: 'rgba(59, 130, 246, 0.1)' },
        { line: '#10b981', area: 'rgba(16, 185, 129, 0.1)' }
      ];
      
      const { line: lineColor, area: areaColor } = colors[index % colors.length];
      
      return Stack({
        children: [
          // Area fill
          CustomPaint({
            painter: {
              svg: {
                createDefaultSvgEl: (context) => ({
                  area: context.createSvgEl("path")
                }),
                paint: ({ area }, { width, height }) => {
                  const path = createAreaPath({ values, width, height });
                  area.setAttribute("fill", areaColor);
                  area.setAttribute("d", path.getD());
                }
              }
            }
          }),
          // Line
          CustomPaint({
            painter: createLinePainter(values, lineColor)
          })
        ]
      });
    }
  }
});

4. Curved Line (Smooth)

const smoothLineChart = LineChart({
  data,
  custom: {
    line: ({ values, index }) => {
      const colors = ['#3b82f6', '#10b981', '#f59e0b'];
      
      return CustomPaint({
        painter: {
          svg: {
            createDefaultSvgEl: (context) => ({
              line: context.createSvgEl("path")
            }),
            paint: ({ line }, { width, height }) => {
              // Create smooth line with Bezier curves
              const path = createSmoothPath({ values, width, height });
              line.setAttribute("fill", "none");
              line.setAttribute("stroke", colors[index % colors.length]);
              line.setAttribute("stroke-width", "2");
              line.setAttribute("d", path.getD());
            }
          }
        }
      });
    }
  }
});

function createSmoothPath({ values, width, height }) {
  const path = new Path();
  const points = values.map((value, index) => ({
    x: (index / (values.length - 1)) * width,
    y: height - (value / Math.max(...values)) * height
  }));
  
  path.moveTo(points[0]);
  
  // Catmull-Rom spline
  for (let i = 1; i < points.length; i++) {
    const p0 = points[Math.max(0, i - 2)];
    const p1 = points[i - 1];
    const p2 = points[i];
    const p3 = points[Math.min(points.length - 1, i + 1)];
    
    const cp1x = p1.x + (p2.x - p0.x) / 6;
    const cp1y = p1.y + (p2.y - p0.y) / 6;
    const cp2x = p2.x - (p3.x - p1.x) / 6;
    const cp2y = p2.y - (p3.y - p1.y) / 6;
    
    path.cubicTo({ x: cp1x, y: cp1y }, { x: cp2x, y: cp2y }, p2);
  }
  
  return path;
}

Adding Animation

import { StatefulWidget, State, AnimationController, Animation } from '@meursyphus/flitter';

class AnimatedLine extends StatefulWidget {
  constructor({ values, color }) {
    super();
    this.values = values;
    this.color = color;
  }

  createState() {
    return new AnimatedLineState();
  }
}

class AnimatedLineState extends State {
  initState() {
    super.initState();
    this.controller = new AnimationController({
      duration: { milliseconds: 1500 }
    });
    this.animation = new Animation({
      controller: this.controller,
      value: Tween({ begin: 0, end: 1 })
    });
    this.controller.forward();
  }

  build() {
    return AnimatedBuilder({
      animation: this.animation,
      builder: () => {
        const progress = this.animation.value;
        const visibleValues = this.widget.values.slice(
          0, 
          Math.floor(this.widget.values.length * progress)
        );
        
        return CustomPaint({
          painter: createLinePainter(visibleValues, this.widget.color)
        });
      }
    });
  }

  dispose() {
    this.controller.dispose();
    super.dispose();
  }
}

// Usage
const animatedChart = LineChart({
  data,
  custom: {
    line: ({ values, index }) => {
      const colors = ['#3b82f6', '#10b981', '#f59e0b'];
      return new AnimatedLine({
        values,
        color: colors[index % colors.length]
      });
    }
  }
});

Performance Optimization Tips

  1. Limit Data Points: Sample large datasets for display
  2. Path Optimization: Simplify complex paths
  3. Limit Animations: Use only when necessary
  4. Memory Management: Remove old data in real-time charts

Next Steps