line_chart.dart 4.47 KB
/*
 * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import 'package:meta/meta.dart';
import 'package:pdf/pdf.dart';

import '../geometry.dart';
import '../widget.dart';
import 'chart.dart';
import 'grid_cartesian.dart';

class LineChartValue extends ChartValue {
  const LineChartValue(this.x, this.y);
  final double x;
  final double y;

  PdfPoint get point => PdfPoint(x, y);
}

class LineDataSet extends Dataset {
  LineDataSet({
    @required this.data,
    String legend,
    this.pointColor,
    this.pointSize = 3,
    PdfColor color = PdfColors.blue,
    this.lineWidth = 2,
    this.drawLine = true,
    this.drawPoints = true,
    this.drawSurface = false,
    this.surfaceOpacity = .2,
    this.surfaceColor,
    this.isCurved = false,
    this.smoothness = 0.35,
  })  : assert(drawLine || drawPoints || drawSurface),
        super(
          legend: legend,
          color: color,
        );

  final List<LineChartValue> data;

  final bool drawLine;

  final double lineWidth;

  final bool drawPoints;
  final PdfColor pointColor;
  final double pointSize;

  final bool drawSurface;
  final PdfColor surfaceColor;
  final double surfaceOpacity;

  final bool isCurved;
  final double smoothness;

  @override
  void layout(Context context, BoxConstraints constraints,
      {bool parentUsesSize = false}) {
    box = PdfRect.fromPoints(PdfPoint.zero, constraints.biggest);
  }

  void _drawLine(Context context, ChartGrid grid, bool moveTo) {
    if (data.length < 2) {
      return;
    }

    var t = const PdfPoint(0, 0);

    final p = grid.toChart(data.first.point);
    if (moveTo) {
      context.canvas.moveTo(p.x, p.y);
    } else {
      context.canvas.lineTo(p.x, p.y);
    }

    for (var i = 1; i < data.length; i++) {
      final p = grid.toChart(data[i].point);

      if (!isCurved) {
        context.canvas.lineTo(p.x, p.y);
        continue;
      }

      final pp = grid.toChart(data[i - 1].point);
      final pn = grid.toChart(data[i + 1 < data.length ? i + 1 : i].point);

      final c1 = PdfPoint(pp.x + t.x, pp.y + t.y);

      t = PdfPoint(
          (pn.x - pp.x) / 2 * smoothness, (pn.y - pp.y) / 2 * smoothness);

      final c2 = PdfPoint(p.x - t.x, p.y - t.y);

      context.canvas.curveTo(c1.x, c1.y, c2.x, c2.y, p.x, p.y);
    }
  }

  void _drawSurface(Context context, ChartGrid grid) {
    if (data.length < 2) {
      return;
    }

    final y = (grid is CartesianGrid) ? grid.xAxisOffset : 0;
    _drawLine(context, grid, true);

    final pe = grid.toChart(data.last.point);
    context.canvas.lineTo(pe.x, y);
    final pf = grid.toChart(data.first.point);
    context.canvas.lineTo(pf.x, y);
  }

  void _drawPoints(Context context, ChartGrid grid) {
    for (final value in data) {
      final p = grid.toChart(value.point);
      context.canvas.drawEllipse(p.x, p.y, pointSize, pointSize);
    }
  }

  @override
  void paintBackground(Context context) {
    if (data.isEmpty) {
      return;
    }

    final grid = Chart.of(context).grid;

    if (drawSurface) {
      _drawSurface(context, grid);

      if (surfaceOpacity != 1) {
        context.canvas
          ..saveContext()
          ..setGraphicState(
            PdfGraphicState(opacity: surfaceOpacity),
          );
      }

      context.canvas
        ..setFillColor(surfaceColor ?? color)
        ..fillPath();

      if (surfaceOpacity != 1) {
        context.canvas.restoreContext();
      }
    }
  }

  @override
  void paint(Context context) {
    super.paint(context);

    if (data.isEmpty) {
      return;
    }

    final grid = Chart.of(context).grid;

    if (drawLine) {
      _drawLine(context, grid, true);

      context.canvas
        ..setStrokeColor(color)
        ..setLineWidth(lineWidth)
        ..setLineCap(PdfLineCap.round)
        ..setLineJoin(PdfLineJoin.round)
        ..strokePath();
    }

    if (drawPoints) {
      _drawPoints(context, grid);

      context.canvas
        ..setColor(pointColor ?? color)
        ..fillPath();
    }
  }
}