Search code examples
javascriptsvghighchartspdf-generation

Add picture issue when exporting Highcharts to pdf - Highcharts warning: Invalid tagName


I have an export of Highcharts into a pdf file with added text and images (based on this). It used to work until recently but now it's not exporting the added pictures, everything else is fine.

The JavaScript is throwing the following error "Highcharts warning: Invalid tagName image in config". Fiddle reproducing the error here.

The line that causes the problem is svgImg.appendChild(svgimg).

I'm not sure if I changed something or if there was a library change somewhere. Any help is appreciated.

Here is the js:

$(function () {
    
    Highcharts.getSVG = function(charts,texts, options, callback) {
        
  var svgArr = [],
    top = 0,
    width = 0,
    newLine = false,
    txt1;
    addSVG = function(svgres,i) {
        
      // Grab width/height from exported chart
      var svgWidth = +svgres.match(
          /^<svg[^>]*width\s*=\s*\"?(\d+)\"?[^>]*>/
        )[1],
        svgHeight = +svgres.match(
          /^<svg[^>]*height\s*=\s*\"?(\d+)\"?[^>]*>/
        )[1],
        // Offset the position of this chart in the final SVG
        svg;


      
          if (svgWidth > 1100) {
           if(i==5){
                  top = 1000;
                  svg = svgres.replace('<svg', '<g transform="translate(0,' + top + ')"');
              }else{
                  svg = svgres.replace('<svg', '<g transform="translate(' + width + ', 0 )"');
              }
            top = Math.max(top, svgHeight);
    
          } else {
            if (newLine) {
              if(i==4){
                  width = 1000;
              }
              svg = svgres.replace('<svg', '<g transform="translate(' + width + ', ' + top + ')"');
              top += svgHeight;
              width += svgWidth;
              newLine = false;
            } else {
              newLine = true;
              if(i==5){
                  top = 1000;
                  svg = svgres.replace('<svg', '<g transform="translate(0,' + top + ')" ');
              }else{
                  svg = svgres.replace('<svg', '<g transform="translate(0,' + top + ')" ');
              }
              
              top = Math.max(top, svgHeight);
              width += svgWidth;
              //width = Math.max(width, chart.chartWidth);
            }
          }
       
      svg = svg.replace('</svg>', '</g>');
      svgArr.push(svg);
      
       txt = texts[i];
       
        txt1 = '<svg width="350" height="75" viewBox="0 0 350 75"><rect x="10" y="50" 
          width="300" height="40" style="fill: white; stroke:black;stroke-width:2"/><g 
          style="overflow:hidden; font-size:14; font-family: Arial"></text><text x="20" y="65" 
          style="fill: black">Text test text text</text></g><g style="overflow:hidden; font- 
          size:14; font-family: Arial"></text><text x="20" y="82" style="fill: black">Text 
          test text text</text></g></svg>' ;
                
      svgArr.push(txt1);
      
    },
    exportChart = function(i) {
      if (i === charts.length) {
          
          // add SVG image to exported svg
        addSVG(svgImg.outerHTML);
          
          //console.log(top+'-----'+width);
        return callback('<svg height="2000" width="2000" version="1.1" 
        xmlns="http://www.w3.org/2000/svg">' + svgArr.join('') + '</svg>');
      }
      charts[i].getSVGForLocalExport(options, {}, function() {
        console.log("Failed to get SVG");
      }, function(svg) {
        addSVG(svg,i);
        return exportChart(i + 1); // Export next only when this SVG is received
      });
    };
//     console.log(svgArr);  
  exportChart(0);
};
    
Highcharts.exportCharts = function(charts,texts, options) {
  options = Highcharts.merge(Highcharts.getOptions().exporting, options);

  // Get SVG asynchronously and then download the resulting SVG
  Highcharts.getSVG(charts,texts, options, function(svg) {
    Highcharts.downloadSVGLocal(svg, options, function() {
      console.log("Failed to export on client side");
    });
  });
};

//Set global default options for all charts
Highcharts.setOptions({
  exporting: {
    fallbackToExportServer: false // Ensure the export happens on the client side or not at all
  }
});



var chart1 = new Highcharts.chart('container', {

    chart: {
        type: 'column',
        styledMode: true
    },

    title: {
        text: 'Styling axes and columns'
    },

    yAxis: [{
        className: 'highcharts-color-0',
        title: {
            text: 'Primary axis'
        }
    }, {
        className: 'highcharts-color-1',
        opposite: true,
        title: {
            text: 'Secondary axis'
        }
    }],

    plotOptions: {
        column: {
            borderRadius: 5
        }
    },

    series: [{
        data: [1, 3, 2, 4]
    }, {
        data: [324, 124, 547, 221],
        yAxis: 1
    }]

});

  
var texts = $('.HC');
     
  function toDataURL(url, callback) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function () {
        var reader = new FileReader();
        reader.onloadend = function () {
            callback(reader.result);
        };
        reader.readAsDataURL(xhr.response);
    };
    xhr.open('GET', url);
    xhr.responseType = 'blob';
    xhr.send();
};   
     
var svgImg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgImg.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
svgImg.setAttribute('height', '200');
svgImg.setAttribute('width', '200');
svgImg.setAttribute('id', 'test');

var svgimg = document.createElementNS('http://www.w3.org/2000/svg', 'image');
svgimg.setAttribute('height', '768');
svgimg.setAttribute('width', '1024');
svgimg.setAttribute('id', 'testimg');

// convert image and add to svg image object
toDataURL('https://www.highcharts.com/samples/graphics/sun.png', function (dataUrl) {
    svgimg.setAttributeNS('http://www.w3.org/1999/xlink', 'href', dataUrl);
});

svgimg.setAttribute('x', '0');
svgimg.setAttribute('y', '0');
svgImg.appendChild(svgimg);

     
    $("#export2pdf").click(function() {
        Highcharts.exportCharts([chart1],texts, {
          type: 'application/pdf',
          filename: 'Feedback',
        });
      });

});

Solution

  • It is related to the new Highcharts Security policy. You need to modify the default AST settings:

    Highcharts.AST.allowedTags.push('image');
    Highcharts.AST.allowedAttributes.push('xlink:href');
    

    Live demo: https://jsfiddle.net/BlackLabel/8snjc6gy/

    Docs: https://www.highcharts.com/docs/chart-concepts/security