[RGRAPH SUPPORT] Line chart memory leak in a one page application

Line chart memory leak in a one page application

Post a reply
Posted by Mohamed at 15:00 on 4th June 2021
Hello RGraph team,
I actually using RGraph.line to show a curve in one of pages in a web app. And every thing is good as far.
the switch between pages is based on literal template. The problem occures when the page is left, the Rgraph.line remains in Heap. I tried setting the line element to null but the GC is not collecting it
here the script file and the html simplefied and snipped together
Can please tell me how can i free the Rgraph.line? It's very critical in my usecase.
Thank you in advance.
Mohamed

///////////////////////////////////////////////////////////////////////////
/* script.js */////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

// data Set
let myData = [5,10,0,9,8,152,56,1,4,25,10]

let template_p1 = `<div id='curve'>
                    <canvas id="Curvecsv">[No canvas support]</canvas>
                 </div>`;

let template_p2 = `<div> this is a test page </div>`;

// variables
let selection = 1; //<-- page tracker
let drawable = null; // <-- oop object for drawing
let p1_loaded = false; // <-- load trackers
let p2_loaded =false;

//events
const installEvents = () =>
document.querySelectorAll('.item').forEach( link =>
    link.addEventListener('click', () => {
             selection = Number(link.getAttribute('id'));
        })
    )
    
/*OOP container*/
class DrawableItem {
    constructor(name,id){
        let _name = null;
        this.set_name = name => _name = name;
        this.name = () => _name;

        let _id = null;
        this.set_id = id => _id = id;
        this.id = () => _id;

        let _line = null;
        this.draw = inputData => {
            RGraph.reset(document.getElementById(`Curvecsv`));
            if(!_line) {
                _line = new RGraph.Line({
                        id : `Curvecsv`,
                        data: [],
                        options: {
                            titleColor: 'green',
                            backgroundGridBorder: true,
                            shadow: false,
                            yaxisTickmarks: false,
                            yaxisTickmarksCount: null,
                            yaxisScale: false,
                            colors: ['#000']
                        }
                    });
                //fit the graph to parent div
                _line.canvas.style.width='100%';
                _line.canvas.style.height='100%';
                _line.canvas.width = _line.canvas.offsetWidth ;
                _line.canvas.height = _line.canvas.offsetHeight ;
            }

            _line.original_data[0] = inputData;
            _line.set('title', 'this is a test curve');
            _line.draw();

        }
        this.reset = () => _line = null;

        this.set_name(name);
        this.set_id(id);
    }
}


/**Subroutines */
const sleep = ms => {
    let unixtime_ms = new Date().getTime();
    while(new Date().getTime() < unixtime_ms + ms) {}
}

const loadPage = () => {
    console.log(selection);
    if(selection === 2) {
        if(!p2_loaded) {
            drawable.reset();
            document.querySelector('.content').innerHTML = template_p2;
            p2_loaded = true; p1_loaded = false;
        }
    
    } else if(selection === 1){
        if(!p1_loaded) {
            document.querySelector('.content').innerHTML = template_p1;
            p2_loaded = false; p1_loaded = true;
        }
    }
    sleep(50);
    setTimeout(draw,500);
}

const draw = () => {
    if(selection === 1) {
        if(p1_loaded) {
            drawable.draw(myData);
        }
    }
    sleep(50);
    setTimeout(loadPage,500);
}

/**Setup */
const setup = () => {
    installEvents();

    drawable = new DrawableItem('test',1);
    loadPage();
}

//execution
setup();

///////////////////////////////////////////////////////////////////////////
/* script.js *////end//////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

<!-- ----------------------------------------------------------------- -->
<!-- index.html -->
<!-- ----------------------------------------------------------------- -->
<html><body>
    <header><nav><ul>
        <li class="item" id ="1"><a class= "link" href="#"> page 1 </a></li>
        <li class="item" id ="2"><a class= "link" href="#"> page 2 </a></li>
    </ul></nav></header>
    <main>
        <div class="content" display="block"></div>
    </main>
</body>
    <script type="text/javascript" src="RGraph.common.core.js"></script>
    <script type="text/javascript" src="RGraph.line.js"></script>
    <script type="text/javascript" src="script.js"></script>
</html>
<!-- ----------------------------------------------------------------- -->
<!-- end of index.html -->
<!-- ----------------------------------------------------------------- -->

Posted by Richard at 15:48 on 4th June 2021
Well the memory "leak" is probably a reference to the object lying around. For JavaScripts GC to free up an objects memory there would need to be no more references to it.

Two such references I can think of are the __object__ property that's added to the canvas (you can get rid of that by setting: line.canvas.__object__ = null) and perhaps the RGraph.cache variable which is used to cache the drawing of background grids. You can get rid of that by setting: RGraph.cache = null.

Richard

Posted by Mohamed at 16:27 on 4th June 2021
Hello Richard,
thank you for you fast answer.
I extended my reset function with your recomandation as follow:
this.reset = () => {
            _line.canvas.__object__ = null;
            _line.cache = null;
            _line = null;
        };

but still not working sadly.
Is there a function in RGraph, that complete detachs a draw element? like the RGraph.clear and RGraph.reset?

Best Regards
Mohamed

Posted by Richard at 18:20 on 4th June 2021
There's no such function I'm afraid.

When you create the chart object, try using window._line instead of just _line. This would make it use a global - but hopefully just a single global. eg:



// let _line = null; // Don't need this anymore

this.draw = inputData => {

    RGraph.reset(document.getElementById(`Curvecsv`));

    if(!window._line) { // Check if the _line global variable exists
        window._line = new RGraph.Line({
                id : `Curvecsv`,

// ...

Richard

Posted by Mohamed at 15:48 on 10th June 2021
Hello Richard,

thank you for your answer.

So at first storing the _line object in window is antipatern in my case that I have to use OOP (actually the solution I'm working on it uses a multitude of graphs, which will be loaded dynamically that's why for each needed curve, a drawItem will be generated).

That's why to have more control of the memory management, I tooke recourse of modifying the RGraph Lib Structure by changing the Module IIFE "(function (win,doc,undefined){ ...})(windows,document)"
in RGraph.common.core.js to a regular function declaration as:

function load_RGraph(win,doc,undefined) {....};
load_RGraph(window,document);

by this way I can delete the RGraph object from window completely and reload it when it will be needed (by click on page 1 for example), through:
RGraph = null;

And the same for the Line element in Rgraph.line.js
"(function (conf){...})()" to a function declaration like
const RGraph_line = function (conf) {...};
and each time it needed I set the RGraph.line expression after loading RGraph
RGraph.Line = RGraph_line;

It's not a perfect way, because the RGraph Module adds some listnener to window too, wich remains a leak in actual case, but is not critical.

Kind Regards
Mohamed

Posted by Richard at 16:56 on 11th June 2021
> So at first storing the _line object in window is antipatern in my
> case that I have to use OOP (actually the solution I'm working on
> it uses a multitude of graphs, which will be loaded dynamically
> that's why for each needed curve, a drawItem will be generated).

There's a Registry object in RGraph that you could use if you prefer. It's used inside RGraph for a few settings, but you could always use it for your own things too if you wish.

RGraph.Registry.set('myObject1', _line);

And then to retrieve it again you'd call:

var _line = RGraph.Registry.get('myObject1');

if (!_line) {

    // Create the line chart...

    // And put it in the registry
    RGraph.Registry.set('myObject1', _line);
}

Post a reply

What's your name?
What do you want to say?