Skip to content

6.4 ChartJS Module

Juan Morales edited this page Mar 5, 2021 · 4 revisions

Module: ChartJS

Features

This modules generates a simple graphic using the ChartJS javascript library.

The idea behind this module is to provide an idea/example, about how to generate graphics using JS libraries.

Code analysis

Please check the comments

<?php declare(strict_types=1);

namespace Footprint\Modules;

use Footprint\Interfaces\IModule;
use Footprint\Tracker;

class ChartJS implements IModule
{
    //Module ID
    const MODULE_ID = "CHARTJS";

    protected $keys = [];
    protected $data = [];
    protected $labels = [];
    protected $fileHandler;

    /*
     * The module takes two arguments in the constructor.
     * 1) $filename : which holds an string with the filepath/filename where the final report will be generated.
     * 2) $keys : an array that holds the "keys" that this module is interested in. Lets imagine that we a using the Tracker with multiple modules, and of thos modules is the Memory Module. So at the end we would like to generate a chart (in this case a line chart) only for the values logged in the Memory Module, but specifucally the keys Memory::KEY_MEM_USAGE and Memory::KEY_MEM_PEAK , if so ... we pass this values in the $keys array.
     */
    public function __construct(string $filename, array $keys = []) {
        //We gnerate the file handler to be used during the chart generation
        if (!$this->fileHandler = fopen($filename, "w+")) {
            throw new Exception("Could not create file ${$filename}");
        }

        $this->keys = $keys;
    }

    public function getId() {
        return self::MODULE_ID;
    }

    public function onInit(Tracker &$tracker) {
        return;
    }

    /*
     * When the Tracker is finished, will invoke this method.
     * Basically the report content, or most of it, is saved in a variable called $reportTemplate .
     *
     * The module uses the content of this variable to generate the report (the line chart).
     * The content of $reportTemplate is basically that, a template that will be filld in.
     *
     * You can see that the template value has some "tags" like {{labels}} which will be use later,
     * to inject/replace the tag, by actual/real data coming from the Tracker.
     */
    public function onEnd(Tracker &$tracker) {
        $reportTemplate = '
        <!DOCTYPE html>
        <html>
            <head>
                <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.bundle.js"></script>
            </head>
            <body>
                <div style="width: 100%; height: 30%;">
                    <canvas id="myChart"></canvas>
                </div>
                
                <script>
                    let ctx = document.getElementById("myChart").getContext("2d");
                    
                    let myData = {
                        labels: {{labels}},
                        datasets:[
                            {{data}}
                        ],
                    };

                    let myOptions = {
                        tooltips: {
                            mode: "index",
                            intersect: false
                        },
                        scales: {
                            yAxes: [{
                                ticks: {
                                    beginAtZero: true,
                                }
                            }],
                            xAxes: [{
                                type: "time",
                                distribution: "series",
                                ticks: {
                                    source: "auto",
                                    autoSkip: true
                                },
                                time: {
                                    unit: "second",
                                    stepSize: 1,
                                }
                            }]
                        }
                    };

                    let myChart = new Chart(ctx, {
                        type: "line",
                        data: myData,
                        options: myOptions
                    });
                </script>
            </body>
        </html>';

        $colors = [
            "red",
            "blue",
            "green",
            "black",
            "purple",
            "orange"
        ];

        $colorIndex = 0;

        //Start PHP code resposible to generate a JS string to be injected in the $reportTemplate
        $labels = json_encode($this->labels);
        $datasetTemplate = "{
            label: '{{label}}',
            data: {{data}},
            borderColor: '{{color}}',
            pointBackgroundColor: '{{color}}',
            borderWidth: 1,
            fill: false,
            lineTension: 0.1
        },";
        
        $data = "";

        foreach($this->keys as $key) {
            $tmp = str_replace("{{label}}", $key, $datasetTemplate);
            $tmp = str_replace("{{data}}", json_encode($this->data[$key]), $tmp);
            $tmp = str_replace("{{color}}", $colors[$colorIndex], $tmp);
            $data .= $tmp;

            $colorIndex++;

            if ($colorIndex == count($colors) - 1) {
                $colorIndex = 0;
            } 
        }

        $data[-1] = \chr(32);

        //End of previous Start
        $reportTemplate = str_replace("{{labels}}", $labels, $reportTemplate);
        $reportTemplate = str_replace("{{data}}", $data, $reportTemplate);

        //Then we write the content of $reportTemplate into the report
        fwrite($this->fileHandler, $reportTemplate);
        fclose($this->fileHandler);

        return;
    }

    public function onLoad(Tracker &$tracker) {
        return;
    }

    public function onUnload(Tracker &$tracker) {
        return;
    }

    /*
     * Every time the Tracker sends a log(), we store the values of the keys
     * we are interested in (during the __construct).
     */
    public function onLog(Tracker &$tracker) {
        $this->labels[] = explode("_", $tracker->getLogId())[0];

        foreach ($this->keys as $key) {
            $this->data[$key][] = $tracker->getLogDataByKey($key);
        }

        return;
    }

    public function onLogBuild(Tracker &$tracker) {
        return;
    }

    /*
     * Method to "manually" add keys to the module.
     * This needs to be call before the Tracker starts.
     */
    public function addKey(string $key) {
        $this->keys[] = $key;
        return $this;
    }

    public function removeKey(string $key) {
        unset($this->keys[$key]);
        return $this;
    }

    public function getKeys() : array {
        return [];
    }
}

and that is it!


Sample output containing all keys from the Memory and Time modules.

1