Copied RSS Feed

.NET MAUI

Visualize Multi-Dimensional Data Using the .NET MAUI Parallel Coordinate Chart

TL;DR: Let’s see how to create a Parallel Coordinate Chart using the .NET MAUI Toolkit Charts. This chart is ideal for visualizing multi-dimensional data, making it easy to compare multiple variables. We’ll cover data collection, axis setup, series customization, and visual styling. 

Welcome to the latest edition of the Chart of the Week blog series!

Today, we’ll explore an exciting data visualization technique: the Parallel Coordinate Chart, using Syncfusion .NET MAUI Toolkit Cartesian Chart. This chart is ideal for analyzing high-dimensional datasets, making it a powerful tool for data scientists and developers. By the end of this tutorial, you’ll have a fully functional Parallel Coordinate Chart that you can integrate into your .NET MAUI apps to visualize complex relationships among multiple variables.

What is a parallel coordinate chart?

A parallel coordinate chart is a visualization technique used to plot multi-dimensional data. Unlike traditional charts that represent data in two dimensions, this chart allows you to compare multiple numerical variables simultaneously. Each variable is represented on its own vertical axis, and data points are connected across these axes to form patterns that highlight correlations and trends, making it easier to identify clusters, patterns, or outliers.

Key reasons to use a parallel coordinate chart

  • Handles high-dimensional data: Effectively displays datasets with multiple variables, simplifying patterns and correlation analysis.
  • Identifies trends and anomalies: Aids in spotting trends, clusters, and unusual data points across different parameters.
  • Compares multiple categories simultaneously: Ideal for comparing different items (e.g., airlines, stocks, sensors) based on multiple attributes.
  • Enhances decision-making: Offers a comprehensive view of data, enabling analysts to make informed decisions quickly.
  • Used in various industries: Common in finance, healthcare, aviation, and environmental studies for real-time data analysis.

Use cases of parallel coordinate charts

  • Flight performance monitoring: Track flight delays, fuel usage, and passenger load in real time.
  • Cryptocurrency market analysis: Compare multiple cryptocurrencies based on price, volume, and volatility.
  • Air quality and pollution tracking: Monitor pollution levels (CO, NO2, PM2.5, PM10) across cities.

The following image shows the parallel coordinate chart that we intend to create.

Let’s get started!

Step 1: Collecting data

Here, we’ll visualize trends in flight operations and punctuality using the .NET MAUI Parallel Coordinate Chart. For this, we’ll collect the data from Year-to-date airline on-time performance for each month report.

Step 2: Create a custom .NET MAUI Parallel Coordinate Chart

Now, create a custom ParallelCoordinateChart class by inheriting it from the SfCartesianChart. Initialize the chart setup using the GenerateChart method. 

public class ParallelCoordinateChart : SfCartesianChart
{
    public ParallelCoordinateChart()
    {
        GenerateChart();
    }
}

Step 3: Configure the axes, series, and visual styling

Define the GenerateChart() method to manage the complete chart setup, including the configuration of axes, series, and visual styles such as color palettes.

private void GenerateChart()
{
    var xAxes = GenerateXAxes();
    var yAxes = GenerateYAxesList();
    this.XAxes.Add(xAxes);
    foreach (var yaxis in yAxes)
    {
        this.YAxes.Add(yaxis);
    }

    var paletteBrushes = new List<Brush>
    {
        new SolidColorBrush(Color.FromArgb("#800080")),
        new SolidColorBrush(Color.FromArgb("#6495ED")),
        new SolidColorBrush(Color.FromArgb("#32CD32")),
        new SolidColorBrush(Color.FromArgb("#FFD700")),
        new SolidColorBrush(Color.FromArgb("#FF6347")),
        new SolidColorBrush(Color.FromArgb("#8A2BE2")),
    };
    this.PaletteBrushes = paletteBrushes;

    var seriesList = GenerateSeries(yAxes);
    foreach (var series in seriesList)
    {
        this.Series.Add(series);
    }
}

Step 4: Generate the .NET MAUI Parallel Coordinate Chart axes

To create a Parallel Coordinate Chart, we need to define both the XAxes and YAxes.

The X-axes represent different variables, while the Y-axes represent their respective values.

Refer to the following code example to generate the X-axes for the chart.

private NumericalAxis GenerateXAxes()
{
    var xAxes = new NumericalAxis()
    {
        Minimum = 0,
        Maximum = 6,
        Interval = 1,
        ShowMajorGridLines = false,
        PlotOffsetStart = 50,
        PlotOffsetEnd = 50,
        CrossesAt = double.MaxValue,
        EdgeLabelsDrawingMode = EdgeLabelsDrawingMode.Center,
        AxisLineStyle = new ChartLineStyle()
        {
            StrokeWidth = 0,
        }
    };

    xAxes.LabelCreated += (s, e) =>
    {
        e.Label = e.Position switch
        {
            0 => "Year",
            1 => "Operations",
            2 => "On-Time Arrivals",
            3 => "Late Arrivals",
            4 => "Late Departures",
            5 => "Cancelled",
            6 => "Diverted",
            _ => string.Empty
        };
    };

    return xAxes;
}

Refer to the following code example to generate Y-axes for the chart.

private List<NumericalAxis> GenerateYAxesList()
{
    var viewModelData = new ViewModel().Source;
    var firstItem = viewModelData.First();
    var properties = firstItem.GetType().GetProperties();
    var list = new List<NumericalAxis>();

    foreach (var property in properties)
    {
        var yAxis = new NumericalAxis()
        {
            ShowMajorGridLines = false,
            LabelsPosition = AxisElementPosition.Outside,
        };

        switch (property.Name)
        {
            case "Year":
                yAxis.Minimum = 0;
                yAxis.Maximum = 28;
                yAxis.Interval = 4;
                yAxis.CrossesAt = 0;
                yAxis.EdgeLabelsDrawingMode = EdgeLabelsDrawingMode.Fit;

                string[] years = Enumerable.Range(1995, 29).Select(y => y.ToString()).ToArray();

                yAxis.LabelCreated += (s, e) =>
                {
                    if (e.Position >= 0 && e.Position < years.Length)
                    {
                        e.Label = years[(int)e.Position];
                    }
                    else
                    {
                        e.Label = string.Empty;
                    }
                };
                break;

            case "Operations":
                yAxis.Minimum = 4600000;
                yAxis.Maximum = 7500000;
                yAxis.Interval = 500000;
                yAxis.CrossesAt = 1;
                yAxis.LabelStyle = new ChartAxisLabelStyle()
                {
                    LabelFormat = "#,##0,K"
                };
                yAxis.EdgeLabelsDrawingMode = EdgeLabelsDrawingMode.Fit;
                yAxis.EdgeLabelsVisibilityMode = EdgeLabelsVisibilityMode.AlwaysVisible;
                break;

            case "PercentOnTimeArrivals":
                yAxis.Minimum = 70;
                yAxis.Maximum = 90;
                yAxis.Interval = 5;
                yAxis.CrossesAt = 2;
                yAxis.EdgeLabelsDrawingMode = EdgeLabelsDrawingMode.Fit;
                yAxis.EdgeLabelsVisibilityMode = EdgeLabelsVisibilityMode.AlwaysVisible;
                break;

            case "PercentLateArrivals":
                yAxis.Minimum = 8;
                yAxis.Maximum = 26;
                yAxis.Interval = 2;
                yAxis.CrossesAt = 3;
                yAxis.EdgeLabelsDrawingMode = EdgeLabelsDrawingMode.Fit;
                yAxis.EdgeLabelsVisibilityMode = EdgeLabelsVisibilityMode.AlwaysVisible;
                break;

            case "PercentLateDepartures":
                yAxis.Minimum = 8;
                yAxis.Maximum = 22;
                yAxis.Interval = 2;
                yAxis.CrossesAt = 4;
                yAxis.EdgeLabelsDrawingMode = EdgeLabelsDrawingMode.Fit;
                yAxis.EdgeLabelsVisibilityMode = EdgeLabelsVisibilityMode.AlwaysVisible;
                break;

            case "PercentCancelled":
                yAxis.Minimum = 1;
                yAxis.Maximum = 7;
                yAxis.Interval = 1;
                yAxis.CrossesAt = 5;
                yAxis.EdgeLabelsDrawingMode = EdgeLabelsDrawingMode.Fit;
                yAxis.EdgeLabelsVisibilityMode = EdgeLabelsVisibilityMode.AlwaysVisible;
                break;

            case "PercentDiverted":
                yAxis.Minimum = 0.1;
                yAxis.Maximum = 0.3;
                yAxis.Interval = 0.02;
                yAxis.CrossesAt = 6;
                yAxis.LabelsPosition = AxisElementPosition.Inside;
                yAxis.TickPosition = AxisElementPosition.Inside;
                yAxis.EdgeLabelsDrawingMode = EdgeLabelsDrawingMode.Fit;
                yAxis.EdgeLabelsVisibilityMode = EdgeLabelsVisibilityMode.AlwaysVisible;
                break;
        }

        list.Add(yAxis);
    }
    return list;
}

Step 5: Generate the series

Then, create multiple LineSeries to represent multi-dimensional data, ensuring the values are normalized.

private List<LineSeries> GenerateSeries(List<NumericalAxis> yAxes)
{
    var viewModel = new ViewModel();
    var seriesList = new List<LineSeries>();

    foreach (var chartModel in viewModel.DataSource)
    {
        var itemSource = new ObservableCollection<SeriesModel>();

        for (int axisIndex = 0; axisIndex < yAxes.Count; axisIndex++)
        {
            double yValue = 0;

            if (chartModel.Variable.Count == 0) continue;
            var model = chartModel.Variable[0] as FlightDataModel;
            if (model == null) continue;

            switch (axisIndex)
            {
                case 0:
                    yValue = Normalize(YearIndex(model.Year), 0, 28);
                    break;
                case 1:
                    yValue = Normalize(model.Operations, 4600000, 7500000);
                    break;
                case 2:
                    yValue = Normalize(model.PercentOnTimeArrivals, 70, 90);
                    break;
                case 3:
                    yValue = Normalize(model.PercentLateArrivals, 8, 26);
                    break;
                case 4:
                    yValue = Normalize(model.PercentLateDepartures, 8, 22);
                    break;
                case 5:
                    yValue = Normalize(model.PercentCancelled, 1, 7);
                    break;
                case 6:
                    yValue = Normalize(model.PercentDiverted, 0.1, 0.3);
                    break;
            }
            itemSource.Add(new SeriesModel(axisIndex, yValue));
        }

        var series = new LineSeries()
        {
            ItemsSource = itemSource,
            XBindingPath = nameof(SeriesModel.XValues),
            YBindingPath = nameof(SeriesModel.YValues),
        };

        seriesList.Add(series);
    }

    return seriesList;
}

private double Normalize(double value, double min, double max)
{
    if (max == min) return 0;
    return (((value - min) / (max - min)) * 28);
}

private int YearIndex(string year)
{
    return int.TryParse(year, out int yearInt) ? yearInt - 1995 : -1;
}

Step 6: Define the ViewModel

The ViewModel is responsible for generating the data used in the .NET MAUI Parallel Coordinate Chart.

public class ViewModel
{
    public ObservableCollection<ChartModel> DataSource { get; set; }

    public ObservableCollection<FlightDataModel> Source { get; set; }

    public ViewModel()
    {
        Source = new ObservableCollection<FlightDataModel>(ReadCSV());
        DataSource = new ObservableCollection<ChartModel>();
        foreach (var data in Source)
        {
            DataSource.Add(new ChartModel(new List<object> { data }));
        }
    }

    private IEnumerable<FlightDataModel> ReadCSV()
    {
        Assembly executingAssembly = typeof(App).GetTypeInfo().Assembly;
        Stream inputStream = executingAssembly.GetManifestResourceStream("ParallelCoordinateChart.Resources.Raw.data.csv");

        string line;
        List<string> lines = new List<string>();

        using StreamReader reader = new StreamReader(inputStream);
        while ((line = reader.ReadLine()) != null)
        {
            lines.Add(line);
        }

        return lines.Skip(1).Select(line =>
        {
            string[] data = line.Split(',');

            return new FlightDataModel
            {
                Year = data[0],
                Operations = Convert.ToDouble(data[1]),
                PercentOnTimeArrivals = Convert.ToDouble(data[2]),
                PercentLateArrivals = Convert.ToDouble(data[3]),
                PercentLateDepartures = Convert.ToDouble(data[4]),
                PercentCancelled = Convert.ToDouble(data[5]),
                PercentDiverted = Convert.ToDouble(data[6])
            };
        });
    }
}

Step 7: Add the .NET MAUI Parallel Coordinate Chart to the XAML page

Finally, integrate the ParallelCoordinateChart into your XAML page to complete the setup.

<ContentPage.Content>
    <Border StrokeThickness="4" Margin="10" Stroke="Gray">
        <Border.StrokeShape>
            <RoundRectangle Fill="Black" CornerRadius="10" />
        </Border.StrokeShape>
        <local:ParallelCoordinateChart Margin="5,10,0,10">
            <local:ParallelCoordinateChart.Title>
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="{OnPlatform Android=68,Default=80,iOS=68}"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="55"/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <Image Grid.Column="0"  
                       Grid.RowSpan="2" 
                       Source="world.png"
                       Margin="{OnPlatform Default='0,-25,0,0', iOS='0,-15,0,0', Android='0,-15,0,0'}"
                       HeightRequest="70"
                       WidthRequest="50"/>
                    <StackLayout Grid.Column="1" 
                             Grid.Row="0" 
                             Margin="7,7,0,0">
                        <Label Text="Trends in Flight Operations and Punctuality (1995-2023)"   
                           FontSize="{OnPlatform Android=12,Default=16,iOS=12}"                     
                           FontAttributes="Bold" 
                           FontFamily="centurygothic"
                           TextColor="Black"/>
                        <Label Text="This chart visualizes changes in flight operations, on-time arrivals, delays, cancellations, and diversions from 1995 to 2023." 
                           FontSize="{OnPlatform Android=10,Default=12,iOS=10}" 
                           FontFamily="centurygothic"
                           TextColor="Black" 
                           Margin="0,2,0,0"/>
                    </StackLayout>
                </Grid>
            </local:ParallelCoordinateChart.Title>
        </local:ParallelCoordinateChart>
    </Border>
</ContentPage.Content>

After executing the above code examples, we’ll get the output that resembles the following image.

Visualize multi-dimensional data with .NET MAUI Parallel Coordinate Chart

Tips for effective use of parallel coordinate chart

  • Color coding: Use colors to represent different categories or clusters within your data.
  • Axis scaling: Ensure axes are scaled appropriately to reflect data accurately.
  • Legends and labels: Include clear labeling and a legend for better interpretability.

Best practices for a parallel coordinate chart

  • Limit data points: Avoid clutter by filtering or sampling large datasets.
  • Arrange axes logically: Place related variables next to each other for better readability.
  • Apply transparency and colors: Use semi-transparent lines and colors to distinguish categories.
  • Normalize data: Convert variables to a common scale for accurate comparisons.
  • Remove unnecessary axes: Keep only relevant variables to simplify the chart.

GitHub reference

For more details, refer to the visualizing multi-dimensional data with .NET MAUI Parallel Coordinate Chart GitHub demo.

Conclusion

Thank you for reading! We’ve explored how to create a Parallel Coordinate Chart using the Syncfusion .NET MAUI Toolkit Charts. Share your feedback in the comments, and if you need help, contact us through our support forum, support portal, or feedback portal. We are always eager to help you! 

Meet the Author

Ezhilarasan Elangovan

Ezhilarasan Elangovan has been a .NET developer at Syncfusion since 2023, working with custom controls for Microsoft technologies.