Flutter绘制线,然后中间用空心圆连接,并且不穿过圆
效果如图:
import 'dart:math';
import 'package:flutter/material.dart';
class DataPoint {
final double x;
final double y;
DataPoint(this.x, this.y);
}
class ChartPainter extends CustomPainter {
final List<DataPoint> dataPoints;
final List<int> pointY;
final int numHorizontalLine;
final double selectedRadius;
ChartPainter(
this.dataPoints,
this.pointY,
this.numHorizontalLine,
this.selectedRadius,
);
double? startX, startY, endX, endY; //坐标的起始和结束点
double? horizontalSpacing;
// 记录点击的坐标点索引
int selectedPointIndex = -1;
@override
void paint(Canvas canvas, Size size) {
initPoint(size);
drawHorizontalLine(canvas);
drawCurve(canvas);
}
initPoint(Size size) {
startX = 40;
endX = size.width - startX!;
endY = 20;
startY = size.height - endY!;
horizontalSpacing = (startY! - endY!) / (numHorizontalLine - 1);
}
//绘制背景横线和坐标值
drawHorizontalLine(Canvas canvas) {
final Paint linePaint = Paint()..strokeWidth = 1.0;
final TextPainter textPainter = TextPainter(
textAlign: TextAlign.right,
textDirection: TextDirection.rtl,
);
for (int i = 0; i < numHorizontalLine; i++) {
final double y = startY! - i * horizontalSpacing!;
if (i == 0) {
linePaint.color = Colors.black;
} else {
linePaint.color = Color(0x28000000);
}
canvas.drawLine(
Offset(startX!, y), Offset(endX! + startX!, y), linePaint);
final String label = pointY[i].toString();
textPainter.text = TextSpan(
text: label,
style: TextStyle(color: Colors.black),
);
textPainter.layout();
textPainter.paint(
canvas,
Offset(0, y - textPainter.height / 2),
);
}
}
// 绘制曲线图
void drawCurve(Canvas canvas) {
final Paint curvePaint = Paint()
..color = Colors.blue
..strokeWidth = 2.0
..style = PaintingStyle.stroke;
final double circleRadius = 4.0; // 圆的半径
final double xScale = endX! / (dataPoints.length - 1);
final double yScale = (startY! - endY!) / (pointY.last - pointY.first);
for (int i = 0; i < dataPoints.length; i++) {
final double x = startX! + i * xScale;
final double y = startY! - (dataPoints[i].y - pointY.first) * yScale;
final double prevX = i > 0 ? startX! + (i - 1) * xScale : x;
final double prevY = i > 0 ? startY! - (dataPoints[i - 1].y - pointY.first) * yScale : y;
final double angle = atan2(y - prevY, x - prevX); // 计算线段的角度
final double offsetX = cos(angle) * circleRadius; // 计算连接到圆边界的偏移量
final double offsetY = sin(angle) * circleRadius;
// 计算线段起点和终点,使其连接到圆的边界
final double lineStartX = prevX + offsetX;
final double lineStartY = prevY + offsetY;
final double lineEndX = x - offsetX;
final double lineEndY = y - offsetY;
if (i == 0) {
canvas.drawCircle(Offset(x, y), circleRadius, curvePaint); // 绘制圆
} else {
canvas.drawLine(Offset(lineStartX, lineStartY), Offset(lineEndX, lineEndY), curvePaint); // 绘制线段
canvas.drawCircle(Offset(x, y), circleRadius, curvePaint); // 绘制圆
}
}
}
//检测点击事件
void onTapDown(Offset offset) {
for (int i = 0; i < dataPoints.length; i++) {
final double x = startX! + i * ((endX!) / (dataPoints.length - 1));
final double y = startY! -
(dataPoints[i].y - pointY.first) *
((startY! - endY!) / (pointY.last - pointY.first));
print(
"==>${offset.dx}->${offset.dy}==>$x==>$y==>${(offset.dx - x).abs()}=>$selectedRadius=>${(offset.dy - y).abs()}");
if ((offset.dx - x).abs() <= selectedRadius &&
(offset.dy - y).abs() <= selectedRadius) {
selectedPointIndex = i;
break;
} else {
selectedPointIndex = -1;
}
}
print("==$selectedPointIndex");
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
外部调用
GestureDetector(
onTapDown: (TapDownDetails detail) {
chartController.onTapDown(detail);
},
child: CustomPaint(
size: Size(Get.width - 20.w, 280.h),
painter:ChartPainter([
DataPoint(0, 100),
DataPoint(1, 50),
DataPoint(2, 200),
DataPoint(3, 250),
DataPoint(4, 100),
DataPoint(5, 150),
DataPoint(6, 50),
DataPoint(7, 20),
], [
0,
50,
100,
150,
200,
250,
300
], 7,5),
),
)