highcharts / node-export-server

Highcharts Node.js export server
Other
354 stars 260 forks source link

plotBackgroundImage doesn't work with node-export-server #186

Closed raf18seb closed 2 years ago

raf18seb commented 5 years ago

Like in the title. Node export server is not able to process neither .jpg nor base64 images.

Reproduction steps

I use highcharts-export-server --infile test.json In test.json file I have this config like this:

{

  chart: {
    type: 'bubble',
    plotBackgroundImage: 'https://www.highcharts.com/samples/graphics/skies.jpg'
  },

  xAxis: {
    min: 0,
    max: 1.3
  },

  yAxis: {
    min: 0,
    max: 70
  },

  series: [{
    color: 'yellow',
    name: 'Sun',
    data: [{
      x: 1,
      y: 55,
      z: 0
    }]
  }]

}

jsFiddle with above config: https://jsfiddle.net/BlackLabel/2tvcdqrs

highcharts-export-server v2.0.16 node v10.15.1

hlorofos commented 5 years ago

Dear highcharts representatives, any progress on this?

TorsteinHonsi commented 5 years ago

@cvasseng , any comment? Async problems maybe?

cvasseng commented 5 years ago

Did you pass it as written? If so, try formatting as JSON instead of JavaScript:

{

  "chart": {
    "type": "bubble",
    "plotBackgroundImage": "https://www.highcharts.com/samples/graphics/skies.jpg"
  },

  "xAxis": {
    "min": 0,
    "max": 1.3
  },

  "yAxis": {
    "min": 0,
    "max": 70
  },

  "series": [{
    "color": "yellow",
    "name": "Sun",
    "data": [{
      "x": 1,
      "y": 55,
      "z": 0
    }]
  }]

}

If that still doesn't work, please try the latest version 2.0.19 which seems to produce the correct result on my local test setup.

jeremyjclarke commented 5 years ago

Reviving this topic. This JSON doesn't produce a background image on the Export Server test page either: https://export.highcharts.com/

@highcharts - any ideas?

brunaobh commented 4 years ago

I'm facing the same issue here - Node export server is not able to process neither .png nor base64 images.

Reproduction steps I use highcharts-export-server --infile test.json In test.json file I have this config like this:

{

  chart: {
    type: 'bubble',
    plotBackgroundImage: 'https://www.highcharts.com/samples/graphics/skies.jpg'
  },

  xAxis: {
    min: 0,
    max: 1.3
  },

  yAxis: {
    min: 0,
    max: 70
  },

  series: [{
    color: 'yellow',
    name: 'Sun',
    data: [{
      x: 1,
      y: 55,
      z: 0
    }]
  }]

}

highcharts-export-server v2.0.24 node v8.16.1

@highcharts - How can we fix that?

softfrog commented 4 years ago

+1

any response @highcharts?

pawelfus commented 4 years ago

Just debugged, it seems that the reason lies in.. Highcharts core. Exporting server expects chart.events.load to be called after all images are loaded. In fact, that applies to all symbol-like generated images (e.g. markers), but does not work with other images (e.g. plotBackgroundImage).

Workaround is to create a dummy series with marker that will load the image, snippet with explanation:

{
  "chart": {
    "type": "bubble",
    "plotBackgroundImage": "https://www.highcharts.com/samples/graphics/skies.jpg"
  },

  "series": [{
    // Dummy series, that is rendered under the actual one
    "type": "scatter",
    // Hidden from legend
    "showInLegend": false,
    "marker": {
      // Visible, but hidden
      "radius": 0.0001,
      // Path to image we want to use
      "symbol": "url(https://www.highcharts.com/samples/graphics/skies.jpg)"
    },
    "data": [] // copy of one point in original series
  }, {
    "color": "yellow",
    "name": "Sun",
    "data": [{
      "x": 1,
      "y": 55,
      "z": 0
    }]
  }]
}

Working example:

{
  "chart": {
    "type": "bubble",
    "plotBackgroundImage": "https://www.highcharts.com/samples/graphics/skies.jpg"
  },

  "xAxis": {
    "min": 0,
    "max": 1.3
  },

  "yAxis": {
    "min": 0,
    "max": 70
  },

  "series": [{
    "type": "scatter",
    "showInLegend": false,
    "marker": {
      "radius": 0.0001,
      "symbol": "url(https://www.highcharts.com/samples/graphics/skies.jpg)"
    },
    "data": [{
      "x": 1,
      "y": 55
    }]
  }, {
    "color": "yellow",
    "name": "Sun",
    "data": [{
      "x": 1,
      "y": 55,
      "z": 0
    }]
  }]
}
softfrog commented 4 years ago

Aha! I'm using the background image for a polar chart, so just set the marker in the dummy series to show centered on the chart and plotted the actual series on top of that. Completely avoided using plotBackgroundImage (I'm exporting to pdf so don't need any live behaviour). Works like a charm.

Thanks @pawelfus !

sesolaga commented 4 years ago

I'm facing a similar issue. The SVGs are loaded like 2 times out of 10 when used in the markers:

"series": [
    {
      "type": "line",
      "lineWidth": 0,
      "data": [
        {
          "x": 1592586000000,
          "y": 86,
          "marker": {
            "symbol": "url(https://dots.dataontouchdev.com/images/weather/yr_weather_symbols/svg/01d.svg)",
            "width": 40,
            "height": 40
          }
        },
...
pawelfus commented 4 years ago

Just to avoid answering in every ticket (#43 and #238), I will just answer here. Is that ok with you @100phel ? I believe, there's no point to copy my answer in every ticket.

Just tested my solution above - you are right, sometimes it fails. After some tests it seems ~30% of the exported charts didn't include images properly. I don't know the reason. This seems to be happening only when exporting chart from JSON options, SVG seems to be fine..

There's an alternative solution - add resources and force chart to wait for loading all images, like this:

highcharts-export-server --infile test.json --resources '{ "files": "test.js", "asyncLoading": true }'

And test.js file:

function checkImages() {
    if (Array.prototype.filter.call(document.images, function(img) { return !img.complete }).length === 0) {
        highexp.done();
    } else {
        setTimeout(checkImages, 0);
    }
}

setTimeout(checkImages, 0);

With this workaround, it's no longer necessary to create a dummy series.

sesolaga commented 4 years ago

@pawelfus Thank you for your time testing this but your solution still doesn't work for me. checkImages does get called - I've checked that by adding one line to your code:

function checkImages() {
    if (Array.prototype.filter.call(document.images, function(img) { return !img.complete }).length === 0) {
        throw new Error('Images: ' + JSON.stringify(document.images))

        highexp.done();
    } else {
        setTimeout(checkImages, 0);
    }
}

setTimeout(checkImages, 0);

And the output is:

phantom worker 1 unexpected data - Error: Images: {"length":0}

So, it seems like document doesn't contain any images and that's why highexp.done() gets called right away. And I still don't get any symbols on the chart.

sesolaga commented 4 years ago

Here's my json file:

{
  "title": "Observation Forecast - Luke Lightfoot",
  "xAxis": [
    {
      "type": "datetime"
    }
  ],
  "yAxis": [
    {
      "title": {
        "text": "Temperature"
      }
    }
  ],
  "series": [
    {
      "type": "line",
      "lineWidth": 0,
      "data": [
        {
          "x": 1592888400000,
          "y": 73,
          "marker": {
            "symbol": "url(https://dots.dataontouchdev.com/images/weather/yr_weather_symbols/svg/22n.svg)",
            "width": 50,
            "height": 50
          }
        }
      ],
      "showInLegend": false
    }
  ],
  "asyncRendering": true,
  "resources": {
    "files": "resources.js"
  }
}

And here's the video just in case: https://www.awesomescreenshot.com/video/251118?key=53fb64c0330b20760898eda540b68660

Also, here's the expected output - I got it from pasting the above options file in the web version of https://export.highcharts.com/

https://tinyurl.com/yacoccus

But it works, like I said before, in about 20% of the cases.

(resources.js === test.js)

pawelfus commented 4 years ago

Ah, my bad! highexp.done() should be called before creating the chart. My code above doesn't make sense.

I have tried something like this (pre-load images):

function checkImages() {
    if (Array.prototype.filter.call(document.images, function(img) { return !img.complete }).length === 0) {
        highexp.done();
    } else {
        setTimeout(checkImages, 0);
    }
}

function loadImages() {
    var img = document.createElement('img');

    img.src = 'https://dots.dataontouchdev.com/images/weather/yr_weather_symbols/svg/22n.svg';
    img.style.position = 'absolute';
    img.style.top = '-50px';
    img.style.height = '40px';
    img.style.width = '40px';
    document.body.appendChild(img);
}

loadImages();
setTimeout(checkImages, 0);

Tested 10x with your config - works fine. Could you check it and let me know the results?

sesolaga commented 4 years ago

@pawelfus Your solution in the web version works, thank you! But when I try it in the terminal I get the following error:

phantom worker 1 unexpected data - TypeError: null is not an object (evaluating 'document.body.appendChild')

When I add throw new Error(document.body) the result is:

phantom worker 1 unexpected data - Error: null

So, I guess document.body is null.

pawelfus commented 4 years ago

Hmm.. try then document.querySelector('body') or document.querySelectorAll('body')[0]? Perhaps we have different PhantomJS versions, hard to say.

sesolaga commented 4 years ago

@pawelfus I think I get the error because loadImages is called before the document.body is created... neither document.querySelector('body') nor document.querySelectorAll('body')[0] works

pawelfus commented 4 years ago

That might be it, did you try putting the code into DOMContentLoaded event's callback?

sesolaga commented 4 years ago

@pawelfus it worked! Thank you very much! The final solution looks like this:

function checkImages() {
    if (document.body === null || Array.prototype.filter.call(document.images, function(img) { return !img.complete }).length === 0) {
        highexp.done();
    } else {
        setTimeout(checkImages, 0);
    }
}

function loadImages() {
    var img = document.createElement('img');

    img.src = 'https://dots.dataontouchdev.com/images/weather/yr_weather_symbols/svg/22n.svg';
    img.style.position = 'absolute';
    img.style.top = '-50px';
    img.style.height = '40px';
    img.style.width = '40px';

    document.body.appendChild(img);
}

window.addEventListener('DOMContentLoaded', function(event) {
    loadImages();
});

setTimeout(checkImages, 0);
robertoferreirajrr commented 3 years ago

Any official fix for this issue?

sesolaga commented 3 years ago

@pawelfus The solution above worked until 2 days ago. Now I'm getting error: callback, resources, and customCode have been disabled for this server

So, seems like we can't use the resources parameter anymore.

pawelfus commented 3 years ago

@100phel See: https://github.com/highcharts/node-export-server#breaking-changes-in-v210

Edit: More info: https://github.com/highcharts/node-export-server/security/advisories/GHSA-hfwx-c7q6-g54c

sesolaga commented 3 years ago

@pawelfus I use the Highcharts export server (https://export.highcharts.com), so I can't utilize the --allowCodeExecution option that they suggest. Anyway, the resources parameter was utilized in order to overcome the bug that the library has - not loading the images. Now that we can't use this option anymore, is there any other way?

pawelfus commented 3 years ago

FYI @PaulDalek @cvasseng

icjosh10 commented 3 years ago

None of these worked for me. Using chart to display to Discord using Node but no background Image tutorial I've used works 😢

PaulDalek commented 2 years ago

Sorry for the late reply. This was a big deal in the PhantomJS version of the export server. It required using the asyncLoading along with resources and triggering the highexp.done() in order to wait for images. In the new Puppeteer version this is no longer required, as it waits for the images to load. All you need to do is to simply place the URL as a value of a specific option (in this case plotBackgroundImage) and that’s it. Here is the result: chart