ajaymohare's blog

By ajaymohare, 2 months ago, In English

Hello everyone,

I was trying to download my Codeforces rating graph by right clicking on it and using "Save image as...".

But the downloaded image was completely blank.

At first I thought it was a browser issue, so I inspected the graph.

I ran:

document.querySelectorAll("#usersRatingGraphPlaceholder canvas")

and got:

NodeList(2) [
    canvas.flot-base,
    canvas.flot-overlay
]

Then I checked which element is actually on top of the graph:

document.elementFromPoint(300, 250)

It returned:

<canvas class="flot-overlay" ...></canvas>

So when I right click inside the graph, the element being targeted is the flot-overlay canvas.

This seems to be why "Save image as..." saves a blank image instead of the visible rating graph.

I then tried exporting flot-base directly. This gave me the actual graph, but the rating values on the left and the dates at the bottom were missing.

After inspecting the graph structure, I found this:

canvas.flot-base
div.flot-text
canvas.flot-overlay
div.legend

The axis text is rendered separately inside flot-text, so exporting only the base canvas does not include it.

I made a small PoC to export the base canvas and draw the text labels on a new canvas:

function downloadRatingGraph() {
    const container = document.querySelector(
        "#usersRatingGraphPlaceholder"
    );

    if (!container) return;

    const base = container.querySelector(".flot-base");

    if (!base) return;

    const canvas = document.createElement("canvas");
    canvas.width = base.width;
    canvas.height = base.height;

    const ctx = canvas.getContext("2d");

    ctx.fillStyle = "#fff";
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    ctx.drawImage(base, 0, 0);

    const containerRect = container.getBoundingClientRect();

    const scaleX = base.width / containerRect.width;
    const scaleY = base.height / containerRect.height;

    container
        .querySelectorAll(".flot-text div")
        .forEach(element => {
            if (element.children.length > 0) return;

            const text = element.textContent.trim();

            if (!text) return;

            const rect = element.getBoundingClientRect();
            const style = getComputedStyle(element);

            const x =
                (rect.left - containerRect.left) * scaleX;

            const y =
                (rect.top - containerRect.top) * scaleY;

            const fontSize =
                parseFloat(style.fontSize) * scaleY;

            ctx.font =
                `$$${fontSize}px $$${style.fontFamily}`;

            ctx.fillStyle = style.color || "#000";
            ctx.textBaseline = "top";

            ctx.fillText(text, x, y);
        });

    const link = document.createElement("a");

    link.download = "rating-graph.png";
    link.href = canvas.toDataURL("image/png");

    link.click();
}

After running:

downloadRatingGraph();

I was able to download the graph with the rating values and date labels.

This is just a small PoC and there may be a cleaner solution in Flot itself.

Note: AI was used during debugging and for helping with the PoC. I manually tested the code and verified the results myself.

Thanks :)

Full text and comments »

  • Vote: I like it
  • +16
  • Vote: I do not like it