David PHAM-VAN

Update Chart Widget

... ... @@ -30,10 +30,12 @@ export 'package:barcode/barcode.dart';
part 'widgets/annotations.dart';
part 'widgets/barcode.dart';
part 'widgets/basic.dart';
part 'widgets/chart/bar_chart.dart';
part 'widgets/chart/chart.dart';
part 'widgets/chart/linear_grid.dart';
part 'widgets/chart/grid_axis.dart';
part 'widgets/chart/grid_cartesian.dart';
part 'widgets/chart/legend.dart';
part 'widgets/chart/line_chart.dart';
part 'widgets/chart/bar_chart.dart';
part 'widgets/clip.dart';
part 'widgets/container.dart';
part 'widgets/content.dart';
... ...
... ... @@ -18,53 +18,60 @@
part of widget;
class BarDataSet extends DataSet {
class BarDataSet extends Dataset {
BarDataSet({
@required this.data,
String legend,
this.borderColor,
this.borderWidth = 1.5,
this.color = PdfColors.blue,
PdfColor color = PdfColors.blue,
this.drawBorder = true,
this.drawSurface = true,
this.surfaceOpacity = 1,
this.width = 20,
this.width = 10,
this.offset = 0,
this.margin = 5,
}) : assert(drawBorder || drawSurface);
}) : assert(drawBorder || drawSurface),
super(
legend: legend,
color: color,
);
final List<LineChartValue> data;
final double width;
final double offset;
final double margin;
final bool drawBorder;
final PdfColor borderColor;
final double borderWidth;
final bool drawSurface;
final PdfColor color;
final double surfaceOpacity;
final double width;
final double offset;
void _drawSurface(Context context, ChartGrid grid, LineChartValue value) {
final double y = (grid is LinearGrid) ? grid.xAxisOffset : 0;
final PdfPoint p = grid.tochart(value.point);
final double y = (grid is CartesianGrid) ? grid.xAxisOffset : 0;
final PdfPoint p = grid.toChart(value.point);
context.canvas.drawRect(p.x + offset - width / 2, y, width, p.y);
}
@override
void paintBackground(Context context, ChartGrid grid) {}
void layout(Context context, BoxConstraints constraints,
{bool parentUsesSize = false}) {
box = PdfRect.fromPoints(PdfPoint.zero, constraints.biggest);
}
@override
void paintForeground(Context context, ChartGrid grid) {
if (data.isEmpty) {
return;
}
void paint(Context context) {
super.paint(context);
if (data.isEmpty) {
return;
}
final ChartGrid grid = Chart.of(context).grid;
if (drawSurface) {
for (final LineChartValue value in data) {
_drawSurface(context, grid, value);
... ...
... ... @@ -19,15 +19,39 @@
part of widget;
/// This widget is in preview and the API is subject to change
class Chart extends Widget {
class Chart extends Widget implements Inherited {
Chart({
@required this.grid,
@required this.data,
@required this.datasets,
this.overlay,
this.title,
this.bottom,
this.left,
this.right,
});
/// The Coordinate system that will layout the content
final ChartGrid grid;
final List<DataSet> data;
/// The list of dataset to display
final List<Dataset> datasets;
/// Legend for this chart
final Widget overlay;
final Widget title;
final Widget bottom;
final Widget left;
final Widget right;
Context _context;
Widget _child;
static Chart of(Context context) => context.inherited[Chart];
PdfPoint _computeSize(BoxConstraints constraints) {
if (constraints.isTight) {
... ... @@ -50,51 +74,64 @@ class Chart extends Widget {
return constraints.constrain(PdfPoint(width, height));
}
Widget _build(Context context) {
return Column(
children: <Widget>[
if (title != null) title,
Expanded(
child: Row(
children: <Widget>[
if (left != null) left,
Expanded(
child: Stack(
children: <Widget>[
grid,
if (overlay != null) overlay,
],
),
),
if (right != null) right,
],
),
),
if (bottom != null) bottom,
],
);
}
@override
void layout(Context context, BoxConstraints constraints,
{bool parentUsesSize = false}) {
box = PdfRect.fromPoints(PdfPoint.zero, _computeSize(constraints));
grid.layout(context, box.size);
_context = context.inheritFrom(this);
_child = _build(_context);
_child.layout(_context, BoxConstraints.tight(box.size));
}
@override
void paint(Context context) {
super.paint(context);
super.paint(_context);
final Matrix4 mat = Matrix4.identity();
mat.translate(box.x, box.y);
context.canvas
_context.canvas
..saveContext()
..setTransform(mat);
grid.paintBackground(context, box.size);
grid.clip(context, box.size);
for (DataSet dataSet in data) {
dataSet.paintBackground(context, grid);
}
grid.unClip(context, box.size);
grid.paint(context, box.size);
grid.clip(context, box.size);
for (DataSet dataSet in data) {
dataSet.paintForeground(context, grid);
}
grid.unClip(context, box.size);
grid.paintForeground(context, box.size);
context.canvas.restoreContext();
_child.paint(_context);
_context.canvas.restoreContext();
}
}
abstract class ChartGrid {
void layout(Context context, PdfPoint size);
void paintBackground(Context context, PdfPoint size);
void paint(Context context, PdfPoint size);
void paintForeground(Context context, PdfPoint size);
void clip(Context context, PdfPoint size);
void unClip(Context context, PdfPoint size);
abstract class ChartGrid extends Widget {
@override
void layout(Context context, BoxConstraints constraints,
{bool parentUsesSize = false}) {
box = PdfRect.fromPoints(PdfPoint.zero, constraints.biggest);
}
PdfPoint tochart(PdfPoint p);
PdfPoint toChart(PdfPoint p);
}
@immutable
... ... @@ -102,7 +139,31 @@ abstract class ChartValue {
const ChartValue();
}
abstract class DataSet {
void paintBackground(Context context, ChartGrid grid);
void paintForeground(Context context, ChartGrid grid);
abstract class Dataset extends Widget {
Dataset({
this.legend,
this.color,
});
final String legend;
final PdfColor color;
void paintBackground(Context context) {}
Widget legendeShape() {
return Container(
decoration: BoxDecoration(
color: color,
border: const BoxBorder(
left: true,
top: true,
bottom: true,
right: true,
color: PdfColors.black,
width: .5,
),
),
);
}
}
... ...
/*
* 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.
*/
// ignore_for_file: omit_local_variable_types
part of widget;
typedef GridAxisFormat = String Function(num value);
abstract class GridAxis extends Widget {
GridAxis({
GridAxisFormat format,
this.textStyle,
this.margin,
double marginStart,
double marginEnd,
PdfColor color,
double width,
bool divisions,
double divisionsWidth,
PdfColor divisionsColor,
bool divisionsDashed,
bool ticks,
bool axisTick,
}) : format = format ?? _defaultFormat,
color = color ?? PdfColors.black,
width = width ?? 1,
divisions = divisions ?? false,
divisionsWidth = divisionsWidth ?? .5,
divisionsColor = divisionsColor ?? PdfColors.grey,
_marginStart = marginStart ?? 0,
_marginEnd = marginEnd ?? 0,
ticks = ticks ?? false,
_axisTick = axisTick,
divisionsDashed = divisionsDashed ?? false;
Axis direction;
final GridAxisFormat format;
final TextStyle textStyle;
final double margin;
double _crossAxisPosition = 0;
double _textMargin;
final double _marginStart;
double _marginEnd;
final PdfColor color;
final double width;
final bool divisions;
final double divisionsWidth;
final PdfColor divisionsColor;
final bool divisionsDashed;
final bool ticks;
bool _axisTick;
double axisPosition = 0;
static String _defaultFormat(num v) => v.toString();
double transfer(num input) {
return input.toDouble();
}
double toChart(num input);
void paintBackground(Context context);
}
class FixedAxis<T extends num> extends GridAxis {
FixedAxis(
this.values, {
GridAxisFormat format,
TextStyle textStyle,
double margin,
double marginStart,
double marginEnd,
PdfColor color,
double width,
bool divisions,
double divisionsWidth,
PdfColor divisionsColor,
bool divisionsDashed,
bool ticks,
bool axisTick,
}) : assert(_isSortedAscending(values)),
super(
format: format,
textStyle: textStyle,
margin: margin,
marginStart: marginStart,
marginEnd: marginEnd,
color: color,
width: width,
divisions: divisions,
divisionsWidth: divisionsWidth,
divisionsColor: divisionsColor,
divisionsDashed: divisionsDashed,
ticks: ticks,
axisTick: axisTick,
);
static FixedAxis<int> fromStrings(
List<String> values, {
TextStyle textStyle,
double margin,
double marginStart,
double marginEnd,
PdfColor color,
double width,
bool divisions,
double divisionsWidth,
PdfColor divisionsColor,
bool divisionsDashed,
bool ticks,
bool axisTick,
}) {
return FixedAxis<int>(
List<int>.generate(values.length, (int index) => index),
format: (num v) => values[v],
textStyle: textStyle,
margin: margin,
marginStart: marginStart,
marginEnd: marginEnd,
color: color,
width: width,
divisions: divisions,
divisionsWidth: divisionsWidth,
divisionsColor: divisionsColor,
divisionsDashed: divisionsDashed,
ticks: ticks,
axisTick: axisTick,
);
}
final List<T> values;
static bool _isSortedAscending(List<num> list) {
num prev = list.first;
for (final num elem in list) {
if (prev > elem) {
return false;
}
prev = elem;
}
return true;
}
@override
double toChart(num input) {
final double offset = transfer(values.first);
final double total = transfer(values.last) - offset;
final double start = _crossAxisPosition + _marginStart;
switch (direction) {
case Axis.horizontal:
return box.left +
start +
(box.width - start - _marginEnd) *
(transfer(input) - offset) /
total;
case Axis.vertical:
return box.bottom +
start +
(box.height - start - _marginEnd) *
(transfer(input) - offset) /
total;
}
return null;
}
@override
void layout(Context context, BoxConstraints constraints,
{bool parentUsesSize = false}) {
assert(Chart.of(context) != null,
'$runtimeType cannot be used without a Chart widget');
final PdfPoint size = constraints.biggest;
final TextStyle style = Theme.of(context).defaultTextStyle.merge(textStyle);
final PdfFont font = style.font.getFont(context);
double maxWidth = 0;
double maxHeight = 0;
PdfFontMetrics metricsFirst;
PdfFontMetrics metrics;
for (final T value in values) {
metrics = font.stringMetrics(format(value)) * style.fontSize;
metricsFirst ??= metrics;
maxWidth = math.max(maxWidth, metrics.maxWidth);
maxHeight = math.max(maxHeight, metrics.maxHeight);
}
switch (direction) {
case Axis.horizontal:
_textMargin = margin ?? 2;
_axisTick ??= false;
final double minStart = metricsFirst.maxWidth / 2;
_marginEnd = math.max(_marginEnd, metrics.maxWidth / 2);
_crossAxisPosition = math.max(_crossAxisPosition, minStart);
axisPosition = math.max(axisPosition, maxHeight + _textMargin);
box = PdfRect(0, 0, size.x, axisPosition);
break;
case Axis.vertical:
_textMargin = margin ?? 10;
_axisTick ??= true;
_marginEnd = math.max(_marginEnd, metrics.maxHeight / 2);
final double minStart = metricsFirst.maxHeight / 2;
_marginEnd = math.max(_marginEnd, metrics.maxWidth / 2);
_crossAxisPosition = math.max(_crossAxisPosition, minStart);
axisPosition = math.max(axisPosition, maxWidth + _textMargin);
box = PdfRect(0, 0, axisPosition, size.y);
break;
}
}
void _drawYValues(Context context) {
context.canvas
..moveTo(axisPosition, box.top)
..lineTo(axisPosition, box.bottom + _crossAxisPosition);
if (_axisTick && _textMargin > 0) {
context.canvas
..moveTo(axisPosition, box.bottom + _crossAxisPosition)
..lineTo(
axisPosition - _textMargin / 2, box.bottom + _crossAxisPosition);
}
if (ticks && _textMargin > 0) {
for (final num x in values) {
final double p = toChart(x);
context.canvas
..moveTo(axisPosition, p)
..lineTo(axisPosition - _textMargin / 2, p);
}
}
context.canvas
..setStrokeColor(color)
..setLineWidth(width)
..setLineCap(PdfLineCap.joinBevel)
..strokePath();
for (final T y in values) {
final String v = format(y);
final TextStyle style =
Theme.of(context).defaultTextStyle.merge(textStyle);
final PdfFont font = style.font.getFont(context);
final PdfFontMetrics metrics = font.stringMetrics(v) * style.fontSize;
final double p = toChart(y);
context.canvas
..setColor(style.color)
..drawString(
style.font.getFont(context),
style.fontSize,
v,
axisPosition - _textMargin - metrics.maxWidth,
p - (metrics.ascent + metrics.descent) / 2,
);
}
}
void _drawXValues(Context context) {
context.canvas
..moveTo(box.left + _crossAxisPosition, axisPosition)
..lineTo(box.right, axisPosition);
if (_axisTick && _textMargin > 0) {
context.canvas
..moveTo(box.left + _crossAxisPosition, axisPosition)
..lineTo(box.left + _crossAxisPosition, axisPosition - _textMargin);
}
if (ticks && _textMargin > 0) {
for (final num x in values) {
final double p = toChart(x);
context.canvas
..moveTo(p, axisPosition)
..lineTo(p, axisPosition - _textMargin);
}
}
context.canvas
..setStrokeColor(color)
..setLineWidth(width)
..setLineCap(PdfLineCap.joinBevel)
..strokePath();
for (final num x in values) {
final String v = format(x);
final TextStyle style =
Theme.of(context).defaultTextStyle.merge(textStyle);
final PdfFont font = style.font.getFont(context);
final PdfFontMetrics metrics = font.stringMetrics(v) * style.fontSize;
final double p = toChart(x);
context.canvas
..setColor(style.color)
..drawString(
style.font.getFont(context),
style.fontSize,
v,
p - metrics.maxWidth / 2,
axisPosition - metrics.ascent - _textMargin,
);
}
}
@override
void paintBackground(Context context) {
if (!divisions) {
return;
}
final CartesianGrid grid = Chart.of(context).grid;
switch (direction) {
case Axis.horizontal:
for (final num x in values.sublist(_marginStart > 0 ? 0 : 1)) {
final double p = toChart(x);
context.canvas.drawLine(p, grid.gridBox.top, p, grid.gridBox.bottom);
}
break;
case Axis.vertical:
for (final num y in values.sublist(_marginStart > 0 ? 0 : 1)) {
final double p = toChart(y);
context.canvas.drawLine(grid.gridBox.left, p, grid.gridBox.right, p);
}
break;
}
if (divisionsDashed) {
context.canvas.setLineDashPattern(<int>[4, 2]);
}
context.canvas
..setStrokeColor(divisionsColor)
..setLineWidth(divisionsWidth)
..setLineCap(PdfLineCap.joinMiter)
..strokePath();
if (divisionsDashed) {
context.canvas.setLineDashPattern();
}
}
@override
void debugPaint(Context context) {
switch (direction) {
case Axis.horizontal:
context.canvas
..setFillColor(PdfColors.grey300)
..drawRect(box.x, box.y, box.width, box.height)
..fillPath();
break;
case Axis.vertical:
context.canvas
..setFillColor(PdfColors.grey300)
..drawRect(box.x, box.y + _crossAxisPosition, box.width,
box.height - _crossAxisPosition)
..fillPath();
break;
}
}
@override
void paint(Context context) {
super.paint(context);
switch (direction) {
case Axis.horizontal:
_drawXValues(context);
break;
case Axis.vertical:
_drawYValues(context);
break;
}
}
}
... ...
/*
* 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.
*/
// ignore_for_file: omit_local_variable_types
part of widget;
class CartesianGrid extends ChartGrid {
CartesianGrid({
@required GridAxis xAxis,
@required GridAxis yAxis,
}) : _xAxis = xAxis..direction = Axis.horizontal,
_yAxis = yAxis..direction = Axis.vertical;
final GridAxis _xAxis;
final GridAxis _yAxis;
PdfRect gridBox;
@override
void layout(Context context, BoxConstraints constraints,
{bool parentUsesSize = false}) {
assert(Chart.of(context) != null,
'$runtimeType cannot be used without a Chart widget');
super.layout(context, constraints, parentUsesSize: parentUsesSize);
final List<Dataset> datasets = Chart.of(context).datasets;
final PdfPoint size = constraints.biggest;
// In simple conditions, this loop will run only 2 times.
int count = 5;
while (count-- > 0) {
_xAxis._crossAxisPosition = _yAxis.axisPosition;
_xAxis.axisPosition =
math.max(_xAxis.axisPosition, _yAxis._crossAxisPosition);
_xAxis.layout(context, constraints);
assert(_xAxis.box != null);
_yAxis._crossAxisPosition = _xAxis.axisPosition;
_yAxis.axisPosition =
math.max(_yAxis.axisPosition, _xAxis._crossAxisPosition);
_yAxis.layout(context, constraints);
assert(_yAxis.box != null);
if (_yAxis._crossAxisPosition == _xAxis.axisPosition &&
_xAxis._crossAxisPosition == _yAxis.axisPosition) {
break;
}
}
final double width = _yAxis.axisPosition;
final double height = _xAxis.axisPosition;
gridBox = PdfRect(width, height, size.x - width, size.y - height);
for (final Dataset dataset in datasets) {
dataset.layout(context, BoxConstraints.tight(gridBox.size));
dataset.box =
PdfRect.fromPoints(PdfPoint(width, height), dataset.box.size);
}
}
@override
PdfPoint toChart(PdfPoint p) {
return PdfPoint(
_xAxis.toChart(p.x),
_yAxis.toChart(p.y),
);
}
double get xAxisOffset => _xAxis.axisPosition;
double get yAxisOffset => _yAxis.axisPosition;
void paintBackground(Context context) {
_xAxis.paintBackground(context);
_yAxis.paintBackground(context);
}
void clip(Context context, PdfPoint size) {
context.canvas
..saveContext()
..drawRect(
gridBox.left,
gridBox.bottom,
gridBox.width,
gridBox.height,
)
..clipPath();
}
@override
void paint(Context context) {
super.paint(context);
final List<Dataset> datasets = Chart.of(context).datasets;
clip(context, box.size);
for (Dataset dataSet in datasets) {
dataSet.paintBackground(context);
}
context.canvas.restoreContext();
paintBackground(context);
clip(context, box.size);
for (Dataset dataSet in datasets) {
dataSet.paint(context);
}
context.canvas.restoreContext();
_xAxis.paint(context);
_yAxis.paint(context);
}
}
... ...
/*
* 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.
*/
// ignore_for_file: omit_local_variable_types
part of widget;
class ChartLegend extends StatelessWidget {
ChartLegend({
this.textStyle,
this.position = Alignment.topRight,
this.direction = Axis.vertical,
this.decoration,
this.padding = const EdgeInsets.all(5),
}) : assert(position != null);
final TextStyle textStyle;
final Alignment position;
final Axis direction;
final BoxDecoration decoration;
final EdgeInsets padding;
Widget _buildLegend(Context context, Dataset dataset) {
final TextStyle style = Theme.of(context).defaultTextStyle.merge(textStyle);
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
width: style.fontSize,
height: style.fontSize,
margin: const EdgeInsets.only(right: 5),
child: dataset.legendeShape(),
),
Text(
dataset.legend,
style: textStyle,
)
],
);
}
@override
Widget build(Context context) {
assert(Chart.of(context) != null,
'$runtimeType cannot be used without a Chart widget');
final List<Dataset> datasets = Chart.of(context).datasets;
final Widget wrap = Wrap(
direction: direction,
spacing: 10,
runSpacing: 10,
children: <Widget>[
for (final Dataset dataset in datasets)
if (dataset.legend != null) _buildLegend(context, dataset)
],
);
return Align(
alignment: position,
child: Container(
decoration: decoration ?? const BoxDecoration(color: PdfColors.white),
padding: padding,
child: wrap,
),
);
}
}
... ...
... ... @@ -26,12 +26,13 @@ class LineChartValue extends ChartValue {
PdfPoint get point => PdfPoint(x, y);
}
class LineDataSet extends DataSet {
class LineDataSet extends Dataset {
LineDataSet({
@required this.data,
String legend,
this.pointColor,
this.pointSize = 3,
this.color = PdfColors.blue,
PdfColor color = PdfColors.blue,
this.lineWidth = 2,
this.drawLine = true,
this.drawPoints = true,
... ... @@ -40,12 +41,16 @@ class LineDataSet extends DataSet {
this.surfaceColor,
this.isCurved = false,
this.smoothness = 0.35,
}) : assert(drawLine || drawPoints || drawSurface);
}) : assert(drawLine || drawPoints || drawSurface),
super(
legend: legend,
color: color,
);
final List<LineChartValue> data;
final bool drawLine;
final PdfColor color;
final double lineWidth;
final bool drawPoints;
... ... @@ -59,6 +64,12 @@ class LineDataSet extends DataSet {
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;
... ... @@ -66,7 +77,7 @@ class LineDataSet extends DataSet {
PdfPoint t = const PdfPoint(0, 0);
final PdfPoint p = grid.tochart(data.first.point);
final PdfPoint p = grid.toChart(data.first.point);
if (moveTo) {
context.canvas.moveTo(p.x, p.y);
} else {
... ... @@ -74,16 +85,16 @@ class LineDataSet extends DataSet {
}
for (int i = 1; i < data.length; i++) {
final PdfPoint p = grid.tochart(data[i].point);
final PdfPoint p = grid.toChart(data[i].point);
if (!isCurved) {
context.canvas.lineTo(p.x, p.y);
continue;
}
final PdfPoint pp = grid.tochart(data[i - 1].point);
final PdfPoint pp = grid.toChart(data[i - 1].point);
final PdfPoint pn =
grid.tochart(data[i + 1 < data.length ? i + 1 : i].point);
grid.toChart(data[i + 1 < data.length ? i + 1 : i].point);
final PdfPoint c1 = PdfPoint(pp.x + t.x, pp.y + t.y);
... ... @@ -101,28 +112,30 @@ class LineDataSet extends DataSet {
return;
}
final double y = (grid is LinearGrid) ? grid.xAxisOffset : 0;
final double y = (grid is CartesianGrid) ? grid.xAxisOffset : 0;
_drawLine(context, grid, true);
final PdfPoint pe = grid.tochart(data.last.point);
final PdfPoint pe = grid.toChart(data.last.point);
context.canvas.lineTo(pe.x, y);
final PdfPoint pf = grid.tochart(data.first.point);
final PdfPoint pf = grid.toChart(data.first.point);
context.canvas.lineTo(pf.x, y);
}
void _drawPoints(Context context, ChartGrid grid) {
for (final LineChartValue value in data) {
final PdfPoint p = grid.tochart(value.point);
final PdfPoint p = grid.toChart(value.point);
context.canvas.drawEllipse(p.x, p.y, pointSize, pointSize);
}
}
@override
void paintBackground(Context context, ChartGrid grid) {
void paintBackground(Context context) {
if (data.isEmpty) {
return;
}
final ChartGrid grid = Chart.of(context).grid;
if (drawSurface) {
_drawSurface(context, grid);
... ... @@ -145,11 +158,15 @@ class LineDataSet extends DataSet {
}
@override
void paintForeground(Context context, ChartGrid grid) {
void paint(Context context) {
super.paint(context);
if (data.isEmpty) {
return;
}
final ChartGrid grid = Chart.of(context).grid;
if (drawLine) {
_drawLine(context, grid, true);
... ...
/*
* 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.
*/
// ignore_for_file: omit_local_variable_types
part of widget;
typedef GridAxisFormat = String Function(double value);
class LinearGrid extends ChartGrid {
LinearGrid({
@required this.xAxis,
@required this.yAxis,
this.xMargin = 10,
this.yMargin = 2,
this.textStyle,
this.lineWidth = 1,
this.color = PdfColors.black,
this.separatorLineWidth = .5,
this.drawXDivisions = false,
this.drawYDivisions = true,
this.separatorColor = PdfColors.grey,
this.xAxisFormat = _defaultFormat,
this.yAxisFormat = _defaultFormat,
}) : assert(_isSortedAscending(xAxis)),
assert(_isSortedAscending(yAxis));
final List<double> xAxis;
final List<double> yAxis;
final double xMargin;
final double yMargin;
final TextStyle textStyle;
final double lineWidth;
final PdfColor color;
final double separatorLineWidth;
final PdfColor separatorColor;
final bool drawXDivisions;
final bool drawYDivisions;
final GridAxisFormat xAxisFormat;
final GridAxisFormat yAxisFormat;
TextStyle style;
PdfFont font;
PdfRect gridBox;
double xOffset;
double xTotal;
double yOffset;
double yTotal;
static String _defaultFormat(double v) => v.toString();
static bool _isSortedAscending(List<double> list) {
double prev = list.first;
for (final double elem in list) {
if (prev > elem) {
return false;
}
prev = elem;
}
return true;
}
@override
void layout(Context context, PdfPoint size) {
style = Theme.of(context).defaultTextStyle.merge(textStyle);
font = style.font.getFont(context);
double xMaxWidth = 0;
double xMaxHeight = 0;
for (final double value in xAxis) {
final PdfFontMetrics metrics =
font.stringMetrics(xAxisFormat(value)) * style.fontSize;
xMaxWidth = math.max(xMaxWidth, metrics.width);
xMaxHeight = math.max(xMaxHeight, metrics.maxHeight);
}
double yMaxWidth = 0;
double yMaxHeight = 0;
for (final double value in yAxis) {
final PdfFontMetrics metrics =
font.stringMetrics(yAxisFormat(value)) * style.fontSize;
yMaxWidth = math.max(yMaxWidth, metrics.width);
yMaxHeight = math.max(yMaxHeight, metrics.maxHeight);
}
gridBox = PdfRect.fromLTRB(
yMaxWidth + xMargin,
xMaxHeight + yMargin,
size.x - xMaxWidth / 2,
size.y - yMaxHeight / 2,
);
xOffset = xAxis.reduce(math.min);
yOffset = yAxis.reduce(math.min);
xTotal = xAxis.reduce(math.max) - xOffset;
yTotal = yAxis.reduce(math.max) - yOffset;
}
@override
PdfPoint tochart(PdfPoint p) {
return PdfPoint(
gridBox.left + gridBox.width * (p.x - xOffset) / xTotal,
gridBox.bottom + gridBox.height * (p.y - yOffset) / yTotal,
);
}
double get xAxisOffset => gridBox.bottom;
double get yAxisOffset => gridBox.left;
void _drawAxis(Context context, PdfPoint size) {
context.canvas
..moveTo(size.x, gridBox.bottom)
..lineTo(gridBox.left - xMargin / 2, gridBox.bottom)
..moveTo(gridBox.left, gridBox.bottom)
..lineTo(gridBox.left, size.y)
..setStrokeColor(color)
..setLineWidth(lineWidth)
..setLineCap(PdfLineCap.joinMiter)
..strokePath();
}
void _drawYDivisions(Context context, PdfPoint size) {
for (final double y in yAxis.sublist(1)) {
final PdfPoint p = tochart(PdfPoint(0, y));
context.canvas.drawLine(
gridBox.left,
p.y,
size.x,
p.y,
);
}
context.canvas
..setStrokeColor(separatorColor)
..setLineWidth(separatorLineWidth)
..setLineCap(PdfLineCap.joinMiter)
..strokePath();
}
void _drawXDivisions(Context context, PdfPoint size) {
for (final double x in xAxis.sublist(1)) {
final PdfPoint p = tochart(PdfPoint(x, 0));
context.canvas.drawLine(
p.x,
size.y,
p.x,
gridBox.bottom,
);
}
context.canvas
..setStrokeColor(separatorColor)
..setLineWidth(separatorLineWidth)
..setLineCap(PdfLineCap.joinMiter)
..strokePath();
}
void _drawYValues(Context context, PdfPoint size) {
for (final double y in yAxis) {
final String v = yAxisFormat(y);
final PdfFontMetrics metrics = font.stringMetrics(v) * style.fontSize;
final PdfPoint p = tochart(PdfPoint(0, y));
context.canvas
..setColor(style.color)
..drawString(
style.font.getFont(context),
style.fontSize,
v,
gridBox.left - xMargin - metrics.width,
p.y - (metrics.ascent + metrics.descent) / 2,
);
}
}
void _drawXValues(Context context, PdfPoint size) {
for (final double x in xAxis) {
final String v = xAxisFormat(x);
final PdfFontMetrics metrics = font.stringMetrics(v) * style.fontSize;
final PdfPoint p = tochart(PdfPoint(x, 0));
context.canvas
..setColor(style.color)
..drawString(
style.font.getFont(context),
style.fontSize,
v,
p.x - metrics.width / 2,
-metrics.descent,
);
}
}
@override
void paintBackground(Context context, PdfPoint size) {}
@override
void paint(Context context, PdfPoint size) {
if (drawXDivisions) {
_drawXDivisions(context, size);
}
if (drawYDivisions) {
_drawYDivisions(context, size);
}
}
@override
void paintForeground(Context context, PdfPoint size) {
_drawAxis(context, size);
_drawXValues(context, size);
_drawYValues(context, size);
}
@override
void clip(Context context, PdfPoint size) {
context.canvas
..saveContext()
..drawRect(
gridBox.left,
gridBox.bottom,
size.x - gridBox.left,
size.y - gridBox.bottom,
)
..clipPath();
}
@override
void unClip(Context context, PdfPoint size) {
context.canvas.restoreContext();
}
}
... ... @@ -35,11 +35,11 @@ void main() {
pdf.addPage(Page(
pageFormat: PdfPageFormat.standard.landscape,
build: (Context context) => Chart(
grid: LinearGrid(
xAxis: <double>[0, 1, 2, 3, 4, 5, 6],
yAxis: <double>[0, 3, 6, 9],
grid: CartesianGrid(
xAxis: FixedAxis<int>(<int>[0, 1, 2, 3, 4, 5, 6]),
yAxis: FixedAxis<int>(<int>[0, 3, 6, 9], divisions: true),
),
data: <DataSet>[
datasets: <Dataset>[
LineDataSet(
data: const <LineChartValue>[
LineChartValue(1, 1),
... ... @@ -56,11 +56,11 @@ void main() {
pdf.addPage(Page(
pageFormat: PdfPageFormat.standard.landscape,
build: (Context context) => Chart(
grid: LinearGrid(
xAxis: <double>[0, 1, 2, 3, 4, 5, 6],
yAxis: <double>[0, 3, 6, 9],
grid: CartesianGrid(
xAxis: FixedAxis<int>(<int>[0, 1, 2, 3, 4, 5, 6]),
yAxis: FixedAxis<int>(<int>[0, 3, 6, 9], divisions: true),
),
data: <DataSet>[
datasets: <Dataset>[
LineDataSet(
data: const <LineChartValue>[
LineChartValue(1, 1),
... ... @@ -77,11 +77,11 @@ void main() {
test('Default ScatterChart without dots', () {
pdf.addPage(Page(
build: (Context context) => Chart(
grid: LinearGrid(
xAxis: <double>[0, 1, 2, 3, 4, 5, 6],
yAxis: <double>[0, 3, 6, 9],
grid: CartesianGrid(
xAxis: FixedAxis<int>(<int>[0, 1, 2, 3, 4, 5, 6]),
yAxis: FixedAxis<int>(<int>[0, 3, 6, 9], divisions: true),
),
data: <DataSet>[
datasets: <Dataset>[
LineDataSet(
data: const <LineChartValue>[
LineChartValue(1, 1),
... ... @@ -99,11 +99,11 @@ void main() {
pdf.addPage(Page(
pageFormat: PdfPageFormat.standard.landscape,
build: (Context context) => Chart(
grid: LinearGrid(
xAxis: <double>[0, 1, 2, 3, 4, 5, 6],
yAxis: <double>[0, 3, 6, 9],
grid: CartesianGrid(
xAxis: FixedAxis<int>(<int>[0, 1, 2, 3, 4, 5, 6]),
yAxis: FixedAxis<int>(<int>[0, 3, 6, 9], divisions: true),
),
data: <DataSet>[
datasets: <Dataset>[
LineDataSet(
data: const <LineChartValue>[
LineChartValue(1, 1),
... ... @@ -128,11 +128,11 @@ void main() {
width: 200,
height: 100,
child: Chart(
grid: LinearGrid(
xAxis: <double>[0, 1, 2, 3, 4, 5, 6],
yAxis: <double>[0, 3, 6, 9],
grid: CartesianGrid(
xAxis: FixedAxis<int>(<int>[0, 1, 2, 3, 4, 5, 6]),
yAxis: FixedAxis<int>(<int>[0, 3, 6, 9], divisions: true),
),
data: <DataSet>[
datasets: <Dataset>[
LineDataSet(
data: const <LineChartValue>[
LineChartValue(1, 1),
... ... @@ -150,11 +150,11 @@ void main() {
pdf.addPage(Page(
pageFormat: PdfPageFormat.standard.landscape,
build: (Context context) => Chart(
grid: LinearGrid(
xAxis: <double>[0, 1, 2, 3, 4, 5, 6],
yAxis: <double>[0, 3, 6, 9],
grid: CartesianGrid(
xAxis: FixedAxis<int>(<int>[0, 1, 2, 3, 4, 5, 6]),
yAxis: FixedAxis<int>(<int>[0, 3, 6, 9], divisions: true),
),
data: <DataSet>[
datasets: <Dataset>[
LineDataSet(
drawPoints: false,
isCurved: true,
... ...