David PHAM-VAN

Add curved LineDataSet Chart

... ... @@ -3,6 +3,7 @@
## 1.8.0
- Improve Table.fromTextArray()
- Add curved LineDataSet Chart
## 1.7.1
... ...
... ... @@ -30,6 +30,9 @@ export 'package:barcode/barcode.dart';
part 'widgets/annotations.dart';
part 'widgets/barcode.dart';
part 'widgets/basic.dart';
part 'widgets/chart/chart.dart';
part 'widgets/chart/linear_grid.dart';
part 'widgets/chart/line_chart.dart';
part 'widgets/clip.dart';
part 'widgets/container.dart';
part 'widgets/content.dart';
... ... @@ -54,4 +57,3 @@ part 'widgets/text_style.dart';
part 'widgets/theme.dart';
part 'widgets/widget.dart';
part 'widgets/wrap.dart';
part 'widgets/chart.dart';
... ...
/*
* 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 Chart extends Widget {
Chart({
@required this.grid,
@required this.data,
});
final ChartGrid grid;
final List<DataSet> data;
PdfPoint _computeSize(BoxConstraints constraints) {
if (constraints.isTight) {
return constraints.smallest;
}
double width = constraints.maxWidth;
double height = constraints.maxHeight;
const double aspectRatio = 1;
if (!width.isFinite) {
width = height * aspectRatio;
}
if (!height.isFinite) {
height = width * aspectRatio;
}
return constraints.constrain(PdfPoint(width, height));
}
@override
void layout(Context context, BoxConstraints constraints,
{bool parentUsesSize = false}) {
box = PdfRect.fromPoints(PdfPoint.zero, _computeSize(constraints));
grid.layout(context, box.size);
}
@override
void paint(Context context) {
super.paint(context);
final Matrix4 mat = Matrix4.identity();
mat.translate(box.x, box.y);
context.canvas
..saveContext()
..setTransform(mat);
grid.paintBackground(context, box.size);
for (DataSet dataSet in data) {
dataSet.paintBackground(context, grid);
}
for (DataSet dataSet in data) {
dataSet.paintForeground(context, grid);
}
grid.paintForeground(context, box.size);
context.canvas.restoreContext();
}
}
abstract class ChartGrid {
void layout(Context context, PdfPoint size);
void paintBackground(Context context, PdfPoint size);
void paintForeground(Context context, PdfPoint size);
PdfPoint tochart(PdfPoint p);
}
@immutable
abstract class ChartValue {
const ChartValue();
}
abstract class DataSet {
void paintBackground(Context context, ChartGrid grid);
void paintForeground(Context context, ChartGrid grid);
}
... ...
/*
* 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 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,
this.pointColor,
this.pointSize = 3,
this.color = PdfColors.blue,
this.lineWidth = 2,
this.drawLine = true,
this.drawPoints = true,
this.isCurved = false,
this.smoothness = 0.35,
}) : assert(drawLine || drawPoints);
final List<LineChartValue> data;
final PdfColor pointColor;
final double pointSize;
final PdfColor color;
final double lineWidth;
final bool drawLine;
final bool drawPoints;
final bool isCurved;
final double smoothness;
void _drawLine(Context context, ChartGrid grid) {
if (data.length < 2) {
return;
}
PdfPoint t = const PdfPoint(0, 0);
final PdfPoint p = grid.tochart(data.first.point);
context.canvas.moveTo(p.x, p.y);
for (int i = 1; i < data.length; i++) {
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 pn =
grid.tochart(data[i + 1 < data.length ? i + 1 : i].point);
final PdfPoint 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 PdfPoint 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 _drawPoints(Context context, ChartGrid grid) {
for (final LineChartValue value in data) {
final PdfPoint p = grid.tochart(value.point);
context.canvas.drawEllipse(p.x, p.y, pointSize, pointSize);
}
}
@override
void paintBackground(Context context, ChartGrid grid) {}
@override
void paintForeground(Context context, ChartGrid grid) {
if (data.isEmpty) {
return;
}
if (drawLine) {
_drawLine(context, grid);
context.canvas
..setStrokeColor(color)
..setLineWidth(lineWidth)
..setLineCap(PdfLineCap.joinRound)
..setLineJoin(PdfLineCap.joinRound)
..strokePath();
}
if (drawPoints) {
_drawPoints(context, grid);
context.canvas
..setColor(pointColor ?? color)
..fillPath();
}
}
}
... ...
... ... @@ -18,86 +18,6 @@
part of widget;
bool _isSortedAscending(List<double> list) {
double prev = list.first;
for (double elem in list) {
if (prev > elem) {
return false;
}
prev = elem;
}
return true;
}
class Chart extends Widget {
Chart({
@required this.grid,
@required this.data,
});
final ChartGrid grid;
final List<DataSet> data;
PdfPoint _computeSize(BoxConstraints constraints) {
if (constraints.isTight) {
return constraints.smallest;
}
double width = constraints.maxWidth;
double height = constraints.maxHeight;
const double aspectRatio = 1;
if (!width.isFinite) {
width = height * aspectRatio;
}
if (!height.isFinite) {
height = width * aspectRatio;
}
return constraints.constrain(PdfPoint(width, height));
}
@override
void layout(Context context, BoxConstraints constraints,
{bool parentUsesSize = false}) {
box = PdfRect.fromPoints(PdfPoint.zero, _computeSize(constraints));
grid.layout(context, box.size);
}
@override
void paint(Context context) {
super.paint(context);
final Matrix4 mat = Matrix4.identity();
mat.translate(box.x, box.y);
context.canvas
..saveContext()
..setTransform(mat);
grid.paintBackground(context, box.size);
for (DataSet dataSet in data) {
dataSet.paintBackground(context, grid);
}
for (DataSet dataSet in data) {
dataSet.paintForeground(context, grid);
}
grid.paintForeground(context, box.size);
context.canvas.restoreContext();
}
}
abstract class ChartGrid {
void layout(Context context, PdfPoint size);
void paintBackground(Context context, PdfPoint size);
void paintForeground(Context context, PdfPoint size);
PdfPoint tochart(PdfPoint p);
}
class LinearGrid extends ChartGrid {
LinearGrid({
@required this.xAxis,
... ... @@ -107,7 +27,7 @@ class LinearGrid extends ChartGrid {
this.textStyle,
this.lineWidth = 1,
this.color = PdfColors.black,
this.separatorLineWidth = 1,
this.separatorLineWidth = .5,
this.separatorColor = PdfColors.grey,
}) : assert(_isSortedAscending(xAxis)),
assert(_isSortedAscending(yAxis));
... ... @@ -132,6 +52,17 @@ class LinearGrid extends ChartGrid {
double yOffset;
double yTotal;
static bool _isSortedAscending(List<double> list) {
double prev = list.first;
for (double elem in list) {
if (prev > elem) {
return false;
}
prev = elem;
}
return true;
}
@override
PdfPoint tochart(PdfPoint p) {
return PdfPoint(
... ... @@ -216,78 +147,3 @@ class LinearGrid extends ChartGrid {
@override
void paintForeground(Context context, PdfPoint size) {}
}
@immutable
abstract class ChartValue {
const ChartValue();
}
class LineChartValue extends ChartValue {
const LineChartValue(this.x, this.y);
final double x;
final double y;
PdfPoint get point => PdfPoint(x, y);
}
abstract class DataSet {
void paintBackground(Context context, ChartGrid grid);
void paintForeground(Context context, ChartGrid grid);
}
class LineDataSet extends DataSet {
LineDataSet({
@required this.data,
this.pointColor = PdfColors.blue,
this.pointSize = 3,
this.lineColor = PdfColors.blue,
this.lineWidth = 2,
this.drawLine = true,
this.drawPoints = true,
}) : assert(drawLine || drawPoints);
final List<LineChartValue> data;
final PdfColor pointColor;
final double pointSize;
final PdfColor lineColor;
final double lineWidth;
final bool drawLine;
final bool drawPoints;
double maxValue;
@override
void paintBackground(Context context, ChartGrid grid) {
if (drawLine) {
LineChartValue lastValue;
for (LineChartValue value in data) {
if (lastValue != null) {
final PdfPoint p1 = grid.tochart(lastValue.point);
final PdfPoint p2 = grid.tochart(value.point);
context.canvas.drawLine(p1.x, p1.y, p2.x, p2.y);
}
lastValue = value;
}
context.canvas
..setStrokeColor(lineColor)
..setLineWidth(lineWidth)
..setLineCap(PdfLineCap.joinRound)
..setLineJoin(PdfLineCap.joinRound)
..strokePath();
}
if (drawPoints) {
for (LineChartValue value in data) {
final PdfPoint p = grid.tochart(value.point);
context.canvas
..setColor(pointColor)
..drawEllipse(p.x, p.y, pointSize, pointSize)
..fillPath();
}
}
}
@override
void paintForeground(Context context, ChartGrid grid) {}
}
... ...
... ... @@ -33,6 +33,7 @@ void main() {
group('LineChart test', () {
test('Default LineChart', () {
pdf.addPage(Page(
pageFormat: PdfPageFormat.standard.landscape,
build: (Context context) => Chart(
grid: LinearGrid(
xAxis: <double>[0, 1, 2, 3, 4, 5, 6],
... ... @@ -53,6 +54,7 @@ void main() {
test('Default LineChart without lines connecting points', () {
pdf.addPage(Page(
pageFormat: PdfPageFormat.standard.landscape,
build: (Context context) => Chart(
grid: LinearGrid(
xAxis: <double>[0, 1, 2, 3, 4, 5, 6],
... ... @@ -95,6 +97,7 @@ void main() {
test('ScatterChart with custom points and lines', () {
pdf.addPage(Page(
pageFormat: PdfPageFormat.standard.landscape,
build: (Context context) => Chart(
grid: LinearGrid(
xAxis: <double>[0, 1, 2, 3, 4, 5, 6],
... ... @@ -110,7 +113,7 @@ void main() {
drawLine: false,
pointColor: PdfColors.red,
pointSize: 4,
lineColor: PdfColors.purple,
color: PdfColors.purple,
lineWidth: 4,
),
],
... ... @@ -120,6 +123,7 @@ void main() {
test('ScatterChart with custom size', () {
pdf.addPage(Page(
pageFormat: PdfPageFormat.standard.landscape,
build: (Context context) => SizedBox(
width: 200,
height: 100,
... ... @@ -141,6 +145,29 @@ void main() {
),
));
});
test('LineChart with curved lines', () {
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],
),
data: <DataSet>[
LineDataSet(
drawPoints: false,
isCurved: true,
data: const <LineChartValue>[
LineChartValue(1, 1),
LineChartValue(3, 7),
LineChartValue(5, 3),
],
),
],
),
));
});
});
tearDownAll(() {
... ...