2017-11-12 1 views
0

Ich habe versucht, hier den Rat zu folgen:exportieren SVG png oder andere Bild mit Styling, mit JavaScript

SVG to Canvas with d3.js

sowie die this npm module meine c3 zu exportieren (die auf d3 basiert) Plots zu einer Bilddatei - .png für jetzt.

im Browser sieht die Image-Datei wie folgt aus:

enter image description here

Der save-svg-as-png Modulknoten erzeugt jedoch diese:

enter image description here

Das Skript aus der SO oben genannten post erzeugt folgendes in einer neuen Registerkarte:

enter image description here

Wie Sie sehen können, werden die Pfade geschlossen und dann ausgefüllt, als ob die .css ignoriert wird.

Hier ist der Code, die Plots zu erzeugen:

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

und (kopiert aus der oben erwähnte SO post):

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

Ich habe auch ein Beispielprojekt mit diesem Code hochgeladen github:

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

könnte jemand beraten Wie sieht die exportierte Datei so aus, als würde sie im Browser gerendert? Vielen Dank!

Update # 1: Unter https://stackoverflow.com/users/3702797/kaiido ‚Anregung folgend, in plotMultiline.js geändert I

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

zu

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

und erzeugt dann saveSvgAsPng folgende Bild-Datei:

enter image description here

Dies behebt die geschlossene Form fill Problem, aber der Hintergrund ist tatsächlich transparent, nicht weiß, wie man aus dem folgenden Screenshot sehen:

enter image description here

Aber das ist eigentlich gut genug für meine Zwecke.

Antwort

1

Dies liegt daran, dass c3 einige Regeln aus dem übergeordneten div-Container mit einem class="c3"-Attribut festlegt.

Insbesondere finden Sie eine

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

Regel.
Diese Regel passt nicht mehr, wenn Sie Ihre SVG in eine eigenständige Version konvertieren.

Um dies zu umgehen, können Sie diese Klasse einfach auf dem übergeordneten Knoten svg setzen.

svg.classList.add('c3'); 

Aber Sie werden die

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

Regel verlieren. So könnte man es sich gesetzt haben, und wandeln es .c3 svg, svg.c3 {...

Alternativ zum Beispiel, könnten Sie getComputedStyle alle Knoten Ihres svg, und die Standardwerte diejenigen herauszufiltern, aber das wäre noch eine Menge Arbeit zu tun, für den Browser ...

+0

danke für Ihren Vorschlag. Ich fand, dass 'svg.classList.add' nicht funktionierte, aber' .classed ('c3', true) 'auf dem Svg-Objekt hat funktioniert (siehe meine Änderungen an der meine Frage). Ich musste keine CSS ändern, um den gewünschten Effekt zu erhalten (bisher). –

+1

@ShafiqueJamal yes 'svg.classList.add' erwartet, dass' svg' ein SVGSVGElement ist (d. H. Der DOM-Knoten direkt). In deinem Fall ist es ein d3-Objekt. Um den DOM-Knoten zu erhalten, würden Sie seine '.node()' Methode aufrufen, aber wie Sie herausgefunden haben, können Sie auch direkt die d3 '.classed' Methode verwenden. Und für den weißen Hintergrund scheint es standardmäßig sogar auf c3 transparent zu sein. Sie können immer einen Stil auf Ihrem Svg-Knoten hinzufügen ('svg.style ('background-color', 'white');'). – Kaiido