background
The company recently introduced the Stack of Flutter technology, Google’s mobile UI framework for quickly building high-quality native user interfaces on iOS and Android. However, because Flutter is still in its early development stage, its ecological construction is not yet complete. For example, the chart UI component is needed in the project. After some research, Google/ Charts has the most powerful function and rich style (see online Gallery for details), so it was introduced into the project. However, Charts only implements straight line line charts, so the fork Charts project has to implement the smooth curve effect itself.
Based on using
- The Ole/Charts library is powerful but not very document-friendly, with pure sample code in the online gallery and little Api explanation.
- Demo effect of feasibility analysis
- Carefully study the results of the optimization
- Use code and comments
return Container(
height: 150.0,
child: charts.LineChart(
_createChartData(), // The list of points on the line chart
animate: true./ / animation
defaultRenderer: charts.LineRendererConfig( // The configuration of line drawing
includeArea: true,
includePoints: true,
includeLine: true,
stacked: false,
),
domainAxis: charts.NumericAxisSpec( // Spindle configuration
tickFormatterSpec: DomainFormatterSpec(widget.dateRange), // Format the tick value, where num is converted to String
renderSpec: charts.SmallTickRendererSpec( // The configuration of the spindle drawing
tickLengthPx: 0.// The scale marks the length of the highlight
labelOffsetFromAxisPx: 12.// Scale text distance from the axis of the displacement
labelStyle: charts.TextStyleSpec( // The scale text style
color: ChartUtil.getChartColor(HColors.lightGrey),
fontSize: HFontSizes.smaller.toInt(),
),
axisLineStyle: charts.LineStyleSpec( // Axis style
color: ChartUtil.getChartColor(ChartUtil.lightBlue),
),
),
tickProviderSpec: charts.BasicNumericTickProviderSpec( // Axis calibration configuration
dataIsInWholeNumbers: false,
desiredTickCount: widget.data.length, // Expect to display several ticks
),
),
primaryMeasureAxis: charts.NumericAxisSpec( // Cross axis configuration, the parameters refer to the spindle configuration
showAxisLine: false.// Display axis
tickFormatterSpec: MeasureFormatterSpec(),
tickProviderSpec: charts.BasicNumericTickProviderSpec(
dataIsInWholeNumbers: false,
desiredTickCount: 4,
),
renderSpec: charts.GridlineRendererSpec( // Cross the axis calibration horizontal lines
tickLengthPx: 0,
labelOffsetFromAxisPx: 12,
labelStyle: charts.TextStyleSpec(
color: ChartUtil.getChartColor(HColors.lightGrey),
fontSize: HFontSizes.smaller.toInt(),
),
lineStyle: charts.LineStyleSpec(
color: ChartUtil.getChartColor(ChartUtil.lightBlue),
),
axisLineStyle: charts.LineStyleSpec(
color: ChartUtil.getChartColor(ChartUtil.lightBlue),
),
),
),
selectionModels: [ // Set the click selected event
charts.SelectionModelConfig(
type: charts.SelectionModelType.info,
listener: _onSelectionChanged,
)
],
behaviors: [
charts.InitialSelection(selectedDataConfig: [ // Set it to default
charts.SeriesDatumConfig<num> ('LineChart', _index)
]),
],
),
);
Copy the code
Smooth curve effect
Although the basic line graph effect was pretty good, the UI design was smooth curve effect, and the engineer agreed that the curve effect was more elegant, so he decided to challenge himself and implement smooth curve effect himself. Through a layer of source code analysis, finally found that the realization of line map line location, rewriting the implementation can achieve smooth curve effect
line_chart.dart
defaultRenderer: charts.LineRendererConfig( // The configuration of line drawing
includeArea: true,
includePoints: true,
includeLine: true,
stacked: false,),Copy the code
line_renderer.dart
if(config.includeLine) { ... canvas.drawLine( clipBounds: _getClipBoundsForExtent(line.positionExtent), dashPattern: line.dashPattern, points: line.points, stroke: line.color, strokeWidthPx: line.strokeWidthPx, roundEndCaps: line.roundEndCaps); }}); }});Copy the code
chart_canvas.dart
@override
void drawLine(
...
_linePainter.draw(
canvas: canvas,
paint: _paint,
points: points,
clipBounds: clipBounds,
fill: fill,
stroke: stroke,
roundEndCaps: roundEndCaps,
strokeWidthPx: strokeWidthPx,
dashPattern: dashPattern);
}
Copy the code
Now that we have found the specific entry point for drawing polylines, all that remains is how to draw a smooth curve based on the given data set, and the range of the curve should not exceed the range of the data set. Three curve drawing algorithms were tried before and after, the first two were abandoned because they exceeded the scope of the data set, and the final curve effect was drawn by the third algorithm.
- Spline interpolation
Spline interpolation is a common interpolation method for obtaining smooth curves in industrial design, and cubic spline is widely used. The algorithm is based on Java cubic spline interpolation and the code is interpolation. Dart
class Interpolation {
int n;
List<num> xs;
List<num> ys;
bool spInitialized;
List<num> spY2s;
Interpolation(List<num> _xs, List<num> _ys) {
this.n = _xs.length;
this.xs = _xs;
this.ys = _ys;
this.spInitialized = false;
}
num spline(num x) {
if (!this.spInitialized) {
// Assume Natural Spline Interpolation
num p, qn, sig, un;
List<num> us;
us = new List<num>(n - 1);
spY2s = new List<num>(n);
us[0] = spY2s[0] = 0.0;
for (int i = 1; i <= n - 2; i++) {
sig = (xs[i] - xs[i - 1]) / (xs[i + 1] - xs[i - 1]);
p = sig * spY2s[i - 1] + 2.0;
spY2s[i] = (sig - 1.0) / p;
us[i] = (ys[i + 1] - ys[i]) / (xs[i + 1] - xs[i]) -
(ys[i] - ys[i - 1]) / (xs[i] - xs[i - 1]);
us[i] = (6.0 * us[i] / (xs[i + 1] - xs[i - 1]) - sig * us[i - 1]) / p;
}
qn = un = 0.0;
spY2s[n - 1] = (un - qn * us[n - 2]) / (qn * spY2s[n - 2] + 1.0);
for (int k = n - 2; k >= 0; k--) {
spY2s[k] = spY2s[k] * spY2s[k + 1] + us[k];
}
this.spInitialized = true;
}
int klo, khi, k;
num h, b, a;
klo = 0;
khi = n - 1;
while (khi - klo > 1) {
k = (khi + klo) >> 1;
if (xs[k] > x)
khi = k;
else
klo = k;
}
h = xs[khi] - xs[klo];
if (h == 0.0) {
throw new Exception('h = = 0.0');
}
a = (xs[khi] - x) / h;
b = (x - xs[klo]) / h;
return a * ys[klo] +
b * ys[khi] +
((a * a * a - a) * spY2s[klo] + (b * b * b - b) * spY2s[khi]) *
(h * h) /
6.0; }}Copy the code
line_painter.dart
/// Draws smooth lines between each point.
void _drawSmoothLine(Canvas canvas, Paint paint, List<Point> points) {
var interval = 0.1;
var interpolationPoints = List<Point>();
for (int k = 0; k < points.length; k++) {
if ((k + 1) < points.length) {
num temp = 0;
while (temp < points[k + 1].x) {
temp = temp + interval;
interpolationPoints.add(Point(temp, 0.0)); }}}var tempX = points.map((item) => item.x).toList();
var tempY = points.map((item) => item.y).toList();
var ip = Interpolation(tempX, tempY);
for (int j = 0; j < interpolationPoints.length; j++) {
interpolationPoints[j] =
Point(interpolationPoints[j].x, ip.spline(interpolationPoints[j].x));
}
interpolationPoints.addAll(points);
interpolationPoints.sort((a, b) {
if (a.x == b.x)
return 0;
else if (a.x < b.x)
return - 1;
else
return 1;
});
final path = new Path();
path.moveTo(interpolationPoints[0].x.toDouble(), interpolationPoints[0].y.toDouble());
for (int i = 1; i < interpolationPoints.length; i++) {
path.lineTo(interpolationPoints[i].x.toDouble(), interpolationPoints[i].y.toDouble());
}
canvas.drawPath(path, paint);
}
Copy the code
Final rendering
It looks perfect, but there is a fatal problem. The vertices of the curves can be out of range of the line chart data
- Bessel curve
Cubic Bezier curve is such a curve, which is a smooth curve drawn according to the coordinates of arbitrary points in four positions. The difficulty is the calculation of two control points. The algorithm smoothly fits broken line segments by referring to Bezier curve, and the code is implemented as follows: Line_Painter
/// Draws smooth lines between each point.
void _drawSmoothLine(Canvas canvas, Paint paint, List<Point> points) {
var targetPoints = List<Point>();
targetPoints.add(points[0]);
targetPoints.addAll(points);
targetPoints.add(points[points.length - 1]);
final path = new Path();
for (int i = 1; i < targetPoints.length - 2; i++) {
path.moveTo(
targetPoints[i].x.toDouble(), targetPoints[i].y.toDouble());
var controllerPoint1 = Point(
targetPoints[i].x + (targetPoints[i + 1].x - targetPoints[i - 1].x) / 4,
targetPoints[i].y + (targetPoints[i + 1].y - targetPoints[i - 1].y) / 4,);var controllerPoint2 = Point(
targetPoints[i + 1].x - (targetPoints[i + 2].x - targetPoints[i].x) / 4,
targetPoints[i + 1].y - (targetPoints[i + 2].y - targetPoints[i].y) / 4,); path.cubicTo( controllerPoint1.x, controllerPoint1.y, controllerPoint2.x, controllerPoint2.y, targetPoints[i +1].x, targetPoints[i + 1].y);
}
canvas.drawPath(path, paint);
}
Copy the code
Smooth curves are also possible, but there is still the problem of out-of-bounds vertices
- Bessel curves (Formal X)
Since the previous RN project used Victory-native/Victory-Chart, the source code and documentation found that its curve effect implementation relied on D3. curveMonotoneX of D3-SHAp. The algorithm was based on Monotone.js, and the implementation code is as follows:
Note: Since the algorithm needs the current point and the previous two points to draw a curve, a point is artificially added at the end of the data set of broken line points, otherwise the curve drawn will lack the last segment
line_painter.dart
/// Draws smooth lines between each point.
void _drawSmoothLine(Canvas canvas, Paint paint, List<Point> points) {
var targetPoints = List<Point>();
targetPoints.addAll(points);
targetPoints.add(Point(
points[points.length - 1].x * 2, points[points.length - 1].y * 2));
var x0,
y0,
x1,
y1,
t0,
path = Path();
for (int i = 0; i < targetPoints.length; i++) {
var t1;
var x = targetPoints[i].x;
var y = targetPoints[i].y;
if (x == x1 && y == y1) return;
switch (i) {
case 0:
path.moveTo(x, y);
break;
case 1:
break;
case 2:
t1 = MonotoneX.slope3(x0, y0, x1, y1, x, y);
MonotoneX.point(
path,
x0,
y0,
x1,
y1,
MonotoneX.slope2(x0, y0, x1, y1, t1),
t1);
break;
default:
t1 = MonotoneX.slope3(x0, y0, x1, y1, x, y);
MonotoneX.point(
path,
x0,
y0,
x1,
y1,
t0,
t1);
}
x0 = x1;
y0 = y1;
x1 = x;
y1 = y;
t0 = t1;
}
canvas.drawPath(path, paint);
}
Copy the code
The final image, the vertices are all points in the line chart data set, perfect!
- The source code
See GitHub dev at github.com/123lxw123/c…
The copyright of this article belongs to Zaihui RESEARCH and development team, welcome to reprint, reprint please reserve source. @123lxw123