Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SSR (Server-Side Rendering & Canvas) #5641

Closed
JonasJonny opened this issue Oct 14, 2023 · 13 comments
Closed

SSR (Server-Side Rendering & Canvas) #5641

JonasJonny opened this issue Oct 14, 2023 · 13 comments

Comments

@JonasJonny
Copy link

G2 Version: "@antv/g2": "^5.1.5",

Good evening,
I wanted to try SSR with Canvas but without luck.
I followed test code

function createNodeGCanvas(width, height) {
  // Create a node-canvas instead of HTMLCanvasElement
  const nodeCanvas = createCanvas(width, height);
  // A standalone offscreen canvas for text metrics
  const offscreenNodeCanvas = createCanvas(1, 1);

  // Create a renderer, unregister plugin relative to DOM.
  const renderer = new Renderer();
  // Remove html plugin to ssr.
  const htmlRendererPlugin = renderer.getPlugin('html-renderer');
  renderer.unregisterPlugin(htmlRendererPlugin);
  const domInteractionPlugin = renderer.getPlugin('dom-interaction');
  renderer.unregisterPlugin(domInteractionPlugin);

  renderer.registerPlugin(
    new DragAndDropPlugin.Plugin({ dragstartDistanceThreshold: 10 }),
  );

  return new Canvas({
    width,
    height,
    canvas: nodeCanvas,
    renderer,
    offscreenCanvas: offscreenNodeCanvas,
  });
}

async function renderG2BySSR() {
  const width = 600;
  const height = 400;

  const gCanvas = createNodeGCanvas(width, height);

  // A tabular data to be visualized.
  const data = [
    { genre: 'Sports', sold: 275 },
    { genre: 'Strategy', sold: 115 },
    { genre: 'Action', sold: 120 },
    { genre: 'Shooter', sold: 350 },
    { genre: 'Other', sold: 150 },
  ];

  const chart = new Chart({
    width,
    height,
    canvas: gCanvas,
    createCanvas: () => {
      return createCanvas(width, height);
    },
  });

  chart
    .interval()
    .data(data)
    .encode('x', 'genre')
    .encode('y', 'sold')
    .encode('color', 'genre');

  await chart.render();
}

from https://github.com/antvis/G2/blob/10a4b51e65fe58c68105118736a65870acc91550/__tests__/unit/ssr/index.spec.ts but receiving

ReferenceError: document is not defined
at normalizeContainer (/***/node_modules/@antv/g2/lib/api/src/api/utils.ts:40:23)

I tried await canvas.ready; as https://github.com/antvis/G/blob/next/__tests__/integration/__node__tests__/canvas/text.spec.js#L39 with the same result :(.

I want to export chart to PDF for report purpose and I need to somehow get the canvas/SVG code on server.

@pearmini
Copy link
Member

There is something missing in the doc. You should also inject JSDOM to the global. If you still have troubles with JSDOM, I can give you more information.

@JonasJonny
Copy link
Author

I am sorry @pearmini but I need your help.

  1. I tried to inspire here but
    TypeError: container.appendChild is not a function ... /node_modules/@antv/g-canvas/src/Canvas2DContextService.ts:15:27
  2. Then I tried global.window = dom.window; global.document = dom.window.document; but
    ReferenceError: Node is not defined ... /node_modules/@antv/g-plugin-canvas-renderer/dist/index.js:224 !(object.compareDocumentPosition(parent) & Node.DOCUMENT_POSITION_CONTAINS)
  3. I tried more but again ReferenceError: document is not defined

As I wrote, my goal is to get SVG or Image for PDF export.
I guess many people wants to achieve this so your contribution will be appreciated.
Thank you.

@JonasJonny
Copy link
Author

I took some code from https://github.com/antvis/G2/pull/4123/files.

function createNodeGCanvas(width, height) {
  const nodeCanvas = createCanvas(width, height);
  ... // Renderer() code above

  // create JSDOM
  const dom = new JSDOM(`<div id="container"></div>`);
  global.window = dom.window;
  global.document = dom.window.document;

  if (global.window) {
    Object.defineProperty(global.window, 'TextEncoder', {
      writable: true,
      value: util.TextEncoder,
    });

    Object.defineProperty(global.window, 'TextDecoder', {
      writable: true,
      value: util.TextDecoder,
    });
  }

  return new Canvas({
    container: 'container',
    width,
    height,
    canvas: nodeCanvas,
    renderer,
    offscreenCanvas: offscreenNodeCanvas,
    document: dom.window.document,
    requestAnimationFrame: dom.window.requestAnimationFrame,
    cancelAnimationFrame: dom.window.cancelAnimationFrame,
  });
}

=>

TypeError: container.appendChild is not a function
    at Canvas2DContextService.init (/node_modules/@antv/g-canvas/src/Canvas2DContextService.ts:15:27)
    at Canvas.initRenderer (/node_modules/@antv/g-lite/src/Canvas.ts:352:41)
    at new Canvas (/node_modules/@antv/g-lite/src/Canvas.ts:128:15)

@pearmini
Copy link
Member

@JonasJonny, is it OK that I provide a demo or a tool to help you in this week?

@JonasJonny
Copy link
Author

Is it OK that I provide a demo or a tool to help you in this week?

Sure, that would help. Thanks

@xiaoiver
Copy link
Contributor

xiaoiver commented Nov 9, 2023

@JonasJonny, I wrote an example using JSDOM in SSR:
https://stackblitz.com/edit/stackblitz-starters-6zfeng?file=index.js

Here are the steps to implement it:

  • Since the latest d3 supports ESM only, we have to downgrade the relative packages to 2.x with overrides in package.json.
  • Create a SVG renderer called @antv/g-svg.
  • Create a JSDOM as container and pass it to G2 Chart's constructor.
  • Render the chart as usual.
  • Get the DOM(SVG) from container and serialize it to string with xmlserializer.

If you want to generate PNG instead of SVG, here's another example doing SSR with node-canvas:
https://stackblitz.com/edit/stackblitz-starters-evrvef?file=index.js
But it looks like SVG is good enough for what you're looking for.

@xiaoiver xiaoiver closed this as completed Nov 9, 2023
@pearmini
Copy link
Member

@JonasJonny Sorry for the late reply. Here is SSR tool for G2: https://github.com/pearmini/g2-ssr-node

@JonasJonny
Copy link
Author

JonasJonny commented Nov 23, 2023

Thank you @xiaoiver and @pearmini.
Moreover big one for robust example as https://github.com/pearmini/g2-ssr-node.

Both approaches still require a downgrade to 2.x version of d3 because of "The awkward valley to ESM: Node.js, Victory, and D3".
Am I right in thinking that a downgrade will not be needed if a server will be ES6?

@pearmini
Copy link
Member

Both approaches still require a downgrade to 2.x version of D2.

What is D2?

@pearmini
Copy link
Member

Both approaches still require a downgrade to 2.x version of d3 because of "The awkward valley to ESM: Node.js, Victory, and D3".

I can build a ESM version for https://github.com/pearmini/g2-ssr-node

@JonasJonny
Copy link
Author

@pearmini I think you already invested a lot of your time.
My question is simply about the possibility. If I refactor my server to ESM then if I can use G2 SSR and newest d3 packages?

@pearmini
Copy link
Member

@pearmini I think you already invested a lot of your time. My question is simply about the possibility. If I refactor my server to ESM then if I can use G2 SSR and newest d3 packages?

Sure, but I think the refactor is not required. You server can remain commonjs if you build a commonjs version of G2 and G.

@JonasJonny
Copy link
Author

JonasJonny commented Nov 24, 2023

Your server can remain commonjs if you build a commonjs version of G2 and G.

Unfortunately security of my project cannot accept Vulnerabilities of d3 2.x packages (To build CommonJS).
When I tried your 0.1.0 version yesterday, "downgrade" as @xiaoiver pointed out was needed too.

In the end I wrote script to replace all require('d3-****') paths in node_modules/@antv to https://www.npmjs.com/package/victory-vendor.
Now my code works as expected with newer versions of d3 which are compiled by Victory to CommonJS.

Once again Thank you @pearmini for your time. I learned a lot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants