DEV Community

Cover image for Creating a COVID-19 Data Visualization with Symfony UX
Quentin Ferrer
Quentin Ferrer

Posted on

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:

Top comments (10)

Collapse
 
roanny profile image
Roanny Lamas López

Hello,
Very good article! I tried to reproduce it but I am having trouble displaying the graphics on the canvas. Could you tell me what am I doing wrong?
Greetings

Collapse
 
qferrer profile image
Quentin Ferrer

Hello @roanny ! Thanks a lot. What is not working? Could you drop your code or share it using Github Gist?

Collapse
 
roanny profile image
Roanny Lamas López

Thank you very much for your quick response! I tell you that I installed the necessary packages as shown in the article, but the result I get is a blank canva. However when inspecting the page I see that the data does indeed come into view. Greetings

gist.github.com/roanny/bef2d814b66...

Thread Thread
 
qferrer profile image
Quentin Ferrer

I don't see any problem. Could you try it on another browser? Do you have any log messages in the console?

Collapse
 
luckyduck profile image
Jan Brinkmann

Hi. I am facing the same problem. Canvas is in the page (checked using the inspector), but blank page. How did you solve it?

Collapse
 
jeremymoorecom profile image
Jeremy Moore

Another great example. So glad to see somebody writing about Symfony 5 and real life examples showing off new features. I very much appreciate your articles ~ Thank you

Collapse
 
duboiss profile image
Steven

A nice article again.
You made me discover the scoped_clients parameter of http client, thanks!

Collapse
 
anguz profile image
Angel Guzman

Good work!

Collapse
 
mouerr profile image
Mouerr

nice article thank you

Collapse
 
geekabel profile image
Godwin

Great tutorial ! I learn how to use correctly scoped_clients . thanks But a small question, How can I download an image from an Api on my computer .