✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.

Shapes in Python

How to make SVG shapes in python. Examples of lines, circle, rectangle, and path.


Write, deploy, & scale Dash apps and Python data visualizations on a Kubernetes Dash Enterprise cluster.
Get Pricing  |  Demo Dash Enterprise  |  Dash Enterprise Overview


New to Plotly?

Plotly is a free and open-source graphing library for Python. We recommend you read our Getting Started guide for the latest installation or upgrade instructions, then move on to our Plotly Fundamentals tutorials or dive straight in to some Basic Charts tutorials.

Adding Lines and Polygons to Figures

As a general rule, there are two ways to add shapes (lines or polygons) to figures:

  1. Trace types in the scatter family (e.g. scatter, scatter3d, scattergeo etc) can be drawn with mode="lines" and optionally support a fill="self" attribute, and so can be used to draw open or closed shapes on figures.
  2. Standalone lines, ellipses and rectangles can be added to figures using fig.add_shape(), and they can be positioned absolutely within the figure, or they can be positioned relative to the axes of 2d cartesian subplots i.e. in data coordinates.

Note: there are special methods add_hline, add_vline, add_hrect and add_vrect for the common cases of wanting to draw horizontal or vertical lines or rectangles that are fixed to data coordinates in one axis and absolutely positioned in another.

The differences between these two approaches are that:

  • Traces can optionally support hover labels and can appear in legends.
  • Shapes can be positioned absolutely or relative to data coordinates in 2d cartesian subplots only.
  • Traces cannot be positioned absolutely but can be positioned relative to date coordinates in any subplot type.
  • Traces also support optional text, although there is a textual equivalent to shapes in text annotations.

Shape-drawing with Scatter traces

There are two ways to draw filled shapes: scatter traces and layout.shapes which is mostly useful for the 2d subplots, and defines the shape type to be drawn, and can be rectangle, circle, line, or path (a custom SVG path). You also can use scatterpolar, scattergeo, scattermapbox to draw filled shapes on any kind of subplots. To set an area to be filled with a solid color, you need to define Scatter.fill="toself" that connects the endpoints of the trace into a closed shape. If mode=line (default value), then you need to repeat the initial point of a shape at the of the sequence to have a closed shape.

In [1]:
import plotly.graph_objects as go

fig = go.Figure(go.Scatter(x=[0,1,2,0], y=[0,2,0,0], fill="toself"))
fig.show()

You can have more shapes either by adding more traces or interrupting the series with None.

In [2]:
import plotly.graph_objects as go

fig = go.Figure(go.Scatter(x=[0,1,2,0,None,3,3,5,5,3], y=[0,2,0,0,None,0.5,1.5,1.5,0.5,0.5], fill="toself"))
fig.show()

Shapes in Dash

Dash is the best way to build analytical apps in Python using Plotly figures. To run the app below, run pip install dash, click "Download" to get the code and run python app.py.

Get started with the official Dash docs and learn how to effortlessly style & deploy apps like this with Dash Enterprise.

Out[3]:

Vertical and Horizontal Lines Positioned Relative to the Axis Data

In [4]:
import plotly.graph_objects as go

fig = go.Figure()

# Create scatter trace of text labels
fig.add_trace(go.Scatter(
    x=[2, 3.5, 6],
    y=[1, 1.5, 1],
    text=["Vertical Line",
          "Horizontal Dashed Line",
          "Diagonal dotted Line"],
    mode="text",
))

# Set axes ranges
fig.update_xaxes(range=[0, 7])
fig.update_yaxes(range=[0, 2.5])

# Add shapes
fig.add_shape(type="line",
    x0=1, y0=0, x1=1, y1=2,
    line=dict(color="RoyalBlue",width=3)
)
fig.add_shape(type="line",
    x0=2, y0=2, x1=5, y1=2,
    line=dict(
        color="LightSeaGreen",
        width=4,
        dash="dashdot",
    )
)
fig.add_shape(type="line",
    x0=4, y0=0, x1=6, y1=2,
    line=dict(
        color="MediumPurple",
        width=4,
        dash="dot",
    )
)
fig.update_shapes(dict(xref='x', yref='y'))
fig.show()

Lines Positioned Relative to the Plot & to the Axis Data

In [5]:
import plotly.graph_objects as go

fig = go.Figure()

# Create scatter trace of text labels
fig.add_trace(go.Scatter(
    x=[2, 6], y=[1, 1],
    text=["Line positioned relative to the plot",
          "Line positioned relative to the axes"],
    mode="text",
))

# Set axes ranges
fig.update_xaxes(range=[0, 8])
fig.update_yaxes(range=[0, 2])

fig.add_shape(type="line",
    xref="x", yref="y",
    x0=4, y0=0, x1=8, y1=1,
    line=dict(
        color="LightSeaGreen",
        width=3,
    ),
)
fig.add_shape(type="line",
    xref="paper", yref="paper",
    x0=0, y0=0, x1=0.5,
    y1=0.5,
    line=dict(
        color="DarkOrange",
        width=3,
    ),
)

fig.show()

Rectangles Positioned Relative to the Axis Data

In [6]:
import plotly.graph_objects as go

fig = go.Figure()

fig.add_trace(go.Scatter(
    x=[1.5, 4.5],
    y=[0.75, 0.75],
    text=["Unfilled Rectangle", "Filled Rectangle"],
    mode="text",
))

# Set axes properties
fig.update_xaxes(range=[0, 7], showgrid=False)
fig.update_yaxes(range=[0, 3.5])

# Add shapes
fig.add_shape(type="rect",
    x0=1, y0=1, x1=2, y1=3,
    line=dict(color="RoyalBlue"),
)
fig.add_shape(type="rect",
    x0=3, y0=1, x1=6, y1=2,
    line=dict(
        color="RoyalBlue",
        width=2,
    ),
    fillcolor="LightSkyBlue",
)
fig.update_shapes(dict(xref='x', yref='y'))
fig.show()

Rectangle Positioned Relative to the Plot & to the Axis Data

In [7]:
import plotly.graph_objects as go

fig = go.Figure()

# Create scatter trace of text labels
fig.add_trace(go.Scatter(
    x=[1.5, 3],
    y=[2.5, 2.5],
    text=["Rectangle reference to the plot",
          "Rectangle reference to the axes"],
    mode="text",
))

# Set axes properties
fig.update_xaxes(range=[0, 4])
fig.update_yaxes(range=[0, 4])

# Add shapes
fig.add_shape(type="rect",
    xref="x", yref="y",
    x0=2.5, y0=0,
    x1=3.5, y1=2,
    line=dict(
        color="RoyalBlue",
        width=3,
    ),
    fillcolor="LightSkyBlue",
)
fig.add_shape(type="rect",
    xref="paper", yref="paper",
    x0=0.25, y0=0,
    x1=0.5, y1=0.5,
    line=dict(
        color="LightSeaGreen",
        width=3,
    ),
    fillcolor="PaleTurquoise",
)

fig.show()

A Rectangle Placed Relative to the Axis Position and Length

A shape can be placed relative to an axis's position on the plot by adding the string ' domain' to the axis reference in the xref or yref attributes for shapes. The following code places a rectangle that starts at 60% and ends at 70% along the x-axis, starting from the left, and starts at 80% and ends at 90% along the y-axis, starting from the bottom.

In [8]:
import plotly.graph_objects as go
import plotly.express as px

df = px.data.wind()
fig = px.scatter(df, y="frequency")

fig.update_layout(xaxis=dict(domain=[0, 0.5]), yaxis=dict(domain=[0.25, 0.75]))

# Add a shape whose x and y coordinates refer to the domains of the x and y axes
fig.add_shape(type="rect",
    xref="x domain", yref="y domain",
    x0=0.6, x1=0.7, y0=0.8, y1=0.9,
)

fig.show()

Highlighting Time Series Regions with Rectangle Shapes

Note: there are special methods add_hline, add_vline, add_hrect and add_vrect for the common cases of wanting to draw horizontal or vertical lines or rectangles that are fixed to data coordinates in one axis and absolutely positioned in another.

In [9]:
import plotly.graph_objects as go

fig = go.Figure()

# Add scatter trace for line
fig.add_trace(go.Scatter(
    x=["2015-02-01", "2015-02-02", "2015-02-03", "2015-02-04", "2015-02-05",
       "2015-02-06", "2015-02-07", "2015-02-08", "2015-02-09", "2015-02-10",
       "2015-02-11", "2015-02-12", "2015-02-13", "2015-02-14", "2015-02-15",
       "2015-02-16", "2015-02-17", "2015-02-18", "2015-02-19", "2015-02-20",
       "2015-02-21", "2015-02-22", "2015-02-23", "2015-02-24", "2015-02-25",
       "2015-02-26", "2015-02-27", "2015-02-28"],
    y=[-14, -17, -8, -4, -7, -10, -12, -14, -12, -7, -11, -7, -18, -14, -14,
       -16, -13, -7, -8, -14, -8, -3, -9, -9, -4, -13, -9, -6],
    mode="lines",
    name="temperature"
))

# Add shape regions
fig.add_vrect(
    x0="2015-02-04", x1="2015-02-06",
    fillcolor="LightSalmon", opacity=0.5,
    layer="below", line_width=0,
),

fig.add_vrect(
    x0="2015-02-20", x1="2015-02-22",
    fillcolor="LightSalmon", opacity=0.5,
    layer="below", line_width=0,
)

fig.show()

Circles Positioned Relative to the Axis Data

In [10]:
import plotly.graph_objects as go

fig = go.Figure()

# Create scatter trace of text labels
fig.add_trace(go.Scatter(
    x=[1.5, 3.5],
    y=[0.75, 2.5],
    text=["Unfilled Circle",
          "Filled Circle"],
    mode="text",
))

# Set axes properties
fig.update_xaxes(range=[0, 4.5], zeroline=False)
fig.update_yaxes(range=[0, 4.5])

# Add circles
fig.add_shape(type="circle",
    xref="x", yref="y",
    x0=1, y0=1, x1=3, y1=3,
    line_color="LightSeaGreen",
)
fig.add_shape(type="circle",
    xref="x", yref="y",
    fillcolor="PaleTurquoise",
    x0=3, y0=3, x1=4, y1=4,
    line_color="LightSeaGreen",
)

# Set figure size
fig.update_layout(width=800, height=800)

fig.show()

Highlighting Clusters of Scatter Points with Circle Shapes

In [11]:
import plotly.graph_objects as go

import numpy as np
np.random.seed(1)

# Generate data
x0 = np.random.normal(2, 0.45, 300)
y0 = np.random.normal(2, 0.45, 300)

x1 = np.random.normal(6, 0.4, 200)
y1 = np.random.normal(6, 0.4, 200)

# Create figure
fig = go.Figure()

# Add scatter traces
fig.add_trace(go.Scatter(x=x0, y=y0, mode="markers"))
fig.add_trace(go.Scatter(x=x1, y=y1, mode="markers"))

# Add shapes
fig.add_shape(type="circle",
    xref="x", yref="y",
    x0=min(x0), y0=min(y0),
    x1=max(x0), y1=max(y0),
    opacity=0.2,
    fillcolor="blue",
    line_color="blue",
)

fig.add_shape(type="circle",
    xref="x", yref="y",
    x0=min(x1), y0=min(y1),
    x1=max(x1), y1=max(y1),
    opacity=0.2,
    fillcolor="orange",
    line_color="orange",
)


# Hide legend
fig.update_layout(showlegend=False)

fig.show()

Venn Diagram with Circle Shapes

In [12]:
import plotly.graph_objects as go

fig = go.Figure()

# Create scatter trace of text labels
fig.add_trace(go.Scatter(
    x=[1, 1.75, 2.5],
    y=[1, 1, 1],
    text=["$A$", "$A+B$", "$B$"],
    mode="text",
    textfont=dict(
        color="black",
        size=18,
        family="Arail",
    )
))

# Update axes properties
fig.update_xaxes(
    showticklabels=False,
    showgrid=False,
    zeroline=False,
)

fig.update_yaxes(
    showticklabels=False,
    showgrid=False,
    zeroline=False,
)

# Add circles
fig.add_shape(type="circle",
    line_color="blue", fillcolor="blue",
    x0=0, y0=0, x1=2, y1=2
)
fig.add_shape(type="circle",
    line_color="gray", fillcolor="gray",
    x0=1.5, y0=0, x1=3.5, y1=2
)
fig.update_shapes(opacity=0.3, xref="x", yref="y")

fig.update_layout(
    margin=dict(l=20, r=20, b=100),
    height=600, width=800,
    plot_bgcolor="white"
)

fig.show()

Adding Shapes to Subplots

Here we use the different axes (x1, x2) created by make_subplots as reference in order to draw shapes in figure subplots.

In [13]:
import plotly.graph_objects as go
from plotly.subplots import make_subplots

# Create Subplots
fig = make_subplots(rows=2, cols=2)

fig.add_trace(go.Scatter(x=[2, 6], y=[1,1]), row=1, col=1)
fig.add_trace(go.Bar(x=[1,2,3], y=[4,5,6]), row=1, col=2)
fig.add_trace(go.Scatter(x=[10,20], y=[40,50]), row=2, col=1)
fig.add_trace(go.Bar(x=[11,13,15], y=[8,11,20]), row=2, col=2)

# Add shapes
fig.update_layout(
    shapes=[
        dict(type="line", xref="x", yref="y",
            x0=3, y0=0.5, x1=5, y1=0.8, line_width=3),
        dict(type="rect", xref="x2", yref='y2',
             x0=4, y0=2, x1=5, y1=6),
        dict(type="rect", xref="x3", yref="y3",
             x0=10, y0=20, x1=15, y1=30),
        dict(type="circle", xref="x4", yref="y4",
             x0=5, y0=12, x1=10, y1=18)])
fig.show()

Adding the Same Shapes to Multiple Subplots

The same shape can be added to multiple facets by using the 'all' keyword in the row and col arguments. For example

In [14]:
import plotly.express as px

df = px.data.tips()
fig = px.scatter(df, x="total_bill", y="tip", facet_row="smoker", facet_col="sex")
# Adds a rectangle to all facets
fig.add_shape(
    dict(type="rect", x0=25, x1=35, y0=4, y1=6, line_color="purple"),
    row="all",
    col="all",
)
# Adds a line to all the rows of the second column
fig.add_shape(
    dict(type="line", x0=20, x1=25, y0=5, y1=6, line_color="yellow"), row="all", col=2
)

# Adds a circle to all the columns of the first row
fig.add_shape(
    dict(type="circle", x0=10, y0=2, x1=20, y1=7), row=1, col="all", line_color="green"
)
fig.show()

SVG Paths

In [15]:
import plotly.graph_objects as go

fig = go.Figure()

# Create scatter trace of text labels
fig.add_trace(go.Scatter(
    x=[2, 1, 8, 8],
    y=[0.25, 9, 2, 6],
    text=["Filled Triangle",
          "Filled Polygon",
          "Quadratic Bezier Curves",
          "Cubic Bezier Curves"],
    mode="text",
))

# Update axes properties
fig.update_xaxes(
    range=[0, 9],
    zeroline=False,
)

fig.update_yaxes(
    range=[0, 11],
    zeroline=False,
)

# Add shapes
fig.update_layout(
    shapes=[
        # Quadratic Bezier Curves
        dict(
            type="path",
            path="M 4,4 Q 6,0 8,4",
            line_color="RoyalBlue",
        ),
        # Cubic Bezier Curves
        dict(
            type="path",
            path="M 1,4 C 2,8 6,4 8,8",
            line_color="MediumPurple",
        ),
        # filled Triangle
        dict(
            type="path",
            path=" M 1 1 L 1 3 L 4 1 Z",
            fillcolor="LightPink",
            line_color="Crimson",
        ),
        # filled Polygon
        dict(
            type="path",
            path=" M 3,7 L2,8 L2,9 L3,10, L4,10 L5,9 L5,8 L4,7 Z",
            fillcolor="PaleTurquoise",
            line_color="LightSeaGreen",
        ),
    ]
)

fig.show()

Drawing shapes with a Mouse on Cartesian plots

introduced in plotly 4.7

You can create layout shapes programmatically, but you can also draw shapes manually by setting the dragmode to one of the shape-drawing modes: 'drawline','drawopenpath', 'drawclosedpath', 'drawcircle', or 'drawrect'. If you need to switch between different shape-drawing or other dragmodes (panning, selecting, etc.), modebar buttons can be added in the config to select the dragmode. If you switch to a different dragmode such as pan or zoom, you will need to select the drawing tool in the modebar to go back to shape drawing.

This shape-drawing feature is particularly interesting for annotating graphs, in particular image traces or layout images.

Once you have drawn shapes, you can select and modify an existing shape by clicking on its boundary (note the arrow pointer). Its fillcolor turns to pink to highlight the activated shape and then you can

  • drag and resize it for lines, rectangles and circles/ellipses
  • drag and move individual vertices for closed paths
  • move individual vertices for open paths.

An activated shape is deleted by clicking on the eraseshape button.

Drawing or modifying a shape triggers a relayout event, which can be captured by a callback inside a Dash application.

In [16]:
import plotly.graph_objects as go
fig = go.Figure()
text="Click and drag here <br> to draw a rectangle <br><br> or select another shape <br>in the modebar"
fig.add_annotation(
            x=0.5,
            y=0.5,
            text=text,
            xref="paper",
            yref="paper",
            showarrow=False,
            font_size=20
)
# shape defined programatically
fig.add_shape(editable=True,
              x0=-1, x1=0, y0=2, y1=3,
              xref='x', yref='y')
# define dragmode and add modebar buttons
fig.update_layout(dragmode='drawrect')
fig.show(config={'modeBarButtonsToAdd':['drawline',
                                        'drawopenpath',
                                        'drawclosedpath',
                                        'drawcircle',
                                        'drawrect',
                                        'eraseshape'
                                       ]})

Style of user-drawn shapes

The layout newshape attribute controls the visual appearance of new shapes drawn by the user. newshape attributes have the same names as layout shapes.

Note on shape opacity: having a new shape's opacity > 0.5 makes it possible to activate a shape by clicking inside the shape (for opacity <= 0.5 you have to click on the border of the shape), but you cannot start a new shape within an existing shape (which is possible for an opacity <= 0.5).

In [17]:
import plotly.graph_objects as go
fig = go.Figure()
text="Click and drag<br> to draw a rectangle <br><br> or select another shape <br>in the modebar"
fig.add_annotation(
            x=0.5,
            y=0.5,
            text=text,
            xref="paper",
            yref="paper",
            showarrow=False,
            font_size=20
)
# shape defined programatically
fig.add_shape(line_color='yellow',
              fillcolor='turquoise',
              opacity=0.4,
              editable=True,
              x0=0, x1=1, y0=2, y1=3,
              xref='x', yref='y'
)
fig.update_layout(dragmode='drawrect',
                  # style of new shapes
                  newshape=dict(line_color='yellow',
                                fillcolor='turquoise',
                                opacity=0.5))
fig.show(config={'modeBarButtonsToAdd':['drawline',
                                        'drawopenpath',
                                        'drawclosedpath',
                                        'drawcircle',
                                        'drawrect',
                                        'eraseshape'
                                       ]})

Reference

See https://plotly.com/python/reference/layout/shapes/ for more information and chart attribute options!

What About Dash?

Dash is an open-source framework for building analytical applications, with no Javascript required, and it is tightly integrated with the Plotly graphing library.

Learn about how to install Dash at https://dash.plot.ly/installation.

Everywhere in this page that you see fig.show(), you can display the same figure in a Dash application by passing it to the figure argument of the Graph component from the built-in dash_core_components package like this:

import plotly.graph_objects as go # or plotly.express as px
fig = go.Figure() # or any Plotly Express function e.g. px.bar(...)
# fig.add_trace( ... )
# fig.update_layout( ... )

import dash
import dash_core_components as dcc
import dash_html_components as html

app = dash.Dash()
app.layout = html.Div([
    dcc.Graph(figure=fig)
])

app.run_server(debug=True, use_reloader=False)  # Turn off reloader if inside Jupyter