Click here to Skip to main content
15,888,052 members
Articles / Programming Languages / C#
Tip/Trick

Draw a Radar Chart using a Canvas

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
26 Oct 2018CPOL2 min read 8.4K   4   2
X axis grows to the right, but Y axis grows to the bottom... not the top

Introduction

I'm building an application using Xamarin Forms. I wanted to have an interface showing the relative position of two objects, my device and another object with GPS. In most of the cases, I will use a map such as Google map to show the location of the other object but how could I provide any information to the users even without Internet access.

Using the Code

This tip shows how to build a radar using SKCanvasView, a control belonging to SkiaSharp.Views.Forms that is a library in Xamarin.Form; but the same logic can be apply to any other canvas such as HTML5 Canvas.

To start, I add the control to my interface:

XML
<skia:SKCanvasView x:Name="CanvasBase" PaintSurface="OnCanvasViewPaintSurface" 
VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
</skia:SKCanvasView>

The attribute PaintSurface is attached to a handler OnCanvasViewPaintSurface that takes care of drawing the elements of the radar in the canvas.

C#
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) 
{ 
    SKImageInfo info = args.Info; 
    SKSurface surface = args.Surface; 
    SKCanvas canvas = surface.Canvas; 

    int margin = 6; 
    canvas.Clear(); 

    //draw in a centered square. 
    //So, first calculate the side of the squared from the smallest side minus a margin 
    float side = ((info.Height < info.Width) ? info.Height : info.Width) - margin; 

    using (SKPaint paint = new SKPaint 
          { Style = SKPaintStyle.Stroke, 
            Color = Color.Black.ToSKColor(), 
            StrokeWidth = 10 
          }) { 
                //draw a circle 
                canvas.DrawCircle(info.Width / 2, info.Height / 2, side / 2, paint); 

                //draw a vertical line respecting the margin 
                canvas.DrawLine(info.Width / 2, (info.Height - side)/ 2, info.Width / 2, 
                                                 info.Height - (info.Height - side) / 2, paint); 
                //draw an horizontal line respecting the margin 
                canvas.DrawLine((info.Width - side) / 2, info.Height / 2, 
                                 info.Width - (info.Width - side) / 2, info.Height / 2, paint);
             }

     //point representing the device 
     using (SKPaint paintPositionOne = new SKPaint 
           { Style = SKPaintStyle.Fill, 
             Color = Color.Blue.ToSKColor(), 
             StrokeWidth = 10 
           }) { 
                 canvas.DrawCircle(info.Width / 2, info.Height / 2, 20, paintPositionOne);
              }

      //calculate the distance between two points - hypotenuse 
      double distance = CalculateDistance(positionOne, positionTwo); 
      //calculate the scale to display according to the distance 
      int scale = CalculateScale(distance); 
      //calculate the distance between two points - cathetus. 
      //Scaled to make the two points fit in the draw 
      Tuple<float, float> point = CalculatePoint(positionOne, positionTwo, scale); 

      using (SKPaint paintText = new SKPaint 
            { Style = SKPaintStyle.StrokeAndFill, 
              Color = Color.Black.ToSKColor(), 
              StrokeWidth = 1, TextSize = 30 
            }) { 
                  //Draw an N at the north 
                  canvas.DrawText("N", new SKPoint((info.Width / 2) + 10, 0 + 40), paintText); 
                  //draw a legend with the scale, close to the circle at 45 degrees 
                  canvas.DrawText(scale + " meters", new SKPoint((info.Width * 3 / 4) + 60, 
                                 (info.Height * 1 / 4) - 60), paintText);
               } 

      //point representing the second object 
      // canvas coordinates increase to the right 
      float x = (info.Width / 2) + (point.Item1 * size / 2); 
      // canvas coordinates increase to the bottom 
      float y = (info.Height / 2) - (point.Item2 * size / 2); 

      using (SKPaint paintPositionTwo = new SKPaint 
            { Style = SKPaintStyle.Fill, 
              Color = Color.Red.ToSKColor(), 
              StrokeWidth = 10 
             }) { 
                   canvas.DrawCircle(x, y, 20, paintPositionTwo); 
                } 
}

First, I clear the canvas.

The radar needs to be square and leave a margin relative to its parent. I calculate its dimension and call it side.

I draw a circle centered in the canvas and having half of the size as radius.

Next, I draw a vertical line horizontally centered keeping the margins and a horizontal line with similar conditions.

I draw a point in the middle to indicate my device position, I'm always in the center.

In order to draw a point to indicate the other object position, I need to calculate the distance between the two objects. I used Xamarin.Essentials library to handle the GPS of the device and to calculate distances.

Having this distance, I calculate the scale of the radar. I add a label with the estimated distance from me to the circle I previously draw.

Next, I need to calculate the distance between the two objects in the axis X and Y; before I calculated the direct distance - the hypotenuse. So, I use the same library that handles the GPS to calculate the distance between the two points projected to the same X and projected to the same Y. Also, these distances need to be adjusted to the current scale.

To locate the point for the other object, I start horizontally centered and add the distance in the X and vertically centered and remove the distance in the Y, because the X in the canvas grows to the right but the Y in the canvas grows to the bottom opposite to the Y axis normal behavior.

Finally, I draw a label N at the top indicating the north. Canvas could be rotated with information received from a compass if this feature is support by the device.

History

  • 26th October, 2018: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionSource Code Pin
POIHandler18-Aug-19 23:25
POIHandler18-Aug-19 23:25 
QuestionTry upload an image Pin
Alen Toma26-Oct-18 0:33
Alen Toma26-Oct-18 0:33 
Try upload an image so we could se the result

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.