Creating a COVID-19 Data Visualization with Symfony UX

qferrer

Quentin Ferrer

Posted on January 14, 2021

Creating a COVID-19 Data Visualization with Symfony UX

At the beginning of December, Symfony started the keynote with the presentation of Symfony UX, a new JavaScript ecosystem for Symfony.

Symfony UX is a series of tools to create a bridge between Symfony and the JavaScript ecosystem.

To get a full overview of the initiative, you can also watch the Symfony World replay, especially the Fabien’s keynote and Titouan’s talk.

For now, Symfony provides 5 packages:

  • UX Chart.js
  • UX Cropper.js
  • UX Dropzone
  • UX LazyImage
  • UX Swup

In this tutorial, I will introduce you to the UX Chart.js package by graphing some COVID-19 data with the Chart.js library. To do this, we will create a line chart that will display the total number of cases and deaths by country from a free Covid-19 API.

Alt Text

Creating the project

First of all, we need to set up and configure a project:

$ symfony new covid --full
$ cd covid/
Enter fullscreen mode Exit fullscreen mode

Launching the Local Web Server

Start a local web server executing the command:

$ symfony server:start
Enter fullscreen mode Exit fullscreen mode

For the tutorial, we will suppose that the webserver is listening to http://localhost:8000.

Installing Webpack Encore

As we will use a JavaScript library, we need to manage JavaScript in Symfony using Webpack:

$ symfony composer req symfony/webpack-encore-bundle
$ yarn install
Enter fullscreen mode Exit fullscreen mode

Symfony now integrates Stimulus to organize JavaScript code inside projects. If you take a look at the assets/ directory, you can see a new JavaScript directory structure:

  • controllers/: it contains Stimulus controllers of the application. They are automatically registered in app.js,
  • controllers.json: it references Stimulus controllers provided by installed Symfony UX packages.

Installing UX Chart.js

Let's install our first UX package:

$ symfony composer req symfony/ux-chartjs
Enter fullscreen mode Exit fullscreen mode

Symfony Flex has just added a reference to the Javascript code of UX-Chart.js in the package.json:

{
    "devDependencies": {
        "@symfony/ux-chartjs": "file:vendor/symfony/ux-chartjs/Resources/assets"
    },
}
Enter fullscreen mode Exit fullscreen mode

Symfony Flex also added a reference to the Stimulus controller of UX-Chart.js in the assets/controllers.json:

{
    "controllers": {
        "@symfony/ux-chartjs": {
            "chart": {
                "enabled": true,
                "webpackMode": "eager"
            }
        }
    },
    "entrypoints": []
}
Enter fullscreen mode Exit fullscreen mode

Because of these changes, we now need to install the new JavaScript dependencies and compile the new files:

$ yarn install
$ yarn encore dev
Enter fullscreen mode Exit fullscreen mode

Now, the UX package is ready.

Creating the Covid-19 Http Client

Thanks to a free Covid-19 API (https://api.covid19api.com), we will able to fetch the total number of cases and deaths by country by using the following endpoint:

GET https://api.covid19api.com/total/country/$country
Enter fullscreen mode Exit fullscreen mode

$country must be the slug from https://api.covid19api.com/countries.

Symfony provides a HttpClient component to consume APIs. Add a scoped client to auto-configure the client based on the requested URL:

# config/packages/framework.yaml
framework:
   http_client:
        scoped_clients:
            covid:
                base_uri: https://api.covid19api.com
Enter fullscreen mode Exit fullscreen mode

The covid client will have a unique service named covid.

Create a CovidHttpClient service that will be responsible for fetching the total number of cases and deaths by country and group all by date.

<?php

namespace App\HttpClient;

use Symfony\Contracts\HttpClient\HttpClientInterface;

/**
 * Class CovidHttpClient
 * @package App\Client
 */
class CovidHttpClient
{
    /**
     * @var HttpClientInterface
     */
    private $httpClient;

    /**
     * CovidHttpClient constructor.
     *
     * @param HttpClientInterface $covid
     */
    public function __construct(HttpClientInterface $covid)
    {
        $this->httpClient = $covid;
    }

    /**
     * Get total number of cases and deaths by the given country.
     * 
     * @param string $country
     * 
     * @return array
     */
    public function getTotalByCountry(string $country): array
    {
        $response = $this->httpClient->request('GET', "/total/country/$country");
        $data = json_decode($response->getContent(), true);

        $total = [];

        foreach ($data as $dailyData) {
            $date = (new \DateTime($dailyData['Date']))->format('Y-m-d');
            $total[$date] = $dailyData;
        }

        return $total;
    }
}
Enter fullscreen mode Exit fullscreen mode

As we have an argument $covid as HttpClientInterface type, autowiring inject the covid service into the class.

We are now ready to build the chart.

Creating the Covid Controller

Create the controller using the Maker bundle:

symfony console make:controller CovidController
Enter fullscreen mode Exit fullscreen mode

The command creates a CovidController class under the src/Controller/ directory and a template file to templates/covid/index.html.twig.

In the CovidController, implement the index() method:

  • Fetch the total number of cases and deaths by country using the CovidHttpClient service and group all by status;
  • Create a Chart object by using the ChartBuilderInterface builder;
  • Set the data (labels & datasets) to the Chart object;
  • Finally, pass the Chart object to the Twig template covid/index.html.twig.
<?php

namespace App\Controller;

use App\HttpClient\CovidHttpClient;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\UX\Chartjs\Builder\ChartBuilderInterface;
use Symfony\UX\Chartjs\Model\Chart;

class CovidController extends AbstractController
{
    /**
     * @Route("/{country}", name="covid")
     */
    public function index(CovidHttpClient $covidClient, ChartBuilderInterface $chartBuilder, $country = 'france'): Response
    {
        $total = $covidClient->getTotalByCountry($country);
        $totalByStatus = [];
        foreach ($total as $dailyTotal) {
            $totalByStatus['confirmed'][] = $dailyTotal['Confirmed'];
            $totalByStatus['deaths'][] = $dailyTotal['Deaths'];
            $totalByStatus['recovered'][] = $dailyTotal['Recovered'];
            $totalByStatus['active'][] = $dailyTotal['Active'];
        }

        $chart = $chartBuilder->createChart(Chart::TYPE_LINE);
        $chart
            ->setData([
                'labels' => array_keys($total),
                'datasets' => [
                    [
                        'label' => 'Confirmed',
                        'backgroundColor' => 'rgb(120, 161, 187, 0.5)',
                        'data' => $totalByStatus['confirmed']
                    ],
                    [
                        'label' => 'Death',
                        'backgroundColor' => 'rgb(219, 80, 74, 0.5)',
                        'data' => $totalByStatus['deaths']
                    ],
                    [
                        'label' => 'Recovered',
                        'backgroundColor' => 'rgb(147, 196, 139, 0.5)',
                        'data' => $totalByStatus['recovered']
                    ],
                    [
                        'label' => 'Active',
                        'backgroundColor' => 'rgb(252, 191, 73, 0.5)',
                        'data' => $totalByStatus['active']
                    ]
                ]
            ]);

        return $this->render('covid/index.html.twig', [
            'chart' => $chart,
            'country' => $country
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

You can read Chart.js documentation to discover all options.

Rendering the Chart

The last step is to update the templates/covid/index.html.twig file:

{% extends 'base.html.twig' %}

{% block body %}
    <h1>Total number of cases and deaths in {{ country|capitalize }}</h1>
    {{ render_chart(chart) }}
{% endblock %}
Enter fullscreen mode Exit fullscreen mode

It's done! Go to the homepage by specifying the country parameter. The list of countries is available on https://api.covid19api.com/countries.
Here are some examples:

💖 💪 🙅 🚩
qferrer
Quentin Ferrer

Posted on January 14, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related