Блог пользователя ajaymohare

Автор ajaymohare, 2 месяца назад, По-английски

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 :)

  • Проголосовать: нравится
  • +16
  • Проголосовать: не нравится

»
2 месяца назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

Auto comment: topic has been updated by ajaymohare (previous revision, new revision, compare).

»
2 месяца назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

Auto comment: topic has been updated by ajaymohare (previous revision, new revision, compare).

»
2 месяца назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

You could turn this function into an extension using content scripts and instead of creating a link and clicking it by JS without adding it to DOM, add the link above the rating graph

»
2 месяца назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

You can also just screenshot the graph...

»
2 месяца назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

I prefer snipping tool.

»
2 месяца назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

Meanwhile download/copy image of any element of page is builtin in Firefox