SVG to Image export performance issues (using canvg / XMLSerializer / getComputedStyle)

 ̄綄美尐妖づ 提交于 2019-12-11 18:26:13

问题


I am using canvg to convert a chart svg to an image. We have the issue that by default not all CSS attributes are applied to the image so we ended up using getComputedStyle in a loop.

This is a total mess under performance aspects if we have 10 or even 20 charts to be exported at once.

var labels = ['2018-10-01', '2018-10-02', '2018-10-03', '2018-10-04', '2018-10-05', '2018-10-06', '2018-10-07', '2018-10-08', '2018-10-09', '2018-10-10', '2018-10-11', '2018-10-12', '2018-10-13', '2018-10-14', '2018-10-15', '2018-10-16', '2018-10-17', '2018-10-18', '2018-10-19', '2018-10-20', '2018-10-21', '2018-10-22', '2018-10-23', '2018-10-24', '2018-10-25', '2018-10-26', '2018-10-27', '2018-10-28', '2018-10-29', '2018-10-30', '2018-10-31', '2018-11-01', '2018-11-02', '2018-11-03', '2018-11-04', '2018-11-05', '2018-11-06', '2018-11-07', '2018-11-08', '2018-11-09', '2018-11-10', '2018-11-11', '2018-11-12', '2018-11-13', '2018-11-14', '2018-11-15', '2018-11-16', '2018-11-17', '2018-11-18', '2018-11-19', '2018-11-20', '2018-11-21', '2018-11-22', '2018-11-23', '2018-11-24', '2018-11-25', '2018-11-26', '2018-11-27', '2018-11-28', '2018-11-29', '2018-11-30', '2018-12-01', '2018-12-02', '2018-12-03', '2018-12-04', '2018-12-05', '2018-12-06', '2018-12-07', '2018-12-08', '2018-12-09', '2018-12-10', '2018-12-11', '2018-12-12', '2018-12-13', '2018-12-14', '2018-12-15', '2018-12-16', '2018-12-17', '2018-12-18', '2018-12-19', '2018-12-20', '2018-12-21', '2018-12-22', '2018-12-23', '2018-12-24', '2018-12-25', '2018-12-26', '2018-12-27', '2018-12-28', '2018-12-29', '2018-12-30', '2018-12-31', '2019-01-01', '2019-01-02', '2019-01-03', '2019-01-04', '2019-01-05', '2019-01-06', '2019-01-07', '2019-01-08', '2019-01-09', '2019-01-10', '2019-01-11', '2019-01-12', '2019-01-13', '2019-01-14', '2019-01-15', '2019-01-16', '2019-01-17', '2019-01-18', '2019-01-19', '2019-01-20', '2019-01-21', '2019-01-22', '2019-01-23', '2019-01-24', '2019-01-25', '2019-01-26', '2019-01-27', '2019-01-28', '2019-01-29', '2019-01-30', '2019-01-31', '2019-02-01', '2019-02-02', '2019-02-03', '2019-02-04', '2019-02-05', '2019-02-06', '2019-02-07', '2019-02-08', '2019-02-09', '2019-02-10', '2019-02-11', '2019-02-12', '2019-02-13', '2019-02-14', '2019-02-15', '2019-02-16', '2019-02-17', '2019-02-18', '2019-02-19', '2019-02-20', '2019-02-21', '2019-02-22', '2019-02-23', '2019-02-24', '2019-02-25', '2019-02-26', '2019-02-27', '2019-02-28'];
var columns = ['data101', 'data2', 'data347'];
var data = [

  [0, 0, 2, 2, 1, 2, 7, 3, 1, 7, 5, 5, 5, 5, 6, 6, 11, 7, 2, 7, 16, 7, 3, 5, 10, 9, 11, 7, 3, 7, 7, 10, 10, 9, 18, 10, 20, 13, 9, 19, 16, 13, 20, 18, 14, 15, 18, 20, 19, 11, 13, 13, 12, 16, 11, 12, 21, 20, 23, 19, 19, 23, 23, 24, 23, 25, 21, 23, 20, 22, 21, 23, 24, 25, 27, 29, 28, 25, 24, 17, 20, 24, 22, 27, 21, 27, 19, 26, 31, 27, 28, 27, 21, 20, 27, 22, 22, 19, 17, 21, 23, 19, 22, 20, 21, 25, 15, 19, 20, 19, 21, 28, 17, 20, 14, 18, 17, 20, 27, 21, 18, 18, 20, 16, 27, 16, 16, 9, 18, 8, 19, 13, 8, 16, 15, 16, 9, 15, 10, 13, 10, 11, 10, 13, 12, 7, 14, 16, 13, 14, 8],
  [0, 0, 338, 1201, 1268, 1371, 1286, 1148, 446, 288, 228, 253, 193, 201, 283, 393, 436, 379, 421, 444, 444, 417, 513, 353, 364, 399, 238, 191, 305, 337, 365, 349, 365, 244, 101, 39, 55, 72, 151, 98, 31, 127, 114, 92, 104, 196, 307, 245, 84, 168, 41, 38, 292, 488, 536, 569, 495, 448, 408, 358, 344, 380, 328, 334, 332, 330, 345, 312, 369, 377, 356, 301, 226, 273, 237, 116, 178, 133, 114, 138, 95, 143, 74, 74, 83, 47, 75, 101, 96, 59, 46, 128, 70, 57, 93, 80, 94, 93, 63, 86, 81, 63, 70, 102, 91, 67, 69, 68, 88, 76, 79, 70, 119, 88, 74, 94, 76, 54, 82, 90, 75, 130, 67, 78, 106, 91, 81, 27, 77, 21, 104, 83, 55, 60, 62, 304, 393, 191, 292, 77, 76, 55, 125, 89, 99, 127, 60, 75, 99, 120, 56],
  [0, 0, 0, 1419, 7454, 12638, 10944, 7652, 4272, 11219, 9071, 7207, 7929, 8373, 9566, 6310, 7406, 9286, 8415, 7659, 6457, 3380, 10902, 10952, 10508, 7219, 4625, 4484, 4396, 5178, 5991, 7927, 14132, 14307, 5094, 10011, 6257, 9184, 18574, 12597, 11415, 7118, 9991, 10225, 14337, 4417, 12701, 17833, 23553, 10037, 4833, 5894, 19421, 14735, 12597, 8730, 5888, 11836, 13143, 17219, 10492, 10528, 8649, 11868, 10502, 6758, 7672, 8479, 11142, 22330, 26595, 4423, 17434, 8709, 9657, 7823, 9135, 19765, 18016, 16010, 8419, 7300, 8877, 9611, 9050, 8680, 8211, 6635, 3069, 10739, 6288, 6761, 7807, 16243, 20415, 23051, 19727, 8721, 6445, 8585, 13688, 14728, 17113, 16255, 3898, 4622, 3869, 3774, 4190, 3461, 4824, 4608, 4613, 3677, 3648, 3575, 3556, 4036, 3732, 2517, 4676, 4129, 3250, 4142, 3987, 4396, 3362, 2964, 1849, 2609, 2851, 3003, 3583, 3473, 3190, 2658, 4363, 3959, 4588, 3771, 4315, 3178, 3354, 3159, 2695, 4114, 4292, 3322, 1218, 3526, 3717]
];
var colors = ['#0065A3', '#767670', '#D73648', '#7FB2CE', '#00345B'];
var padding = 5;

//prepare chart data
var columnData = [];
var chartDataColumns = [];
var chartData = [];

chartData.push([columns[0]].concat(data[0]));

chartDataColumns = [
  ['x'].concat(labels)
].concat(chartData);

var chart1 = c3.generate({
  bindto: d3.select('#chart1'),
  data: {
    x: 'x',
    columns: [['x'].concat(labels)].concat(chartData),
    type: 'line',
    onmouseover: function(d) {
      chart1.focus(d.id);
      chart2.focus(d.id);
    },
    onmouseout: function() {
      chart1.revert();
      chart2.revert();
    }
  },
  legend: {
    position: 'right',
    show: true,
    item: {
      onclick: function(id) {
        if (chart1) chart1.toggle(id);
        if (chart2) chart2.toogle(id);
      },
      onmouseover: function(id) {
        if (chart1) chart1.focus(id);
        if (chart2) chart2.focus(id);
      },
      onmouseout: function(id) {
        if (chart1) chart1.revert();
        if (chart2) chart2.revert();
      }
    }
  },
  tooltip: {
    show: true,
    format: {
      value: function(value) {
        return d3.format(",.0f")(value);
      }
    }
  },
  zoom: {
    enabled: true
  },
  axis: {
    x: {
      type: 'timeseries',
      tick: {
        rotate: 90,
        format: '%Y-%m-%d'
      }
    },
    y: {
      label: 'sample-data',
      tick: {
        format: d3.format(",")
      }
    }
  },
  color: {
    pattern: colors
  }
});


var chart2 = c3.generate({
  bindto: d3.select('#chart2'),
  data: {
    columns: [[columns[0]].concat(data[0])],
    type: 'pie',
    onmouseover: function(id) {
      if (chart1) chart1.focus(id);
      if (chart2) chart2.focus(id);
    },
    onmouseout: function(id) {
      if (chart1) chart1.revert();
      if (chart2) chart2.revert();
    }
  },
  legend: {
    position: 'right',
    show: true,
    item: {
      onclick: function(id) {
        if (chart1) chart1.toggle(id);
        if (chart2) chart2.toogle(id);
      },
      onmouseover: function(id) {
        if (chart1) chart1.focus(id);
        if (chart2) chart2.focus(id);
      },
      onmouseout: function(id) {
        if (chart1) chart1.revert();
        if (chart2) chart2.revert();
      }
    }

  },
  color: {
    pattern: colors
  },
});



for (var i = 1; i < columns.length; i++) {
  setTimeout(function(column) {
    chart1.load({
      columns: [
        [columns[column]].concat(data[column])
      ]
    });
    chart2.load({
      columns: [[columns[column]].concat(data[column])]
    });

  }, (i * 5000 / columns.length), i);
}

document.getElementById("exportButton").onclick = function() {
  exportChartToImage();
};

function exportChartToImage() {
  var createImagePromise = new Promise(function(resolve, reject) {
    var images = [];
    d3.selectAll('svg').each(function() {
      if (this.parentNode) {
        images.push(getSvgImage(this.parentNode, true));
      }
    });
    if (images.length > 0)
      resolve(images);
    else
      reject(images);
  });
  createImagePromise.then(function(images) {
      images.forEach(function(img, n) {
        img.toBlob(function(blob) {
          saveAs(blob, "image_" + (n + 1) + ".png");
        });
      });
    })
    .catch(function(error) {
      throw error;
    });
};

/**
 * Converts a SVG-Chart to a canvas and returns it.
 */
function getSvgImage(svgContainer, png) {
  var svgEl = d3.select(svgContainer).select('svg').node();
  var svgCopyEl = svgEl.cloneNode(true);


  if (!svgCopyEl)
    return;
  //remove elements not for printing
  lensObject = d3.selectAll(".hidden-print").remove().exit();

  //add temp document objects
  var emptySvgEl = d3.select(document.createElementNS("http://www.w3.org/2000/svg", "svg")).attr("id", "emptysvg")
    .attr("version", 1.1)
    .attr("height", 2)
    .node();
  var canvasComputed = d3.select(document.createElement("canvas")).attr("id", "canvasComputed").node();

  var container = d3.select(document.createElement("div")).attr("style", "display: none;")
    .attr("class", "c3").node();
  svgContainer.append(container);
  container.append(svgCopyEl);
  container.append(emptySvgEl);
  container.append(canvasComputed);

  //apply all CSS styles to SVG
  exportStyles(svgCopyEl, emptySvgEl);

  // transform SVG to canvas using external canvg
  canvg(canvasComputed, new XMLSerializer().serializeToString(svgCopyEl));

  //remove temp document objects
  canvasComputed.remove();
  emptySvgEl.remove();
  svgCopyEl.remove();

  container.remove();

  return canvasComputed;
}

function exportStyles(svg, emptySvg) {
  var tree = [];
  var emptySvgDeclarationComputed = getComputedStyle(emptySvg);
  //d3.select(svg).selectAll().each(function() {
  $(svg).find("*").each(function() {
    explicitlySetStyle(this, emptySvgDeclarationComputed);
  });
}


function traverse(obj, tree) {
  tree.push(obj);
  if (obj.hasChildNodes()) {
    var child = obj.firstChild;
    while (child) {
      if (child.nodeType === 1 && child.nodeName != 'SCRIPT') {
        traverse(child, tree);
      }
      child = child.nextSibling;
    }
  }
  return tree;
}

function explicitlySetStyle(element, emptySvgDeclarationComputed) {
  var cSSStyleDeclarationComputed = getComputedStyle(element);
  var i, len, key, value;
  var computedStyleStr = "";
  for (i = 0, len = cSSStyleDeclarationComputed.length; i < len; i++) {
    key = cSSStyleDeclarationComputed[i];
    value = cSSStyleDeclarationComputed.getPropertyValue(key);
    if (value !== emptySvgDeclarationComputed.getPropertyValue(key)) {
      if (key == 'visibility' && value == 'hidden') {
        computedStyleStr += 'display: none;';
      } else {
        computedStyleStr += key + ":" + value + ";";
      }
    }
  }
  element.setAttribute('style', computedStyleStr);
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.12/c3.min.css" rel="stylesheet" />
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.12/c3.min.js"></script>
<!-- Required to convert named colors to RGB -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/canvg/1.4/rgbcolor.min.js"></script>
<!-- Optional if you want blur -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/stackblur-canvas/1.4.1/stackblur.min.js"></script>
<!-- Main canvg code -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/canvg/1.5/canvg.js"></script>
<script src="https://fastcdn.org/FileSaver.js/1.1.20151003/FileSaver.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div id="chart1" class "c3">

</div>

<div id="chart2" class="c3">

</div>

<button type="button" id="exportButton">
export to SVG
</button>

=> http://jsfiddle.net/gothmogg/jLt3yq75/

This sample shows exemplary how we apply the CSS styles to the SVG. If we use normal canvg without getComputedStyle the x and y axes of the c3 chart look like a total mess...

Do you know faster way to get valid images? Is there a way to filter the CSS-styles? Maybe use "c3" styles only?


回答1:


I have managed to solve the performance problems of exporting C3 SVG charts to PNG by avoiding to use getComputedStyle.

Browsing the issues in C3 I found issue #313

https://github.com/c3js/c3/issues/313

For others http://www.nihilogic.dk/labs/canvas2image/ might also be a good place to look at but I found a solution at https://gist.github.com/aendrew/1ad2eed6afa29e30d52e#file-exportchart-js-L29.

I have changed the code from using angular to d3 and now it works (for me).

Hopefully this might help others having the same issue.

Here the working code. Please note: the css styles are only examined and inlined once.

var labels = ['2018-10-01', '2018-10-02', '2018-10-03', '2018-10-04', '2018-10-05', '2018-10-06', '2018-10-07', '2018-10-08', '2018-10-09', '2018-10-10', '2018-10-11', '2018-10-12', '2018-10-13', '2018-10-14', '2018-10-15', '2018-10-16', '2018-10-17', '2018-10-18', '2018-10-19', '2018-10-20', '2018-10-21', '2018-10-22', '2018-10-23', '2018-10-24', '2018-10-25', '2018-10-26', '2018-10-27', '2018-10-28', '2018-10-29', '2018-10-30', '2018-10-31', '2018-11-01', '2018-11-02', '2018-11-03', '2018-11-04', '2018-11-05', '2018-11-06', '2018-11-07', '2018-11-08', '2018-11-09', '2018-11-10', '2018-11-11', '2018-11-12', '2018-11-13', '2018-11-14', '2018-11-15', '2018-11-16', '2018-11-17', '2018-11-18', '2018-11-19', '2018-11-20', '2018-11-21', '2018-11-22', '2018-11-23', '2018-11-24', '2018-11-25', '2018-11-26', '2018-11-27', '2018-11-28', '2018-11-29', '2018-11-30', '2018-12-01', '2018-12-02', '2018-12-03', '2018-12-04', '2018-12-05', '2018-12-06', '2018-12-07', '2018-12-08', '2018-12-09', '2018-12-10', '2018-12-11', '2018-12-12', '2018-12-13', '2018-12-14', '2018-12-15', '2018-12-16', '2018-12-17', '2018-12-18', '2018-12-19', '2018-12-20', '2018-12-21', '2018-12-22', '2018-12-23', '2018-12-24', '2018-12-25', '2018-12-26', '2018-12-27', '2018-12-28', '2018-12-29', '2018-12-30', '2018-12-31', '2019-01-01', '2019-01-02', '2019-01-03', '2019-01-04', '2019-01-05', '2019-01-06', '2019-01-07', '2019-01-08', '2019-01-09', '2019-01-10', '2019-01-11', '2019-01-12', '2019-01-13', '2019-01-14', '2019-01-15', '2019-01-16', '2019-01-17', '2019-01-18', '2019-01-19', '2019-01-20', '2019-01-21', '2019-01-22', '2019-01-23', '2019-01-24', '2019-01-25', '2019-01-26', '2019-01-27', '2019-01-28', '2019-01-29', '2019-01-30', '2019-01-31', '2019-02-01', '2019-02-02', '2019-02-03', '2019-02-04', '2019-02-05', '2019-02-06', '2019-02-07', '2019-02-08', '2019-02-09', '2019-02-10', '2019-02-11', '2019-02-12', '2019-02-13', '2019-02-14', '2019-02-15', '2019-02-16', '2019-02-17', '2019-02-18', '2019-02-19', '2019-02-20', '2019-02-21', '2019-02-22', '2019-02-23', '2019-02-24', '2019-02-25', '2019-02-26', '2019-02-27', '2019-02-28'];
var columns = ['data101', 'data2', 'data347'];
var data = [

  [0, 0, 2, 2, 1, 2, 7, 3, 1, 7, 5, 5, 5, 5, 6, 6, 11, 7, 2, 7, 16, 7, 3, 5, 10, 9, 11, 7, 3, 7, 7, 10, 10, 9, 18, 10, 20, 13, 9, 19, 16, 13, 20, 18, 14, 15, 18, 20, 19, 11, 13, 13, 12, 16, 11, 12, 21, 20, 23, 19, 19, 23, 23, 24, 23, 25, 21, 23, 20, 22, 21, 23, 24, 25, 27, 29, 28, 25, 24, 17, 20, 24, 22, 27, 21, 27, 19, 26, 31, 27, 28, 27, 21, 20, 27, 22, 22, 19, 17, 21, 23, 19, 22, 20, 21, 25, 15, 19, 20, 19, 21, 28, 17, 20, 14, 18, 17, 20, 27, 21, 18, 18, 20, 16, 27, 16, 16, 9, 18, 8, 19, 13, 8, 16, 15, 16, 9, 15, 10, 13, 10, 11, 10, 13, 12, 7, 14, 16, 13, 14, 8],
  [0, 0, 338, 1201, 1268, 1371, 1286, 1148, 446, 288, 228, 253, 193, 201, 283, 393, 436, 379, 421, 444, 444, 417, 513, 353, 364, 399, 238, 191, 305, 337, 365, 349, 365, 244, 101, 39, 55, 72, 151, 98, 31, 127, 114, 92, 104, 196, 307, 245, 84, 168, 41, 38, 292, 488, 536, 569, 495, 448, 408, 358, 344, 380, 328, 334, 332, 330, 345, 312, 369, 377, 356, 301, 226, 273, 237, 116, 178, 133, 114, 138, 95, 143, 74, 74, 83, 47, 75, 101, 96, 59, 46, 128, 70, 57, 93, 80, 94, 93, 63, 86, 81, 63, 70, 102, 91, 67, 69, 68, 88, 76, 79, 70, 119, 88, 74, 94, 76, 54, 82, 90, 75, 130, 67, 78, 106, 91, 81, 27, 77, 21, 104, 83, 55, 60, 62, 304, 393, 191, 292, 77, 76, 55, 125, 89, 99, 127, 60, 75, 99, 120, 56],
  [0, 0, 0, 1419, 7454, 12638, 10944, 7652, 4272, 11219, 9071, 7207, 7929, 8373, 9566, 6310, 7406, 9286, 8415, 7659, 6457, 3380, 10902, 10952, 10508, 7219, 4625, 4484, 4396, 5178, 5991, 7927, 14132, 14307, 5094, 10011, 6257, 9184, 18574, 12597, 11415, 7118, 9991, 10225, 14337, 4417, 12701, 17833, 23553, 10037, 4833, 5894, 19421, 14735, 12597, 8730, 5888, 11836, 13143, 17219, 10492, 10528, 8649, 11868, 10502, 6758, 7672, 8479, 11142, 22330, 26595, 4423, 17434, 8709, 9657, 7823, 9135, 19765, 18016, 16010, 8419, 7300, 8877, 9611, 9050, 8680, 8211, 6635, 3069, 10739, 6288, 6761, 7807, 16243, 20415, 23051, 19727, 8721, 6445, 8585, 13688, 14728, 17113, 16255, 3898, 4622, 3869, 3774, 4190, 3461, 4824, 4608, 4613, 3677, 3648, 3575, 3556, 4036, 3732, 2517, 4676, 4129, 3250, 4142, 3987, 4396, 3362, 2964, 1849, 2609, 2851, 3003, 3583, 3473, 3190, 2658, 4363, 3959, 4588, 3771, 4315, 3178, 3354, 3159, 2695, 4114, 4292, 3322, 1218, 3526, 3717]
];
var colors = ['#0065A3', '#767670', '#D73648', '#7FB2CE', '#00345B'];
var padding = 5;

//prepare chart data
var columnData = [];
var chartDataColumns = [];
var chartData = [];

var C3Styles = null;


chartData.push([columns[0]].concat(data[0]));

chartDataColumns = [
  ['x'].concat(labels)
].concat(chartData);

var chart1 = c3.generate({
  bindto: d3.select('#chart1'),
  data: {
    x: 'x',
    columns: [
      ['x'].concat(labels)
    ].concat(chartData),
    type: 'line',
    onmouseover: function(d) {
      chart1.focus(d.id);
      chart2.focus(d.id);
    },
    onmouseout: function() {
      chart1.revert();
      chart2.revert();
    }
  },
  legend: {
    position: 'right',
    show: true,
    item: {
      onclick: function(id) {
        if (chart1) chart1.toggle(id);
        if (chart2) chart2.toogle(id);
      },
      onmouseover: function(id) {
        if (chart1) chart1.focus(id);
        if (chart2) chart2.focus(id);
      },
      onmouseout: function(id) {
        if (chart1) chart1.revert();
        if (chart2) chart2.revert();
      }
    }
  },
  tooltip: {
    show: true,
    format: {
      value: function(value) {
        return d3.format(",.0f")(value);
      }
    }
  },
  zoom: {
    enabled: true
  },
  axis: {
    x: {
      type: 'timeseries',
      tick: {
        rotate: 90,
        format: '%Y-%m-%d'
      }
    },
    y: {
      label: 'sample-data',
      tick: {
        format: d3.format(",")
      }
    }
  },
  color: {
    pattern: colors
  }
});


var chart2 = c3.generate({
  bindto: d3.select('#chart2'),
  data: {
    columns: [
      [columns[0]].concat(data[0])
    ],
    type: 'pie',
    onmouseover: function(id) {
      if (chart1) chart1.focus(id);
      if (chart2) chart2.focus(id);
    },
    onmouseout: function(id) {
      if (chart1) chart1.revert();
      if (chart2) chart2.revert();
    }
  },
  legend: {
    position: 'right',
    show: true,
    item: {
      onclick: function(id) {
        if (chart1) chart1.toggle(id);
        if (chart2) chart2.toogle(id);
      },
      onmouseover: function(id) {
        if (chart1) chart1.focus(id);
        if (chart2) chart2.focus(id);
      },
      onmouseout: function(id) {
        if (chart1) chart1.revert();
        if (chart2) chart2.revert();
      }
    }

  },
  color: {
    pattern: colors
  },
});



for (var i = 1; i < columns.length; i++) {
  setTimeout(function(column) {
    chart1.load({
      columns: [
        [columns[column]].concat(data[column])
      ]
    });
    chart2.load({
      columns: [
        [columns[column]].concat(data[column])
      ]
    });

  }, (i * 5000 / columns.length), i);
}

document.getElementById("exportButton").onclick = function() {
  exportChartToImage();
};

function exportChartToImage() {
  var createImagePromise = new Promise(function(resolve, reject) {
    var images = [];
    d3.selectAll('svg').each(function() {
      if (this.parentNode) {
        images.push(getSvgImage(this.parentNode, true));
      }
    });
    if (images.length > 0)
      resolve(images);
    else
      reject(images);
  });
  createImagePromise.then(function(images) {
      images.forEach(function(img, n) {
        img.toBlob(function(blob) {
          saveAs(blob, "image_" + (n + 1) + ".png");
        });
      });
    })
    .catch(function(error) {
      throw error;
    });
};

/**
 * Converts a SVG-Chart to a canvas and returns it.
 */
function getSvgImage(svgContainer, png) {
  var svgEl = d3.select(svgContainer).select('svg').node();
  var svgCopyEl = svgEl.cloneNode(true);


  if (!svgCopyEl)
    return;
  //remove elements not for printing
  lensObject = d3.selectAll(".hidden-print").remove().exit();

  //add temp document objects
  var canvasComputed = d3.select(document.createElement("canvas")).attr("id", "canvasComputed").node();
  var container = d3.select(document.createElement("div")).attr("style", "display: none;")
    .attr("class", "c3").node();
  svgContainer.append(container);
  container.append(svgCopyEl);
  container.append(canvasComputed);


  /* taken from https://gist.github.com/aendrew/1ad2eed6afa29e30d52e#file-exportchart-js 
  	and changed from, angular to D3 functions
  */
  /* Take styles from CSS and put as inline SVG attributes so that Canvg
           can properly parse them. */

  var chartStyle;
  if (!C3Styles) {
    // Get rules from c3.css
    var styleSheets = document.styleSheets;
    for (var i = 0; i <= styleSheets.length - 1; i++) {
      if (styleSheets[i].href && (styleSheets[i].href.indexOf('c3.min.css') !== -1 || styleSheets[i].href.indexOf('c3.css') !== -1)) {
        try {
          if (styleSheets[i].rules !== undefined) {
            chartStyle = styleSheets[i].rules;
          } else {
            chartStyle = styleSheets[i].cssRules;
          }
          break;
        }

        //Note that SecurityError exception is specific to Firefox.
        catch (e) {
          if (e.name == 'SecurityError') {
            console.log("SecurityError. Cant read: " + styleSheets[i].href);
            continue;
          }
        }
      }
      if (chartStyle !== null && chartStyle !== undefined) {
        C3Styles = {};
        var selector;
        // Inline apply all the CSS rules as inline
        for (i = 0; i < chartStyle.length; i++) {
          if (chartStyle[i].type === 1) {
            selector = chartStyle[i].selectorText;
            var styleDec = chartStyle[i].style;
            for (var s = 0; s < styleDec.length; s++) {
              C3Styles[styleDec[s]] = styleDec[styleDec[s]];
            }
          }
        }
      }
    }
  }
  if (C3Styles)
    d3.select(svgCopyEl).selectAll('.c3:not(.c3-chart):not(path)').style(C3Styles);


  // SVG doesn't use CSS visibility and opacity is an attribute, not a style property. Change hidden stuff to "display: none"
  d3.select(svgCopyEl).selectAll('*')
    .filter(function(d) {
      return d && d.style && (d.style('visibility') === 'hidden' || d.style('opacity') === '0');
    })
    .style('display', 'none');
  //fix weird back fill
  d3.select(svgCopyEl).selectAll("path").attr("fill", "none");
  //fix no axes
  d3.select(svgCopyEl).selectAll("path.domain").attr("stroke", "black");
  //fix no tick
  d3.select(svgCopyEl).selectAll(".tick line").attr("stroke", "black");

  // transform SVG to canvas using external canvg
  canvg(canvasComputed, new XMLSerializer().serializeToString(svgCopyEl));

  //remove temp document objects
  canvasComputed.remove();
  svgCopyEl.remove();

  container.remove();

  return canvasComputed;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.12/c3.min.css" rel="stylesheet" />
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.12/c3.min.js"></script>
<!-- Required to convert named colors to RGB -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/canvg/1.4/rgbcolor.min.js"></script>
<!-- Optional if you want blur -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/stackblur-canvas/1.4.1/stackblur.min.js"></script>
<!-- Main canvg code -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/canvg/1.5/canvg.js"></script>
<script src="https://fastcdn.org/FileSaver.js/1.1.20151003/FileSaver.min.js"></script>

<div id="chart1" class "c3">

</div>

<div id="chart2" class="c3">

</div>

<button type="button" id="exportButton">
export to SVG
</button>

Unfortunately

document.styleSheets[i].rules

can't be accessed in the script here. It worked in my environment though. Any idea why this does not work here?



来源:https://stackoverflow.com/questions/54944691/svg-to-image-export-performance-issues-using-canvg-xmlserializer-getcompute

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!