Skip to content

Inconsistent behavior of the scale factor for vector exports that include encapsulated rasters #58

@dperdios

Description

@dperdios

Inconsistent behavior of the scale factor for vector exports that include encapsulated rasters

Thank you so much for your work that brings the amazing features of plotly to static images too. This is extremely useful for publications that still rely on static figures (e.g. scientific journals). I was trying out some vector exports for different kind of plots at it was made clear from the plotly documentation that part of figures with specific plots (e.g. surface and mesh3d) would be “rasterized”.

Apparently, increasing the scale factor when calling write_image for vector formats does not produce encapsulated rasters with a higher resolution as it does for png (see details below). The main problem is that the behavior is not consistent between png and vector formats (tested with 'pdf' and 'svg').

Is there a way to have a consistent behavior?

(Sorry for the long issue message, I am trying to give as much info as possible.)

Image resolution using the scale factor

To (somehow) control the image resolution (only relevant for raster formats or raster components encapsulated in vector formats) the write_image (doc) method provides a scale factor. This works as expected for 'png' format but does not seem to work consistently for vector formats such as 'pdf' and 'svg'.

Different exports (using the kaleido engine) of the same image with 'png', 'pdf', and 'svg' formats and using different scale factors of 1 and 2 are attached. The code to reproduce these exports is detailed below. PNG and PDF files are also attached to this message (SVG is not supported as direct upload).

From a quick inspection of the file sizes, it is clear that 'png' does produce a larger file size (of “higher resolution”) when using a greater scale factor. This is not the case for the vector format (i.e. 'pdf' and 'svg'). Their file sizes do not change when using a greater scale factor (though their dimensions are scaled). I believe that the encapsulated rasters are similar (if not identical) despite the different scale factors. This unfortunately prevents from any high-resolution exports in vector formats (most suitable for print).

Additional information about the package versions and the PDF exports are provided below.

Ideal image resolution control

For all raster formats or rasters encapsulated within vector formats, it would be ideal if one could define a dpi parameter to control the final resolution of raster components. This would be much nicer than playing with a scale factor and dividing the final image by that factor to include it somewhere else (e.g. an image within a PDF).

Apparently, the current resolution for png exports is 72 dpi and 96 dpi for the vector exports. Both values are “default” values for screens (though many screens have much better resolutions nowadays), but clearly insufficient for print (for which 300 dpi and even 600 dpi is often required).

Additional observation

For the vector formats, it appears that not only the surface gets rasterized (again this is fine), but also the axis ticks (and the background). I do know if this is the expected behavior, but it would of course be optimal not to raster all components belonging to the surface plot (in particular fonts). I believe this would be much more complicated to control though.

Code

import os
import plotly.graph_objects as go
import pandas as pd
import itertools

# Read data from a csv
#  https://plotly.com/python/3d-surface-plots/
z_data = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/api_docs/mt_bruno_elevation.csv')

# Create figure
fig = go.Figure(data=[go.Surface(z=z_data.values)])

fig.update_layout(
    title='Mt Bruno Elevation',
    autosize=False, width=500, height=500,
    margin=dict(l=65, r=50, b=65, t=90)
)

# Export
export_dir = "images"
if not os.path.isdir(export_dir):
    os.mkdir(path=export_dir)

scale_seq = 1, 2
format_seq = 'png', 'svg', 'pdf'

for scale, fmt in itertools.product(scale_seq, format_seq):
    fig_name = f"export-surface-scale-{scale}.{fmt}"
    file = os.path.join(export_dir, fig_name)
    fig.write_image(file, format=fmt, scale=scale, engine='kaleido')

Additional information

plotly and kaleido versions

plotly==4.12.0
kaleido==0.0.3.post1

png outputs

$ identify -format "format: %m\nfilename: %f\nfile size: %b\npixels: %wx%h\ndpi: %xx%y\n" export-surface-scale-1.png
format: PNG
filename: export-surface-scale-1.png
file size: 95676B
pixels: 500x500
dpi: 72x72
$ identify -format "format: %m\nfilename: %f\nfile size: %b\npixels: %wx%h\ndpi: %xx%y\n" export-surface-scale-2.png
format: PNG
filename: export-surface-scale-2.png
file size: 253429B
pixels: 1000x1000
dpi: 72x72

pdfinfo outputs

From the page size property given in points, it seems like the renderer used a dpi of 96 as it started from a 500px x 500px image (i.e. 500 * 72 / 96 = 375).

$ pdfinfo export-surface-scale-1.pdf
Creator:        Chromium
Producer:       Skia/PDF m83
CreationDate:   Wed Nov 11 16:21:43 2020
ModDate:        Wed Nov 11 16:21:43 2020
Tagged:         no
UserProperties: no
Suspects:       no
Form:           none
JavaScript:     no
Pages:          1
Encrypted:      no
Page size:      375.12 x 375.12 pts
Page rot:       0
File size:      221278 bytes
Optimized:      no
PDF version:    1.4
$ pdfinfo export-surface-scale-2.pdf
Creator:        Chromium
Producer:       Skia/PDF m83
CreationDate:   Wed Nov 11 16:21:47 2020
ModDate:        Wed Nov 11 16:21:47 2020
Tagged:         no
UserProperties: no
Suspects:       no
Form:           none
JavaScript:     no
Pages:          1
Encrypted:      no
Page size:      750 x 750 pts
Page rot:       0
File size:      221264 bytes
Optimized:      no
PDF version:    1.4

export-surface-scale-2.pdf
export-surface-scale-2
export-surface-scale-1.pdf
export-surface-scale-1

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions