Android Question B4XCanvas and drawTextOnPath

Jeffrey Cameron

Well-Known Member
Licensed User
Back from vacation and resuming work on my project referenced in https://www.b4x.com/android/forum/threads/get-list-of-b4xpath-points.103545/ . I need to label each pie segment (inside the chart, not in a separate key label or external label with line pointing to the segment), and I was going to use @Erel's drawTextOnPath example, however since I'm using a B4XCanvas object instead of a plain Canvas object, I get an error due to no "paint" field in the B4XCanvas object:
B4X:
** Activity (main) Resume **
*** Service (starter) Create ***
** Service (starter) Start **
** Activity (main) Create, isFirst = true **
Error occurred on line: 448 (PieChart)
java.lang.NoSuchFieldException: No field paint in class Lanywheresoftware/b4a/objects/B4XCanvas; (declaration of 'anywheresoftware.b4a.objects.B4XCanvas' appears in /data/app/phx.test-2/base.apk)
 at java.lang.Class.getDeclaredField(Native Method) …<snip>
I'm looking to place text at the outer path of each segment, for example "Label 1", "Label 2", "Label 3":

Is there any other way to accomplish this with a B4XCanvas object or should I scrap cross-platform and just go with the standard Canvas object in order to use the drawTextOnPath method?
 

Jeffrey Cameron

Well-Known Member
Licensed User
It should be simple to create this chart with B4XCanvas.DrawTextRotated
Simple for @Erel is half-a-day of head-banging for me ;)

Ok, I'm the first admit my math skills fall somewhere between a beagle and a fig tree and I can't seem to come up with a formula to calculate the text position that works across all quadrants of the pie chart. I suspect I'm just being obtuse, but is there no one formula that will give me the necessary offset to print the text or will I have to adjust based quadrant or something. Here's what I'm going for:

Simple, yes? For any given slice I can easily calculate the mid-point of the slice's arc, and it's distance from the center X/Y position (CX,CY on the image):
B4X:
MidAngle = (SegmentEndAngle + SegmentStartAngle) / 2
MidPoint = DegreesToRadians(MidAngle)
MidX = CX + Radius * Cos(MidPoint)
MidY = CY + Radius * Sin(MidPoint)
That will give me the x/y of the outer edge of the midpoint of the segment, magenta "+" I've indicated on the image. The text rotation will simply be the MidAngle + 90.0 to convert from the diagonal.
Using the B4XCanvas object's DrawTextRotated method with a "CENTER" text alignment, every variation I've come up "kind of" works for one quadrant or one-and-a-half quadrants but the other slice's text are way too far towards the center to too far out towards the edge.

Is there a single formula that can adjust MidX/MidY for the Font height/width based on where it falls on circle?
 
Last edited:

Jeffrey Cameron

Well-Known Member
Licensed User
Ok, I was being obtuse, I figured it out and even adjusted the position to be sure it wasn't cut off by the arc of the segment. In case someone else is as thick as I am, here's what I came up with. I'm sure it can be optimized and adjusted to face the text up/down based on quadrant but this is basic working method:
B4X:
Private Sub DrawSegmentText(oCan As B4XCanvas, CX As Int, CY As Int, AngleStart As Float, AngleEnd As Float, _
      Radius As Int, Text As String, TextFont As Typeface, TextSize As Float, TextColor As Int)
 Dim pdMid As Double
 Dim piMidX As Int
 Dim piMidY As Int
 Dim pdAngle As Double
 Dim poFont As B4XFont
 Dim poSize As B4XRect
 Dim piStartX As Int
 Dim piStartY As Int
 Dim piEndY As Int
 Dim piEndX As Int
 Dim pdChordLen As Double
 Dim pdSagitta As Double
 
 If Text.Trim.Length = 0 Then
  Return
 End If
 ' setup our font and measure the text we need to print
 poFont = moXUI.CreateFont(TextFont, TextSize)
 poSize = oCan.MeasureText(Text, poFont)
 ' calculate mid point of outer arc
 pdAngle = (AngleEnd + AngleStart) / 2
 pdMid = DegreeToRadian(pdAngle)
 ' calculate the height of the arc "bulge" so we can make sure the text falls below it and the edges won't be cut off by the falling arc
 piStartX = CX + Radius * Cos(DegreeToRadian(AngleStart))
 piStartY = CY + Radius * Sin(DegreeToRadian(AngleStart))
 piEndX = CX + Radius * Cos(DegreeToRadian(AngleEnd))
 piEndY = CY + Radius * Sin(DegreeToRadian(AngleEnd))
 ' formula requires 1/2 of the chord length
 pdChordLen = Sqrt(Power(piEndX - piStartX, 2) + Power(piEndY - piStartY, 2)) / 2
 pdSagitta = Radius - Sqrt(Power(Radius, 2) - Power(pdChordLen, 2))
 ' adjust text position
 piMidX = CX + (Radius - (poSize.Width / 2)) * Cos(pdMid)
 piMidY = CY + (Radius - pdSagitta - poSize.Height) * Sin(pdMid)
 oCan.DrawTextRotated(Text, piMidX, piMidY, poFont, TextColor, "CENTER", pdAngle + 90)
End Sub

Private Sub DegreeToRadian(DegreeValue As Double) As Double
 Return DegreeValue * 0.017453292
End Sub
 

Jeffrey Cameron

Well-Known Member
Licensed User
Here's a screenshot of my test program:

and I've attached a copy of my test program if you're that bored ;) I probably don't need to subtract the arc height as long as the label text size is small enough, how you go about determining that is a problem for another day.
 

Attachments

Top