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

标签 javascript css d3.js svg c3.js

我已尝试遵循此处的建议:

SVG to Canvas with d3.js

以及 this npm module将我的 c3(基于 d3)绘图导出到图像文件 - 现在为 .png。

在浏览器中,图像文件如下所示:

enter image description here

但是,save-svg-as-png 节点模块会生成以下内容:

enter image description here

上述 SO 帖子中的脚本在新选项卡中生成以下内容:

enter image description here

如您所见,路径是闭合的,然后被填充,就好像 .css 被忽略了一样。

这是生成图的代码:

# 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;

和(从上述 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;

我还上传了一个带有这段代码的示例元素到github:

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

有人可以建议如何使导出的文件看起来像在浏览器中呈现的那样吗?谢谢!

更新 #1:关注 https://stackoverflow.com/users/3702797/kaiido下面的建议,在plotMultiline.js我改了

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

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

然后saveSvgAsPng生成如下图片文件:

enter image description here

这解决了闭合形状填充问题,但背景实际上是透明的,而不是白色,如您从以下屏幕截图中看到的那样:

enter image description here

但这实际上已经足够满足我的目的了。

最佳答案

这是因为 c3 从父 div 容器设置了一些规则,它有一个 class="c3" 属性。

特别是,你会发现一个

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

规则。
当您将 svg 转换为独立文件时,此规则将不再匹配。

要解决这个问题,您可以简单地在父 svg 节点上设置此类。

svg.classList.add('c3');

但是你会失去的

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

规则。 所以你可能必须自己设置它,并将其转换为例如 .c3 svg, svg.c3 {...

或者,您可以getComputedStyle 您的 svg 的所有节点,并过滤掉默认节点,但这对浏览器来说仍然需要大量工作...

关于javascript - 使用 JavaScript 将 svg 导出为 png 或其他具有样式的图像,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47244193/

相关文章:

javascript - 对 MM :SS transition 使用 d3.interpolateString

jquery - 如何将类应用于仅单击/选定的 div

javascript - Firefox 中 D3 强制布局性能缓慢

javascript - 创建用于输入表单验证的 jQuery 全局变量

javascript - 从 IBAN 号码生成 BIC 号码

css - Div 在 Div 之下

javascript - 在另一个 div 中垂直居中对齐一个 div

javascript - D3 将多个 child 添加到同一个 parent

c# - 如何在 C# 中的代码后面获取 javascript 值

javascript - 三.JS给物体赋 Material Color