В данной статье мне хочется рассказать, как я визуализировал статистику по происходящей пандемии, и поделиться с какими особенностями можно встретиться, используя для этого Avalonia UI.



От автора
Я делал данный проект просто, что бы занять себя и не думать. Данный проект не претендует ни на какую достоверность. Если вам захочется — все распространяется под MIT лицензией и вы можете использовать мои наработки.

Введение


В этой статье подразумевается, что вы уже можете создать простое приложение с использованием AvaloniaUI. Если же нет, можно обратиться к туториалам AvaloniaUI или Habr.

Данные


В первую очередь встал вопрос где же взять актуальные данные и быстрым поиском в гугле было найдено следующее апи, которое удовлетворяло всем моим требованиям:

  1. Общая информация по миру
  2. Общая информация по каждой стране
  3. История по каждой стране

Структура приложения


Приложение состоит из 3х различных экранов.

Скрины






Перемещения между экранами построены при помощи роутинга. Интересной особенностью в данном моменте была передача тригера для роута в UserControl.

Для этого его ViewModel должна была принять Action:

 public CountriesVM(IScreen screen, Action<Country> a)

 CurrentCountyCommand = 
            CountriesCommand = ReactiveCommand.CreateFromObservable(
                () => Router.Navigate.Execute(new CountriesVM(this,GoToCurrentCountry))
                );
....
  public void GoToCurrentCountry(Country c)
        {
            CurrentCountyCommand.Execute(c);
        }

И вызовом этого экшена служит выбор элемента в списке:

public Country SelectedCountry
        {
            get => _country;
            set
            {
                this.RaiseAndSetIfChanged(ref _country, value);
                A.Invoke(value);
            }
        }

Запросы


Для обращений к апи была использована библиотека flurl. Которая внутри себя использует HttpClient и Newtosoft, а сама является расширением для строк.

По этому получение модельки из запроса можно реализовать в одну строку:

 var allCases = await "https://corona.lmao.ninja/all".GetJsonAsync<AllCases>();

Изображения


Для работы с изображением при помощи flurl получался stream который было необходимо упаковать в memorystream для создания экземпляра Bitmap:

private async Task<Bitmap> GetFlag(String flagUrl)
        {
            var flagStream = await flagUrl.GetStreamAsync();
            var memoryStream = new MemoryStream();
            flagStream.CopyTo(memoryStream);
            memoryStream.Position = 0;
            var bitmap = new Bitmap(memoryStream);
            return bitmap;
        }

Коллекция и фильтрация


Для отображения коллекции была использована Dinamic Data из ReactiveUI .

 SourceList<Country> countries = new SourceList<Country>();
 private ReadOnlyObservableCollection<Country> _collection;
 public ReadOnlyObservableCollection<Country> Collection => _collection;

countries.Connect().Filter(filter).ObserveOn(RxApp.MainThreadScheduler).Bind(out _collection).Subscribe();

Так же я решил добавить фильтр для быстрого поиска страны по ее имени или сокращению ISO3.

Для этого строка поиска была привязана к свойству:


        public string FilterName
        {
            get => _filterName;
            set { this.RaiseAndSetIfChanged(ref _filterName, value); }
        }

Добавлено правило фильтрации:

        private Func<Country, bool> Filter(string filterName)
        {
            if (string.IsNullOrEmpty(filterName)) return x => true;
            return x => x.Name.ToUpper().Contains(filterName.ToUpper()) || x.ISO3.Contains(filterName.ToUpper());
        }

И создан сам фильтр:

  var filter = this.WhenValueChanged(x => x.FilterName).Select(Filter);

Отображение графиков


Для графиков я воспользовался Oxyplot библиотекой для AvaloniaUI.

К сожалению в nuget версия устарела или я ее не нашел, так что вы можете воспользоваться собранным мной nuget пакетом, или утянуть исходники с github.

Для работы необходимо добавить в App.xaml:

<StyleInclude Source="resm:OxyPlot.Avalonia.Themes.Default.xaml?assembly=OxyPlot.Avalonia"/>

Для отображения оси с конкретной датой, а не произвольным числом (в которое oxyplot конвертирует дату для внутренней работы) необходимо указать тип оси в разметке:

           <avalonia:Plot.Axes>
               <avalonia:DateTimeAxis StringFormat="yyyy-MM-dd" Position="Bottom"></avalonia:DateTimeAxis>
           </avalonia:Plot.Axes>

Исходники и приложения


Используя dotnet publish я собрал бинарники для основных платформ (тут), а для тех, кому станет интереснее. исходники можно найти на github.

Вместо послесловия


Данное приложение базируется на стороннем апи, и надеюсь в скором времени данное апи перестанут поддерживать, ибо будет незачем.

Мойте ручки, и сидите дома. А поддержку по Авалонии можно найти тут.