flutter) How do I make arrow lines with canvas? - flutter

I want to put an arrow at the end (or on both sides) of a line drawn with a drawLine() of canvas.
Like this
Can I know how to do this?

That's actually not that hard to do - there's just a little math involved.
Let's have a line and paint set up like this:
final paint = Paint()
..color = Colors.black
..strokeWidth = 2;
final p1 = Offset(50, 50);
final p2 = Offset(250, 150);
canvas.drawLine(p1, p2, paint);
this will give us:
Now if we think of this line as a vector, we remember a vector has a direction. To get the direction we must find the angle between the start and the end of the line. This can be done using the atan2 function of dart's math library.
So import the math library
import 'dart:math' as math;
and add the following lines
final dX = p2.dx - p1.dx;
final dY = p2.dy - p1.dy;
final angle = math.atan2(dY, dX);
Afterwards let's determine a size and an angle for the arrow we're about to put at the end of the line:
final arrowSize = 15;
final arrowAngle= 25 * math.pi / 180;
Everything that's left is actually drawing the arrow. This is basically done by drawing a triangle at the proper angle the line is facing using a path.
final path = Path();
path.moveTo(p2.dx - arrowSize * math.cos(angle - arrowAngle),
p2.dy - arrowSize * math.sin(angle - arrowAngle));
path.lineTo(p2.dx, p2.dy);
path.lineTo(p2.dx - arrowSize * math.cos(angle + arrowAngle),
p2.dy - arrowSize * math.sin(angle + arrowAngle));
path.close();
canvas.drawPath(path, paint);
Which will finally give us:

Related

How do I find the point intersecting a line?

If I have the following chart in Flutter:
Where the green graph is a Path object with many lineTo segments, how do I find the y-coordinate for a point with a given x-coordinate?
As you can see in the image, there is a gray dotted line at a specific point on the x-axis and I want to draw a point where it intersects with the green graph.
Here is an example path:
final path = Path();
path.moveTo(0, 200);
path.lineTo(10, 210);
path.lineTo(30, 190);
path.lineTo(55, 150);
path.lineTo(80, 205);
path.lineTo(100, 0);
And I want to find the y-coordinate for the point at dx = 75.
The easiest way to achieve this for any path that only has a single point for every x (i.e. where there is only a single graph / line from left to right) is using the binary search algorithm.
You can then simply use the distance of the path, which is obtained using Path.computeMetrics, to perform binary search and find the offset via Path.getTangentForOffset:
const searchDx = 75;
const iterations = 12;
final pathMetric = path.computeMetrics().first;
final pathDistance = pathMetric.length;
late Offset closestOffset;
var closestDistance = pathDistance / 2;
for (var n = 1; n <= iterations; n++) {
closestOffset = pathMetric.getTangentForOffset(closestDistance)!.position;
if (closestOffset.dx == searchDx) break;
final change = pathDistance / pow(2, n);
if (closestOffset.dx < searchDx) {
closestDistance += change;
} else {
closestDistance -= change;
}
}
print(closestOffset); // Offset(75.0, 193.9)
Note that if you want to run significantly more iterations (which should not be necessary due to the nature of binary search), you should replace final change = pathDistance / pow(2, n); by a cheaper operation like storing the left and right points of your current search interval.
You can find the full working code as an example on Dartpad.

Flutter CustomPaint Moving a circle along a path

I'm learning CustomPaint by making my own line chart. I have already made the chart itself so now i'm making the things around it. One them is a trackball/line whenever you pan. So far i've made the dashed line from top to bottom, and it works perfectly, now i want to make the the ball that's gonna follow along with the line. My problem is getting the ball and dashed line to always be in sync. right now it's the dashed line that follows my finger and the ball that does'nt match up. I found this answer on stack overflow but it's not working quite right for me. This video is how it currently functions with the following code,
where dragX is the current x-axis position of your finger in pixels.
void drawTrackballLine(Canvas canvas, Size size) {
double dashHeight = 5;
double dashSpace = trackBallOptions.dashSpacing;
double y = 0;
final paint = Paint()
..color = trackBallOptions.color
..strokeWidth = trackBallOptions.strokeWidth;
while (y < size.height) {
canvas.drawLine(
Offset(dragX, y),
Offset(dragX, y + dashHeight),
paint,
);
y += dashSpace + dashHeight;
}
}
void drawTrackball(Canvas canvas, Size size, Path path) {
final pathMetric = path.computeMetrics().elementAt(0);
final value = pathMetric.length * (dragX / size.width);
final pos = pathMetric.getTangentForOffset(value);
canvas.drawCircle(pos!.position, 5, Paint());
}

Draw a line perpendicular to an arc at a particular point with Flutter

