boogheta / coronavirus-countries

COVID-19 interactive dashboard for the whole world
https://boogheta.github.io/coronavirus-countries/
GNU Affero General Public License v3.0
55 stars 16 forks source link

image exports #29

Open robindemourat opened 4 years ago

robindemourat commented 4 years ago

Wouldn't it be useful to be able to export a given view in SVG/PNG (with a white/transparent background ?) for further work with other tools ?

boogheta commented 4 years ago

definitely yes, but I'm not sure how to do that easily. I would very much like also to be able to generate social cards images on the fly for each view so that when someone shares a url, the tweet/post immediately shows the actual viz, but manet seems to break on the interface's js unfortunately :(

robindemourat commented 4 years ago

For the svg and png exports here is some code (tested and working in the console ;)):

/**
 * Get the vis content and remove black surfaces
 */
function retrieveSVG(selector) {
  // get element
  const el = document.querySelector(selector);
  // clone it for modifications
  const svg = el.cloneNode(true);
  // remove surfaces
  svg.querySelectorAll('.surface').forEach(e => e.parentNode.removeChild(e))
  return svg;
}

/**
 * Serialize a svg to a url
 */
function svgToDataURL(svg) {
  const serializer = new XMLSerializer();
  let source = serializer.serializeToString(svg);
  //add name spaces if needed (bit from https://stackoverflow.com/questions/23218174/how-do-i-save-export-an-svg-file-after-creating-an-svg-with-d3-js-ie-safari-an)
  if(!source.match(/^<svg[^>]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)){
      source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
  }
  if(!source.match(/^<svg[^>]+"http\:\/\/www\.w3\.org\/1999\/xlink"/)){
      source = source.replace(/^<svg/, '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
  }
  //add xml declaration
  source = '<?xml version="1.0" standalone="no"?>\r\n' + source;
  return "data:image/svg+xml;charset=utf-8,"+encodeURIComponent(source);
}

/**
 * Turn a svg element into a png data url
 */
function svgToPng(el, fileName) {
  return new Promise(function(resolve, reject) {
    const serializer = new XMLSerializer();
    const svgString = serializer.serializeToString(el);
    const canvas =  document.createElement('canvas');
    canvas.width = el.getAttribute('width');
    canvas.height = el.getAttribute('height');
    const ctx = canvas.getContext("2d");
    const DOMURL = self.URL || self.webkitURL || self;
    document.body.appendChild(canvas);
    const img = new Image();
    const svg = new Blob([svgString], {type: "image/svg+xml;charset=utf-8"});
    const url = DOMURL.createObjectURL(svg);
    img.onload = function() {
        ctx.drawImage(img, 0, 0);
        const pngURL = canvas.toDataURL("image/png");
        resolve(pngURL)
        document.body.removeChild(canvas)
    };
    img.onerror = reject;
    img.src = url;
  })

}

/**
 * Download a svg string
 */
function download(url, fileName) {
  const dl = document.createElement("a");
  document.body.appendChild(dl); // This line makes it work in Firefox.
  dl.setAttribute("href", url);
  dl.setAttribute("download", fileName);
  dl.click()
  document.body.removeChild(dl)
}

/**
 * Actionable function for svg export
 */
function downloadAsSVG() {
  const svg = retrieveSVG('.svg svg')
  const url = svgToDataURL(svg)
  download(url, 'graph.svg')
}

/**
 * Actionable function for png export
 */
function downloadAsPNG() {
  const svg = retrieveSVG('.svg svg')
  svgToPng(svg)
  .then(url => {
    download(url, 'test.png')
  })
}

For the social cards, too bad for manet ... hard to see a way to handle all the possible permutations with another method (will think about it !).

boogheta commented 4 years ago

Really cool, thanks, will try and add it! (maybe in the OpenData/Download section?)

And out of curiosity I just retried with Manet and it seems to work now! I guess I has some bad js at the time that was breaking phantom?... Will try to use it as well then!

boogheta commented 4 years ago

After further experimentation and reflexion, I'm afraid there's no acceptable solution without generating the html with a different image value in the social meta unfortunately, so letting one download the image of his current view is probably the easiest

boogheta commented 4 years ago

Issues to adress for image downloads: