简体   繁体   English

使用JavaScript将svg导出为具有样式的png或其他图像

[英]Exporting svg to png or other image with styling, using JavaScript

I have tried to follow the advice here: 我尝试遵循以下建议:

SVG to Canvas with d3.js 通过d3.js将SVG转换为Canvas

as well as the this npm module to export my c3 (which is based on d3) plots to an image file - .png for now. 以及使用此npm模块将我的c3(基于d3)绘图导出到图像文件-.png。

In the browser, the image file looks like this: 在浏览器中,图像文件如下所示:

在此处输入图片说明

The save-svg-as-png node module, however, produces this: 但是, save-svg-as-png节点模块将产生以下结果:

在此处输入图片说明

The script from the above mentioned SO post produces the following in a new tab: 上述SO帖子中的脚本在新选项卡中产生以下内容:

在此处输入图片说明

As you can see, the paths are closed and then filled in, as though the .css is being ignored. 如您所见,路径被关闭然后被填充,就像.css被忽略一样。

Here is the code to produce the plots: 这是生成绘图的代码:

# plotMultiline.js
import * as c3 from 'c3';
import * as d3 from 'd3';
import { saveSvgAsPng } from 'save-svg-as-png';
import createImageFromSVG from './createImageFromSVG';

const plotMultiline = (
  chartID, dataToPlot,
  headingsAndLabels,
  xHeading,
  nTicks,
  xRange,
  xLines = [],
  title,
  xAxisTitle,
  note,
  yTitle = 'Frequency of occurrence',
  inset = null,
) => {
  d3.select('body').append('div')
    .attr('id', chartID);

  const yDatas = Object.entries(headingsAndLabels).map(([columnName, newLabel]) =>
    [newLabel, ...dataToPlot.map(d => d[columnName])]);
  const firstKey = Object.keys(dataToPlot[0])[0];
  const secondKey = Object.keys(dataToPlot[0])[1];
  const xMin = +dataToPlot[0][firstKey];
  const xMax = +[...dataToPlot].pop()[secondKey];
  const xTickValuesAll = [...dataToPlot.map(d => +d[firstKey])];
  const nXTickValuesAll = xTickValuesAll.length;
  const xTickValuesIndices =
    [...Array(nTicks).keys()].map(d => d * Math.ceil(nXTickValuesAll / nTicks))
      .filter(d => d <= nXTickValuesAll - 1);

  let xTickValues = [];
  if (nTicks) {
    if (typeof nTicks === 'number') {
      xTickValues = [...xTickValuesIndices.map(i => +xTickValuesAll[i]), xMax];
    } else if (nTicks === 'integer') {
      xTickValues = [...xTickValuesAll, xMax].filter(d => Math.round(d) === d);
    }
  } else {
    xTickValues = [...xTickValuesAll, xMax];
  }
  const rightPadding = (xTickValues[1] - xTickValues[0]) / 5;

  const chart = c3.generate({
    bindto: `#${chartID}`,
    title: {
      text: title,
    },
    point: {
      show: false,
    },
    size: {
      width: 960,
      height: 500,
    },
    padding: {
      bottom: 20,
      top: 20,
    },
    data: {
      x: xHeading,
      columns: yDatas,
    },
    legend: {
      position: 'inset',
      inset,
    },
    axis: {
      x: {
        tick: {
          outer: false,
          values: xTickValues,
        },
        min: xMin,
        max: xMax,
        padding: { left: 0, right: rightPadding },
        label: {
          text: xAxisTitle || xHeading,
          position: 'outer-center',
        },
        height: 50,
      },
      y: {
        padding: { top: 0, bottom: 0 },
        label: {
          text: yTitle,
          position: 'outer-middle',
        },
      },
    },
    grid: {
      x: {
        show: true,
        lines: xLines,
      },
      y: {
        show: true,
      },
    },
  });

  d3.select(`#${chartID} svg`).attr('id', `svg-${chartID}`);

  if (note) {
    d3.select(`#${chartID} svg`).append('text')
      .attr('x', 630)
      .attr('y', 485)
      .classed('note', true)
      .text(note);
  }

  if (xRange) {
    const xRangeMin = xRange[0];
    const xRangeMax = xRange[1];

    chart.axis.range({
      min: {
        x: xRangeMin,
      },
      max: {
        x: xRangeMax,
      },
    });
  }

  setTimeout(() => {
    d3.select(`#${chartID}`)
      .append('button')
      .on('click', () => saveSvgAsPng(d3.select(`#svg-${chartID}`)[0]['0'], `#svg-${chartID}.png`))
      .classed('btn btn-success', true)
      .attr('id', 'button-library');

    d3.select(`#${chartID}`)
      .append('button')
      .on('click', () => createImageFromSVG(`#svg-${chartID}`))
      .classed('btn btn-success', true)
      .attr('id', 'button-so-script');
  }, 1000);
};

export default plotMultiline;

and (copied from the above-mentioned SO post): 和(摘自上述SO帖子):

# createImageFromSVG
import * as d3 from 'd3';

const createImageFromSVG = (selectorForSVG) => {
  // get styles from all required stylesheets
  // http://www.coffeegnome.net/converting-svg-to-png-with-canvg/
  let style = '\n';
  for (let i = 0; i < document.styleSheets.length; i++) {
    const sheet = document.styleSheets[i];

    if (sheet.href) {
      const { rules } = sheet;
      if (rules) {
        for (let j = 0; j < rules.length; j++) {
          style += (`${rules[j].cssText}\n`);
        }
      }
    }
  }

  const svg = d3.select(selectorForSVG);
  const img = new Image();
  const serializer = new XMLSerializer();

  // prepend style to svg
  svg.insert('defs', ':first-child');
  d3.select('svg defs')
    .append('style')
    .attr('type', 'text/css')
    .html(style);

  // generate IMG in new tab
  const svgStr = serializer.serializeToString(svg.node());
  img.src = `data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(svgStr)))}`;

  const popUp = window.open();
  if (popUp) {
    popUp.document.write(`<img src="${img.src}"/>`);
  }
};

export default createImageFromSVG;

I have also uploaded a sample project with this code to github: 我还将带有此代码的示例项目上载到github:

https://github.com/shafiquejamal/export-svg-to-png https://github.com/shafiquejamal/export-svg-to-png

Could someone advise on how to make the exported file look like what is rendered in the browser? 有人可以建议如何使导出的文件看起来像浏览器中呈现的内容吗? Thanks! 谢谢!

UPDATE #1: Following https://stackoverflow.com/users/3702797/kaiido 's suggestion below, in plotMultiline.js I changed 更新#1:按照https://stackoverflow.com/users/3702797/kaiido的建议,在plotMultiline.js我更改了

d3.select(`#${chartID} svg`).attr('id', `svg-${chartID}`)

to

d3.select(`#${chartID} svg`).attr('id', `svg-${chartID}`)
.classed('c3', true);

and then saveSvgAsPng generates the following image file: 然后saveSvgAsPng生成以下图像文件:

在此处输入图片说明

This fixes the closed-shape fill issue, but the background is actually transparent, not white, as you can see from the following screenshot: 这可以解决封闭形状的填充问题,但是背景实际上是透明的,而不是白色,如下面的屏幕截图所示:

在此处输入图片说明

But this is actually good enough for my purposes. 但这实际上足以满足我的目的。

This is because c3 sets some rules from the parent div container, which has a class="c3" attribute. 这是因为c3从父div容器设置了一些规则,该容器具有class="c3"属性。

In particular, you will find a 特别是,您会发现

.c3 path, .c3 line {
    fill: none;
    stroke: rgb(0, 0, 0);
}

rule. 规则。
This rule won't match anymore when you will convert your svg to a standalone. 当您将svg转换为独立版本时,此规则将不再匹配。

To workaround this, you might simply set this class on the parent svg node instead. 要解决此问题,您可以简单地在父svg节点上设置此类。

svg.classList.add('c3');

But you will loose the 但是你会失去

.c3 svg {
    font: 10px sans-serif;
    -webkit-tap-highlight-color: transparent;
}

rule. 规则。 So you might have to set it yourself, and convert it to eg .c3 svg, svg.c3 {... 因此,您可能需要自己进行设置,然后将其转换为.c3 svg, svg.c3 {...

Alternatively, you could getComputedStyle all the nodes of your svg, and filter out the defaults ones, but that would still be a lot of work to do for the browser... 另外,您可以getComputedStyle svg的所有节点,并过滤掉默认节点,但是对于浏览器来说,这仍然是很多工作……

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM