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

使用JavaScript将svg导出为png或具有样式的其他图像,javascript,css,d3.js,svg,c3.js,Javascript,Css,D3.js,Svg,C3.js,我在这里尝试了以下建议: 以及将我的c3(基于d3)绘图导出到图像文件-.png的步骤 在浏览器中,图像文件如下所示: 但是,将svg另存为png节点模块会产生以下结果: 上述SO post中的脚本在新选项卡中生成以下内容: 正如您所看到的,路径是关闭的,然后填充,就好像.css被忽略一样 以下是生成绘图的代码: # plotMultiline.js import * as c3 from 'c3'; import * as d3 from 'd3'; import { saveSvgA

我在这里尝试了以下建议:

以及将我的c3(基于d3)绘图导出到图像文件-.png的步骤

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

但是,
将svg另存为png
节点模块会产生以下结果:

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

正如您所看到的,路径是关闭的,然后填充,就好像.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;

然后
saveSvgAsPng
生成以下图像文件:

这修复了闭合形状填充问题,但背景实际上是透明的,而不是白色的,如以下屏幕截图所示:


但就我的目的而言,这实际上已经足够好了。

这是因为c3从父div容器设置了一些规则,父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;
}
规则。 因此,您可能需要自己设置它,并将其转换为例如
.c3svg、svg.c3{…


或者,您可以
getComputedStyle
您的svg的所有节点,并过滤掉默认的节点,但是对于浏览器来说,这仍然需要做很多工作…

谢谢您的建议。我发现
svg.classList.add
不起作用,但是svg对象上的
.classed('c3',true)
确实起作用(请参阅我对“我的问题”的编辑)。我不需要修改任何css来获得我想要的效果(到目前为止)。@ShafiqueJamal yes
svg.classList.add
希望
svg
是一个SVGSVGElement(即直接是DOM节点)。在您的情况下,它是一个d3对象。要获得DOM节点,您可以调用它的
.node()
方法,但正如您所发现的,您也可以直接使用d3
.classed
方法。对于白色背景,默认情况下,即使在c3上它也是透明的。您可以始终在svg节点上添加样式(
svg.style(
)。
.c3 path, .c3 line {
    fill: none;
    stroke: rgb(0, 0, 0);
}
svg.classList.add('c3');
.c3 svg {
    font: 10px sans-serif;
    -webkit-tap-highlight-color: transparent;
}