observablehq / feedback

Customer submitted bugs and feature requests
42 stars 3 forks source link

Option to download images no longer exists #463

Closed tobiassjosten closed 2 years ago

tobiassjosten commented 2 years ago

Describe the bug There used to be options to download a cell as a PNG or SVG (as seen in #344) but that seems to be gone now, with no other way to export the charts you've created.

To Reproduce Steps to reproduce the behavior:

  1. Click a cell's three-dots menu.

Expected behavior Listed options to "Download SVG" and "Download PNG".

Screenshots Screenshot

Desktop (please complete the following information):

mbostock commented 2 years ago

The presence of these menu actions depends on the contents of the cell; not every cell can be downloaded as PNG or SVG. So, if the contents of the cell have changed (e.g., and it’s no longer an SVG or Canvas element, and is instead arbitrary HTML), these download actions may no longer be available.

tobiassjosten commented 2 years ago

Thanks! But nothing has changed in my notebook and now none of my 20+ charts have that option. :/ Same computer, browser, even the same tab. I just refreshed the page and now the option is gone from everywhere.

CobusT commented 2 years ago

Could you publish your notebook (either listed or unlisted) so we can have a look? If not possible, can you share what your cells contain?

I did notice that Plot cells with legends seem to have this problem. Not sure if that is what you are running into.

tobiassjosten commented 2 years ago

I tried publishing but now I'm getting "RuntimeError: Cloud files are only available in private notebooks.". :/ So here goes with the contents of a cell, instead:

{
  const ldpsAttacks = attacks.filter(x => x.ldps > 0 && x.attack != "AXK" && x.stance == "None")
  return Plot.plot({
    inset: 8,
    color: {
      legend: true,
    },
    x: {
      label: "limb dps →",
      grid: true,
    },
    y: {
      label: null,
    },
    marks: [
      Plot.ruleX([0], {stroke: "white"}),
      Plot.dot(ldpsAttacks, {x: "ldps", y: "target", z: "damage", stroke: "type"}),
      Plot.ruleY(ldpsAttacks, {x: "ldps", y: "target", z: "damage", stroke: "grey"}),
    ]
  })
}

Does that help at all?

Going through more of the charts now, some I'm still able to download. They seem to mainly be bar or box marks, whereas charts with dot marks can't be downloaded. Not sure that's true for all of them, though.

One thing, however, is that I could definitely download some of these before and now I can't without having made any changes to them.

mbostock commented 2 years ago

When you use Plot’s color legend, it generates a FIGURE element to surround the SVG chart element to include any additional legend elements. Color legends may be either SVG or HTML depending on whether the color scale is continuous (ramp) or discrete (swatches). As a result we don’t currently support download as PNG or download as SVG for plots with legends. (Due to security restrictions, it’s not possible to take arbitrary HTML and rasterize it to PNG or convert it to SVG, so we can only show these options on cells that strictly produce SVG or Canvas elements.)

If you remove the legend: true you should see the image download options. (You can also render the legend into a separate cell using plot.legend.)

tobiassjosten commented 2 years ago

That was it, thank you so much! 🙌 Now it's working perfectly.

But I'm 100% certain this used to work just a week or two ago (though, then I screenshotted the legend to get it as an image – this seems a lot nicer). Did it change recently?

mbostock commented 2 years ago

Yes, it’s not your imagination. What changed is that in Plot 0.5.2, swatches legends are now rendered with little SVG elements so that they support patterns and gradients. Previously (in Plot 0.5.1 and below) the swatches elements were rendered as DIV elements. As a result the heuristic that Observable uses to detect whether a cell contains a “primary” SVG or Canvas element no longer triggers with Plot 0.5.2 and the download as image options are no longer available. The test looks like this:

function findSVG(value) {
  if (value instanceof SVGSVGElement) return value;
  if (value instanceof Element) {
    const svgs = value.querySelectorAll("svg");
    if (svgs.length === 1 && isMainImage(value, svgs[0])) return svgs[0];
  }
  return null;
}

Since svgs.length is now >1 this function returns null, and there are no download as image options.

We could change this heuristic to make it more generous, but that would mean in other cases you might not get the full image downloaded. Still, we should probably relax this test to return the first “main” image.

mbostock commented 2 years ago

I’ve updated the logic to take the first “main” image rather than requiring there to be exactly one SVG or Canvas element.

tobiassjosten commented 2 years ago

Thank you so much for taking the time to both improve and explain this! 🙏

Euphrasiologist commented 2 years ago

I'm glad I found this thread, as I was wondering why my PNG's didn't have their legends. Is there a way to get around this? Or is designing legends which are purely SVG, without wrapping in a figure the only option? Cheers! M