In [1]:
import PIL
import os
import pandas as pd
import numpy as np
import IPython.display as ip
import requests
from io import BytesIO

from lets_plot import *
LetsPlot.setup_html()
LetsPlot.set({'magick_export': True})

mpg = pd.read_csv('https://raw.githubusercontent.com/JetBrains/lets-plot-docs/master/data/mpg.csv')
airquality_df = pd.read_csv("https://raw.githubusercontent.com/vincentarelbundock/Rdatasets/master/csv/datasets/airquality.csv")
pie_df = pd.read_csv("https://raw.githubusercontent.com/JetBrains/lets-plot/refs/heads/master/docs/f-25a/data/gdp_forecast_2025_trillion_usd.csv", encoding ='utf-8')
markdown_df = pd.read_csv("https://raw.githubusercontent.com/JetBrains/lets-plot-docs/master/data/mpg.csv")
markdown_df.drop(columns=["Unnamed: 0"], inplace=True)

boat_response = requests.get('https://github.com/JetBrains/lets-plot-docs/raw/master/source/examples/cookbook/images/fisher_boat.png')
boat_raster = PIL.Image.open(BytesIO(boat_response.content))
boat = np.asarray(boat_raster)

def show(p, name, w=None, h=None, dpi=None, unit=None, scale=1, show_svg=True, out=None):
    if (out is None) or isinstance(out, str):
        out = name + ".png"
        png_img = p.to_png(out, w=w, h=h, dpi=dpi, unit=unit, scale=scale)
        out_dir = os.path.dirname(png_img)
    
        if show_svg:
            svg_img = os.path.join(out_dir, name + '.svg')
            svg = p.to_svg(svg_img)
            ip.display(ip.HTML("<h4>SVG</h4>"))
            ip.display(ip.SVG(p.to_svg()))
    
        ip.display(ip.HTML("<h4>ImageMagick</h4>"))
        ip.display(ip.Image(png_img))
    elif isinstance(out, BytesIO):
        # file-like object
        svg = BytesIO()
        p.to_svg(svg)
        p.to_png(out, w=w, h=h, dpi=dpi, unit=unit, scale=scale)
        if show_svg:
            ip.display(ip.HTML("<h4>SVG</h4>"))
            ip.display(ip.SVG(svg.getvalue()))
    
        ip.display(ip.HTML("<h4>ImageMagick</h4>"))
        ip.display(ip.Image(out.getvalue()))
    else:
        raise ValueError("Unsupported out type: " + type(out))
        
    return

Basic plots

In [2]:
barplot_df = {
    'val': ['3', '1', '2', '3', '1', '3', '2', '1', '3', '2', '2', '2', '2', '2', '2'],
    'vai': [3, 1,2, 3, 1, 3, 2, 1, 3, 2, 2, 2, 2, 2, 2]
}
p = ggplot(barplot_df) + geom_bar(aes(x=as_discrete('vai'), fill=as_discrete('vai', levels=[1, 2, 3])))

show(p, "magick_barplot")
Unsupported attribute `fill-rule` in Path
Fontconfig warning: "/home/ikupriyanov/.fonts.conf", line 11: invalid attribute 'name'

SVG

1 2 3 0 1 2 3 4 5 6 7 8 count vai vai 1 2 3

ImageMagick

Path

In [3]:
common_args = {'color': "black", 'alpha': .95}
quantiles = np.linspace(0, 1, 15)
fill_diverging = scale_fill_gradient2(low="white", mid="#3F8F77", high="white", midpoint=0.5)

p = ggplot(mpg, aes("hwy", group="drv"))
p1 = p + geom_density(**common_args) + \
    ggtitle("geom_density(): fill=None in aes (default)")
p2 = p + geom_density(aes(fill='..quantile..'), quantiles=quantiles, **common_args) + fill_diverging + \
    ggtitle("geom_density(): fill='..quantile..' in aes")
p = gggrid([p1, p2], ncol=1) + ggsize(700, 500)

show(p, "magick_path")
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path

SVG

15 20 25 30 35 40 45 0 0.05 0.1 geom_density(): fill=None in aes (default) density hwy 15 20 25 30 35 40 45 0 0.05 0.1 geom_density(): fill='..quantile..' in aes density hwy quantile 0 0.2 0.4 0.6 0.8 1

ImageMagick

Variadic Path

In [4]:
p = ggplot(airquality_df) \
    + geom_line(
        aes(
            x = 'Day', 
            y = 'Temp', 
            size = 'Wind', # <--- wind varies by day
            color = as_discrete('Month')
        )
    ) \
    + scale_size([0.5, 5.0]) \
    + ggsize(700, 500)

show(p, "magick_variadic_path")
Unsupported attribute `fill-rule` in Path

SVG

0 5 10 15 20 25 30 55 60 65 70 75 80 85 90 95 Temp Day Wind 5 10 15 20 Month 5 6 7 8 9

ImageMagick

Fonts

In [5]:
fonts = {
    "y": [ 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0 ],
    "label": [ "Arial", "Calibri", "Garamond", "Geneva", "Georgia", "Helvetica", "Lucida Grande", "Rockwell", "Times New Roman", "Verdana", "sans-serif", "serif", "monospace (imi)" ],
    "family": [ "Arial", "Calibri", "Garamond", "Geneva", "Georgia", "Helvetica", "Lucida Grande", "Rockwell", "Times New Roman", "Verdana", "sans-serif", "serif", "monospace" ]
}

p = ggplot(fonts, aes(y='y', label='label', family='family')) + geom_text(size=10)

show(p, "magick_fonts.bmp")
Unsupported attribute `fill-rule` in Path

SVG

Arial Calibri Garamond Geneva Georgia Helvetica Lucida Grande Rockwell Times New Roman Verdana sans-serif serif monospace (imi) -0.4 -0.2 0 0.2 0.4 0 2 4 6 8 10 12 y x

ImageMagick

Markdown

In [6]:
p = (ggplot(markdown_df) 
    + geom_point(aes(x='displ', y='cty', color='drv'), size=8) 
    + scale_color_manual(['#66c2a5', '#fc8d62', '#8da0cb'], guide='none') 

    # Enable Markdown in all titles
    + theme(title=element_markdown()) 

    # Adjust style of title and subtitle
    + theme(plot_title=element_text(size=30, family='Georgia', hjust=0.5), 
            plot_subtitle=element_text(family='Georgia', hjust=0.5)) 

    + labs(

        # Span styling, mixing style and emphasis
        title=
            """<span style="color:#66c2a5">**Forward**</span>, """
            """<span style="color:#8da0cb">**Rear**</span> and """
            """<span style="color:#fc8d62">**4WD**</span> Drivetrain""",

        # Simple emphasis
        subtitle='**City milage** *vs* **displacement**', 

        # multiline caption, multiline style span, links 
        caption="<span style='color:grey'>"
                "Powered by <a href='https://lets-plot.org'>Lets-Plot</a>.  \n"
                "Visit the <a href='https://github.com/jetbrains/lets-plot/issues'>issue tracker</a> for feedback." 
                "</span>",

        # Axis titles
        x='Displacement (***inches***)',
        y='Miles per gallon (***cty***)'
    )
)

show(p, "magick_markdown")
Unsupported attribute `fill-rule` in Path

SVG

2 3 4 5 6 7 10 15 20 25 30 35 Forward, Rear and 4WD Drivetrain City milage vs displacement Miles per gallon (cty) Displacement (inches) Powered by Lets-Plot. Visit the issue tracker for feedback.

ImageMagick

Pie

In [7]:
p = ggplot(pie_df, aes(fill='Country', slice='GDP_2025_Trillion_USD')) \
    + geom_pie(size=.6, size_unit='x', stat='identity') \
    + ggtitle('GDP forecast 2025 (trillion US$) by country', 
              subtitle='Source: <a href="https://en.wikipedia.org/wiki/List_of_countries_by_GDP_(nominal)">Wikipedia</a>') \
    + scale_fill_gradient(low="blue", high="yellow") \
    + theme_void() + theme(plot_title=element_text(hjust=0.5), plot_subtitle=element_text(hjust=0.5))

show(p, "magick_pie")
Unsupported attribute `fill-rule` in Path

SVG

GDP forecast 2025 (trillion US$) by country Source: Wikipedia Country United States China Germany Japan India United Kingdom France Italy Canada Brazil

ImageMagick

Power degree

In [8]:
power_df = {'x': list(range(-10, 11))}
p = ggplot() + \
    geom_function(aes(x='x'), data=power_df, fun=lambda x: x**3 - 100 * x) + \
    ggtitle("Graph of the \( y = x^3 - 100 x \)") + \
    theme(text=element_text(family="Times New Roman"), \
          plot_title=element_text(size=20, face='bold'))

show(p, "magick_power")
Unsupported attribute `fill-rule` in Path
<>:4: SyntaxWarning: invalid escape sequence '\('
<>:4: SyntaxWarning: invalid escape sequence '\('
/tmp/ipykernel_398742/2621143040.py:4: SyntaxWarning: invalid escape sequence '\('
  ggtitle("Graph of the \( y = x^3 - 100 x \)") + \

SVG

-10 -8 -6 -4 -2 0 2 4 6 8 10 -400 -300 -200 -100 0 100 200 300 400 Graph of the y=x 3100x y x

ImageMagick

Polar plot

In [9]:
p = ggplot(mpg) \
    + geom_bar(aes(x='model', y='cty', fill='cty'), stat='identity', position='dodge') \
    + scale_fill_gradient(low='red', high='white', limits=(5,40)) \
    + theme(
        axis_line_y=element_line(color='red', size=2),
        axis_line_x=element_line(color='blue', size=2),
        axis_ticks_length_y=5,
        axis_ticks_length_x=10,
        axis_ticks_y=element_line(size=5, color='red'), 
        axis_ticks_x=element_line(size=3, color='blue'),
        axis_text_x=element_text(color='blue', angle=10),
        axis_text_y=element_text(color='red'),
        panel_inset=[20, 140, 30, 135]                      # New! Expand the panel to fit axis labels.
    ) \
    + ggsize(900, 500) \
    + coord_polar(transform_bkgr=False)                     # Keep the old school rectangular background.

ggsave(p, "magick_polar.svg")
show(p, "magick_polar")
Unsupported attribute `fill-rule` in Path

SVG

a4 a4 quattro a6 quattro c1500 suburban 2wd corvette k1500 tahoe 4wd malibu caravan 2wd dakota pickup 4wd durango 4wd ram 1500 pickup 4wd expedition 2wd explorer 4wd f150 pickup 4wd mustang civic sonata tiburon grand cherokee 4wd range rover navigator 2wd mountaineer 4wd altima maxima pathfinder 4wd grand prix forester awd impreza awd 4runner 4wd camry camry solara corolla land cruiser wagon 4wd toyota tacoma 4wd gti jetta new beetle passat 0 10 20 30 cty model cty 10 20 30 40

ImageMagick

Curve

In [10]:
def curve_plot(curvature=0.5, angle=90.0, ncp=5):
    return ggplot() \
            + geom_curve(x=-10, y=1, xend=10, yend=-1, 
                         curvature=curvature, angle=angle, ncp=ncp,
                         arrow=arrow(ends='both')) \
            + ggtitle("curvature={0}, angle={1}, ncp={2}".format(curvature, angle, ncp)) \
            + xlim(-15,15)

p = gggrid([
    curve_plot(angle=0),
    curve_plot(ncp=1),
    curve_plot(angle=45),
    curve_plot(curvature=-1, angle=45),
    curve_plot(curvature=0.7, angle=30),
    curve_plot(curvature=-0.7, angle=30),
], ncol=2)

show(p, "magick_curve")
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path

SVG

-15 -10 -5 0 5 10 15 -1 -0.5 0 0.5 1 curvature=0.5, angle=0, ncp=5 y x -15 -10 -5 0 5 10 15 -1 -0.5 0 0.5 1 curvature=0.5, angle=90.0, ncp=1 y x -15 -10 -5 0 5 10 15 -1 -0.5 0 0.5 1 curvature=0.5, angle=45, ncp=5 y x -15 -10 -5 0 5 10 15 -1 -0.5 0 0.5 1 curvature=-1, angle=45, ncp=5 y x -15 -10 -5 0 5 10 15 -1 -0.5 0 0.5 1 curvature=0.7, angle=30, ncp=5 y x -15 -10 -5 0 5 10 15 -1 -0.5 0 0.5 1 curvature=-0.7, angle=30, ncp=5 y x

ImageMagick

Clip Path

In [11]:
p = ggplot(mpg) \
        + geom_bar(aes(fill=as_discrete('cyl')), size=0, position='dodge', show_legend=False) + coord_cartesian(ylim=[0, 70]) + ggtitle("Title", "Subtitle")

show(p, "magick_clip")
Unsupported attribute `fill-rule` in Path

SVG

-0.4 -0.3 -0.2 -0.1 0 0.1 0.2 0.3 0.4 0 10 20 30 40 50 60 70 Title Subtitle count x

ImageMagick

In [12]:
p = ggplot(mpg) \
    + geom_bar(aes(fill=as_discrete('cyl')), size=0, position='dodge', show_legend=False) + coord_cartesian(ylim=[0, 70]) + ggtitle("Title", "Subtitle") \
    + coord_polar()

show(p, "magick_clip_polar")
Unsupported attribute `fill-rule` in Path

SVG

-0.4 -0.2 0 0.2 0.4 0 50 Title Subtitle count x

ImageMagick

imshow

greyscale

In [13]:
arr = np.array([[150,   75,   0],
               [  200, 150, 75]])
arr_nans = np.array([
    [150, np.nan ,0],
    [np.nan, 150 ,75]
    ])

p = gggrid([
    ggplot() + geom_imshow(arr, norm=False),
    ggplot() + geom_imshow(arr, cmap="viridis", norm=False),
    ggplot() + geom_imshow(arr_nans, cmap="viridis", norm=False),
], ncol=3) + ggsize(1000, 210)
    
show(p, "magick_imshow_greyscale")
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path

SVG

0 1 2 0 1 y x 0 50 100 150 200 0 1 2 0 1 y x 0 50 100 150 200 0 1 2 0 1 y x 50 100 150

ImageMagick

rgb

In [14]:
# An 2x2 pix image by default has extent [-0.5, 1.5, -0.5, 1.5]
arr = np.array([
            [[150, 0, 0, 255], [0, 150, 0, 255]],
            [[0, 0, 150, 255], [150, 150, 0, 255]]
        ])

p = ggplot() + geom_imshow(arr) + ggsize(300, 200)
show(p, "magick_imshow")
Unsupported attribute `fill-rule` in Path

SVG

0 1 0 1 y x

ImageMagick

In [15]:
p = ggplot() + geom_imshow(image_data=boat)
show(p, "magick_boat")
Unsupported attribute `fill-rule` in Path

SVG

0 50 100 150 200 0 50 100 150 200 y x

ImageMagick

In [16]:
dim = 100

ext_base = [0, dim, 0, dim]
ext_flip_x = [dim, 0, 0, dim]
ext_flip_y = [dim, 0, dim, 0]
ext_transpose = [0, dim, dim, 0]

label_data = dict(
    x = [50, 150, 50, 150],
    y = [200, 200, 0, 0],
    label = ["original", "flip columns", "flip rows", "transpose"]
)

p = (ggplot() + 
     geom_imshow(boat, extent=[v + dim if i in (2, 3) else v for i, v in enumerate(ext_base)]) + 
     geom_imshow(boat, extent=[v + dim for v in ext_flip_x]) + 
     geom_imshow(boat, extent=[v + dim if i in (0, 1) else v for i, v in enumerate(ext_flip_y)]) + 
     geom_imshow(boat, extent=ext_transpose) + 
     geom_hline(yintercept=100, color="yellow", tooltips="none") + 
     geom_vline(xintercept=100, color="yellow", tooltips="none") + 
     geom_label(aes("x", "y", label="label"), data=label_data) +
     ggsize(700, 700)
)

show(p, "magick_boat_extent")
Unsupported attribute `fill-rule` in Path

SVG

original flip columns flip rows transpose 0 20 40 60 80 100 120 140 160 180 200 0 20 40 60 80 100 120 140 160 180 200 y x

ImageMagick

image_matrix

In [17]:
from lets_plot.bistro.im import image_matrix
In [18]:
M = 2  # rows
N = 3  # columns
X = np.empty([M, N], dtype=object)
X.fill(boat)
p = image_matrix(X)
show(p, "image_matrix")
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path

SVG

ImageMagick

In [19]:
X1 = np.empty([M, N], dtype=object)
for row in range(M):
    for col in range(N):
        v = (row + col + 1) * 10
        X1[row][col] = boat[v:-v,v:-v,:]

p = image_matrix(X1)
show(p, "magick_image_matrix_sized")
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path

SVG

ImageMagick

In [20]:
p = image_matrix(X, scale=.3)
show(p, "magick_image_matrix_scaled")
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path

SVG

ImageMagick

greyscale

In [21]:
img_gs = boat[:,:,0]

def _degrade(grayscale_img:np.ndarray, coef:float):
    vmin = grayscale_img.min()
    vmax = grayscale_img.max()
    middle = vmin + (vmax - vmin) / 2

    rows, cols = grayscale_img.shape
    for row in range(rows):
        for col in range(cols):
            v = float(grayscale_img[row][col])
            v_new = middle + (v - middle) * coef
            grayscale_img[row][col] = int(v_new)

X2 = np.empty([M, N], dtype=object)
img_copy = img_gs.copy()
for row in range(M):
    for col in range(N):
        X2[row][col] = img_copy
        img_copy = img_copy.copy()
        _degrade(img_copy, coef=.7)
        print("[{}, {}] data range: [{}-{}]".format(row, col, img_copy.min(), img_copy.max()))
[0, 0] data range: [38-216]
[0, 1] data range: [64-189]
[0, 2] data range: [82-170]
[1, 0] data range: [95-156]
[1, 1] data range: [104-146]
[1, 2] data range: [110-139]
In [22]:
p = image_matrix(X2)
show(p, "magick_image_matrix_greyscale")
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path

SVG

ImageMagick

In [23]:
ggplot() + geom_imshow(X2[0][0], cmap="viridis")
Out[23]:
In [24]:
p = image_matrix(X2, cmap="viridis", norm=False)
show(p, "magick_image_matrix_cmap")
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path
Unsupported attribute `fill-rule` in Path

SVG

ImageMagick

geom_raster

In [25]:
# reshape 3-dimentional ndarray to 2-dimentional long-form ndarray
# and then to data frame with columns x,y,r,g,b
cols, rows, _ = boat.shape
img_lf = boat.reshape(cols * rows, -1)
img_df = pd.DataFrame(img_lf,columns=['r','g','b'])
X_mesh, Y_mesh = np.meshgrid(np.arange(rows), np.arange(cols))
img_df['x'] = X_mesh.reshape(-1)
img_df['y'] = Y_mesh.reshape(-1)[::-1]

# Pack color components values to 24-bit RGB values 
c_fill = img_df.apply(lambda row: ((int(row['r'] * 255) << 16) + 
                                   (int(row['g'] * 255) << 8) +
                                   int(row['b'] * 255)),
                      axis=1)

# Show image in pseudo colors with only few gradations 
p = (ggplot(img_df) + geom_raster(aes('x', 'y', fill=c_fill), show_legend=False)
        + scale_fill_brewer()
        + ggtitle('Raster geom with brewer palette') + ggsize(800,500))
show(p, "magick_raster")
Unsupported attribute `fill-rule` in Path

SVG

0 50 100 150 200 0 20 40 60 80 100 120 140 160 180 200 220 Raster geom with brewer palette y x

ImageMagick

Size and Scale

In [26]:
p = ggplot() + geom_blank() + ggsize(200, 200)
show(p, "magick_ggsize_with_scale", scale=2)
Unsupported attribute `fill-rule` in Path

SVG

0 -0.5 0 0.5 y x

ImageMagick

In [27]:
p = ggplot() + geom_blank()
show(p, "magick_ggsave", w=10, h=10, unit='cm', dpi=72, show_svg=False)
Unsupported attribute `fill-rule` in Path

ImageMagick

In [28]:
p = ggplot() + geom_blank()
show(p, "magick_ggsave_with_scale", w=10, h=10, unit='cm', dpi=72, scale=2, show_svg=False)
Unsupported attribute `fill-rule` in Path

ImageMagick

In [29]:
filelike_obj = BytesIO()
show(p, "magick_filelike", out=filelike_obj)
Unsupported attribute `fill-rule` in Path

SVG

-0.4 -0.2 0 0.2 0.4 -0.4 -0.2 0 0.2 0.4 y x

ImageMagick

Performance

line

In [30]:
n = 50_000
x = list(range(n))
y = [0]*n
p = ggplot({'x': x, 'y': y}) + geom_line(aes(x='x', y='y'))
show(p, "magick_perf_line")
Unsupported attribute `fill-rule` in Path

SVG

0 10,000 20,000 30,000 40,000 50,000 -0.4 -0.2 0 0.2 0.4 y x

ImageMagick

point

In [31]:
n = 50_000
x = list(range(n))
y = [0]*n
p = ggplot({'x': x, 'y': y}) + geom_point(aes(x='x', y='y'))
p

show(p, "magick_perf_point")
Unsupported attribute `fill-rule` in Path

SVG

0 10,000 20,000 30,000 40,000 50,000 -0.4 -0.2 0 0.2 0.4 y x

ImageMagick

In [ ]: