[feature request] Download SVG functionality with CSS styles attached (from another cell)

With SVG graphics, i think it is a good pratice to externalize presentation attributes as CSS instructions (in a specific cell), since it makes the SVG code more concise and readable, and the styles reusable across different charts.

If i export my SVG cell as a standalone SVG file with image, the styles will be missing and the graphic will render incorrectly.

So far, to fix this, i need to copy and paste the styles and add them to the SVG file in a <style> block. Or better, in a

<style type="text/css">
     <![CDATA[
...
 ]]>
 </style>

block, so it will render properly in Illustrator for instance.

I wish i could, in Observable, benefit from a SVG export functionality allowing to inject those styles in the exported file, just selecting for instance the cell with the relevant styles.

hi @emauviere you could already attach the external styles defined in a different cell inside your svg cell.

svg_with_style = html`<svg viewBox="0 0 10 10">
  ${style}
  <circle class="circle-style" cx="5" cy="5" r="4" />
</svg>`
style = html`
  <style>
    .circle-style {
      fill: orange;
      stroke: purple;
      stroke-width: 1px;
    }
  </style>
`

Then the downloaded svg would come with defined styles. It seems to render properly with Illustrator

1 Like

Hi @radames,
and thank you for the hint!
It works indeed in Illustrator, actually, i had quite an old version (CS6), but with a more recent version (2020), styles are properly recognized on import.

I could write also:

common_styles = html`
<style>
  svg .circle-style {
      fill: orange;
      stroke: purple;
      stroke-width: 5px;
  }
</style>
`
circle = {
  const svg = d3.select(DOM.svg(100, 100)) ;
  
  svg.append("style")
     .text(common_styles.innerHTML) ;
  
  svg.append("g").append("circle")
    .attr('class','circle-style')
    .attr("cx", 50)
    .attr("cy", 50)
    .attr("r", 30) ;

  return svg.node() ;
}

Say i have multiple charts sharing same styles: which annoys me is the repetition within each svg of these common_styles. I wish i could embed them only on exporting a specific svg cell.

hi @emauviere , I see the problem, you could automated this process and use some zip hack to download all at once.
The only annoying step is to name each cell, you need the reference cell and the file name, afaik it’s not straightforward to get the cell name without using the runtime.

data_zip = {
  const cells = [
    { name: "file1.svg", ref: svg_no_style_1 },
    { name: "file2.svg", ref: svg_no_style_2 },
    { name: "file3.svg", ref: svg_no_style_3 }
  ];
  const z = zip();

  for (const cell of cells) {
    const clone = cell.ref.cloneNode(true);
    clone.appendChild(style);
    z.file(cell.name, serialize(clone));
  }
  return z.generateAsync({ type: "blob" });
}
DOM.download(
  new Blob([data_zip], { type: "application/zip" }),
  "files.zip",
  "Download ZIP"
)
import { zip } from "@fil/jszip"
import { serialize } from "@mbostock/saving-svg"

also here

3 Likes