I want to draw a line perpendicular to an arc at a particular point. I'm using path.arcTo to draw the arc. I have to say, I already managed to do something similar, by drawing the arc, and then increase the radius and tell flutter to draw another arc, but with an invalid sweepAngle, in that way flutter draws the perpendicular line I want. However, this at the beginning worked, but later on gave me some bugs. I'm sharing with you the initial code that I'm using. I'd like to know if there is a specific method or way to achieve what the first image shows, after drawing the arc:
Offset center = Offset(screenSize.width / 2, screenSize.height / 2);
double degreesToRadians(double degrees) => degrees * (math.pi / 180);
path.arcTo(
Rect.fromCircle(center: center, radius: 150),
degreesToRadians(180),
degreesToRadians(135),
true);
I'm working directly with degrees. Therefore the function degreesToRadians.
My current result is just this:
Thanks in advance.
So, what I did was to declare final outerRadiusPoint (the point where I wanted to take my line to go to) by establishing a sizeToIncrease for the radius, obtaining cosine of startingAngle and sine of startingAngle, adding center.dx and center.dy respectively and multiplying each by the size we want our new radius to be.
After that, we draw our arc with path.arcTo. Then we obtain the offset of the end of the arc with PathMetrics and PathMetric. With that we are now able to move the path to that position and start drawing again to the Offset of outerRadiusPoint.
final outerRadiusPoint = new Point(
(sizeForRadius + sizeToIncrease) * cos(degreesToRadians(startingAngle)) + center.dx,
(sizeForRadius + sizeToIncrease) * sin(degreesToRadians(startingAngle)) + center.dy);
path.arcTo(
Rect.fromCircle(center: center, radius: sizeForRadius),
degreesToRadians(initialPoint),
degreesToRadians(buttonArcLength),
true);
PathMetrics pms = path.computeMetrics();
PathMetric pm = pms.last;
Offset pmoffset = pm.getTangentForOffset(pm.length).position;
path.lineTo(outerRadiusPoint.x, outerRadiusPoint.y);
path.moveTo(pmoffset.dx, pmoffset.dy);

How to change the distance/radius from center that the objects are instantiated

How would it be possible to move the 8 instantiatedObjects "cubes" closer to the pillar.
public void instantiateInCircle()
{
for (int i = 0; i < amount; i++)
{
float radius = 8;
float angle = i * Mathf.PI * 2f / radius;
Vector3 newPos = transform.position + (new Vector3(Mathf.Cos(angle) * radius, spawnHeight, Mathf.Sin(angle) * radius ));
GameObject instantiatedObject = Instantiate(itemToSpawn, newPos, Quaternion.Euler(0, 0, 0));
instantiatedObject.transform.LookAt(spawnAroundThis.transform);
instantiatedObject.transform.parent = spawnAroundThis.transform;
instantiatedObject.transform.localScale = new Vector3(scale, scale, scale);
//this seems to work-ish , not sure if its good math but hey :)
//Thanks BugFinder!
instantiatedObject.transform.position = instantiatedObject.transform.position += instantiatedObject.transform.forward * distance;
}
}
Ideally these could be moved closer to the pillar
Due to the fact your code already points the object towards where you are trying to get closer, you need only move the object forward until its at the correct distance.
Decreasing the value of the radius variable should spawn them closer to the pillar.
Since you are offsetting the cube position from the pillar position by the cosine and sine components of the radius, it will place them correctly spaced around the pillar.
Also, I believe
float angle = i * Mathf.PI * 2f / radius;
Should really be
float angle = i * Mathf.PI * 2f / amount;
The radius should not affect the angle the object is spawned, but the number of objects should.

Flutter bezier curves automatically deciding the control point

I want to draw a bezier curve between the given two locations (offsets) with the flutter. I can see the following function available to do so,
https://api.flutter.dev/flutter/dart-ui/Path/quadraticBezierTo.html
Here we have to specify start and end points those I already know. And also have to specify the control point (x1,y1). I there a way we can generate this control point? May be to have more control of the curve we can have a factor (that decides how far away this point from the baseline) and based on that factor we can generate the control point.
Please find my scretch below
You can use cubicTo function
https://api.flutter.dev/flutter/dart-ui/Path/cubicTo.html
void cubicTo (
double x1,
double y1,
double x2,
double y2,
double x3,
double y3
)
this creates a bezier curve from the baseline (x1,y1) to (x2,y2) with (x3,y3) as control point.
class HalfCirclePainter extends CustomPainter {
HalfCirclePainter({this.scrollOffset});
final double scrollOffset;
#override
void paint(Canvas canvas, Size size) {
Paint circle = new Paint()
..color = Colors.red
..style = PaintingStyle.fill;
Path topCirclePath = new Path()
..moveTo(0, 0)
..lineTo(0, scrollOffset < size.height ? scrollOffset : size.height)
..cubicTo(
20,
size.height,
size.width - 20,
size.height,
size.width,
scrollOffset < size.height ? scrollOffset : size.height,
)
..lineTo(size.width, 0)
..lineTo(0, 0)
..close();
canvas.drawPath(topCirclePath, circle);
}
}
This code draws a semi circle, I hope this will be helpful.
Thank you for #pskink to help me. As you mentioned your answer as a comment and the code is not that readable when displaying in the comments. Here I am listing the code as the answer. So it is easy to grasp for someone who looking into this.
void drawBezierLine(Canvas canvas, Offset start, Offset end){
Offset middle = end/2 + start/2;
Offset baselineVector = end - start;
Offset controlPoint = middle + Offset(-baselineVector.dy, baselineVector.dx);
Paint paint = Paint()
..color = Colors.red
..style = PaintingStyle.stroke
..strokeWidth = 8.0;
var path = Path();
path.moveTo(start.dx, start.dy);
path.quadraticBezierTo(controlPoint.dx, controlPoint.dy, end.dx, end.dy);
canvas.drawPath(path, paint);
}
So here we can give start and end offsets to the function and it will draw the Bezier curve based on it. The hight of the Bezier(factor) curve corresponds to the length of the base line.