David PHAM-VAN

Added surface fill and Bar Charts

... ... @@ -33,6 +33,7 @@ part 'widgets/basic.dart';
part 'widgets/chart/chart.dart';
part 'widgets/chart/linear_grid.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';
... ...
/*
* 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 BarDataSet extends DataSet {
BarDataSet({
@required this.data,
this.borderColor,
this.borderWidth = 1.5,
this.color = PdfColors.blue,
this.drawBorder = true,
this.drawSurface = true,
this.surfaceOpacity = 1,
this.width = 20,
this.offset = 0,
this.margin = 5,
}) : assert(drawBorder || drawSurface);
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;
void _drawSurface(Context context, ChartGrid grid, LineChartValue value) {
final double y = (grid is LinearGrid) ? 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) {}
@override
void paintForeground(Context context, ChartGrid grid) {
if (data.isEmpty) {
return;
}
if (data.isEmpty) {
return;
}
if (drawSurface) {
for (final LineChartValue value in data) {
_drawSurface(context, grid, value);
}
if (surfaceOpacity != 1) {
context.canvas
..saveContext()
..setGraphicState(
PdfGraphicState(opacity: surfaceOpacity),
);
}
context.canvas
..setFillColor(color)
..fillPath();
if (surfaceOpacity != 1) {
context.canvas.restoreContext();
}
}
if (drawBorder) {
for (final LineChartValue value in data) {
_drawSurface(context, grid, value);
}
context.canvas
..setStrokeColor(borderColor ?? color)
..setLineWidth(borderWidth)
..strokePath();
}
}
}
... ...
... ... @@ -18,6 +18,7 @@
part of widget;
/// This widget is in preview and the API is subject to change
class Chart extends Widget {
Chart({
@required this.grid,
... ... @@ -68,12 +69,17 @@ class Chart extends Widget {
..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();
}
... ... @@ -82,8 +88,12 @@ class Chart extends Widget {
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);
PdfPoint tochart(PdfPoint p);
}
... ...
... ... @@ -35,21 +35,31 @@ class LineDataSet extends DataSet {
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);
}) : assert(drawLine || drawPoints || drawSurface);
final List<LineChartValue> data;
final PdfColor pointColor;
final double pointSize;
final bool drawLine;
final PdfColor color;
final double lineWidth;
final bool drawLine;
final bool drawPoints;
final PdfColor pointColor;
final double pointSize;
final bool drawSurface;
final PdfColor surfaceColor;
final double surfaceOpacity;
final bool isCurved;
final double smoothness;
void _drawLine(Context context, ChartGrid grid) {
void _drawLine(Context context, ChartGrid grid, bool moveTo) {
if (data.length < 2) {
return;
}
... ... @@ -57,7 +67,11 @@ class LineDataSet extends DataSet {
PdfPoint t = const PdfPoint(0, 0);
final PdfPoint p = grid.tochart(data.first.point);
if (moveTo) {
context.canvas.moveTo(p.x, p.y);
} else {
context.canvas.lineTo(p.x, p.y);
}
for (int i = 1; i < data.length; i++) {
final PdfPoint p = grid.tochart(data[i].point);
... ... @@ -82,6 +96,20 @@ class LineDataSet extends DataSet {
}
}
void _drawSurface(Context context, ChartGrid grid) {
if (data.length < 2) {
return;
}
final double y = (grid is LinearGrid) ? grid.xAxisOffset : 0;
_drawLine(context, grid, true);
final PdfPoint pe = grid.tochart(data.last.point);
context.canvas.lineTo(pe.x, y);
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);
... ... @@ -90,7 +118,31 @@ class LineDataSet extends DataSet {
}
@override
void paintBackground(Context context, ChartGrid grid) {}
void paintBackground(Context context, ChartGrid grid) {
if (data.isEmpty) {
return;
}
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 paintForeground(Context context, ChartGrid grid) {
... ... @@ -99,7 +151,7 @@ class LineDataSet extends DataSet {
}
if (drawLine) {
_drawLine(context, grid);
_drawLine(context, grid, true);
context.canvas
..setStrokeColor(color)
... ...
... ... @@ -18,6 +18,8 @@
part of widget;
typedef GridAxisFormat = String Function(double value);
class LinearGrid extends ChartGrid {
LinearGrid({
@required this.xAxis,
... ... @@ -28,7 +30,11 @@ class LinearGrid extends ChartGrid {
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));
... ... @@ -41,20 +47,24 @@ class LinearGrid extends ChartGrid {
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;
PdfFontMetrics xAxisFontMetric;
PdfFontMetrics yAxisFontMetric;
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 (double elem in list) {
for (final double elem in list) {
if (prev > elem) {
return false;
}
... ... @@ -64,30 +74,34 @@ class LinearGrid extends ChartGrid {
}
@override
PdfPoint tochart(PdfPoint p) {
return PdfPoint(
gridBox.left + gridBox.width * (p.x - xOffset) / xTotal,
gridBox.bottom + gridBox.height * (p.y - yOffset) / yTotal,
);
}
@override
void layout(Context context, PdfPoint size) {
style = Theme.of(context).defaultTextStyle.merge(textStyle);
font = style.font.getFont(context);
xAxisFontMetric =
font.stringMetrics(xAxis.reduce(math.max).toStringAsFixed(1)) *
(style.fontSize);
yAxisFontMetric =
font.stringMetrics(yAxis.reduce(math.max).toStringAsFixed(1)) *
(style.fontSize);
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(
yAxisFontMetric.width + xMargin,
xAxisFontMetric.height + yMargin,
size.x - xAxisFontMetric.width / 2,
size.y - yAxisFontMetric.height / 2);
yMaxWidth + xMargin,
xMaxHeight + yMargin,
size.x - xMaxWidth / 2,
size.y - yMaxHeight / 2,
);
xOffset = xAxis.reduce(math.min);
yOffset = yAxis.reduce(math.min);
... ... @@ -96,54 +110,136 @@ class LinearGrid extends ChartGrid {
}
@override
void paintBackground(Context context, PdfPoint size) {
xAxis.asMap().forEach((int i, double x) {
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,
x.toStringAsFixed(1),
gridBox.left +
gridBox.width * i / (xAxis.length - 1) -
xAxisFontMetric.width / 2,
0,
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));
for (double y in yAxis.where((double y) => y != yAxis.first)) {
final double textWidth =
(font.stringMetrics(y.toStringAsFixed(1)) * (style.fontSize)).width;
final double yPos = gridBox.bottom + gridBox.height * y / yAxis.last;
context.canvas
..setColor(style.color)
..drawString(
style.font.getFont(context),
style.fontSize,
y.toStringAsFixed(1),
xAxisFontMetric.width / 2 - textWidth / 2,
yPos - font.ascent,
v,
p.x - metrics.width / 2,
-metrics.descent,
);
}
}
context.canvas.drawLine(
gridBox.left,
yPos + font.descent + font.ascent - separatorLineWidth / 2,
gridBox.right,
yPos + font.descent + font.ascent - separatorLineWidth / 2);
@override
void paintBackground(Context context, PdfPoint size) {}
@override
void paint(Context context, PdfPoint size) {
if (drawXDivisions) {
_drawXDivisions(context, size);
}
if (drawYDivisions) {
_drawYDivisions(context, size);
}
}
context.canvas
..setStrokeColor(separatorColor)
..setLineWidth(separatorLineWidth)
..strokePath();
@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
..setStrokeColor(color)
..setLineWidth(lineWidth)
..drawLine(gridBox.left, gridBox.bottom, gridBox.right, gridBox.bottom)
..drawLine(gridBox.left, gridBox.bottom, gridBox.left, gridBox.top)
..strokePath();
..saveContext()
..drawRect(
gridBox.left,
gridBox.bottom,
size.x - gridBox.left,
size.y - gridBox.bottom,
)
..clipPath();
}
@override
void paintForeground(Context context, PdfPoint size) {}
void unClip(Context context, PdfPoint size) {
context.canvas.restoreContext();
}
}
... ...