Render labels in circular path and aligned center to the width of the arc,
Intially, i have tried by point on the circle formula to position the label as below,
label_X_Position = (float) (centreX) - (labelRadius ) * Math.sin(angleForLabel));
label_Y_Position = (float) ((centreY) + (labelRadius) * Math.cos(angleForLabel));
canvas.drawText(String.valueOf(labelText), (float) label_X_Position, (float) label_Y_Position, paint);
but when the length of the label value increased it doesn't looks like circular path. For better understanding i have attached a diagramatic representation for my requirment,
in the above image, while placing the value 100000 as per existing implementation, the text value will start from the arc. I need that to be shared based on its length.
Related
Can anyone explain to me what is the cause of this behavior?
The problem is that, from "off" to "2" always shows perfectly, above the radius I gave.
Radius is +35 than the circle's radius.
Now when I write digits, as it goes down, it starts to mess up.
And in terms of alphabets, it touches the edge and overlaps it.
can anyone tell me the reason for this? because radius is always more than the current circle's radius so the alphabets should appear similarly like "off".
computation of xy points...
// Angles are in radians.
val startAngle = Math.PI * (9 / 8.0)
val angle = startAngle + pos.ordinal * (Math.PI / 4)
x = (radius * cos(angle)).toFloat() + width / 2
y = (radius * sin(angle)).toFloat() + height / 2
I played around with the degrees and it seems like the closer to 0 degree starts to mess up, as the degrees increase, it keeps adding more space in radius.
Illustrated here... I would like to what what is causing this behavior, or just explain the reason/ math behind it. thanks
From the comments it looks like you are following this code lab code https://github.com/google-developer-training/android-advanced/tree/master/CustomFanController
You just need to take into account text ascent and decent. So draw the numbers on the circumference of the circle
val yPos = (pointPosition.y - (paint.descent() + paint.ascent()) / 2).toInt()
canvas.drawText(label, pointPosition.x, yPos.toFloat(), paint)
The above is based on Android Center text on canvas
This does draw the text at the correct place, but if the text is too large it does overlap
Im trying to create an custom view that displays a dial with the numbers 1-10 around it. Im using trigonometry to find the X and Y positions for the numbers of the dial. I have no problems to find the positions around the circle but im unable to align them further in towards center of the dial. Look at number 6 for example, i just want it to be placed slightly above the thick white tick mark.
I have tried versions of "shortening the radius".
val diameter = Math.min(width, height)
val radius = diameter / 2
val distance = radius * 0.20f //20% of radius
And then deduct 'distance' from radius to find the X and Y positions there and then add the numbers on those positions with no luck.
Below is the code that calculates the X and Y positions and adds the numbers displayed in the dial screenshot.
for (i in 1..10) {
canvas?.drawText(i.toString(),cx.toFloat() +
(Math.cos(Math.toRadians(degrees.getInt(feetNumber,0).toDouble())).toFloat()) *
radius - (paint.measureText(i.toString()) / 2),
cy.toFloat() +
(Math.sin(Math.toRadians(degrees.getInt(feetNumber,0).toDouble())).toFloat()) *
radius + (paint.measureText(i.toString()) / 2), paint)
feetNumber++;
}
I have added the degrees in a array resource file.
<resources>
<array
name="degrees">
<item>270</item>
<item>306</item>
<item>342</item>
<item>18</item>
<item>54</item>
<item>90</item>
<item>126</item>
<item>162</item>
<item>198</item>
<item>234</item>
</array>
</resources>
I would be very grateful if any one can help me to understand how to draw the numbers a short space after the thick tick marks where you usually find the numbers in a dial.
I have found a solution to my problem. The problem was that i cant calulate the radius in the canvas?.drawText() so i needed to privide pre-calculated value.
val radius = diameter / 2
val mark = radius * 0.30f
val shorterRadius = radius - mark
and then use the shorterRadius inside canvas?.drawText() along with the rest of the calculations.
Then my dial look like the attached picture.
Background
I'm drawing a custom View, which consists of an arc along which images are drawn.
A bit like this "Wheel of Fortune" screenshot, where only part of a large disc is visible and, as the user drags the view, images become visible/hidden as appropriate and are drawn at the appropriate position and angle along the disc's edge.
This works fine; I use the code below to create a large bounding box (four times the width of the view, to get a more subtle arc), which I use with Path.arcTo() to draw the visible top edge of the disc.
Because the bounding box is square, the arc drawn (if I were to draw 360°) would be circular.
// Disc dimensions (based on this View's width/height/padding)
final int radius = width * 2;
final float halfWidth = width / 2f;
final float top = mTopPadding;
// Create a large, square bounding box to draw the disc in.
// Centre horizontally; top edge of the disc == top edge of this View (+ padding)
final RectF discBounds =
new RectF(-radius + halfWidth, top, radius + halfWidth, radius * 2 + top);
// Create an arc along the circumference of the disc,
// but only where it will intersect with this View
double arcSweep = Math.toDegrees(Math.asin(halfWidth / radius)) * 2;
double startAngle = 180 + ((180 - arcSweep) / 2d);
mDiscPath.reset();
mDiscPath.arcTo(discBounds, (float) startAngle, (float) arcSweep);
// Close the shape so that we fill the rest of this View
// (the area underneath the arc) with the disc bg colour
mDiscPath.lineTo(width, height);
mDiscPath.lineTo(0, height);
I then create another Path and again call arcTo(), using the exact same bounding box so that the same arc radius is maintained.
This time the sweep angle of the arc is longer, since there may be only two or three images shown within the View at one time, but an arbitrary number of images off-screen (in my case, up to about ten).
// Create another arc, along which the images should move,
// based on the number and width of the images.
// We will later use a PathMeasure object created from
// this Path to determine where to draw each image
arcSweep = (mTotalWidth * 180) / (radius * Math.PI);
startAngle = 180 + ((180 - arcSweep) / 2d);
mImagePath.reset();
mImagePath.arcTo(discBounds, (float) startAngle, (float) arcSweep);
Problem
In onDraw(), the mDiscPath is drawn as the background (canvas.drawPath(mDiscPath, fillPaint)), and then the appropriate bitmaps are drawn based on a PathMeasure object created from mImagePath and how far the user has dragged.
However, it's noticeable that the images do not precisely follow the expected path as the disc is "rotated". This causes problems, as the images need to align accurately to the edge of the disc.
For troubleshooting, I started drawing mImagePath using canvas.drawPath(mImagePath, strokePaint)) to see why the image path didn't seem to follow the disc path.
In the screenshot below, to make the problem more obvious, the regular bitmaps are not drawn on top of the disc, and mImagePath was translated downwards by 4dp (i.e. the problem is also visible when not translated).
Here we can see three independent instances of the custom View stacked on top of one another.
But it's clear that the black line (mImagePath) does not match the radius of the top of the coloured disc (mDiscPath) in each case. i.e. The radius of the black arc appears to be large than the disc's radius.
The arcs for both Path objects were created using the same bounding box, so I would expect both arcs to have the same radius.
The line on the bottom disc seems to match up well, but the top two discs are clearly wrong.
The only real difference between the discs is the number of images displayed, and therefore the sweep angle of the image path (89°, 169°, 222° respectively for the three views).
Question
Why, if the exact same square RectF bounding box is being used to create two Path objects, why do arcs drawn from these Paths have different radii?
Am I missing something? Should I be using a different API?
Postscript
I've ensured the bounding box is correctly sized and doesn't change between creating the two paths.
The start and sweep angles look correct in all cases (i.e. the midpoint of each arc is at 270°).
Creating brand new Paths or resetting the existing Paths makes no difference.
Using the same arc sweep for both Paths does work as expected.
I've tested on various devices and orientations, with and without software rendering.
I want to move an object on a circle around a given point. I am using OpenGL on Android and my viewport is the screen resolution in landscape mode (1280 * 800). The point I want to rotate an object around is e.g (500, 300) and this is where the user pressed. I also have the radius of the desired circle r.
To sum it up, I've the center of the circle, the radius, and the angle (amount I want to move the object with each iteration of the game loop)
So far I tried this:
this.setPosX(((float)Math.cos(angle)*radius + center.x) * width);
this.setPosY(((float)Math.sin(angle)*radius + center.y) * height);
This will create a movement along an ellipsis, not a circle...
Can anyone please help me?
It produces an ellipse cause circles DON'T have heights. Try this instead
this.setPosX(((float)Math.cos(angle)*radius ) + center.x);
this.setPosY(((float)Math.sin(angle)*radius ) +center.y);
Just remove the width and height factors at the end. If you want a circle you cannot multiply the coordinates with different factors.
The formula is not correct because you are multiplying the correct value for a point around a circle by other different values which are not meaningful.
Think about the fact that you have c(x,y) which is the center and you need to move around by a value which is given by r(cos(angle)*radius, sin(angle)*radius).
What you obtain is p(x + cos(angle)*radius, y + sin(angle)*radius).
If you multiply these two coordinates by two other values (width and height in your formula) you are changing the factor either for the circle either for the center so you end up with not only an ellipse but an ellipse which changes its center.
So:
circle: p(c.x + cos(angle)*r, y + sin(angle)*r)
ellipse: p(c.x + cos(angle)*w, y + sin(angle)*h)
your formula: p((c.x + cos(angle)*r)*w, (c.y + sin(angle)*r)*h) (which makes no sense)
I'm still facing a difficulty to project a list of geological positions to pixels. I have a custom view (derived from SurvaceView) for simply painting the coordinates.
My code:
x = (int) sView.getHeight() * ( (pos.getLat() - minLat) / (maxLat - minLat)) + margin;
...
myPoints.add(new Point(x,y));
onDraw(Canvas canvas) {
for (Point p : myPoints) {
canvas.drawPoint(p.x, p.y, myPaint);
}
.... doesn't fit the entire view. I am becoming desperate. Do you have a idea?
Thanks
It's a bit unclear what you want.
But if I understand you correctly, you want to plot a set of GPS-coordinates to a bitmap, and you want the resulting plot to fit the size of the bitmap.
If so, I suggest going for the following strategy:
Find the minimum and maximum latitudes and longitudes, MinLon, MaxLon, MinLat and MaxLat.
Subtract the minimum values from all the points (rebasing the data set to (0,0)).
Find the horizontal scale factor: ScaleX := (MyBitmap.Width) / (MaxLat - MinLat)
Find the vertical scale factor: ScaleY := (MyBitmap.Height) / (MaxLon - MinLon)
Loop through all the points in your dataset, and apply the lowest scale factor of ScaleX and ScaleY.
You will then have a set of points with coordinates that can be plotted to your bitmap.
It will not be a cartographically correct projection, but it'll probably serve a starting point, at least.