apache / echarts

Apache ECharts is a powerful, interactive charting and data visualization library for browser
https://echarts.apache.org
Apache License 2.0
60.58k stars 19.62k forks source link

ECharts server-side rendering SVG contains invalid font property #16127

Closed htr3n closed 2 years ago

htr3n commented 2 years ago

Version

5.2.2 (and 5.2.1)

Reproduction link

https://codesandbox.io/s/echarts-ssr-uxfju

Steps to reproduce

  1. Create a server-side rendering of ECharts (5.2.2) with JsDOM and Node Canvas
  2. Configure to render a simple chart to SVG
const echarts = require("echarts");
const { JSDOM } = require("jsdom");
const { createCanvas } = require("canvas");
const fs = require("fs");

const config = {
  option: {
    animation: false,
    fontSize: 10,
    fontFamily: "Arial",
    xAxis: {
      data: ["1493190351122"],
      axisLabel: {
        fontSize: 10,
        fontFamily: "Arial"
      }
    },
    yAxis: {
      name: "Y Axis",
      type: "value",
      nameTextStyle: {
        fontWeight: "bold",
        fontSize: 10,
        fontFamily: "Arial"
      }
    },
    series: [
      {
        type: "line",
        name: "Series 1",
        data: [39]
      }
    ]
  }
};

function render() {
  const ctx = createCanvas(1920, 1080);

  if (ctx) {
    ctx.font = "Arial";
  }

  echarts.setCanvasCreator(() => {
    return ctx;
  });

  const virtualDom = new JSDOM();
  global.window = virtualDom.window;
  global.document = virtualDom.window.document;
  const root = global.document.createElement("div");
  if (root) {
    root.style.cssText = "width: 1920px; height: 1080px;";
    Object.defineProperty(root, "clientWidth", { value: 1920 });
    Object.defineProperty(root, "clientHeight", { value: 1080 });
    let chart = echarts.init(root, null, {
      width: 1920,
      height: 1080,
      renderer: "svg"
    });
    if (chart) {
      chart.setOption(config.option);
      let result = root.querySelector("svg")?.outerHTML ?? null;
      chart.dispose();
      return result;
    }
  }
  return null;
}

const buffer = render();
console.log(buffer);

fs.writeFile(`output.svg`, buffer, function (err) {
  if (err) return console.log(err);
});

What is expected?

  1. The SVG <text> elements should have the correct font family as specified.
  2. The <text> elements inside the resulting SVG should have valid font specification inside style="".
<text xml:space="preserve" style="font: normal sans-serif 12px;">

What is actually happening?

  1. The SVG <text> elements do not have the correct font family as specified.
  2. The SVG's <text> elements contain invalid font specification so that the SVG text will be rendered incorrectly.
<text xml:space="preserve" style="font: sans-serif 12px normal normal normal 12px;">

Per CSS font property (see https://developer.mozilla.org/en-US/docs/Web/CSS/font), it should be

[ [ <'font-style'> || <font-variant-css21> || <'font-weight'> || <'font-stretch'> ]? <'font-size'> [ / <'line-height'> ]? <'font-family'> ] | caption | icon | menu | message-box | small-caption | status-bar

I found this bug when I tried to configure the Y axis nameTextStyle to bold and small size and realised that the font styles and weight I set there do not affect the outcome at all.

This issue is only seen with server-side rendering. I use an identical configuration for a React front-end, it works fine, produces valid CSS.

<text xml:space="preserve"
  fill="#101010"
  fill-opacity="1"
  stroke="none"
  transform="matrix(0,-1,1,0,27,122)"
  dominant-baseline="central"
  text-anchor="middle"
  x="0"
  y="-8.5"
  style="font: bold normal normal 12px Arial;">Axis Title
</text>
echarts-bot[bot] commented 2 years ago

Hi! We've received your issue and please be patient to get responded. 🎉 The average response time is expected to be within one day for weekdays.

In the meanwhile, please make sure that it contains a minimum reproducible demo and necessary images to illustrate. Otherwise, our committers will ask you to do so.

A minimum reproducible demo should contain as little data and components as possible but can still illustrate your problem. This is the best way for us to reproduce it and solve the problem faster.

You may also check out the API and chart option to get the answer.

If you don't get helped for a long time (over a week) or have an urgent question to ask, you may also send an email to dev@echarts.apache.org. Please attach the issue link if it's a technical question.

If you are interested in the project, you may also subscribe to our mailing list.

Have a nice day! 🍵

htr3n commented 2 years ago

After a few tests around setting font for server-side ECharts rendering, I realised it's pretty broken. For instance, I set the fontFamily property of chart option as 'Liberation Sans' which is available on Linux/AWS Node.js Lambda. The rendering produces something like style="font: Sans 9px normal normal normal 9px;", which drops Liberation. Due to spaces?

htr3n commented 2 years ago

Just to confirm the bug regarding a font family with spaces. When I set fontFamily to FreeSans, it's correctly rendered to style="font: FreeSans 9px normal normal normal 9px;" in the <text> elements of the resulting SVG.

plainheart commented 2 years ago

The font issue has been raised by #15064 and we've improved it in ecomfe/zrender#836. Please feel free to try to build from the next branch and then check if it works. Also, we add a new SSR in #15880, which needs no third-party framework to work.

If you don't want to build manually, you can also use the nightly version.

htr3n commented 2 years ago

@plainheart thanks for your info. Unfortunately, the nightly build breaks my current implementation which worked fine with 5.2.2. The new built-in feature SSR looks interesting though. I will look into it.

plainheart commented 2 years ago

Made a simple example, it's easy enough to get started. https://codepen.io/plainheart/pen/RwLbqye

htr3n commented 2 years ago

Confirm that the font issue seems to be fixed with "echarts-nightly": "^5.3.0-dev.20211128". I'm wondering, when do you plan to release 5.3? Thanks

plainheart commented 2 years ago

Thanks for your confirmation. Maybe about 1 month.

htr3n commented 2 years ago

@plainheart a bit off topic but the new SSR function with renderToSVGString() requires that echarts.init() must have width and height (maybe the same when using canvas). The generated SVG has width and height, how does that scale when fixed width and height are set? Does that defeat the purpose of SVG, supposed to be scalable? Thanks

pissang commented 2 years ago

@htr3n We are considering providing an option that can use viewBox instead of fixed width and height.

htr3n commented 2 years ago

@pissang @plainheart Regarding the nightly build that you recommended, I've noticed a strange issue. I tried to load the legend icons as dataURI per documentation here: https://echarts.apache.org/en/option.html#legend.data.icon. The dataURI is a wrapper for an SVG icon.

    legend: {
        show: true,
        data: [
            {
                name: 'LegendIcon',
                icon: 'image://data:image/svg+xml;base64,PHN2ZyB4bWxuc....'
            }
        ]
    }

I use the same code on my React UI and it can be rendered successfully. But the new nightly build server-side rendering implementation renderToSVGString() raised an error: ReferenceError: HTMLImageElement is not defined. It seems it could not load the dataURI?

    "echarts-nightly": "^5.3.0-dev.20211212",

Updated:

If I replace the dataURI 'image://data:image/svg...' with SVG path, for instance, taken from ECharts web site

icon: 'path://M30.9,53.2C16.8,53.2,5.3,41.7,5.3,27.6S16.8,2,30.9,2C45,2,56.4,13.5,56.4,27.6S45,53.2,30.9,53.2z M30.9,3.5C17.6,3.5,6.8,14.4,6.8,27.6c0,13.3,10.8,24.1,24.101,24.1C44.2,51.7,55,40.9,55,27.6C54.9,14.4,44.1,3.5,30.9,3.5z M36.9,35.8c0,0.601-0.4,1-0.9,1h-1.3c-0.5,0-0.9-0.399-0.9-1V19.5c0-0.6,0.4-1,0.9-1H36c0.5,0,0.9,0.4,0.9,1V35.8z M27.8,35.8 c0,0.601-0.4,1-0.9,1h-1.3c-0.5,0-0.9-0.399-0.9-1V19.5c0-0.6,0.4-1,0.9-1H27c0.5,0,0.9,0.4,0.9,1L27.8,35.8L27.8,35.8z'

then it works.

My wild guess is that, the dataURI needs a Canvas to render properly?

In case of using SVG path data, path://M... is not sufficient to set SVG styles such as stroke (colour) and stroke-width.

plainheart commented 2 years ago

It may be related to this line. https://github.com/ecomfe/zrender/blob/next/src/svg/graphic.ts#L207

htr3n commented 2 years ago

@plainheart Does that mean my guess is correct? The legend icon needs a Canvas to render properly, doesn't it?

htr3n commented 2 years ago

Perhaps I can provide more details into the error after catching it

Failed to render graph due to ReferenceError: HTMLImageElement is not defined
    at brushSVGImage (/.../lambda/node_modules/echarts-nightly/dist/echarts.js:41046:30)
    at brush$1 (/.../lambda/node_modules/echarts-nightly/dist/echarts.js:41131:20)
    at SVGPainter._paintList (/.../lambda/node_modules/echarts-nightly/dist/echarts.js:41772:31)
    at SVGPainter.renderToVNode (/.../lambda/node_modules/echarts-nightly/dist/echarts.js:41692:18)
    at SVGPainter.renderToString (/.../lambda/node_modules/echarts-nightly/dist/echarts.js:41719:39)
    at ECharts.renderToSVGString (/.../lambda/node_modules/echarts-nightly/dist/echarts.js:29889:24)
    at n (/.../lambda/dist/dev.js:2:36414)
    at c (/.../lambda/dist/dev.js:2:37712)
    at n (/.../lambda/dist/dev.js:2:50942)
    at l (/.../lambda/dist/dev.js:2:51931)
htr3n commented 2 years ago

Following up on the legend icon issue, if I use SVG path data path://M..., the rendering works and ECharts generates some SVG code like this

<path d="M 0 3.7391 L 5.4348 7 M 0 10.2609 L 5.4348 7 L 7.6087 7 M 9.7826 7 L 11.9565 7 M 14.1304 7 L 16.3043 7 M 18.4783 7 L 20.6522 7 L 25 3.7391 M 20.6522 7 L 25 10.2609" fill="#5f4890" fill-opacity="1" stroke="none" transform="matrix(1,0,0,1,616.5208,5)"></path>

How do I set the attributes stroke or stroke-width of that SVG element <path d="..."/> as it seems ECharts render engine (zrender) already transformed the input SVG path into something else?

I've looked left and right into the documentation https://echarts.apache.org/en/option.html#legend.data.icon but could not find any settings?

htr3n commented 2 years ago

Should I report a bug regarding the legend icon image://data:image... with the new engine implementation?

plainheart commented 2 years ago

No need to do that. We will look into this issue.

htr3n commented 2 years ago

@plainheart Is this fixed in the release 5.3.0?

plainheart commented 2 years ago

@htr3n It should be fixed in 5.3.0. Please try it again. Let us know if you still have issues.

htr3n commented 2 years ago

@plainheart Just confirm the issue with invalid font properties is fixed in 5.3.0. I think we should split the issue with the legend icon image://data:image... so that this ticket can be closed?

plainheart commented 2 years ago

@htr3n Sorry for my late reply. Just checked this icon issue with the latest version, it seems to be working for me. How is it on your side?

htr3n commented 2 years ago

@plainheart Hi, sorry for my late reply. I can confirm it's fixed in 5.3.0