Exporting SVG from Plotly Offline after interaction

I have a Jupyter notebook that contains code to generate a 3D scatterplot via Plotly Offline:

The relevant code to generate said scatterplot is:

from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
import plotly.graph_objs as go
import numpy as np

init_notebook_mode(connected=True)

x = subsetLog10.iloc[:, 0]
y = subsetLog10.iloc[:, 1]
z = subsetLog10.iloc[:, 2]

splot = go.Scatter3d(
    x=np.array(x),
    y=np.array(y),
    z=np.array(z),
    mode='markers',
    marker=dict(
        color='rgb(255, 0, 0)',
        size=1,
        symbol='circle',
        line=dict(
            color='rgb(255, 0, 0)',
            width=1
        )
    ),
    opacity=0.1
)

pdata = [splot]
layout = go.Layout(
    width=1024,
    height=1024,
    scene = dict(
        xaxis = dict(title=x.name, range = [0,6]),
        yaxis = dict(title=y.name, range = [0,6]),
        zaxis = dict(title=z.name, range = [0,6]))
)
fig = go.Figure(data=pdata, layout=layout)

iplot(fig, image='svg', filename='scatterplot.svg', image_width=1280, image_height=1280)

This makes a nice plot. This also exports a file called scatterplot.svg.

The issue I have is that the SVG is rendered with initial camera orbit settings, and those initial zoom and rotation parameters cut off parts of the axes. Additionally, other zoom and rotation settings would be better for viewing the dataset.

Is there a way to export an SVG only after zoom and rotation are applied by the person running the notebook?

Best for #api:python

Hi @AlexReynolds,

If you’re working in the Jupyter Notebook and you display the figure as a FigureWidget then the current zoom/rotation state is synced back the the FigureWidget object, making it possible to access the rotated stated.

fig = go.FigureWidget(data=data, layout=layout)
fig

Display figure, rotate it as desired

Then set the view properties explicitly (so that they will be used when saving to an image)

eye = fig.layout.scene.camera.eye
eye.x = eye.x
eye.y = eye.y
eye.z = eye.z

Perform your image save as before

iplot(fig, image='svg', filename='scatterplot.svg', image_width=1280, image_height=1280)

Also, just in case you weren’t aware, if you need more options when exporting your image you can use the new offline orca-based image export support (https://plot.ly/python/static-image-export/).

Hope that helps!
-Jon

Interesting idea; however, this did not work. Running a cell containing the iplot call resets the figure to the default scene parameters, before saving the SVG. I’m not sure if this is a bug or is expected behavior.

Hi @AlexReynolds ,

If you evaluate eye.x before and after you rotate do you see a change in its value? If the figure is displayed as a FigureWidget this property should be updated to reflect the current rotation (and this was the case when I tested it).

-Jon

Perhaps I’m not setting up the FigureWidget or environment correctly, or I’m not evaluating or tracking the eye property correctly. Would you have a notebook published anywhere, that I can try out as a way to confirm what the problem might be?

Hi @AlexReynolds,

Here’s a notebook that covers what I mean: https://plot.ly/~jon.mease/297/import-plotly-import-plotlyio-as-pio-pl/#/

Is this roughly what you were trying?
-Jon

I think I made some progress by setting up a virtual environment with specific versions of plotly.py and dependencies:

#!/bin/bash

conda create -n plotlywidget python=3.6
conda activate plotlywidget
pip install plotly==3.4.0
pip install plotly-orca==1.1.1
pip install "notebook>=5.3" "ipywidgets>=7.2"
pip install jupyterlab==0.35
export NODE_OPTIONS=--max-old-space-size=4096
jupyter labextension install @jupyter-widgets/jupyterlab-manager@0.38 --no-build
jupyter labextension install plotlywidget@0.5.0  --no-build
jupyter labextension install @jupyterlab/plotly-extension@0.18  --no-build
jupyter lab build
conda deactivate plotlywidget

HTML export worked correctly!

I am able to export SVG without error messages from within Jupyter. However, when I try to open the resulting SVG file in Adobe Illustrator CC 2018, I get an error message about the file being corrupt, and Illustrator does not open the file. I can open the SVG file in a web browser, but there is a rendering error message:

This page contains the following errors:

error on line 1 at column 479653: attributes construct error

Below is a rendering of the page up to the first error.

The problem appears to be localized to the figure legend. The legend only shows the color marker for the first trace or group of data; no other legend items are shown.

Separately from SVG issues, I ran into the following error message when attempting to export PNG or PDF from the Jupyter notebook via plotly.io.write_image():

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-14-c622c1ec90c2> in <module>
      1 # Write image using orca
----> 2 pio.write_image(fig, '/tmp/3d_rotated.pdf', validate=False)

~/anaconda3/lib/python3.6/site-packages/plotly/io/_orca.py in write_image(fig, file, format, scale, width, height, validate)
   1486                         width=width,
   1487                         height=height,
-> 1488                         validate=validate)
   1489 
   1490     # Open file

~/anaconda3/lib/python3.6/site-packages/plotly/io/_orca.py in to_image(fig, format, width, height, scale, validate)
   1391 Unfortunately, we don't yet know of an easy way to install poppler on Windows.
   1392 """
-> 1393         raise ValueError(err_message)
   1394 
   1395 

ValueError: 
The image request was rejected by the orca conversion utility
with the following error:
   525: plotly.js error

From reading your other comments on the forum related to this subject, it looks like specific versions of components are needed for plotlywidget to work. Is the version of orca that I am installing the correct version?

pip install plotly-orca==1.1.1

Are there other Python libraries that are required for exporting PNG and PDF?

I thought there might be additional dependencies for exporting these formats, but the Jupyter error message is the same with these libraries:

pip install psutil==5.4.8
conda install -c conda-forge poppler -y

I’m on OS X and also tried installing poppler from Homebrew: brew install poppler. This included several libraries for image handling libpng, libjpeg, etc.

Are there perhaps other dependencies that I overlooked?

Thanks very much for your help!

Hi @AlexReynolds, I haven’t seen a situation before where a browser throws errors when displaying an SVG image produced by plotly.js. So I’d like to track that down if we can. Would you be able to post a full minimal example of the figure that produces this SVG image? Also, how did you save this svg image, did you use plotly.io.write_image for this? Or one of the plot approaches?

As for the second situation, the poppler library is only required for .eps conversion so there shouldn’t be any additional dependencies needed for .png or .pdf. If you remove validate=False from the write_image call do you get any validation errors?

Also, could you try exporting this simple figure to an image an see what happens?

import plotly.io as pio
pio.write_image(go.Figure(data=[{'y': [1, 3, 2]}]), 'simple_fig.png')

Does it produce an image file named simple_fig.png in your current directory?

Thanks!
-Jon

Hi @jmmease,

Thanks for your ongoing help!

I set up a branch of my demo Github project called fcsPlotly that contains the corrupted SVG file and the notebook code that generates it. Something like the following should get you there:

$ git clone https://github.com/alexpreynolds/flow-cytometry-visualization.git
$ cd flow-cytometry-visualization
$ git checkout fcsPlotly
$ open 3d_rotated.svg

(Or open 3d_rotated.svg with Adobe Illustrator etc.)

I was able to open the SVG file in Inkscape 0.92.2 without any error messages, but the figure legend is incomplete, and the result looks similar to what Chrome shows (same incomplete legend, just no warning).

If useful for trying to reproduce the error, to get a fresh SVG file, this repo includes a script called setupFCSPlotlyEnvironment.sh that sets up the conda environment I’m running this notebook in.

My notebook raises the ValueError exception whether or not I specify validate=False or True.

The following test script worked in a fresh notebook:

import plotly.io as pio
import plotly.graph_objs as go
pio.write_image(go.Figure(data=[{'y': [1, 3, 2]}]), '/tmp/simple_fig.png')

(Perhaps what I’m seeing is specific to Scatter3d?)

Hi @AlexReynolds,

This is helpful info and a nice example! I probably won’t be able to dig into this for a couple of days, so I’ve created a placehold issue for it in the plotly.py project (https://github.com/plotly/plotly.py/issues/1264).

-Jon

I appreciate all your help. Thanks for taking a look, whenever you can!

Hi @AlexReynolds and @jmmease, did either of you ever find a solution to this issue? I’m currently trying to export a Plotly.py surface plot as an SVG/PDF with specified view settings (the default camera position is no good for my data and also cuts off parts of the axes’ labels), but I can’t get it to work with the now recommended fig.write_image(“myImage.svg”) Kaleido command. Here’s how I’m basically setting up my current figure:

mySurf = go.Figure()
mySurf.add_trace(go.Surface(
    colorbar=go.surface.ColorBar(title='myZ'),
    colorscale='matter',
    name='Net G',
    x=myX,y=myY,z=myZ,opacity=0.9))

I also tried creating the plot as a FigureWidget. (Does that still exist? I can’t find it in the documentation.) However, trying to manipulate the view via mySurf.layout.scene.camera.eye didn’t do anything. I’ve tried this both within a Jupyter notebook and with my code in a standalone script. I can manipulate the figure just fine, but it never exports how I left it.

Was able to scrounge up a solution on my own and thought I’d post it here in case anyone else runs into this challenge in the future. Plotly (both R and Python) does give you the capability to set the initial view for a 3D plot, it’s just not mentioned in the tutorials so you have to dig pretty extensively. Turns out that you can set the view via the layout.scene.camera() property (interface?) of a figure. My solution ended up looking something like this:

import plotly.graph_objs as go

mySurf = go.Figure
mySurf.add_trace(go.Surface(
    colorbar=go.surface.ColorBar(title='myZ'),
    colorscale='matter',
    name='My 3D surface',
    x=myX,y=myY,z=myZ,opacity=0.9))

view = dict(camera=dict(
    # Set the location that appears at the center of the view
    center=dict(x=0,y=-0.1,z=-0.1),
        #default is (0,0,0)
    # Set the place from which the camera "eye" views the plot
    eye=dict(x=1,y=2,z=0.75
        #default is (1.25,1.25,1.25)
))
mySurf.update_layout(scene=view)
mySurf.show()

Note that the coordinates used in center() and eye() are not the numbers used in your actual data. They’re relative to the plot itself. Thus, setting center(x=1,y=0,z=0.25) would produce the same centering regardless of your plot’s scale (I think).

Refs: