Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Draw a Radar Chart using a Canvas

0.00/5 (No votes)
26 Oct 2018CPOL2 min read 8.8K  
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)