Skip to content

Instantly share code, notes, and snippets.

@astrojuanlu
Last active September 25, 2023 13:09
Show Gist options
  • Save astrojuanlu/7284462 to your computer and use it in GitHub Desktop.
Save astrojuanlu/7284462 to your computer and use it in GitHub Desktop.
Interactive Bézier curves with Python using just matplotlib.
import matplotlib
matplotlib.use('webagg')
import numpy as np
from scipy.special import binom
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
class BezierBuilder(object):
"""Bézier curve interactive builder.
"""
def __init__(self, control_polygon, ax_bernstein):
"""Constructor.
Receives the initial control polygon of the curve.
"""
self.control_polygon = control_polygon
self.xp = list(control_polygon.get_xdata())
self.yp = list(control_polygon.get_ydata())
self.canvas = control_polygon.figure.canvas
self.ax_main = control_polygon.axes
self.ax_bernstein = ax_bernstein
# Event handler for mouse clicking
self.cid = self.canvas.mpl_connect('button_press_event', self)
# Create Bézier curve
line_bezier = Line2D([], [],
c=control_polygon.get_markeredgecolor())
self.bezier_curve = self.ax_main.add_line(line_bezier)
def __call__(self, event):
# Ignore clicks outside axes
if event.inaxes != self.control_polygon.axes:
return
# Add point
self.xp.append(event.xdata)
self.yp.append(event.ydata)
self.control_polygon.set_data(self.xp, self.yp)
# Rebuild Bézier curve and update canvas
self.bezier_curve.set_data(*self._build_bezier())
self._update_bernstein()
self._update_bezier()
def _build_bezier(self):
x, y = Bezier(list(zip(self.xp, self.yp))).T
return x, y
def _update_bezier(self):
self.canvas.draw()
def _update_bernstein(self):
N = len(self.xp) - 1
t = np.linspace(0, 1, num=200)
ax = self.ax_bernstein
ax.clear()
for kk in range(N + 1):
ax.plot(t, Bernstein(N, kk)(t))
ax.set_title("Bernstein basis, N = {}".format(N))
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
def Bernstein(n, k):
"""Bernstein polynomial.
"""
coeff = binom(n, k)
def _bpoly(x):
return coeff * x ** k * (1 - x) ** (n - k)
return _bpoly
def Bezier(points, num=200):
"""Build Bézier curve from points.
"""
N = len(points)
t = np.linspace(0, 1, num=num)
curve = np.zeros((num, 2))
for ii in range(N):
curve += np.outer(Bernstein(N - 1, ii)(t), points[ii])
return curve
if __name__ == '__main__':
# Initial setup
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
# Empty line
line = Line2D([], [], ls='--', c='#666666',
marker='x', mew=2, mec='#204a87')
ax1.add_line(line)
# Canvas limits
ax1.set_xlim(0, 1)
ax1.set_ylim(0, 1)
ax1.set_title("Bézier curve")
# Bernstein plot
ax2.set_title("Bernstein basis")
# Create BezierBuilder
bezier_builder = BezierBuilder(line, ax2)
plt.show()
@astrojuanlu
Copy link
Author

Updated to test WebAgg canvas (requires matplotlib >= 1.3) and interactive Bernstein basis.

@Jorge-C
Copy link

Jorge-C commented Dec 4, 2013

I did some minor encoding changes to make it run in python2 (set utf-8 enconding for the file, and imported unicode_literals to avoid having to use u"Bézier...").

Then I realized panning/zooming weren't working as intended, so I added a bit of logic to make that work.

Changes are available in my fork! Too bad there're no pull requests for gists :S

@cdorado
Copy link

cdorado commented Mar 7, 2015

Very useful, thanks!

@eduardojvieira
Copy link

Gracias, lo usaré en un proyecto de generación de trayectorias para un robot móvil

@tabidots
Copy link

I keep getting AttributeError: 'Line2D' object has no attribute 'get_axes' on the line self.ax_main = control_polygon.get_axes().

@ike-taku
Copy link

@tabidots
maybe if you change "control_polygon.get_axes() -> control_polygon.axes", it's work.

@JacoboJaspeM
Copy link

Could someone please explain how to use it?

@astrojuanlu
Copy link
Author

Updated the code to fix the get_axes error.

To use it, download the script and run it. A new browser window will open:

Screenshot_2019-06-18 MPL WebAgg current figures

@YannDubs
Copy link

YannDubs commented Sep 5, 2022

Thanks for the useful gist.
In case anyone wants more interactivity I modified it to be able to drag and remove points. Here's the new gist (created for jupyter notebooks but should work with webagg)

@carlos-adir
Copy link

carlos-adir commented Sep 25, 2023

Very nice this gist.
I created one that allows drag, remove points and also accepts BSplines:
Here you can find the gist

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment