Draw Your Own Pie Chart dynamically

In this tutorial, I’ll show you how to draw your own pie chart just using lines and a list of data items. The code itself is in C#, but the concept can be used on any platform that has a drawing library and ArcSegments.

You can view the entire solution in this GitHub Gist. Okay, let’s get started!

Setting Up

The first thing you’ll need to understand is how the platform’s drawing library makes arcs. In this code, I’ll be using RadPath from Telerik UI for Xamarin most libraries work the same way. RadPath lets you use a custom Geometry which has RadArcSegment object, which has helpful StartAngle and SweepAngle properties.

Let’s start with the data model, with simple Title and Value properties:

public class ChartDataPoint
{
    public string Title { get; set; }
    public double Value { get; set; }
}

Next, let’s create a list that is populated with some activities to represent a 24 hour period.

var dataPoints = new List<ChartDataPoint>
{
    new ChartDataPoint { Title = "Work", Value = 9 },
    new ChartDataPoint { Title = "Commute", Value = 1.5 },
    new ChartDataPoint { Title = "Leisure", Value = 6 },
    new ChartDataPoint { Title = "Sleep", Value = 7.5 },
};

Finally, to finish the setup, create a list of colors we can use for the pie slices.

var colors = new List<Color>
{
    Color.FromHex("#BAB65A"),
    Color.FromHex("#6196D1"),
    Color.FromHex("#3D4268"),
    Color.FromHex("#8A56E2"),
};

Part 1 – Creating the Slices

Now that we have some items, lets move on to generating and drawing the slices. First, we’ll create a container to put the pie and legend into. A Grid is convenient option because we can have two rows, one for the pie and one for the legend.

// Root container to hold the chart and any legend
var container = new Grid();
container.RowDefinitions.Add(new RowDefinition { Height = new GridLength(3, GridUnitType.Star) });
container.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });

Now we can start calulating the slice sizes, to do this, we need a total value to get a percentage of that total. We also need a variable to hold the current position on the 360 degree arc where the slices are rendered.

// Sum up all the values to be displayed
var totalValue = dataPoints.Sum(d => d.Value);

// Variable to keep track of where each slice ended.
double currentPosition = 0;

Now for the guts of the operation. We need iterate over the data points and create the arc segments using the data point’s Value property. The code comments will guide you through what each line does, in a nustshell here’s the lifecycle

  • Calculate the data item’s percentage of the total sum
  • Use that percentage to get what percent of 360 degree pie that slice needs
  • Create the RadArcSegment using the current position for the StartAngle and the angle percentage as the SweepAngle
  • Construct the RadPath using the ArgSegment’s geometry and a color from the colors list
// Iterate over the data points to create slices.
for (int i = 0; i < dataPoints.Count; i++)
{
    // Determine the what percentage that data item's value is of the whole
    double slicePercentage = dataPoints[i].Value / totalValue;

    // Calculate the sweep angle using that percentage amount.
    double sweep = slicePercentage * 360;

    // Create the ArcSegment using the current position and sweep
    var segment = new RadArcSegment
    {
        Center = new Point(0.5, 0.5),
        Size = new Size(1, 1),
        StartAngle = currentPosition,
        SweepAngle = sweep,
    };

    // Important - Calculate the last segment's ending angle in order to have a valid start angle for the next loop.
    currentPosition = currentPosition + sweep - 360;

    // Prepare the required PathFigure and add the ArcSegment
    var figure = new RadPathFigure { StartPoint = new Point(0.5, 0.5) };
    figure.Segments.Add(segment);

    // Create the PathGeometry and add the PathFigure
    var geometry = new RadPathGeometry();
    geometry.Figures.Add(figure);

    // Construct the RadPath
    // - Select a Fill color from the brushes parameter (important: use a modulus to wrap to the beginning)
    // - Use the Geometry created from the value
    var slice = new RadPath
    {
        Fill = new RadSolidColorBrush(colors[i % colors.Count]),
        Geometry = geometry,
        HorizontalOptions = LayoutOptions.Center,
        VerticalOptions = LayoutOptions.Center,
        WidthRequest = 100,
        HeightRequest = 100,
        Margin = new Thickness(0, 20, 0, 0)
    };

    // This isn't necessary, but added for completion.
    Grid.SetRow(slice, 0);

    // Finally, add it to the container.
    container.Children.Add(slice);
}

At this point, you now have a full 360 degree chart, with colored slices for each of the data points representing their percentage of the whole.

Part 2 – Creating the Legend

The next phase of the operation is to create the legend. This needs to create text for each slice, as well as a marker that matches the same color of that slice. This could have been done in the same loop as creating the slice, but having a separate loop lets you decide to use a legend or not.

Again, we iterate over the data points. This time we use the Title property of the data point to create the text. To match the color, we use the same modulus to get index and assign it to a RadBorder that creates a thick bar underneath the text.

// Create a horizontal StackLayout to hold the legend items
var legendPanel = new StackLayout
{
    Orientation = StackOrientation.Horizontal,
    HorizontalOptions = LayoutOptions.Center,
    VerticalOptions = LayoutOptions.Center,
    Margin = new Thickness(0, 16, 0, 0),
    Spacing = 5
};

// Iterate over the data points and create a legend item with a matching color
for (int i = 0; i < dataPoints.Count; i++)
{
    // Use a RadBorder with only a bottom thickness and match the color to the slice
    var legendItem = new RadBorder
    {
        BorderColor = colors[i % colors.Count],
        BorderThickness = new Thickness(0, 0, 0, 2)
    };

    // Create a Label for each data point and use the Title property
    var label = new Label
    {
        Text = dataPoints[i].Title,
        FontSize = 12,
        Margin = new Thickness(0, 0, 0, 2),
        TextColor = Color.DimGray
    };

    legendItem.Content = label;

    legendPanel.Children.Add(legendItem);
}

// Insert the legend panel in the root container's 2nd row.
Grid.SetRow(legendPanel, 1);
container.Children.Add(legendPanel);

The last thing to do is add the entire container to the UI. In this example, I’m just setting the entire page’s content to the container Grid..

this.Content = container;

Wrapping Up

I hope this is useful for those times when you need ultimate control over rendering of a chart. If you need more complex setup, I recommend the RadPieChart itself, which is far more feature complete than drawing a few arcs ūüôā

Custom Events and Xamarin.Forms Effect

This post will walk you through setting up a Xamarin Platform Effect and show you how to wire up an event handler so you can leverage native APIs in the case that the Xamarin.Forms wrapper doesn’t yet have. For this example, I’ll use the Telerik UI for Xamarin RadCalendar.

tl;dr Full source code on GitHub here.

I get frequently asked how to enable RangeSelection (aka Multiple Selection) for the RadCalendar. The native platform calendar controls all have Range Selection available, so we only need to create a  Platform Effect to access that native feature.

Note: If you’re not familiar with Effects,¬†I recommend visiting this article first to understand the fundamentals (it’s a quick tutorial).

The main things I need are:

  • DateTime StartDate
  • DateTime EndDate
  • DateRangeChangedEventArgs (class)
  • DateRangeChanged (event)
  • DateRangeChanged (delegate)

I’ll walk you through the different parts below, you can also see just the relevant classes in this GitHub Gist or get everything in this GitHub repo

Class Library

First, let’s get the event args out of the way because we’ll need this defined before writing the Effect.

(view code) Portable/Effects/DateRangeChangedEventArgs.cs

2018-07-16_2020

Now we can move on to the Effect definition that lives in the class library project.¬† The class defines the rest of items I listed above, I’ve called out how the event is invoked, thus subscribers to the vent will have their event handlers executed.

(view code) Portable/Effects/RangeSelectionEffect.Forms.cs

2018-07-16_2020

With this set up , we can add the Effect to the Xamarin.Forms XAML RadCalendar instance:

(view code) Portable/MainPage.xaml

2018-07-16_2033

Code behind, this is just to set the start and end dates to test the Effect:

(view code) Portable/MainPage.xaml.cs

2018-07-16_2035

But we can’t run it just yet. It’s time to implement the native Effect classes., it’s where the magic happens. I’ll go through each platform separately instead of hitting you over the head with it all at once.

UWP

The calendar control for UWP is the UI for UWP RadCalendar.  In the documentation, we can see it supports multiple selection by flipping the SelectionMode flag to Multiple.

The next thing to consider is how to actually set the date range. This is done using the SelectedDateRange property to an instance of CalendarDateRange. This is what we needed the event for! You’ll see that when the Effect is Attached to the control

(view code) UWP/Effects/RangeSelectionEffect.Uwp.cs

2018-07-16_2018

iOS

For iOS the native control is a UI for Xamarin.iOS TKCalendar and in a similar fashion as UWP, we find the Selection modes to support Range Selection.

Notice we need to use the native control’s selection property and convert from DateTime to NSDate

(view code) iOS/Effects/RangeSelectionEffect.iOS.cs

2018-07-16_2029

Android

And finally, the same approach is used for the native Android calendar, UI for Xamarin.Android RadCalendarView.

Notice we need to use the native control’s selection property and convert from DateTime to Java Calendar

(view code) Android/Effects/RangeSelectionEffect.Android.cs

2018-07-16_2030

And that’s it! All three platform’s Calendar control now will show range selection.

Disclaimer

This isn’t production-ready code, it’s proof of concept and there are no defensive techniques in place (i.e. try/catch). I wanted to keep it as simple as possible to focus on the concepts.

If you want to use the code for your app, I’m happy I could help (MIT license). Just please don’t copy-paste it all and call it a day, then ping me on Twitter later and say “it’s broken!”.¬† The native platform logic should be inside try catch blocks and I would make sure the DateTime conversions are accurate for your needs.

Enjoy!

 

 

 

Epic Build 2018 Playlists

Your one-stop shop for Microsoft Build 2018 sessions, slide decks, post links, keynotes and much more.

Living Document Note:

These Playlists will continue to be reordered and updated in real time as new content becomes available. However, the links will always remain the same and you can refresh the playlist page to get any updated ordering/content.

Migrating from PCLStorage to .NET Standard 2.0

If you’re a Xamarin Forms developer, you’ve likely used PCLStorage (or other Dependency Service) to interact with the target platform’s file system in the portable class library’s code. However, since November 2017, Xamarin.Forms now uses a .NET Standard 2.0 class library and PCLStorage is no longer supported.

System.IO.File

This isn’t a problem because in .NET Core 2.0 you now have access to¬†System.IO.File’s GetFolderPath and SpecialFolder methods (see¬†System.IO.File in .NET Core 2.0 docs).

Thus, you can get a path to the app’s local folder (the one that the app can save to) without having to write a Dependency Service (which is what PCLStorage does) by using:

var localFolder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);

 

IMPORTANT: Make sure you always use Path.Combine to create the file path because different platforms use different path separators

var filePath = Path.Combine(LocalFolder, "notes.txt");

 

With a reference to the file path,  you can now access the file. For example reading the text:

var notes = File.ReadAllText(filePath);

Functional Demo

As a very simple example, I created a couple extension methods for Stream and Byte[] in the below FileExtensions class.

To test it, I created a ContentPage that downloads an image, saves it using the extension method and set a FileImageSource to confirm that it’s a file (instead of just using a StreamImageSource).

 

https://gist.github.com/LanceMcCarthy/693ab82aa498cadf75e9fe778c4242ad

 

Note that the extension methods are very basic and shouldn’t be used in production as-is (i.e. no defensive programming code).

Here is the result at runtime on UWP:

2018-04-19_1134

Custom Themes in UI for Xamarin

Telerik UI for Xamarin comes with two themes out of the box, Default and Blue. You can easily style individual items, but you can also define an entire theme at once in a separate ResourceDictionary.

With this approach you can swap out ResourceDictionaries at runtime for a nice user-selected theme feature in your app’s settings. Let me walk you through a very simple demo to illustrate the approach.

Step 1. Create your custom theme’s ResourceDictionary

Xamarin.Forms doesn’t have a good ResourceDictionary template, so start with a Content Page (XAML) template and then change the ContentPage type to “ResourceDictionary” in both the XAML and code-behind.

Now, you can add in all the styles and colors you want in that one dictionary. You can find the Color names we use for your controls in the Themes Overview documentation.

In the custom dark theme example below, the Color resource key values are pretty clear as to what they do.

https://gist.github.com/LanceMcCarthy/ed6d37d8706182a0419edca635262349

Step 2. ThemeHelper Class

I usually like to create a static class like this so that I can change the theme from anywhere in the app, but isn’t necessary. You can put this logic entirely in your settings page if you’d prefer.

https://gist.github.com/LanceMcCarthy/67c88a7f9945100f0cb016857dbc3ddd

You may have noticed that I’m using a RadResourceDictionary. We created this when Xamarin.Forms didn’t have MergeWith support. You can use a Xamarin.Forms ResourceDictionary type if you’re using a newer version that supports it.

Step 3. Runtime

I’m using a RadSegmentedControl to change the theme, and a RadListView to easily see the difference between themes. You can of course use whatever UI control you’d like to change themes, for example a Picker.

The important thing is that you call the helper class’s¬†ChangeTheme method and pass the theme name that you need to change to:

https://gist.github.com/LanceMcCarthy/c44d0f22f71b04776eb9b3aa4037519c

That’s it!

When selecting one of the options, you’ll get one of the three results you see in this article’s header image.

If you have any questions feel free to reach out to me on Twitter at @lancewmccarthy.