Creating a COVID-19 Data Visualization with Symfony UX
Tags: tutorial, symfony, php, javascript
Published: 2021-01-14 12:46:03

- [Creating the Project](#create-project)
- [Lauching the Local Web Server](#launch-local-web-server)
- [Installing Webpack Encore](#install-webpack-encore)
- [Installing UX Chart.js](#install-ux-chart)
- [Creating the Covid-19 Http Client](#create-covid-http-client)
- [Creating the Covid Controller](#create-covid-controller)
- [Rendering the Chart](#render-chart)

-------

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](https://live.symfony.com/account/replay/video/515) and [Titouan’s talk](https://live.symfony.com/account/replay/video/530).

For now, Symfony provides [5 packages](https://github.com/symfony/ux):
- UX Chart.js
- UX Cropper.js
- UX Dropzone
- UX LazyImage
- UX Swup

In this tutorial, I will introduce you to the [UX Chart.js](https://github.com/symfony/ux-chartjs) package by graphing some COVID-19 data with the [Chart.js](https://www.chartjs.org/) 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](https://covid19api.com).

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/9rsnqoy8h1rpt522syeh.png)

Creating the project <a name="create-project"></a>
--------------------
First of all, we need to set up and configure a project: 

```
$ symfony new covid --full
$ cd covid/
```

Launching the Local Web Server <a name="launch-local-web-server"></a>
------------------------------

Start a local web server executing the command:

```
$ symfony server:start
```

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

Installing Webpack Encore <a name="install-webpack-encore"></a>
-------------------------
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
```

Symfony now integrates [Stimulus](https://stimulus.hotwire.dev) 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 <a name="install-ux-chart"></a>
----------------------
Let's install our first UX package:

```
$ symfony composer req symfony/ux-chartjs
```

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

```json
{
    "devDependencies": {
        "@symfony/ux-chartjs": "file:vendor/symfony/ux-chartjs/Resources/assets"
    },
}
```

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

```json
{
    "controllers": {
        "@symfony/ux-chartjs": {
            "chart": {
                "enabled": true,
                "webpackMode": "eager"
            }
        }
    },
    "entrypoints": []
}
```

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

```
$ yarn install
$ yarn encore dev
```

Now, the UX package is ready. 

Creating the Covid-19 Http Client <a name="create-covid-http-client"></a>
---------------------------------

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
```
*$country must be the slug from https://api.covid19api.com/countries.*

Symfony provides a [HttpClient](https://symfony.com/doc/current/http_client.html) component to consume APIs. Add a scoped client to auto-configure the client based on the requested URL:

```yaml
# config/packages/framework.yaml
framework:
   http_client:
        scoped_clients:
            covid:
                base_uri: https://api.covid19api.com
```

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
<?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;
    }
}
```

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 <a name="create-covid-controller"></a>
---------------------------------

Create the controller using the Maker bundle:

```
symfony console make:controller CovidController
```

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
<?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
        ]);
    }
}
```

You can read [Chart.js documentation](https://www.chartjs.org/docs/latest/) to discover all options.

Rendering the Chart <a name="render-chart"></a>
-------------------

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

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

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

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:
- http://localhost:8000/australia
- http://localhost:8000/france
- http://localhost:8000/united-states